Skip to content

Commit da3972f

Browse files
authored
Print row dimensions to CSV properly. (#15747)
* fix(CSV): Skip group columns when exporting to CSV. * fix(PivotCSV): Fixed exporter when row dimensions exist.
1 parent b325a59 commit da3972f

File tree

5 files changed

+93
-6
lines changed

5 files changed

+93
-6
lines changed

projects/igniteui-angular/src/lib/services/csv/char-separated-value-data.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ export class CharSeparatedValueData {
5252
this._escapeCharacters.push(this._delimiter);
5353

5454
const headers = columns && columns.length ?
55-
columns.map(c => c.header ?? c.field) :
55+
/* When column groups are present, always use the field as it indicates the group the column belongs to.
56+
* Otherwise, in PivotGrid scenarios we can end up with many duplicated column names without a hint what they represent.
57+
*/
58+
columns.map(c => c.columnGroupParent ? c.field : c.header ?? c.field) :
5659
keys;
5760

5861
this._headerRecord = this.processHeaderRecord(headers, this._data.length);

projects/igniteui-angular/src/lib/services/csv/csv-exporter-grid.spec.ts

+45
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import { FilteringLogic } from '../../data-operations/filtering-expression.inter
2222
import { configureTestSuite } from '../../test-utils/configure-suite';
2323
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
2424
import { wait } from '../../test-utils/ui-interactions.spec';
25+
import { IgxPivotGridComponent } from '../../grids/pivot-grid/pivot-grid.component';
26+
import { IgxPivotGridTestBaseComponent } from '../../test-utils/pivot-grid-samples.spec';
27+
import { IgxPivotNumericAggregate } from '../../grids/pivot-grid/pivot-grid-aggregate';
2528

2629
describe('CSV Grid Exporter', () => {
2730
configureTestSuite();
@@ -518,6 +521,48 @@ describe('CSV Grid Exporter', () => {
518521
});
519522
});
520523

524+
describe('Pivot Grid CSV export', () => {
525+
let fix;
526+
let pivotGrid: IgxPivotGridComponent;
527+
beforeEach(() => {
528+
fix = TestBed.createComponent(IgxPivotGridTestBaseComponent);
529+
fix.detectChanges();
530+
pivotGrid = fix.componentInstance.pivotGrid;
531+
pivotGrid.pivotConfiguration = {
532+
columns: [
533+
{
534+
enabled: true,
535+
memberName: 'Country'
536+
}
537+
],
538+
rows: [
539+
{
540+
enabled: true,
541+
memberName: 'ProductCategory'
542+
}
543+
],
544+
values: [
545+
{
546+
enabled: true,
547+
member: 'UnitsSold',
548+
aggregate: {
549+
aggregator: IgxPivotNumericAggregate.sum,
550+
key: 'SUM',
551+
label: 'Sum',
552+
},
553+
}
554+
]
555+
};
556+
fix.detectChanges();
557+
});
558+
559+
it('should export pivot grid successfully.', async () => {
560+
await wait();
561+
const wrapper = await getExportedData(pivotGrid, options);
562+
wrapper.verifyData(wrapper.pivotGridData);
563+
});
564+
});
565+
521566
const getExportedData = (grid, csvOptions: IgxCsvExporterOptions) => {
522567
const result = new Promise<CSVWrapper>((resolve) => {
523568
exporter.exportEnded.pipe(first()).subscribe((value) => {

projects/igniteui-angular/src/lib/services/csv/csv-exporter.ts

+22-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EventEmitter, Injectable } from '@angular/core';
2-
import { DEFAULT_OWNER, IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
2+
import { DEFAULT_OWNER, ExportHeaderType, IColumnInfo, IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
33
import { ExportUtilities } from '../exporter-common/export-utilities';
44
import { CharSeparatedValueData } from './char-separated-value-data';
55
import { CsvFileTypes, IgxCsvExporterOptions } from './csv-exporter-options';
@@ -50,10 +50,29 @@ export class IgxCsvExporterService extends IgxBaseExporter {
5050
private _stringData: string;
5151

5252
protected exportDataImplementation(data: IExportRecord[], options: IgxCsvExporterOptions, done: () => void) {
53-
data = data.map((item) => item.data);
53+
const dimensionKeys = data[0]?.dimensionKeys;
54+
data = dimensionKeys?.length ?
55+
data.map((item) => item.rawData):
56+
data.map((item) => item.data);
5457
const columnList = this._ownersMap.get(DEFAULT_OWNER);
58+
const columns = columnList?.columns.filter(c => c.headerType === ExportHeaderType.ColumnHeader);
59+
if (dimensionKeys) {
60+
const dimensionCols = dimensionKeys.map((key) => {
61+
const columnInfo: IColumnInfo = {
62+
header: key,
63+
field: key,
64+
dataType: 'string',
65+
skip: false,
66+
headerType: ExportHeaderType.ColumnHeader,
67+
columnSpan: 1,
68+
startIndex: 0
69+
};
70+
return columnInfo;
71+
});
72+
columns.unshift(...dimensionCols);
73+
}
5574

56-
const csvData = new CharSeparatedValueData(data, options.valueDelimiter, columnList?.columns);
75+
const csvData = new CharSeparatedValueData(data, options.valueDelimiter, columns);
5776
csvData.prepareDataAsync((r) => {
5877
this._stringData = r;
5978
this.saveFile(options);

projects/igniteui-angular/src/lib/services/csv/csv-verification-wrapper.spec.ts

+8
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,12 @@ export class CSVWrapper {
288288
`B's Beverages${this._delimiter}Victoria Ashworth${this._delimiter}Fauntleroy Circus${this._delimiter}0${this._delimiter}` +
289289
`2500${this._delimiter}5000${this._eor}`;
290290
}
291+
292+
public get pivotGridData() {
293+
return `ProductCategory${this._delimiter}Bulgaria${this._delimiter}USA${this._delimiter}Uruguay${this._eor}` +
294+
`Accessories${this._delimiter}${this._delimiter}293${this._delimiter}${this._eor}` +
295+
`Bikes${this._delimiter}${this._delimiter}${this._delimiter}68${this._eor}` +
296+
`Clothing${this._delimiter}774${this._delimiter}296${this._delimiter}456${this._eor}` +
297+
`Components${this._delimiter}${this._delimiter}240${this._delimiter}${this._eor}`;
298+
}
291299
}

projects/igniteui-angular/src/lib/services/exporter-common/base-export-service.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export interface IExportRecord {
4444
summaryKey?: string;
4545
hierarchicalOwner?: string;
4646
references?: IColumnInfo[];
47+
/* Adding `rawData` and `dimesnionKeys` properties to support properly exporting pivot grid data to CSV. */
48+
rawData?: any;
49+
dimensionKeys?: string[];
4750
}
4851

4952
export interface IColumnList {
@@ -448,13 +451,18 @@ export abstract class IgxBaseExporter {
448451
if (!isSpecialData) {
449452
const owner = record.owner === undefined ? DEFAULT_OWNER : record.owner;
450453
const ownerCols = this._ownersMap.get(owner).columns;
454+
const hasRowHeaders = ownerCols.some(c => c.headerType === ExportHeaderType.RowHeader);
451455

452456
if (record.type !== ExportRecordType.HeaderRecord) {
453457
const columns = ownerCols
454458
.filter(c => c.headerType === ExportHeaderType.ColumnHeader && !c.skip)
455459
.sort((a, b) => a.startIndex - b.startIndex)
456460
.sort((a, b) => a.pinnedIndex - b.pinnedIndex);
457461

462+
if (hasRowHeaders) {
463+
record.rawData = record.data;
464+
}
465+
458466
record.data = columns.reduce((a, e) => {
459467
if (!e.skip) {
460468
let rawValue = resolveNestedPath(record.data, e.field);
@@ -592,6 +600,10 @@ export abstract class IgxBaseExporter {
592600

593601
this.flatRecords.push(pivotGridRecord);
594602
}
603+
604+
if (this.flatRecords.length) {
605+
this.flatRecords[0].dimensionKeys = Object.values(this.pivotGridRowDimensionsMap);
606+
}
595607
}
596608

597609
private prepareHierarchicalGridData(grid: GridType, hasFiltering: boolean, hasSorting: boolean) {
@@ -1342,8 +1354,8 @@ export abstract class IgxBaseExporter {
13421354

13431355
for (const k of Object.keys(groupedRecords)) {
13441356
groupedRecords[k] = groupedRecords[k].filter(row => mapKeys.every(mk => Object.keys(row).includes(mk))
1345-
&& mapValues.every(mv => Object.values(row).includes(mv)));
1346-
1357+
&& mapValues.every(mv => Object.values(row).includes(mv)));
1358+
13471359
if (groupedRecords[k].length === 0) {
13481360
delete groupedRecords[k];
13491361
}

0 commit comments

Comments
 (0)