@slickgrid-universal/graphql 2.5.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/commonjs/index.js +22 -22
  2. package/dist/commonjs/interfaces/graphqlCursorPaginationOption.interface.js +2 -2
  3. package/dist/commonjs/interfaces/graphqlDatasetFilter.interface.js +2 -2
  4. package/dist/commonjs/interfaces/graphqlFilteringOption.interface.js +2 -2
  5. package/dist/commonjs/interfaces/graphqlPaginatedResult.interface.js +2 -2
  6. package/dist/commonjs/interfaces/graphqlPaginationOption.interface.js +2 -2
  7. package/dist/commonjs/interfaces/graphqlResult.interface.js +2 -2
  8. package/dist/commonjs/interfaces/graphqlServiceApi.interface.js +2 -2
  9. package/dist/commonjs/interfaces/graphqlServiceOption.interface.js +2 -2
  10. package/dist/commonjs/interfaces/graphqlSortingOption.interface.js +2 -2
  11. package/dist/commonjs/interfaces/index.js +26 -26
  12. package/dist/commonjs/interfaces/queryArgument.interface.js +2 -2
  13. package/dist/commonjs/services/graphql.service.js +584 -584
  14. package/dist/commonjs/services/graphqlQueryBuilder.js +137 -137
  15. package/dist/commonjs/services/index.js +20 -20
  16. package/dist/esm/index.js +3 -3
  17. package/dist/esm/interfaces/graphqlCursorPaginationOption.interface.js +1 -1
  18. package/dist/esm/interfaces/graphqlDatasetFilter.interface.js +1 -1
  19. package/dist/esm/interfaces/graphqlFilteringOption.interface.js +1 -1
  20. package/dist/esm/interfaces/graphqlPaginatedResult.interface.js +1 -1
  21. package/dist/esm/interfaces/graphqlPaginationOption.interface.js +1 -1
  22. package/dist/esm/interfaces/graphqlResult.interface.js +1 -1
  23. package/dist/esm/interfaces/graphqlServiceApi.interface.js +1 -1
  24. package/dist/esm/interfaces/graphqlServiceOption.interface.js +1 -1
  25. package/dist/esm/interfaces/graphqlSortingOption.interface.js +1 -1
  26. package/dist/esm/interfaces/index.js +10 -10
  27. package/dist/esm/interfaces/queryArgument.interface.js +1 -1
  28. package/dist/esm/services/graphql.service.js +582 -582
  29. package/dist/esm/services/graphqlQueryBuilder.js +134 -134
  30. package/dist/esm/services/index.js +2 -2
  31. package/dist/tsconfig.tsbuildinfo +1 -0
  32. package/dist/{commonjs → types}/index.d.ts +4 -3
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/{esm → types}/interfaces/graphqlCursorPaginationOption.interface.d.ts +11 -10
  35. package/dist/types/interfaces/graphqlCursorPaginationOption.interface.d.ts.map +1 -0
  36. package/dist/{commonjs → types}/interfaces/graphqlDatasetFilter.interface.d.ts +13 -12
  37. package/dist/types/interfaces/graphqlDatasetFilter.interface.d.ts.map +1 -0
  38. package/dist/{esm → types}/interfaces/graphqlFilteringOption.interface.d.ts +10 -9
  39. package/dist/types/interfaces/graphqlFilteringOption.interface.d.ts.map +1 -0
  40. package/dist/{commonjs → types}/interfaces/graphqlPaginatedResult.interface.d.ts +30 -29
  41. package/dist/types/interfaces/graphqlPaginatedResult.interface.d.ts.map +1 -0
  42. package/dist/{commonjs → types}/interfaces/graphqlPaginationOption.interface.d.ts +6 -5
  43. package/dist/types/interfaces/graphqlPaginationOption.interface.d.ts.map +1 -0
  44. package/dist/{commonjs → types}/interfaces/graphqlResult.interface.d.ts +9 -8
  45. package/dist/types/interfaces/graphqlResult.interface.d.ts.map +1 -0
  46. package/dist/{esm → types}/interfaces/graphqlServiceApi.interface.d.ts +23 -22
  47. package/dist/types/interfaces/graphqlServiceApi.interface.d.ts.map +1 -0
  48. package/dist/{esm → types}/interfaces/graphqlServiceOption.interface.d.ts +35 -34
  49. package/dist/types/interfaces/graphqlServiceOption.interface.d.ts.map +1 -0
  50. package/dist/{esm → types}/interfaces/graphqlSortingOption.interface.d.ts +6 -5
  51. package/dist/types/interfaces/graphqlSortingOption.interface.d.ts.map +1 -0
  52. package/dist/{esm → types}/interfaces/index.d.ts +11 -10
  53. package/dist/types/interfaces/index.d.ts.map +1 -0
  54. package/dist/{commonjs → types}/interfaces/queryArgument.interface.d.ts +5 -4
  55. package/dist/types/interfaces/queryArgument.interface.d.ts.map +1 -0
  56. package/dist/{commonjs → types}/services/graphql.service.d.ts +100 -99
  57. package/dist/types/services/graphql.service.d.ts.map +1 -0
  58. package/dist/{esm → types}/services/graphqlQueryBuilder.d.ts +39 -38
  59. package/dist/types/services/graphqlQueryBuilder.d.ts.map +1 -0
  60. package/dist/{esm → types}/services/index.d.ts +3 -2
  61. package/dist/types/services/index.d.ts.map +1 -0
  62. package/package.json +7 -7
  63. package/dist/commonjs/interfaces/graphqlCursorPaginationOption.interface.d.ts +0 -10
  64. package/dist/commonjs/interfaces/graphqlFilteringOption.interface.d.ts +0 -9
  65. package/dist/commonjs/interfaces/graphqlServiceApi.interface.d.ts +0 -22
  66. package/dist/commonjs/interfaces/graphqlServiceOption.interface.d.ts +0 -34
  67. package/dist/commonjs/interfaces/graphqlSortingOption.interface.d.ts +0 -5
  68. package/dist/commonjs/interfaces/index.d.ts +0 -10
  69. package/dist/commonjs/services/graphqlQueryBuilder.d.ts +0 -38
  70. package/dist/commonjs/services/index.d.ts +0 -2
  71. package/dist/esm/index.d.ts +0 -3
  72. package/dist/esm/interfaces/graphqlDatasetFilter.interface.d.ts +0 -12
  73. package/dist/esm/interfaces/graphqlPaginatedResult.interface.d.ts +0 -29
  74. package/dist/esm/interfaces/graphqlPaginationOption.interface.d.ts +0 -5
  75. package/dist/esm/interfaces/graphqlResult.interface.d.ts +0 -8
  76. package/dist/esm/interfaces/queryArgument.interface.d.ts +0 -4
  77. package/dist/esm/services/graphql.service.d.ts +0 -99
