@slickgrid-universal/graphql 2.4.1 → 2.6.0

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