Skip to content

Commit 6b7eccb

Browse files
authored
fix(csv): export summaries - master (#16746)
1 parent 1dd1931 commit 6b7eccb

3 files changed

Lines changed: 71 additions & 12 deletions

File tree

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ export class CharSeparatedValueData {
4242
return this._headerRecord + this._dataRecords;
4343
}
4444

45-
public prepareDataAsync(done: (result: string) => void) {
45+
public prepareDataAsync(done: (result: string) => void, alwaysExportHeaders: boolean = true) {
4646
const columns = this.columns?.filter(c => !c.skip)
4747
.sort((a, b) => a.startIndex - b.startIndex)
4848
.sort((a, b) => a.pinnedIndex - b.pinnedIndex);
4949
const keys = columns && columns.length ? columns.map(c => c.field) : ExportUtilities.getKeysFromData(this._data);
5050

51-
this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
51+
if (this._data && this._data.length > 0) {
52+
this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
53+
}
5254
this._escapeCharacters.push(this._delimiter);
5355

5456
const headers = columns && columns.length ?
@@ -57,7 +59,12 @@ export class CharSeparatedValueData {
5759

5860
this._headerRecord = this.processHeaderRecord(headers, this._data.length);
5961
if (keys.length === 0 || ((!this._data || this._data.length === 0) && keys.length === 0)) {
60-
done('');
62+
// If alwaysExportHeaders is true and we have headers, export headers only
63+
if (alwaysExportHeaders && headers && headers.length > 0) {
64+
done(this._headerRecord);
65+
} else {
66+
done('');
67+
}
6168
} else {
6269
this.processDataRecordsAsync(this._data, keys, (dr) => {
6370
done(this._headerRecord + dr);

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

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ import { ReorderedColumnsComponent,
99
GridIDNameJobTitleComponent,
1010
ProductsComponent,
1111
ColumnsAddedOnInitComponent,
12-
EmptyGridComponent } from '../../../../../test-utils/grid-samples.spec';
12+
EmptyGridComponent,
13+
GridCustomSummaryComponent } from '../../../../../test-utils/grid-samples.spec';
1314
import { SampleTestData } from '../../../../../test-utils/sample-test-data.spec';
1415
import { first } from 'rxjs/operators';
1516
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -37,7 +38,8 @@ describe('CSV Grid Exporter', () => {
3738
IgxTreeGridPrimaryForeignKeyComponent,
3839
ProductsComponent,
3940
ColumnsAddedOnInitComponent,
40-
EmptyGridComponent
41+
EmptyGridComponent,
42+
GridCustomSummaryComponent
4143
]
4244
}).compileComponents();
4345
}));
@@ -403,6 +405,30 @@ describe('CSV Grid Exporter', () => {
403405
wrapper.verifyData('Country,Region,Test Header', 'Only headers should be exported.');
404406
});
405407

408+
it('should export grid with summaries correctly, not as [object Object]', async () => {
409+
const fix = TestBed.createComponent(GridCustomSummaryComponent);
410+
fix.detectChanges();
411+
412+
const grid = fix.componentInstance.grid;
413+
414+
const wrapper = await getExportedData(grid, options);
415+
const exportedData = wrapper['_data'];
416+
417+
expect(exportedData.includes('[object Object]')).toBe(false, 'CSV export should not contain [object Object]');
418+
419+
const lines = exportedData.split('\r\n');
420+
421+
// Skip header line and data lines, check summary lines at the end
422+
const summaryLines = lines.slice(-4);
423+
424+
// Verify at least one summary line contains proper formatting (label: value pattern)
425+
const hasProperlySummary = summaryLines.some(line =>
426+
line.includes(':') && !line.includes('[object Object]')
427+
);
428+
429+
expect(hasProperlySummary).toBe(true, 'Summary data should be formatted as "label: value"');
430+
});
431+
406432
describe('Tree Grid CSV export', () => {
407433
let fix;
408434
let treeGrid: IgxTreeGridComponent;

projects/igniteui-angular/grids/core/src/services/csv/csv-exporter.ts

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EventEmitter, Injectable } from '@angular/core';
2-
import { DEFAULT_OWNER, ExportHeaderType, IColumnInfo, IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
2+
import { DEFAULT_OWNER, ExportHeaderType, ExportRecordType, 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,36 @@ export class IgxCsvExporterService extends IgxBaseExporter {
5050
private _stringData: string;
5151

5252
protected exportDataImplementation(data: IExportRecord[], options: IgxCsvExporterOptions, done: () => void) {
53-
const dimensionKeys = data[0]?.dimensionKeys;
54-
data = dimensionKeys?.length ?
55-
data.map((item) => item.rawData):
56-
data.map((item) => item.data);
53+
const firstDataElement = data[0];
54+
const dimensionKeys = firstDataElement?.dimensionKeys;
55+
56+
const dataRecords = dimensionKeys?.length ?
57+
data.filter(item => item.type !== ExportRecordType.SummaryRecord).map((item) => item.rawData):
58+
data.filter(item => item.type !== ExportRecordType.SummaryRecord).map((item) => item.data);
59+
60+
// Get summary records if exportSummaries is enabled
61+
const summaryRecords: any[] = [];
62+
if (options.exportSummaries) {
63+
const summaries = data.filter(item => item.type === ExportRecordType.SummaryRecord);
64+
for (const summary of summaries) {
65+
// Convert summary record data to a flat object format for CSV
66+
const summaryData: any = {};
67+
if (summary.data) {
68+
for (const [key, value] of Object.entries(summary.data)) {
69+
if (value && typeof value === 'object' && 'label' in value && 'value' in value) {
70+
summaryData[key] = `${value.label}: ${value.value}`;
71+
} else {
72+
summaryData[key] = value;
73+
}
74+
}
75+
}
76+
summaryRecords.push(summaryData);
77+
}
78+
}
79+
80+
// Combine data records and summary records
81+
const allRecords = [...dataRecords, ...summaryRecords];
82+
5783
const columnList = this._ownersMap.get(DEFAULT_OWNER);
5884
const columns = columnList?.columns.filter(c => c.headerType === ExportHeaderType.ColumnHeader);
5985
if (dimensionKeys) {
@@ -72,13 +98,13 @@ export class IgxCsvExporterService extends IgxBaseExporter {
7298
columns.unshift(...dimensionCols);
7399
}
74100

75-
const csvData = new CharSeparatedValueData(data, options.valueDelimiter, columns);
101+
const csvData = new CharSeparatedValueData(allRecords, options.valueDelimiter, columns);
76102
csvData.prepareDataAsync((r) => {
77103
this._stringData = r;
78104
this.saveFile(options);
79105
this.exportEnded.emit({ csvData: this._stringData });
80106
done();
81-
});
107+
}, options.alwaysExportHeaders);
82108
}
83109

84110
private saveFile(options: IgxCsvExporterOptions) {

0 commit comments

Comments
 (0)