@@ -1,585 +1,585 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GraphqlService = void 0;
4
- const common_1 = require("@slickgrid-universal/common");
5
- const graphqlQueryBuilder_1 = require("./graphqlQueryBuilder");
6
- const DEFAULT_ITEMS_PER_PAGE = 25;
7
- const DEFAULT_PAGE_SIZE = 20;
8
- class GraphqlService {
9
- constructor() {
10
- this._currentFilters = [];
11
- this._currentPagination = null;
12
- this._currentSorters = [];
13
- this._datasetIdPropName = 'id';
14
- this.defaultPaginationOptions = {
15
- first: DEFAULT_ITEMS_PER_PAGE,
16
- offset: 0
17
- };
18
- }
19
- /** Getter for the Column Definitions */
20
- get columnDefinitions() {
21
- return this._columnDefinitions;
22
- }
23
- /** Getter for the Grid Options pulled through the Grid Object */
24
- get _gridOptions() {
25
- var _a;
26
- return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions) ? this._grid.getOptions() : {};
27
- }
28
- /** Initialization of the service, which acts as a constructor */
29
- init(serviceOptions, pagination, grid, sharedService) {
30
- var _a, _b;
31
- this._grid = grid;
32
- this.options = serviceOptions || { datasetName: '' };
33
- this.pagination = pagination;
34
- this._datasetIdPropName = this._gridOptions.datasetIdPropertyName || 'id';
35
- if (grid && grid.getColumns) {
36
- this._columnDefinitions = (_b = (_a = sharedService === null || sharedService === void 0 ? void 0 : sharedService.allColumns) !== null && _a !== void 0 ? _a : grid.getColumns()) !== null && _b !== void 0 ? _b : [];
37
- }
38
- }
39
- /**
40
- * Build the GraphQL query, since the service include/exclude cursor, the output query will be different.
41
- * @param serviceOptions GraphqlServiceOption
42
- */
43
- buildQuery() {
44
- var _a, _b;
45
- if (!this.options || !this.options.datasetName || !Array.isArray(this._columnDefinitions)) {
46
- throw new Error('GraphQL Service requires the "datasetName" property to properly build the GraphQL query');
47
- }
48
- // get the column definitions and exclude some if they were tagged as excluded
49
- let columnDefinitions = this._columnDefinitions || [];
50
- columnDefinitions = columnDefinitions.filter((column) => !column.excludeFromQuery);
51
- const queryQb = new graphqlQueryBuilder_1.default('query');
52
- const datasetQb = new graphqlQueryBuilder_1.default(this.options.datasetName);
53
- const nodesQb = new graphqlQueryBuilder_1.default('nodes');
54
- // get all the columnds Ids for the filters to work
55
- const columnIds = [];
56
- if (columnDefinitions && Array.isArray(columnDefinitions)) {
57
- for (const column of columnDefinitions) {
58
- columnIds.push(column.field);
59
- // if extra "fields" are passed, also push them to columnIds
60
- if (column.fields) {
61
- columnIds.push(...column.fields);
62
- }
63
- }
64
- }
65
- // Slickgrid also requires the "id" field to be part of DataView
66
- // add it to the GraphQL query if it wasn't already part of the list
67
- if (columnIds.indexOf(this._datasetIdPropName) === -1) {
68
- columnIds.unshift(this._datasetIdPropName);
69
- }
70
- const columnsQuery = this.buildFilterQuery(columnIds);
71
- let graphqlNodeFields = [];
72
- if (this._gridOptions.enablePagination !== false) {
73
- if (this.options.isWithCursor) {
74
- // ...pageInfo { hasNextPage, endCursor }, edges { cursor, node { _columns_ } }, totalCount: 100
75
- const edgesQb = new graphqlQueryBuilder_1.default('edges');
76
- const pageInfoQb = new graphqlQueryBuilder_1.default('pageInfo');
77
- pageInfoQb.find('hasNextPage', 'hasPreviousPage', 'endCursor', 'startCursor');
78
- nodesQb.find(columnsQuery);
79
- edgesQb.find(['cursor']);
80
- graphqlNodeFields = ['totalCount', nodesQb, pageInfoQb, edgesQb];
81
- }
82
- else {
83
- // ...nodes { _columns_ }, totalCount: 100
84
- nodesQb.find(columnsQuery);
85
- graphqlNodeFields = ['totalCount', nodesQb];
86
- }
87
- // all properties to be returned by the query
88
- datasetQb.find(graphqlNodeFields);
89
- }
90
- else {
91
- // include all columns to be returned
92
- datasetQb.find(columnsQuery);
93
- }
94
- // add dataset filters, could be Pagination and SortingFilters and/or FieldFilters
95
- let datasetFilters = {};
96
- // only add pagination if it's enabled in the grid options
97
- if (this._gridOptions.enablePagination !== false) {
98
- datasetFilters = {
99
- ...this.options.paginationOptions,
100
- first: ((this.options.paginationOptions && this.options.paginationOptions.first) ? this.options.paginationOptions.first : ((this.pagination && this.pagination.pageSize) ? this.pagination.pageSize : null)) || this.defaultPaginationOptions.first
101
- };
102
- if (!this.options.isWithCursor) {
103
- const paginationOptions = (_a = this.options) === null || _a === void 0 ? void 0 : _a.paginationOptions;
104
- datasetFilters.offset = (paginationOptions === null || paginationOptions === void 0 ? void 0 : paginationOptions.hasOwnProperty('offset')) ? +paginationOptions['offset'] : 0;
105
- }
106
- }
107
- if (this.options.sortingOptions && Array.isArray(this.options.sortingOptions) && this.options.sortingOptions.length > 0) {
108
- // orderBy: [{ field:x, direction: 'ASC' }]
109
- datasetFilters.orderBy = this.options.sortingOptions;
110
- }
111
- if (this.options.filteringOptions && Array.isArray(this.options.filteringOptions) && this.options.filteringOptions.length > 0) {
112
- // filterBy: [{ field: date, operator: '>', value: '2000-10-10' }]
113
- datasetFilters.filterBy = this.options.filteringOptions;
114
- }
115
- if (this.options.addLocaleIntoQuery) {
116
- // first: 20, ... locale: "en-CA"
117
- datasetFilters.locale = ((_b = this._gridOptions.translater) === null || _b === void 0 ? void 0 : _b.getCurrentLanguage()) || this._gridOptions.locale || 'en';
118
- }
119
- if (this.options.extraQueryArguments) {
120
- // first: 20, ... userId: 123
121
- for (const queryArgument of this.options.extraQueryArguments) {
122
- datasetFilters[queryArgument.field] = queryArgument.value;
123
- }
124
- }
125
- // with pagination:: query { users(first: 20, offset: 0, orderBy: [], filterBy: []) { totalCount: 100, nodes: { _columns_ }}}
126
- // without pagination:: query { users(orderBy: [], filterBy: []) { _columns_ }}
127
- datasetQb.filter(datasetFilters);
128
- queryQb.find(datasetQb);
129
- const enumSearchProperties = ['direction:', 'field:', 'operator:'];
130
- return this.trimDoubleQuotesOnEnumField(queryQb.toString(), enumSearchProperties, this.options.keepArgumentFieldDoubleQuotes || false);
131
- }
132
- /**
133
- * From an input array of strings, we want to build a GraphQL query string.
134
- * The process has to take the dot notation and parse it into a valid GraphQL query
135
- * Following this SO answer https://stackoverflow.com/a/47705476/1212166
136
- *
137
- * INPUT
138
- * ['firstName', 'lastName', 'billing.address.street', 'billing.address.zip']
139
- * OUTPUT
140
- * firstName, lastName, billing{address{street, zip}}
141
- * @param inputArray
142
- */
143
- buildFilterQuery(inputArray) {
144
- const set = (o = {}, a) => {
145
- const k = a.shift();
146
- o[k] = a.length ? set(o[k], a) : null;
147
- return o;
148
- };
149
- const output = inputArray.reduce((o, a) => set(o, a.split('.')), {});
150
- return JSON.stringify(output)
151
- .replace(/\"|\:|null/g, '')
152
- .replace(/^\{/, '')
153
- .replace(/\}$/, '');
154
- }
155
- clearFilters() {
156
- this._currentFilters = [];
157
- this.updateOptions({ filteringOptions: [] });
158
- }
159
- clearSorters() {
160
- this._currentSorters = [];
161
- this.updateOptions({ sortingOptions: [] });
162
- }
163
- /**
164
- * Get an initialization of Pagination options
165
- * @return Pagination Options
166
- */
167
- getInitPaginationOptions() {
168
- var _a;
169
- const paginationFirst = this.pagination ? this.pagination.pageSize : DEFAULT_ITEMS_PER_PAGE;
170
- return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.isWithCursor) ? { first: paginationFirst } : { first: paginationFirst, offset: 0 };
171
- }
172
- /** Get the GraphQL dataset name */
173
- getDatasetName() {
174
- var _a;
175
- return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.datasetName) || '';
176
- }
177
- /** Get the Filters that are currently used by the grid */
178
- getCurrentFilters() {
179
- return this._currentFilters;
180
- }
181
- /** Get the Pagination that is currently used by the grid */
182
- getCurrentPagination() {
183
- return this._currentPagination;
184
- }
185
- /** Get the Sorters that are currently used by the grid */
186
- getCurrentSorters() {
187
- return this._currentSorters;
188
- }
189
- /*
190
- * Reset the pagination options
191
- */
192
- resetPaginationOptions() {
193
- let paginationOptions;
194
- if (this.options && this.options.isWithCursor) {
195
- // first, last, after, before
196
- paginationOptions = {
197
- after: '',
198
- before: undefined,
199
- last: undefined
200
- };
201
- }
202
- else {
203
- // first, last, offset
204
- paginationOptions = ((this.options && this.options.paginationOptions) || this.getInitPaginationOptions());
205
- paginationOptions.offset = 0;
206
- }
207
- // save current pagination as Page 1 and page size as "first" set size
208
- this._currentPagination = {
209
- pageNumber: 1,
210
- pageSize: paginationOptions.first || DEFAULT_PAGE_SIZE
211
- };
212
- // unless user specifically set "enablePagination" to False, we'll update pagination options in every other cases
213
- if (this._gridOptions && (this._gridOptions.enablePagination || !this._gridOptions.hasOwnProperty('enablePagination'))) {
214
- this.updateOptions({ paginationOptions });
215
- }
216
- }
217
- updateOptions(serviceOptions) {
218
- this.options = { ...this.options, ...serviceOptions };
219
- }
220
- /*
221
- * FILTERING
222
- */
223
- processOnFilterChanged(_event, args) {
224
- const gridOptions = this._gridOptions;
225
- const backendApi = gridOptions.backendServiceApi;
226
- if (backendApi === undefined) {
227
- throw new Error('Something went wrong in the GraphqlService, "backendServiceApi" is not initialized');
228
- }
229
- // keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
230
- this._currentFilters = this.castFilterToColumnFilters(args.columnFilters);
231
- if (!args || !args.grid) {
232
- throw new Error('Something went wrong when trying create the GraphQL Backend Service, it seems that "args" is not populated correctly');
233
- }
234
- // loop through all columns to inspect filters & set the query
235
- this.updateFilters(args.columnFilters, false);
236
- this.resetPaginationOptions();
237
- return this.buildQuery();
238
- }
239
- /*
240
- * PAGINATION
241
- * With cursor, the query can have 4 arguments (first, after, last, before), for example:
242
- * users (first:20, after:"YXJyYXljb25uZWN0aW9uOjM=") {
243
- * totalCount
244
- * pageInfo {
245
- * hasNextPage
246
- * hasPreviousPage
247
- * endCursor
248
- * startCursor
249
- * }
250
- * edges {
251
- * cursor
252
- * node {
253
- * name
254
- * gender
255
- * }
256
- * }
257
- * }
258
- * Without cursor, the query can have 3 arguments (first, last, offset), for example:
259
- * users (first:20, offset: 10) {
260
- * totalCount
261
- * nodes {
262
- * name
263
- * gender
264
- * }
265
- * }
266
- */
267
- processOnPaginationChanged(_event, args) {
268
- const pageSize = +(args.pageSize || ((this.pagination) ? this.pagination.pageSize : DEFAULT_PAGE_SIZE));
269
- this.updatePagination(args.newPage, pageSize);
270
- // build the GraphQL query which we will use in the WebAPI callback
271
- return this.buildQuery();
272
- }
273
- /*
274
- * SORTING
275
- * we will use sorting as per a Facebook suggestion on a Github issue (with some small changes)
276
- * https://github.com/graphql/graphql-relay-js/issues/20#issuecomment-220494222
277
- *
278
- * users (first: 20, offset: 10, orderBy: [{field: lastName, direction: ASC}, {field: firstName, direction: DESC}]) {
279
- * totalCount
280
- * nodes {
281
- * name
282
- * gender
283
- * }
284
- * }
285
- */
286
- processOnSortChanged(_event, args) {
287
- const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ columnId: args.sortCol.id, sortCol: args.sortCol, sortAsc: args.sortAsc });
288
- // loop through all columns to inspect sorters & set the query
289
- this.updateSorters(sortColumns);
290
- // build the GraphQL query which we will use in the WebAPI callback
291
- return this.buildQuery();
292
- }
293
- /**
294
- * loop through all columns to inspect filters & update backend service filteringOptions
295
- * @param columnFilters
296
- */
297
- updateFilters(columnFilters, isUpdatedByPresetOrDynamically) {
298
- var _a, _b, _c, _d;
299
- const searchByArray = [];
300
- let searchValue;
301
- // on filter preset load, we need to keep current filters
302
- if (isUpdatedByPresetOrDynamically) {
303
- this._currentFilters = this.castFilterToColumnFilters(columnFilters);
304
- }
305
- for (const columnId in columnFilters) {
306
- if (columnFilters.hasOwnProperty(columnId)) {
307
- const columnFilter = columnFilters[columnId];
308
- // if user defined some "presets", then we need to find the filters from the column definitions instead
309
- let columnDef;
310
- if (isUpdatedByPresetOrDynamically && Array.isArray(this._columnDefinitions)) {
311
- columnDef = this._columnDefinitions.find((column) => column.id === columnFilter.columnId);
312
- }
313
- else {
314
- columnDef = columnFilter.columnDef;
315
- }
316
- if (!columnDef) {
317
- throw new Error('[GraphQL Service]: Something went wrong in trying to get the column definition of the specified filter (or preset filters). Did you make a typo on the filter columnId?');
318
- }
319
- const fieldName = ((_a = columnDef.filter) === null || _a === void 0 ? void 0 : _a.queryField) || columnDef.queryFieldFilter || columnDef.queryField || columnDef.field || columnDef.name || '';
320
- const fieldType = columnDef.type || common_1.FieldType.string;
321
- let searchTerms = (_b = columnFilter === null || columnFilter === void 0 ? void 0 : columnFilter.searchTerms) !== null && _b !== void 0 ? _b : [];
322
- let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : '';
323
- if (typeof fieldSearchValue === 'undefined') {
324
- fieldSearchValue = '';
325
- }
326
- if (!fieldName) {
327
- throw new Error(`GraphQL filter could not find the field name to query the search, your column definition must include a valid "field" or "name" (optionally you can also use the "queryfield").`);
328
- }
329
- fieldSearchValue = (fieldSearchValue === undefined || fieldSearchValue === null) ? '' : `${fieldSearchValue}`; // make sure it's a string
330
- // run regex to find possible filter operators unless the user disabled the feature
331
- const autoParseInputFilterOperator = (_c = columnDef.autoParseInputFilterOperator) !== null && _c !== void 0 ? _c : this._gridOptions.autoParseInputFilterOperator;
332
- const matches = autoParseInputFilterOperator !== false
333
- ? fieldSearchValue.match(/^([<>!=\*]{0,2})(.*[^<>!=\*])([\*]?)$/) // group 1: Operator, 2: searchValue, 3: last char is '*' (meaning starts with, ex.: abc*)
334
- : [fieldSearchValue, '', fieldSearchValue, '']; // when parsing is disabled, we'll only keep the search value in the index 2 to make it easy for code reuse
335
- let operator = columnFilter.operator || (matches === null || matches === void 0 ? void 0 : matches[1]) || '';
336
- searchValue = (matches === null || matches === void 0 ? void 0 : matches[2]) || '';
337
- const lastValueChar = (matches === null || matches === void 0 ? void 0 : matches[3]) || (operator === '*z' ? '*' : '');
338
- // no need to query if search value is empty
339
- if (fieldName && searchValue === '' && searchTerms.length === 0) {
340
- continue;
341
- }
342
- if (Array.isArray(searchTerms) && searchTerms.length === 1 && typeof searchTerms[0] === 'string' && searchTerms[0].indexOf('..') >= 0) {
343
- if (operator !== common_1.OperatorType.rangeInclusive && operator !== common_1.OperatorType.rangeExclusive) {
344
- operator = (_d = this._gridOptions.defaultFilterRangeOperator) !== null && _d !== void 0 ? _d : common_1.OperatorType.rangeInclusive;
345
- }
346
- searchTerms = searchTerms[0].split('..', 2);
347
- if (searchTerms[0] === '') {
348
- operator = operator === common_1.OperatorType.rangeInclusive ? '<=' : operator === common_1.OperatorType.rangeExclusive ? '<' : operator;
349
- searchTerms = searchTerms.slice(1);
350
- searchValue = searchTerms[0];
351
- }
352
- else if (searchTerms[1] === '') {
353
- operator = operator === common_1.OperatorType.rangeInclusive ? '>=' : operator === common_1.OperatorType.rangeExclusive ? '>' : operator;
354
- searchTerms = searchTerms.slice(0, 1);
355
- searchValue = searchTerms[0];
356
- }
357
- }
358
- if (typeof searchValue === 'string') {
359
- if (operator === '*' || operator === 'a*' || operator === '*z' || lastValueChar === '*') {
360
- operator = ((operator === '*' || operator === '*z') ? 'EndsWith' : 'StartsWith');
361
- }
362
- }
363
- // if we didn't find an Operator but we have a Column Operator inside the Filter (DOM Element), we should use its default Operator
364
- // multipleSelect is "IN", while singleSelect is "EQ", else don't map any operator
365
- if (!operator && columnDef.filter && columnDef.filter.operator) {
366
- operator = columnDef.filter.operator;
367
- }
368
- // No operator and 2 search terms should lead to default range operator.
369
- if (!operator && Array.isArray(searchTerms) && searchTerms.length === 2 && searchTerms[0] && searchTerms[1]) {
370
- operator = this._gridOptions.defaultFilterRangeOperator;
371
- }
372
- // Range with 1 searchterm should lead to equals for a date field.
373
- if ((operator === common_1.OperatorType.rangeInclusive || common_1.OperatorType.rangeExclusive) && Array.isArray(searchTerms) && searchTerms.length === 1 && fieldType === common_1.FieldType.date) {
374
- operator = common_1.OperatorType.equal;
375
- }
376
- // Normalize all search values
377
- searchValue = this.normalizeSearchValue(fieldType, searchValue);
378
- if (Array.isArray(searchTerms)) {
379
- searchTerms.forEach((_part, index) => {
380
- searchTerms[index] = this.normalizeSearchValue(fieldType, searchTerms[index]);
381
- });
382
- }
383
- // when having more than 1 search term (we need to create a CSV string for GraphQL "IN" or "NOT IN" filter search)
384
- if (searchTerms && searchTerms.length > 1 && (operator === 'IN' || operator === 'NIN' || operator === 'NOT_IN')) {
385
- searchValue = searchTerms.join(',');
386
- }
387
- else if (searchTerms && searchTerms.length === 2 && (operator === common_1.OperatorType.rangeExclusive || operator === common_1.OperatorType.rangeInclusive)) {
388
- searchByArray.push({ field: fieldName, operator: (operator === common_1.OperatorType.rangeInclusive ? 'GE' : 'GT'), value: searchTerms[0] });
389
- searchByArray.push({ field: fieldName, operator: (operator === common_1.OperatorType.rangeInclusive ? 'LE' : 'LT'), value: searchTerms[1] });
390
- continue;
391
- }
392
- // if we still don't have an operator find the proper Operator to use by it's field type
393
- if (!operator) {
394
- operator = (0, common_1.mapOperatorByFieldType)(fieldType);
395
- }
396
- // build the search array
397
- searchByArray.push({ field: fieldName, operator: (0, common_1.mapOperatorType)(operator), value: searchValue });
398
- }
399
- }
400
- // update the service options with filters for the buildQuery() to work later
401
- this.updateOptions({ filteringOptions: searchByArray });
402
- }
403
- /**
404
- * Update the pagination component with it's new page number and size
405
- * @param newPage
406
- * @param pageSize
407
- */
408
- updatePagination(newPage, pageSize) {
409
- this._currentPagination = {
410
- pageNumber: newPage,
411
- pageSize
412
- };
413
- let paginationOptions;
414
- if (this.options && this.options.isWithCursor) {
415
- paginationOptions = {
416
- first: pageSize
417
- };
418
- }
419
- else {
420
- paginationOptions = {
421
- first: pageSize,
422
- offset: (newPage > 1) ? ((newPage - 1) * pageSize) : 0 // recalculate offset but make sure the result is always over 0
423
- };
424
- }
425
- this.updateOptions({ paginationOptions });
426
- }
427
- /**
428
- * loop through all columns to inspect sorters & update backend service sortingOptions
429
- * @param columnFilters
430
- */
431
- updateSorters(sortColumns, presetSorters) {
432
- let currentSorters = [];
433
- const graphqlSorters = [];
434
- if (!sortColumns && presetSorters) {
435
- // make the presets the current sorters, also make sure that all direction are in uppercase for GraphQL
436
- currentSorters = presetSorters;
437
- currentSorters.forEach((sorter) => sorter.direction = sorter.direction.toUpperCase());
438
- // display the correct sorting icons on the UI, for that it requires (columnId, sortAsc) properties
439
- const tmpSorterArray = currentSorters.map((sorter) => {
440
- var _a;
441
- const columnDef = (_a = this._columnDefinitions) === null || _a === void 0 ? void 0 : _a.find((column) => column.id === sorter.columnId);
442
- graphqlSorters.push({
443
- field: columnDef ? ((columnDef.queryFieldSorter || columnDef.queryField || columnDef.field) + '') : (sorter.columnId + ''),
444
- direction: sorter.direction
445
- });
446
- // return only the column(s) found in the Column Definitions ELSE null
447
- if (columnDef) {
448
- return {
449
- columnId: sorter.columnId,
450
- sortAsc: sorter.direction.toUpperCase() === common_1.SortDirection.ASC
451
- };
452
- }
453
- return null;
454
- });
455
- // set the sort icons, but also make sure to filter out null values (that happens when columnDef is not found)
456
- if (Array.isArray(tmpSorterArray) && this._grid) {
457
- this._grid.setSortColumns(tmpSorterArray.filter(sorter => sorter) || []);
458
- }
459
- }
460
- else if (sortColumns && !presetSorters) {
461
- // build the orderBy array, it could be multisort, example
462
- // orderBy:[{field: lastName, direction: ASC}, {field: firstName, direction: DESC}]
463
- if (Array.isArray(sortColumns) && sortColumns.length > 0) {
464
- for (const column of sortColumns) {
465
- if (column && column.sortCol) {
466
- currentSorters.push({
467
- columnId: column.sortCol.id + '',
468
- direction: column.sortAsc ? common_1.SortDirection.ASC : common_1.SortDirection.DESC
469
- });
470
- const fieldName = (column.sortCol.queryFieldSorter || column.sortCol.queryField || column.sortCol.field || '') + '';
471
- if (fieldName) {
472
- graphqlSorters.push({
473
- field: fieldName,
474
- direction: column.sortAsc ? common_1.SortDirection.ASC : common_1.SortDirection.DESC
475
- });
476
- }
477
- }
478
- }
479
- }
480
- }
481
- // keep current Sorters and update the service options with the new sorting
482
- this._currentSorters = currentSorters;
483
- this.updateOptions({ sortingOptions: graphqlSorters });
484
- }
485
- /**
486
- * A function which takes an input string and removes double quotes only
487
- * on certain fields are identified as GraphQL enums (except fields with dot notation)
488
- * For example let say we identified ("direction:", "sort") as word which are GraphQL enum fields
489
- * then the result will be:
490
- * FROM
491
- * query { users (orderBy:[{field:"firstName", direction:"ASC"} }]) }
492
- * TO
493
- * query { users (orderBy:[{field: firstName, direction: ASC}})}
494
- *
495
- * EXCEPTIONS (fields with dot notation "." which are inside a "field:")
496
- * these fields will keep double quotes while everything else will be stripped of double quotes
497
- * query { users (orderBy:[{field:"billing.street.name", direction: "ASC"} }
498
- * TO
499
- * query { users (orderBy:[{field:"billing.street.name", direction: ASC}}
500
- * @param inputStr input string
501
- * @param enumSearchWords array of enum words to filter
502
- * @returns outputStr output string
503
- */
504
- trimDoubleQuotesOnEnumField(inputStr, enumSearchWords, keepArgumentFieldDoubleQuotes) {
505
- const patternWordInQuotes = `\s?((field:\s*)?".*?")`;
506
- let patternRegex = enumSearchWords.join(patternWordInQuotes + '|');
507
- patternRegex += patternWordInQuotes; // the last one should also have the pattern but without the pipe "|"
508
- // example with (field: & direction:): /field:s?(".*?")|direction:s?(".*?")/
509
- const reg = new RegExp(patternRegex, 'g');
510
- return inputStr.replace(reg, group1 => {
511
- // remove double quotes except when the string starts with a "field:"
512
- let removeDoubleQuotes = true;
513
- if (group1.startsWith('field:') && keepArgumentFieldDoubleQuotes) {
514
- removeDoubleQuotes = false;
515
- }
516
- const rep = removeDoubleQuotes ? group1.replace(/"/g, '') : group1;
517
- return rep;
518
- });
519
- }
520
- //
521
- // protected functions
522
- // -------------------
523
- /**
524
- * Cast provided filters (could be in multiple formats) into an array of CurrentFilter
525
- * @param columnFilters
526
- */
527
- castFilterToColumnFilters(columnFilters) {
528
- // keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
529
- const filtersArray = (typeof columnFilters === 'object') ? Object.keys(columnFilters).map(key => columnFilters[key]) : columnFilters;
530
- if (!Array.isArray(filtersArray)) {
531
- return [];
532
- }
533
- return filtersArray.map((filter) => {
534
- const tmpFilter = { columnId: filter.columnId || '' };
535
- if (filter.operator) {
536
- tmpFilter.operator = filter.operator;
537
- }
538
- if (filter.targetSelector) {
539
- tmpFilter.targetSelector = filter.targetSelector;
540
- }
541
- if (Array.isArray(filter.searchTerms)) {
542
- tmpFilter.searchTerms = filter.searchTerms;
543
- }
544
- return tmpFilter;
545
- });
546
- }
547
- /** Normalizes the search value according to field type. */
548
- normalizeSearchValue(fieldType, searchValue) {
549
- switch (fieldType) {
550
- case common_1.FieldType.date:
551
- case common_1.FieldType.string:
552
- case common_1.FieldType.text:
553
- case common_1.FieldType.readonly:
554
- if (typeof searchValue === 'string') {
555
- // escape single quotes by doubling them
556
- searchValue = searchValue.replace(/'/g, `''`);
557
- }
558
- break;
559
- case common_1.FieldType.integer:
560
- case common_1.FieldType.number:
561
- case common_1.FieldType.float:
562
- if (typeof searchValue === 'string') {
563
- // Parse a valid decimal from the string.
564
- // Replace double dots with single dots
565
- searchValue = searchValue.replace(/\.\./g, '.');
566
- // Remove a trailing dot
567
- searchValue = searchValue.replace(/\.+$/g, '');
568
- // Prefix a leading dot with 0
569
- searchValue = searchValue.replace(/^\.+/g, '0.');
570
- // Prefix leading dash dot with -0.
571
- searchValue = searchValue.replace(/^\-+\.+/g, '-0.');
572
- // Remove any non valid decimal characters from the search string
573
- searchValue = searchValue.replace(/(?!^\-)[^\d\.]/g, '');
574
- // if nothing left, search for 0
575
- if (searchValue === '' || searchValue === '-') {
576
- searchValue = '0';
577
- }
578
- }
579
- break;
580
- }
581
- return searchValue;
582
- }
583
- }
584
- exports.GraphqlService = GraphqlService;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphqlService = void 0;
4
+ const common_1 = require("@slickgrid-universal/common");
5
+ const graphqlQueryBuilder_1 = require("./graphqlQueryBuilder");
6
+ const DEFAULT_ITEMS_PER_PAGE = 25;
7
+ const DEFAULT_PAGE_SIZE = 20;
8
+ class GraphqlService {
9
+ constructor() {
10
+ this._currentFilters = [];
11
+ this._currentPagination = null;
12
+ this._currentSorters = [];
13
+ this._datasetIdPropName = 'id';
14
+ this.defaultPaginationOptions = {
15
+ first: DEFAULT_ITEMS_PER_PAGE,
16
+ offset: 0
17
+ };
18
+ }
19
+ /** Getter for the Column Definitions */
20
+ get columnDefinitions() {
21
+ return this._columnDefinitions;
22
+ }
23
+ /** Getter for the Grid Options pulled through the Grid Object */
24
+ get _gridOptions() {
25
+ var _a;
26
+ return ((_a = this._grid) === null || _a === void 0 ? void 0 : _a.getOptions) ? this._grid.getOptions() : {};
27
+ }
28
+ /** Initialization of the service, which acts as a constructor */
29
+ init(serviceOptions, pagination, grid, sharedService) {
30
+ var _a, _b;
31
+ this._grid = grid;
32
+ this.options = serviceOptions || { datasetName: '' };
33
+ this.pagination = pagination;
34
+ this._datasetIdPropName = this._gridOptions.datasetIdPropertyName || 'id';
35
+ if (grid && grid.getColumns) {
36
+ this._columnDefinitions = (_b = (_a = sharedService === null || sharedService === void 0 ? void 0 : sharedService.allColumns) !== null && _a !== void 0 ? _a : grid.getColumns()) !== null && _b !== void 0 ? _b : [];
37
+ }
38
+ }
39
+ /**
40
+ * Build the GraphQL query, since the service include/exclude cursor, the output query will be different.
41
+ * @param serviceOptions GraphqlServiceOption
42
+ */
43
+ buildQuery() {
44
+ var _a, _b;
45
+ if (!this.options || !this.options.datasetName || !Array.isArray(this._columnDefinitions)) {
46
+ throw new Error('GraphQL Service requires the "datasetName" property to properly build the GraphQL query');
47
+ }
48
+ // get the column definitions and exclude some if they were tagged as excluded
49
+ let columnDefinitions = this._columnDefinitions || [];
50
+ columnDefinitions = columnDefinitions.filter((column) => !column.excludeFromQuery);
51
+ const queryQb = new graphqlQueryBuilder_1.default('query');
52
+ const datasetQb = new graphqlQueryBuilder_1.default(this.options.datasetName);
53
+ const nodesQb = new graphqlQueryBuilder_1.default('nodes');
54
+ // get all the columnds Ids for the filters to work
55
+ const columnIds = [];
56
+ if (columnDefinitions && Array.isArray(columnDefinitions)) {
57
+ for (const column of columnDefinitions) {
58
+ columnIds.push(column.field);
59
+ // if extra "fields" are passed, also push them to columnIds
60
+ if (column.fields) {
61
+ columnIds.push(...column.fields);
62
+ }
63
+ }
64
+ }
65
+ // Slickgrid also requires the "id" field to be part of DataView
66
+ // add it to the GraphQL query if it wasn't already part of the list
67
+ if (columnIds.indexOf(this._datasetIdPropName) === -1) {
68
+ columnIds.unshift(this._datasetIdPropName);
69
+ }
70
+ const columnsQuery = this.buildFilterQuery(columnIds);
71
+ let graphqlNodeFields = [];
72
+ if (this._gridOptions.enablePagination !== false) {
73
+ if (this.options.isWithCursor) {
74
+ // ...pageInfo { hasNextPage, endCursor }, edges { cursor, node { _columns_ } }, totalCount: 100
75
+ const edgesQb = new graphqlQueryBuilder_1.default('edges');
76
+ const pageInfoQb = new graphqlQueryBuilder_1.default('pageInfo');
77
+ pageInfoQb.find('hasNextPage', 'hasPreviousPage', 'endCursor', 'startCursor');
78
+ nodesQb.find(columnsQuery);
79
+ edgesQb.find(['cursor']);
80
+ graphqlNodeFields = ['totalCount', nodesQb, pageInfoQb, edgesQb];
81
+ }
82
+ else {
83
+ // ...nodes { _columns_ }, totalCount: 100
84
+ nodesQb.find(columnsQuery);
85
+ graphqlNodeFields = ['totalCount', nodesQb];
86
+ }
87
+ // all properties to be returned by the query
88
+ datasetQb.find(graphqlNodeFields);
89
+ }
90
+ else {
91
+ // include all columns to be returned
92
+ datasetQb.find(columnsQuery);
93
+ }
94
+ // add dataset filters, could be Pagination and SortingFilters and/or FieldFilters
95
+ let datasetFilters = {};
96
+ // only add pagination if it's enabled in the grid options
97
+ if (this._gridOptions.enablePagination !== false) {
98
+ datasetFilters = {
99
+ ...this.options.paginationOptions,
100
+ first: ((this.options.paginationOptions && this.options.paginationOptions.first) ? this.options.paginationOptions.first : ((this.pagination && this.pagination.pageSize) ? this.pagination.pageSize : null)) || this.defaultPaginationOptions.first
101
+ };
102
+ if (!this.options.isWithCursor) {
103
+ const paginationOptions = (_a = this.options) === null || _a === void 0 ? void 0 : _a.paginationOptions;
104
+ datasetFilters.offset = (paginationOptions === null || paginationOptions === void 0 ? void 0 : paginationOptions.hasOwnProperty('offset')) ? +paginationOptions['offset'] : 0;
105
+ }
106
+ }
107
+ if (this.options.sortingOptions && Array.isArray(this.options.sortingOptions) && this.options.sortingOptions.length > 0) {
108
+ // orderBy: [{ field:x, direction: 'ASC' }]
109
+ datasetFilters.orderBy = this.options.sortingOptions;
110
+ }
111
+ if (this.options.filteringOptions && Array.isArray(this.options.filteringOptions) && this.options.filteringOptions.length > 0) {
112
+ // filterBy: [{ field: date, operator: '>', value: '2000-10-10' }]
113
+ datasetFilters.filterBy = this.options.filteringOptions;
114
+ }
115
+ if (this.options.addLocaleIntoQuery) {
116
+ // first: 20, ... locale: "en-CA"
117
+ datasetFilters.locale = ((_b = this._gridOptions.translater) === null || _b === void 0 ? void 0 : _b.getCurrentLanguage()) || this._gridOptions.locale || 'en';
118
+ }
119
+ if (this.options.extraQueryArguments) {
120
+ // first: 20, ... userId: 123
121
+ for (const queryArgument of this.options.extraQueryArguments) {
122
+ datasetFilters[queryArgument.field] = queryArgument.value;
123
+ }
124
+ }
125
+ // with pagination:: query { users(first: 20, offset: 0, orderBy: [], filterBy: []) { totalCount: 100, nodes: { _columns_ }}}
126
+ // without pagination:: query { users(orderBy: [], filterBy: []) { _columns_ }}
127
+ datasetQb.filter(datasetFilters);
128
+ queryQb.find(datasetQb);
129
+ const enumSearchProperties = ['direction:', 'field:', 'operator:'];
130
+ return this.trimDoubleQuotesOnEnumField(queryQb.toString(), enumSearchProperties, this.options.keepArgumentFieldDoubleQuotes || false);
131
+ }
132
+ /**
133
+ * From an input array of strings, we want to build a GraphQL query string.
134
+ * The process has to take the dot notation and parse it into a valid GraphQL query
135
+ * Following this SO answer https://stackoverflow.com/a/47705476/1212166
136
+ *
137
+ * INPUT
138
+ * ['firstName', 'lastName', 'billing.address.street', 'billing.address.zip']
139
+ * OUTPUT
140
+ * firstName, lastName, billing{address{street, zip}}
141
+ * @param inputArray
142
+ */
143
+ buildFilterQuery(inputArray) {
144
+ const set = (o = {}, a) => {
145
+ const k = a.shift();
146
+ o[k] = a.length ? set(o[k], a) : null;
147
+ return o;
148
+ };
149
+ const output = inputArray.reduce((o, a) => set(o, a.split('.')), {});
150
+ return JSON.stringify(output)
151
+ .replace(/\"|\:|null/g, '')
152
+ .replace(/^\{/, '')
153
+ .replace(/\}$/, '');
154
+ }
155
+ clearFilters() {
156
+ this._currentFilters = [];
157
+ this.updateOptions({ filteringOptions: [] });
158
+ }
159
+ clearSorters() {
160
+ this._currentSorters = [];
161
+ this.updateOptions({ sortingOptions: [] });
162
+ }
163
+ /**
164
+ * Get an initialization of Pagination options
165
+ * @return Pagination Options
166
+ */
167
+ getInitPaginationOptions() {
168
+ var _a;
169
+ const paginationFirst = this.pagination ? this.pagination.pageSize : DEFAULT_ITEMS_PER_PAGE;
170
+ return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.isWithCursor) ? { first: paginationFirst } : { first: paginationFirst, offset: 0 };
171
+ }
172
+ /** Get the GraphQL dataset name */
173
+ getDatasetName() {
174
+ var _a;
175
+ return ((_a = this.options) === null || _a === void 0 ? void 0 : _a.datasetName) || '';
176
+ }
177
+ /** Get the Filters that are currently used by the grid */
178
+ getCurrentFilters() {
179
+ return this._currentFilters;
180
+ }
181
+ /** Get the Pagination that is currently used by the grid */
182
+ getCurrentPagination() {
183
+ return this._currentPagination;
184
+ }
185
+ /** Get the Sorters that are currently used by the grid */
186
+ getCurrentSorters() {
187
+ return this._currentSorters;
188
+ }
189
+ /*
190
+ * Reset the pagination options
191
+ */
192
+ resetPaginationOptions() {
193
+ let paginationOptions;
194
+ if (this.options && this.options.isWithCursor) {
195
+ // first, last, after, before
196
+ paginationOptions = {
197
+ after: '',
198
+ before: undefined,
199
+ last: undefined
200
+ };
201
+ }
202
+ else {
203
+ // first, last, offset
204
+ paginationOptions = ((this.options && this.options.paginationOptions) || this.getInitPaginationOptions());
205
+ paginationOptions.offset = 0;
206
+ }
207
+ // save current pagination as Page 1 and page size as "first" set size
208
+ this._currentPagination = {
209
+ pageNumber: 1,
210
+ pageSize: paginationOptions.first || DEFAULT_PAGE_SIZE
211
+ };
212
+ // unless user specifically set "enablePagination" to False, we'll update pagination options in every other cases
213
+ if (this._gridOptions && (this._gridOptions.enablePagination || !this._gridOptions.hasOwnProperty('enablePagination'))) {
214
+ this.updateOptions({ paginationOptions });
215
+ }
216
+ }
217
+ updateOptions(serviceOptions) {
218
+ this.options = { ...this.options, ...serviceOptions };
219
+ }
220
+ /*
221
+ * FILTERING
222
+ */
223
+ processOnFilterChanged(_event, args) {
224
+ const gridOptions = this._gridOptions;
225
+ const backendApi = gridOptions.backendServiceApi;
226
+ if (backendApi === undefined) {
227
+ throw new Error('Something went wrong in the GraphqlService, "backendServiceApi" is not initialized');
228
+ }
229
+ // keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
230
+ this._currentFilters = this.castFilterToColumnFilters(args.columnFilters);
231
+ if (!args || !args.grid) {
232
+ throw new Error('Something went wrong when trying create the GraphQL Backend Service, it seems that "args" is not populated correctly');
233
+ }
234
+ // loop through all columns to inspect filters & set the query
235
+ this.updateFilters(args.columnFilters, false);
236
+ this.resetPaginationOptions();
237
+ return this.buildQuery();
238
+ }
239
+ /*
240
+ * PAGINATION
241
+ * With cursor, the query can have 4 arguments (first, after, last, before), for example:
242
+ * users (first:20, after:"YXJyYXljb25uZWN0aW9uOjM=") {
243
+ * totalCount
244
+ * pageInfo {
245
+ * hasNextPage
246
+ * hasPreviousPage
247
+ * endCursor
248
+ * startCursor
249
+ * }
250
+ * edges {
251
+ * cursor
252
+ * node {
253
+ * name
254
+ * gender
255
+ * }
256
+ * }
257
+ * }
258
+ * Without cursor, the query can have 3 arguments (first, last, offset), for example:
259
+ * users (first:20, offset: 10) {
260
+ * totalCount
261
+ * nodes {
262
+ * name
263
+ * gender
264
+ * }
265
+ * }
266
+ */
267
+ processOnPaginationChanged(_event, args) {
268
+ const pageSize = +(args.pageSize || ((this.pagination) ? this.pagination.pageSize : DEFAULT_PAGE_SIZE));
269
+ this.updatePagination(args.newPage, pageSize);
270
+ // build the GraphQL query which we will use in the WebAPI callback
271
+ return this.buildQuery();
272
+ }
273
+ /*
274
+ * SORTING
275
+ * we will use sorting as per a Facebook suggestion on a Github issue (with some small changes)
276
+ * https://github.com/graphql/graphql-relay-js/issues/20#issuecomment-220494222
277
+ *
278
+ * users (first: 20, offset: 10, orderBy: [{field: lastName, direction: ASC}, {field: firstName, direction: DESC}]) {
279
+ * totalCount
280
+ * nodes {
281
+ * name
282
+ * gender
283
+ * }
284
+ * }
285
+ */
286
+ processOnSortChanged(_event, args) {
287
+ const sortColumns = (args.multiColumnSort) ? args.sortCols : new Array({ columnId: args.sortCol.id, sortCol: args.sortCol, sortAsc: args.sortAsc });
288
+ // loop through all columns to inspect sorters & set the query
289
+ this.updateSorters(sortColumns);
290
+ // build the GraphQL query which we will use in the WebAPI callback
291
+ return this.buildQuery();
292
+ }
293
+ /**
294
+ * loop through all columns to inspect filters & update backend service filteringOptions
295
+ * @param columnFilters
296
+ */
297
+ updateFilters(columnFilters, isUpdatedByPresetOrDynamically) {
298
+ var _a, _b, _c, _d;
299
+ const searchByArray = [];
300
+ let searchValue;
301
+ // on filter preset load, we need to keep current filters
302
+ if (isUpdatedByPresetOrDynamically) {
303
+ this._currentFilters = this.castFilterToColumnFilters(columnFilters);
304
+ }
305
+ for (const columnId in columnFilters) {
306
+ if (columnFilters.hasOwnProperty(columnId)) {
307
+ const columnFilter = columnFilters[columnId];
308
+ // if user defined some "presets", then we need to find the filters from the column definitions instead
309
+ let columnDef;
310
+ if (isUpdatedByPresetOrDynamically && Array.isArray(this._columnDefinitions)) {
311
+ columnDef = this._columnDefinitions.find((column) => column.id === columnFilter.columnId);
312
+ }
313
+ else {
314
+ columnDef = columnFilter.columnDef;
315
+ }
316
+ if (!columnDef) {
317
+ throw new Error('[GraphQL Service]: Something went wrong in trying to get the column definition of the specified filter (or preset filters). Did you make a typo on the filter columnId?');
318
+ }
319
+ const fieldName = ((_a = columnDef.filter) === null || _a === void 0 ? void 0 : _a.queryField) || columnDef.queryFieldFilter || columnDef.queryField || columnDef.field || columnDef.name || '';
320
+ const fieldType = columnDef.type || common_1.FieldType.string;
321
+ let searchTerms = (_b = columnFilter === null || columnFilter === void 0 ? void 0 : columnFilter.searchTerms) !== null && _b !== void 0 ? _b : [];
322
+ let fieldSearchValue = (Array.isArray(searchTerms) && searchTerms.length === 1) ? searchTerms[0] : '';
323
+ if (typeof fieldSearchValue === 'undefined') {
324
+ fieldSearchValue = '';
325
+ }
326
+ if (!fieldName) {
327
+ throw new Error(`GraphQL filter could not find the field name to query the search, your column definition must include a valid "field" or "name" (optionally you can also use the "queryfield").`);
328
+ }
329
+ fieldSearchValue = (fieldSearchValue === undefined || fieldSearchValue === null) ? '' : `${fieldSearchValue}`; // make sure it's a string
330
+ // run regex to find possible filter operators unless the user disabled the feature
331
+ const autoParseInputFilterOperator = (_c = columnDef.autoParseInputFilterOperator) !== null && _c !== void 0 ? _c : this._gridOptions.autoParseInputFilterOperator;
332
+ const matches = autoParseInputFilterOperator !== false
333
+ ? fieldSearchValue.match(/^([<>!=\*]{0,2})(.*[^<>!=\*])([\*]?)$/) // group 1: Operator, 2: searchValue, 3: last char is '*' (meaning starts with, ex.: abc*)
334
+ : [fieldSearchValue, '', fieldSearchValue, '']; // when parsing is disabled, we'll only keep the search value in the index 2 to make it easy for code reuse
335
+ let operator = columnFilter.operator || (matches === null || matches === void 0 ? void 0 : matches[1]) || '';
336
+ searchValue = (matches === null || matches === void 0 ? void 0 : matches[2]) || '';
337
+ const lastValueChar = (matches === null || matches === void 0 ? void 0 : matches[3]) || (operator === '*z' ? '*' : '');
338
+ // no need to query if search value is empty
339
+ if (fieldName && searchValue === '' && searchTerms.length === 0) {
340
+ continue;
341
+ }
342
+ if (Array.isArray(searchTerms) && searchTerms.length === 1 && typeof searchTerms[0] === 'string' && searchTerms[0].indexOf('..') >= 0) {
343
+ if (operator !== common_1.OperatorType.rangeInclusive && operator !== common_1.OperatorType.rangeExclusive) {
344
+ operator = (_d = this._gridOptions.defaultFilterRangeOperator) !== null && _d !== void 0 ? _d : common_1.OperatorType.rangeInclusive;
345
+ }
346
+ searchTerms = searchTerms[0].split('..', 2);
347
+ if (searchTerms[0] === '') {
348
+ operator = operator === common_1.OperatorType.rangeInclusive ? '<=' : operator === common_1.OperatorType.rangeExclusive ? '<' : operator;
349
+ searchTerms = searchTerms.slice(1);
350
+ searchValue = searchTerms[0];
351
+ }
352
+ else if (searchTerms[1] === '') {
353
+ operator = operator === common_1.OperatorType.rangeInclusive ? '>=' : operator === common_1.OperatorType.rangeExclusive ? '>' : operator;
354
+ searchTerms = searchTerms.slice(0, 1);
355
+ searchValue = searchTerms[0];
356
+ }
357
+ }
358
+ if (typeof searchValue === 'string') {
359
+ if (operator === '*' || operator === 'a*' || operator === '*z' || lastValueChar === '*') {
360
+ operator = ((operator === '*' || operator === '*z') ? 'EndsWith' : 'StartsWith');
361
+ }
362
+ }
363
+ // if we didn't find an Operator but we have a Column Operator inside the Filter (DOM Element), we should use its default Operator
364
+ // multipleSelect is "IN", while singleSelect is "EQ", else don't map any operator
365
+ if (!operator && columnDef.filter && columnDef.filter.operator) {
366
+ operator = columnDef.filter.operator;
367
+ }
368
+ // No operator and 2 search terms should lead to default range operator.
369
+ if (!operator && Array.isArray(searchTerms) && searchTerms.length === 2 && searchTerms[0] && searchTerms[1]) {
370
+ operator = this._gridOptions.defaultFilterRangeOperator;
371
+ }
372
+ // Range with 1 searchterm should lead to equals for a date field.
373
+ if ((operator === common_1.OperatorType.rangeInclusive || common_1.OperatorType.rangeExclusive) && Array.isArray(searchTerms) && searchTerms.length === 1 && fieldType === common_1.FieldType.date) {
374
+ operator = common_1.OperatorType.equal;
375
+ }
376
+ // Normalize all search values
377
+ searchValue = this.normalizeSearchValue(fieldType, searchValue);
378
+ if (Array.isArray(searchTerms)) {
379
+ searchTerms.forEach((_part, index) => {
380
+ searchTerms[index] = this.normalizeSearchValue(fieldType, searchTerms[index]);
381
+ });
382
+ }
383
+ // when having more than 1 search term (we need to create a CSV string for GraphQL "IN" or "NOT IN" filter search)
384
+ if (searchTerms && searchTerms.length > 1 && (operator === 'IN' || operator === 'NIN' || operator === 'NOT_IN')) {
385
+ searchValue = searchTerms.join(',');
386
+ }
387
+ else if (searchTerms && searchTerms.length === 2 && (operator === common_1.OperatorType.rangeExclusive || operator === common_1.OperatorType.rangeInclusive)) {
388
+ searchByArray.push({ field: fieldName, operator: (operator === common_1.OperatorType.rangeInclusive ? 'GE' : 'GT'), value: searchTerms[0] });
389
+ searchByArray.push({ field: fieldName, operator: (operator === common_1.OperatorType.rangeInclusive ? 'LE' : 'LT'), value: searchTerms[1] });
390
+ continue;
391
+ }
392
+ // if we still don't have an operator find the proper Operator to use by it's field type
393
+ if (!operator) {
394
+ operator = (0, common_1.mapOperatorByFieldType)(fieldType);
395
+ }
396
+ // build the search array
397
+ searchByArray.push({ field: fieldName, operator: (0, common_1.mapOperatorType)(operator), value: searchValue });
398
+ }
399
+ }
400
+ // update the service options with filters for the buildQuery() to work later
401
+ this.updateOptions({ filteringOptions: searchByArray });
402
+ }
403
+ /**
404
+ * Update the pagination component with it's new page number and size
405
+ * @param newPage
406
+ * @param pageSize
407
+ */
408
+ updatePagination(newPage, pageSize) {
409
+ this._currentPagination = {
410
+ pageNumber: newPage,
411
+ pageSize
412
+ };
413
+ let paginationOptions;
414
+ if (this.options && this.options.isWithCursor) {
415
+ paginationOptions = {
416
+ first: pageSize
417
+ };
418
+ }
419
+ else {
420
+ paginationOptions = {
421
+ first: pageSize,
422
+ offset: (newPage > 1) ? ((newPage - 1) * pageSize) : 0 // recalculate offset but make sure the result is always over 0
423
+ };
424
+ }
425
+ this.updateOptions({ paginationOptions });
426
+ }
427
+ /**
428
+ * loop through all columns to inspect sorters & update backend service sortingOptions
429
+ * @param columnFilters
430
+ */
431
+ updateSorters(sortColumns, presetSorters) {
432
+ let currentSorters = [];
433
+ const graphqlSorters = [];
434
+ if (!sortColumns && presetSorters) {
435
+ // make the presets the current sorters, also make sure that all direction are in uppercase for GraphQL
436
+ currentSorters = presetSorters;
437
+ currentSorters.forEach((sorter) => sorter.direction = sorter.direction.toUpperCase());
438
+ // display the correct sorting icons on the UI, for that it requires (columnId, sortAsc) properties
439
+ const tmpSorterArray = currentSorters.map((sorter) => {
440
+ var _a;
441
+ const columnDef = (_a = this._columnDefinitions) === null || _a === void 0 ? void 0 : _a.find((column) => column.id === sorter.columnId);
442
+ graphqlSorters.push({
443
+ field: columnDef ? ((columnDef.queryFieldSorter || columnDef.queryField || columnDef.field) + '') : (sorter.columnId + ''),
444
+ direction: sorter.direction
445
+ });
446
+ // return only the column(s) found in the Column Definitions ELSE null
447
+ if (columnDef) {
448
+ return {
449
+ columnId: sorter.columnId,
450
+ sortAsc: sorter.direction.toUpperCase() === common_1.SortDirection.ASC
451
+ };
452
+ }
453
+ return null;
454
+ });
455
+ // set the sort icons, but also make sure to filter out null values (that happens when columnDef is not found)
456
+ if (Array.isArray(tmpSorterArray) && this._grid) {
457
+ this._grid.setSortColumns(tmpSorterArray.filter(sorter => sorter) || []);
458
+ }
459
+ }
460
+ else if (sortColumns && !presetSorters) {
461
+ // build the orderBy array, it could be multisort, example
462
+ // orderBy:[{field: lastName, direction: ASC}, {field: firstName, direction: DESC}]
463
+ if (Array.isArray(sortColumns) && sortColumns.length > 0) {
464
+ for (const column of sortColumns) {
465
+ if (column && column.sortCol) {
466
+ currentSorters.push({
467
+ columnId: column.sortCol.id + '',
468
+ direction: column.sortAsc ? common_1.SortDirection.ASC : common_1.SortDirection.DESC
469
+ });
470
+ const fieldName = (column.sortCol.queryFieldSorter || column.sortCol.queryField || column.sortCol.field || '') + '';
471
+ if (fieldName) {
472
+ graphqlSorters.push({
473
+ field: fieldName,
474
+ direction: column.sortAsc ? common_1.SortDirection.ASC : common_1.SortDirection.DESC
475
+ });
476
+ }
477
+ }
478
+ }
479
+ }
480
+ }
481
+ // keep current Sorters and update the service options with the new sorting
482
+ this._currentSorters = currentSorters;
483
+ this.updateOptions({ sortingOptions: graphqlSorters });
484
+ }
485
+ /**
486
+ * A function which takes an input string and removes double quotes only
487
+ * on certain fields are identified as GraphQL enums (except fields with dot notation)
488
+ * For example let say we identified ("direction:", "sort") as word which are GraphQL enum fields
489
+ * then the result will be:
490
+ * FROM
491
+ * query { users (orderBy:[{field:"firstName", direction:"ASC"} }]) }
492
+ * TO
493
+ * query { users (orderBy:[{field: firstName, direction: ASC}})}
494
+ *
495
+ * EXCEPTIONS (fields with dot notation "." which are inside a "field:")
496
+ * these fields will keep double quotes while everything else will be stripped of double quotes
497
+ * query { users (orderBy:[{field:"billing.street.name", direction: "ASC"} }
498
+ * TO
499
+ * query { users (orderBy:[{field:"billing.street.name", direction: ASC}}
500
+ * @param inputStr input string
501
+ * @param enumSearchWords array of enum words to filter
502
+ * @returns outputStr output string
503
+ */
504
+ trimDoubleQuotesOnEnumField(inputStr, enumSearchWords, keepArgumentFieldDoubleQuotes) {
505
+ const patternWordInQuotes = `\s?((field:\s*)?".*?")`;
506
+ let patternRegex = enumSearchWords.join(patternWordInQuotes + '|');
507
+ patternRegex += patternWordInQuotes; // the last one should also have the pattern but without the pipe "|"
508
+ // example with (field: & direction:): /field:s?(".*?")|direction:s?(".*?")/
509
+ const reg = new RegExp(patternRegex, 'g');
510
+ return inputStr.replace(reg, group1 => {
511
+ // remove double quotes except when the string starts with a "field:"
512
+ let removeDoubleQuotes = true;
513
+ if (group1.startsWith('field:') && keepArgumentFieldDoubleQuotes) {
514
+ removeDoubleQuotes = false;
515
+ }
516
+ const rep = removeDoubleQuotes ? group1.replace(/"/g, '') : group1;
517
+ return rep;
518
+ });
519
+ }
520
+ //
521
+ // protected functions
522
+ // -------------------
523
+ /**
524
+ * Cast provided filters (could be in multiple formats) into an array of CurrentFilter
525
+ * @param columnFilters
526
+ */
527
+ castFilterToColumnFilters(columnFilters) {
528
+ // keep current filters & always save it as an array (columnFilters can be an object when it is dealt by SlickGrid Filter)
529
+ const filtersArray = (typeof columnFilters === 'object') ? Object.keys(columnFilters).map(key => columnFilters[key]) : columnFilters;
530
+ if (!Array.isArray(filtersArray)) {
531
+ return [];
532
+ }
533
+ return filtersArray.map((filter) => {
534
+ const tmpFilter = { columnId: filter.columnId || '' };
535
+ if (filter.operator) {
536
+ tmpFilter.operator = filter.operator;
537
+ }
538
+ if (filter.targetSelector) {
539
+ tmpFilter.targetSelector = filter.targetSelector;
540
+ }
541
+ if (Array.isArray(filter.searchTerms)) {
542
+ tmpFilter.searchTerms = filter.searchTerms;
543
+ }
544
+ return tmpFilter;
545
+ });
546
+ }
547
+ /** Normalizes the search value according to field type. */
548
+ normalizeSearchValue(fieldType, searchValue) {
549
+ switch (fieldType) {
550
+ case common_1.FieldType.date:
551
+ case common_1.FieldType.string:
552
+ case common_1.FieldType.text:
553
+ case common_1.FieldType.readonly:
554
+ if (typeof searchValue === 'string') {
555
+ // escape single quotes by doubling them
556
+ searchValue = searchValue.replace(/'/g, `''`);
557
+ }
558
+ break;
559
+ case common_1.FieldType.integer:
560
+ case common_1.FieldType.number:
561
+ case common_1.FieldType.float:
562
+ if (typeof searchValue === 'string') {
563
+ // Parse a valid decimal from the string.
564
+ // Replace double dots with single dots
565
+ searchValue = searchValue.replace(/\.\./g, '.');
566
+ // Remove a trailing dot
567
+ searchValue = searchValue.replace(/\.+$/g, '');
568
+ // Prefix a leading dot with 0
569
+ searchValue = searchValue.replace(/^\.+/g, '0.');
570
+ // Prefix leading dash dot with -0.
571
+ searchValue = searchValue.replace(/^\-+\.+/g, '-0.');
572
+ // Remove any non valid decimal characters from the search string
573
+ searchValue = searchValue.replace(/(?!^\-)[^\d\.]/g, '');
574
+ // if nothing left, search for 0
575
+ if (searchValue === '' || searchValue === '-') {
576
+ searchValue = '0';
577
+ }
578
+ }
579
+ break;
580
+ }
581
+ return searchValue;
582
+ }
583
+ }
584
+ exports.GraphqlService = GraphqlService;
585
585
  //# sourceMappingURL=graphql.service.js.map