Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit de52614

Browse files
committed
feat(export): add delimiter/listSeparator override to use with GraphQL
1 parent 7550262 commit de52614

File tree

8 files changed

+114
-32
lines changed

8 files changed

+114
-32
lines changed

src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,17 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
398398
if (backendApi) {
399399
// internalPostProcess only works (for now) with a GraphQL Service, so make sure it is that type
400400
if (backendApi && backendApi.service instanceof GraphqlService) {
401-
backendApi.internalPostProcess = (processResult: any) => {
401+
backendApi.internalPostProcess = (processResult: GraphqlResult) => {
402402
const datasetName = (backendApi && backendApi.service && typeof backendApi.service.getDatasetName === 'function') ? backendApi.service.getDatasetName() : '';
403403
if (processResult && processResult.data && processResult.data[datasetName]) {
404404
this._dataset = processResult.data[datasetName].nodes;
405+
if (processResult.data[datasetName].listSeparator) {
406+
// if the "listSeparator" is available in the GraphQL result, we'll override the ExportOptions Delimiter with this new info
407+
if (!this.gridOptions.exportOptions) {
408+
this.gridOptions.exportOptions = {};
409+
}
410+
this.gridOptions.exportOptions.delimiterOverride = processResult.data[datasetName].listSeparator.toString();
411+
}
405412
this.refreshGridData(this._dataset, processResult.data[datasetName].totalCount);
406413
} else {
407414
this._dataset = [];

src/app/modules/angular-slickgrid/models/exportOption.interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export interface ExportOption {
44
/** export delimiter, can be (comma, tab, ... or even custom string). */
55
delimiter?: DelimiterType | string;
66

7+
/** Allows you to override for the export delimiter, useful when adding the "listSeparator" to the GraphQL query */
8+
delimiterOverride?: DelimiterType | string;
9+
710
/** Defaults to false, which leads to all Formatters of the grid being evaluated on export. You can also override a column by changing the propery on the column itself */
811
exportWithFormatter?: boolean;
912

src/app/modules/angular-slickgrid/models/graphqlResult.interface.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1+
import { DelimiterType } from './delimiterType.enum';
12
import { Metrics } from './metrics.interface';
23
import { Statistic } from './statistic.interface';
34

45
export interface GraphqlResult {
56
data: {
67
[datasetName: string]: {
7-
nodes: any[],
8+
nodes: any[];
89
pageInfo: {
910
hasNextPage: boolean;
10-
},
11-
totalCount: number
11+
};
12+
listSeparator?: DelimiterType;
13+
totalCount: number;
1214
}
1315
};
1416

src/app/modules/angular-slickgrid/models/graphqlServiceOption.interface.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,17 @@ import { GraphqlPaginationOption } from './graphqlPaginationOption.interface';
77

88
export interface GraphqlServiceOption extends BackendServiceOption {
99
/**
10-
* When using Translation, we probably want to add locale in the query for the filterBy/orderBy to work
11-
* ex.: users(first: 10, offset: 0, locale: "en-CA", filterBy: [{field: name, operator: EQ, value:"John"}]) {
10+
* When using Translation, we probably want to add locale as a query parameter for the filterBy/orderBy to work
11+
* ex.: users(first: 10, offset: 0, locale: "en-CA", filterBy: [{field: name, operator: EQ, value:"John"}]) { }
1212
*/
1313
addLocaleIntoQuery?: boolean;
1414

15+
/**
16+
* Add the Current User List Separator to the result query (in English the separator is comma ",").
17+
* This is useful to set the "delimiter" property when using Export CSV, for example French uses semicolon ";" as a delimiter/separator
18+
*/
19+
addListSeparator?: boolean;
20+
1521
/** What is the dataset, this is required for the GraphQL query to be built */
1622
datasetName?: string;
1723

src/app/modules/angular-slickgrid/services/__tests__/export.service.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ describe('ExportService', () => {
250250
describe('startDownloadFile call after all private methods ran ', () => {
251251
let mockCollection: any[];
252252

253+
beforeEach(() => {
254+
mockGridOptions.exportOptions = { delimiterOverride: '' };
255+
});
256+
253257
it(`should have the Order exported correctly with multiple formatters which have 1 of them returning an object with a text property (instead of simple string)`, (done) => {
254258
mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }];
255259
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
@@ -274,6 +278,31 @@ describe('ExportService', () => {
274278
});
275279
});
276280

281+
it(`should have the Order exported correctly with multiple formatters and use a different delimiter when "delimiterOverride" is provided`, (done) => {
282+
mockGridOptions.exportOptions = { delimiterOverride: DelimiterType.doubleSemicolon };
283+
mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }];
284+
jest.spyOn(dataViewStub, 'getLength').mockReturnValue(mockCollection.length);
285+
jest.spyOn(dataViewStub, 'getItem').mockReturnValue(null).mockReturnValueOnce(mockCollection[0]);
286+
const spyOnAfter = jest.spyOn(service.onGridAfterExportToFile, 'next');
287+
const spyUrlCreate = jest.spyOn(URL, 'createObjectURL');
288+
const spyDownload = jest.spyOn(service, 'startDownloadFile');
289+
290+
const optionExpectation = { filename: 'export.csv', format: 'csv', useUtf8WithBom: false };
291+
const contentExpectation =
292+
`"User Id";;"FirstName";;"LastName";;"Position";;"Order"
293+
="1E06";;"John";;"Z";;"SALES_REP";;"<b>10</b>"`;
294+
295+
service.init(gridStub, dataViewStub);
296+
service.exportToFile(mockExportCsvOptions);
297+
298+
setTimeout(() => {
299+
expect(spyOnAfter).toHaveBeenCalledWith(optionExpectation);
300+
expect(spyUrlCreate).toHaveBeenCalledWith(mockCsvBlob);
301+
expect(spyDownload).toHaveBeenCalledWith({ ...optionExpectation, content: removeMultipleSpaces(contentExpectation) });
302+
done();
303+
});
304+
});
305+
277306
it(`should have the UserId escape with equal sign showing as prefix, to avoid Excel casting the value 1E06 to 1 exponential 6,
278307
when "exportCsvForceToKeepAsString" is enable in its column definition`, (done) => {
279308
mockCollection = [{ id: 0, userId: '1E06', firstName: 'John', lastName: 'Z', position: 'SALES_REP', order: 10 }];

src/app/modules/angular-slickgrid/services/__tests__/graphql.service.spec.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,16 @@ describe('GraphqlService', () => {
243243
expect(removeSpaces(query)).toBe(removeSpaces(expectation));
244244
});
245245

246+
it('should include "listSeparator" in the query string when option "addListSeparator" is enabled', () => {
247+
const expectation = `query{ users(first:10, offset:0){ listSeparator, totalCount, nodes{ id, field1, field2 }}}`;
248+
const columns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
249+
250+
service.init({ datasetName: 'users', columnDefinitions: columns, addListSeparator: true }, paginationOptions, gridStub);
251+
const query = service.buildQuery();
252+
253+
expect(removeSpaces(query)).toBe(removeSpaces(expectation));
254+
});
255+
246256
it('should include default locale "en" in the query string when option "addLocaleIntoQuery" is enabled and i18n is not defined', () => {
247257
const expectation = `query{ users(first:10, offset:0, locale: "en"){ totalCount, nodes{ id, field1, field2 }}}`;
248258
const columns = [{ id: 'field1', field: 'field1', width: 100 }, { id: 'field2', field: 'field2', width: 100 }];
@@ -415,6 +425,7 @@ describe('GraphqlService', () => {
415425
});
416426

417427
it('should return a query with the new filter', () => {
428+
serviceOptions.addListSeparator = false;
418429
const expectation = `query{users(first:10, offset:0, filterBy:[{field:gender, operator:EQ, value:"female"}]) { totalCount,nodes{ id,field1,field2 } }}`;
419430
const querySpy = jest.spyOn(service, 'buildQuery');
420431
const resetSpy = jest.spyOn(service, 'resetPaginationOptions');
@@ -441,9 +452,10 @@ describe('GraphqlService', () => {
441452
});
442453

443454
it('should return a query with a new filter when previous filters exists', () => {
455+
serviceOptions.addListSeparator = true;
444456
const expectation = `query{users(first:10, offset:0,
445457
filterBy:[{field:gender, operator:EQ, value:"female"}, {field:firstName, operator:StartsWith, value:"John"}])
446-
{ totalCount,nodes{ id,field1,field2 } }}`;
458+
{ listSeparator, totalCount,nodes{ id,field1,field2 } }}`;
447459
const querySpy = jest.spyOn(service, 'buildQuery');
448460
const resetSpy = jest.spyOn(service, 'resetPaginationOptions');
449461
const mockColumn = { id: 'gender', field: 'gender' } as Column;
@@ -476,6 +488,7 @@ describe('GraphqlService', () => {
476488

477489
describe('processOnPaginationChanged method', () => {
478490
it('should return a query with the new pagination', () => {
491+
serviceOptions.addListSeparator = false;
479492
const expectation = `query{users(first:20, offset:40) { totalCount,nodes { id, field1, field2 }}}`;
480493
const querySpy = jest.spyOn(service, 'buildQuery');
481494

@@ -489,7 +502,8 @@ describe('GraphqlService', () => {
489502
});
490503

491504
it('should return a query with the new pagination and use pagination size options that was passed to service options when it is not provided as argument to "processOnPaginationChanged"', () => {
492-
const expectation = `query{users(first:10, offset:20) { totalCount,nodes { id, field1, field2 }}}`;
505+
serviceOptions.addListSeparator = true;
506+
const expectation = `query{users(first:10, offset:20) { listSeparator, totalCount,nodes { id, field1, field2 }}}`;
493507
const querySpy = jest.spyOn(service, 'buildQuery');
494508

495509
service.init(serviceOptions, paginationOptions, gridStub);
@@ -519,6 +533,7 @@ describe('GraphqlService', () => {
519533

520534
describe('processOnSortChanged method', () => {
521535
it('should return a query with the new sorting when using single sort', () => {
536+
serviceOptions.addListSeparator = false;
522537
const expectation = `query{ users(first:10, offset:0, orderBy:[{field:gender, direction: DESC}]) { totalCount,nodes{ id,field1,field2 } }}`;
523538
const querySpy = jest.spyOn(service, 'buildQuery');
524539
const mockColumn = { id: 'gender', field: 'gender' } as Column;
@@ -532,9 +547,10 @@ describe('GraphqlService', () => {
532547
});
533548

534549
it('should return a query with the multiple new sorting when using multiColumnSort', () => {
550+
serviceOptions.addListSeparator = true;
535551
const expectation = `query{ users(first:10, offset:0,
536552
orderBy:[{field:gender, direction: DESC}, {field:firstName, direction: ASC}]) {
537-
totalCount,nodes{ id,field1,field2 } }}`;
553+
listSeparator, totalCount,nodes{ id,field1,field2 } }}`;
538554
const querySpy = jest.spyOn(service, 'buildQuery');
539555
const mockColumn = { id: 'gender', field: 'gender' } as Column;
540556
const mockColumnName = { id: 'firstName', field: 'firstName' } as Column;
@@ -553,6 +569,7 @@ describe('GraphqlService', () => {
553569
describe('updateFilters method', () => {
554570
beforeEach(() => {
555571
serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'name', field: 'name' }];
572+
serviceOptions.addListSeparator = false;
556573
});
557574

558575
it('should throw an error when filter columnId is not found to be part of the column definitions', () => {
@@ -569,7 +586,9 @@ describe('GraphqlService', () => {
569586
});
570587

571588
it('should return a query with the new filter when filters are passed as a filter trigger by a filter event and is of type ColumnFilters', () => {
572-
const expectation = `query{users(first:10, offset:0, filterBy:[{field:gender, operator:EQ, value:"female"}]) { totalCount,nodes{ id,company,gender,name } }}`;
589+
serviceOptions.addListSeparator = true;
590+
const expectation = `query{users(first:10, offset:0, filterBy:[{field:gender, operator:EQ, value:"female"}]) {
591+
listSeparator, totalCount,nodes{ id,company,gender,name } }}`;
573592
const mockColumn = { id: 'gender', field: 'gender' } as Column;
574593
const mockColumnFilters = {
575594
gender: { columnId: 'gender', columnDef: mockColumn, searchTerms: ['female'], operator: 'EQ' },
@@ -913,7 +932,8 @@ describe('GraphqlService', () => {
913932
});
914933

915934
it('should return a query without any sorting after clearFilters was called', () => {
916-
const expectation = `query{ users(first:10,offset:0) { totalCount,nodes{id, company, gender,name} }}`;
935+
serviceOptions.addListSeparator = true;
936+
const expectation = `query{ users(first:10,offset:0) { listSeparator, totalCount, nodes {id, company, gender,name} }}`;
917937
const mockColumn = { id: 'gender', field: 'gender' } as Column;
918938
const mockColumnFilters = {
919939
gender: { columnId: 'gender', columnDef: mockColumn, searchTerms: ['female'], operator: 'EQ' },
@@ -933,10 +953,13 @@ describe('GraphqlService', () => {
933953
describe('presets', () => {
934954
beforeEach(() => {
935955
serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'duration', field: 'duration' }, { id: 'startDate', field: 'startDate' }];
956+
serviceOptions.addListSeparator = false;
936957
});
937958

938959
it('should return a query with search having a range of exclusive numbers when the search value contains 2 (..) to represent a range of numbers', () => {
939-
const expectation = `query{users(first:10, offset:0, filterBy:[{field:duration, operator:GT, value:"2"}, {field:duration, operator:LT, value:"33"}]) { totalCount,nodes{ id,company,gender,duration,startDate } }}`;
960+
serviceOptions.addListSeparator = true;
961+
const expectation = `query{users(first:10, offset:0, filterBy:[{field:duration, operator:GT, value:"2"}, {field:duration, operator:LT, value:"33"}]) {
962+
listSeparator, totalCount,nodes{ id,company,gender,duration,startDate } }}`;
940963
const presetFilters = [
941964
{ columnId: 'duration', searchTerms: ['2..33'] },
942965
] as CurrentFilter[];
@@ -1044,12 +1067,14 @@ describe('GraphqlService', () => {
10441067
describe('updateSorters method', () => {
10451068
beforeEach(() => {
10461069
serviceOptions.columnDefinitions = [{ id: 'company', field: 'company' }, { id: 'gender', field: 'gender' }, { id: 'name', field: 'name' }];
1070+
serviceOptions.addListSeparator = false;
10471071
});
10481072

10491073
it('should return a query with the multiple new sorting when using multiColumnSort', () => {
1074+
serviceOptions.addListSeparator = true;
10501075
const expectation = `query{ users(first:10, offset:0,
10511076
orderBy:[{field:gender, direction: DESC}, {field:firstName, direction: ASC}]) {
1052-
totalCount,nodes{ id, company, gender, name } }}`;
1077+
listSeparator, totalCount,nodes{ id, company, gender, name } }}`;
10531078
const mockColumnSort = [
10541079
{ columnId: 'gender', sortCol: { id: 'gender', field: 'gender' }, sortAsc: false },
10551080
{ columnId: 'firstName', sortCol: { id: 'firstName', field: 'firstName' }, sortAsc: true }
@@ -1136,8 +1161,9 @@ describe('GraphqlService', () => {
11361161
});
11371162

11381163
it('should return a query without any sorting after clearSorters was called', () => {
1164+
serviceOptions.addListSeparator = true;
11391165
const expectation = `query { users(first:10, offset:0) {
1140-
totalCount, nodes { id,company,gender,name }}}`;
1166+
listSeparator, totalCount, nodes { id,company,gender,name }}}`;
11411167
const mockColumnSort = [
11421168
{ columnId: 'gender', sortCol: { id: 'gender', field: 'gender' }, sortAsc: false },
11431169
{ columnId: 'firstName', sortCol: { id: 'firstName', field: 'firstName' }, sortAsc: true }

0 commit comments

Comments
 (0)