@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.
- package/dist/commonjs/index.js +22 -22
- package/dist/commonjs/interfaces/graphqlCursorPaginationOption.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlDatasetFilter.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlFilteringOption.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlPaginatedResult.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlPaginationOption.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlResult.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlServiceApi.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlServiceOption.interface.js +2 -2
- package/dist/commonjs/interfaces/graphqlSortingOption.interface.js +2 -2
- package/dist/commonjs/interfaces/index.js +26 -26
- package/dist/commonjs/interfaces/queryArgument.interface.js +2 -2
- package/dist/commonjs/services/graphql.service.js +584 -584
- package/dist/commonjs/services/graphqlQueryBuilder.js +137 -137
- package/dist/commonjs/services/index.js +20 -20
- package/dist/esm/index.js +3 -3
- package/dist/esm/interfaces/graphqlCursorPaginationOption.interface.js +1 -1
- package/dist/esm/interfaces/graphqlDatasetFilter.interface.js +1 -1
- package/dist/esm/interfaces/graphqlFilteringOption.interface.js +1 -1
- package/dist/esm/interfaces/graphqlPaginatedResult.interface.js +1 -1
- package/dist/esm/interfaces/graphqlPaginationOption.interface.js +1 -1
- package/dist/esm/interfaces/graphqlResult.interface.js +1 -1
- package/dist/esm/interfaces/graphqlServiceApi.interface.js +1 -1
- package/dist/esm/interfaces/graphqlServiceOption.interface.js +1 -1
- package/dist/esm/interfaces/graphqlSortingOption.interface.js +1 -1
- package/dist/esm/interfaces/index.js +10 -10
- package/dist/esm/interfaces/queryArgument.interface.js +1 -1
- package/dist/esm/services/graphql.service.js +582 -582
- package/dist/esm/services/graphqlQueryBuilder.js +134 -134
- package/dist/esm/services/index.js +2 -2
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/{commonjs → types}/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/graphqlCursorPaginationOption.interface.d.ts +11 -10
- package/dist/types/interfaces/graphqlCursorPaginationOption.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/graphqlDatasetFilter.interface.d.ts +13 -12
- package/dist/types/interfaces/graphqlDatasetFilter.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/graphqlFilteringOption.interface.d.ts +10 -9
- package/dist/types/interfaces/graphqlFilteringOption.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/graphqlPaginatedResult.interface.d.ts +30 -29
- package/dist/types/interfaces/graphqlPaginatedResult.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/graphqlPaginationOption.interface.d.ts +6 -5
- package/dist/types/interfaces/graphqlPaginationOption.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/graphqlResult.interface.d.ts +9 -8
- package/dist/types/interfaces/graphqlResult.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/graphqlServiceApi.interface.d.ts +23 -22
- package/dist/types/interfaces/graphqlServiceApi.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/graphqlServiceOption.interface.d.ts +35 -34
- package/dist/types/interfaces/graphqlServiceOption.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/graphqlSortingOption.interface.d.ts +6 -5
- package/dist/types/interfaces/graphqlSortingOption.interface.d.ts.map +1 -0
- package/dist/{esm → types}/interfaces/index.d.ts +11 -10
- package/dist/types/interfaces/index.d.ts.map +1 -0
- package/dist/{commonjs → types}/interfaces/queryArgument.interface.d.ts +5 -4
- package/dist/types/interfaces/queryArgument.interface.d.ts.map +1 -0
- package/dist/{commonjs → types}/services/graphql.service.d.ts +100 -99
- package/dist/types/services/graphql.service.d.ts.map +1 -0
- package/dist/{esm → types}/services/graphqlQueryBuilder.d.ts +39 -38
- package/dist/types/services/graphqlQueryBuilder.d.ts.map +1 -0
- package/dist/{esm → types}/services/index.d.ts +3 -2
- package/dist/types/services/index.d.ts.map +1 -0
- package/package.json +7 -7
- package/dist/commonjs/interfaces/graphqlCursorPaginationOption.interface.d.ts +0 -10
- package/dist/commonjs/interfaces/graphqlFilteringOption.interface.d.ts +0 -9
- package/dist/commonjs/interfaces/graphqlServiceApi.interface.d.ts +0 -22
- package/dist/commonjs/interfaces/graphqlServiceOption.interface.d.ts +0 -34
- package/dist/commonjs/interfaces/graphqlSortingOption.interface.d.ts +0 -5
- package/dist/commonjs/interfaces/index.d.ts +0 -10
- package/dist/commonjs/services/graphqlQueryBuilder.d.ts +0 -38
- package/dist/commonjs/services/index.d.ts +0 -2
- package/dist/esm/index.d.ts +0 -3
- package/dist/esm/interfaces/graphqlDatasetFilter.interface.d.ts +0 -12
- package/dist/esm/interfaces/graphqlPaginatedResult.interface.d.ts +0 -29
- package/dist/esm/interfaces/graphqlPaginationOption.interface.d.ts +0 -5
- package/dist/esm/interfaces/graphqlResult.interface.d.ts +0 -8
- package/dist/esm/interfaces/queryArgument.interface.d.ts +0 -4
- 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
|