Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@ export class CharSeparatedValueData {
return this._headerRecord + this._dataRecords;
}

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

this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
if (this._data && this._data.length > 0) {
this._isSpecialData = ExportUtilities.isSpecialData(this._data[0]);
}
this._escapeCharacters.push(this._delimiter);

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

this._headerRecord = this.processHeaderRecord(headers, this._data.length);
if (keys.length === 0 || ((!this._data || this._data.length === 0) && keys.length === 0)) {
done('');
// If alwaysExportHeaders is true and we have headers, export headers only
if (alwaysExportHeaders && headers && headers.length > 0) {
done(this._headerRecord);
} else {
done('');
}
} else {
this.processDataRecordsAsync(this._data, keys, (dr) => {
done(this._headerRecord + dr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { ReorderedColumnsComponent,
GridIDNameJobTitleComponent,
ProductsComponent,
ColumnsAddedOnInitComponent,
EmptyGridComponent } from '../../../../../test-utils/grid-samples.spec';
EmptyGridComponent,
GridCustomSummaryComponent } from '../../../../../test-utils/grid-samples.spec';
import { SampleTestData } from '../../../../../test-utils/sample-test-data.spec';
import { first } from 'rxjs/operators';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
Expand Down Expand Up @@ -37,7 +38,8 @@ describe('CSV Grid Exporter', () => {
IgxTreeGridPrimaryForeignKeyComponent,
ProductsComponent,
ColumnsAddedOnInitComponent,
EmptyGridComponent
EmptyGridComponent,
GridCustomSummaryComponent
]
}).compileComponents();
}));
Expand Down Expand Up @@ -403,6 +405,30 @@ describe('CSV Grid Exporter', () => {
wrapper.verifyData('Country,Region,Test Header', 'Only headers should be exported.');
});

it('should export grid with summaries correctly, not as [object Object]', async () => {
const fix = TestBed.createComponent(GridCustomSummaryComponent);
fix.detectChanges();

const grid = fix.componentInstance.grid;

const wrapper = await getExportedData(grid, options);
const exportedData = wrapper['_data'];

expect(exportedData.includes('[object Object]')).toBe(false, 'CSV export should not contain [object Object]');

const lines = exportedData.split('\r\n');

// Skip header line and data lines, check summary lines at the end
const summaryLines = lines.slice(-4);

// Verify at least one summary line contains proper formatting (label: value pattern)
const hasProperlySummary = summaryLines.some(line =>
line.includes(':') && !line.includes('[object Object]')
);

expect(hasProperlySummary).toBe(true, 'Summary data should be formatted as "label: value"');
});

describe('Tree Grid CSV export', () => {
let fix;
let treeGrid: IgxTreeGridComponent;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EventEmitter, Injectable } from '@angular/core';
import { DEFAULT_OWNER, ExportHeaderType, IColumnInfo, IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
import { DEFAULT_OWNER, ExportHeaderType, ExportRecordType, IColumnInfo, IExportRecord, IgxBaseExporter } from '../exporter-common/base-export-service';
import { ExportUtilities } from '../exporter-common/export-utilities';
import { CharSeparatedValueData } from './char-separated-value-data';
import { CsvFileTypes, IgxCsvExporterOptions } from './csv-exporter-options';
Expand Down Expand Up @@ -50,10 +50,36 @@ export class IgxCsvExporterService extends IgxBaseExporter {
private _stringData: string;

protected exportDataImplementation(data: IExportRecord[], options: IgxCsvExporterOptions, done: () => void) {
const dimensionKeys = data[0]?.dimensionKeys;
data = dimensionKeys?.length ?
data.map((item) => item.rawData):
data.map((item) => item.data);
const firstDataElement = data[0];
const dimensionKeys = firstDataElement?.dimensionKeys;

const dataRecords = dimensionKeys?.length ?
data.filter(item => item.type !== ExportRecordType.SummaryRecord).map((item) => item.rawData):
data.filter(item => item.type !== ExportRecordType.SummaryRecord).map((item) => item.data);

// Get summary records if exportSummaries is enabled
const summaryRecords: any[] = [];
if (options.exportSummaries) {
const summaries = data.filter(item => item.type === ExportRecordType.SummaryRecord);
for (const summary of summaries) {
// Convert summary record data to a flat object format for CSV
const summaryData: any = {};
if (summary.data) {
for (const [key, value] of Object.entries(summary.data)) {
if (value && typeof value === 'object' && 'label' in value && 'value' in value) {
summaryData[key] = `${value.label}: ${value.value}`;
} else {
summaryData[key] = value;
}
}
}
summaryRecords.push(summaryData);
}
}

// Combine data records and summary records
const allRecords = [...dataRecords, ...summaryRecords];

const columnList = this._ownersMap.get(DEFAULT_OWNER);
const columns = columnList?.columns.filter(c => c.headerType === ExportHeaderType.ColumnHeader);
if (dimensionKeys) {
Expand All @@ -72,13 +98,13 @@ export class IgxCsvExporterService extends IgxBaseExporter {
columns.unshift(...dimensionCols);
}

const csvData = new CharSeparatedValueData(data, options.valueDelimiter, columns);
const csvData = new CharSeparatedValueData(allRecords, options.valueDelimiter, columns);
csvData.prepareDataAsync((r) => {
this._stringData = r;
this.saveFile(options);
this.exportEnded.emit({ csvData: this._stringData });
done();
});
}, options.alwaysExportHeaders);
}

private saveFile(options: IgxCsvExporterOptions) {
Expand Down
Loading