@teselagen/ui 0.8.6 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/DataTable/EditabelCell.d.ts +10 -0
  2. package/DataTable/defaultProps.d.ts +43 -0
  3. package/DataTable/utils/computePresets.d.ts +1 -0
  4. package/DataTable/utils/getAllRows.d.ts +1 -1
  5. package/DataTable/utils/handleCopyColumn.d.ts +1 -1
  6. package/DataTable/utils/handleCopyTable.d.ts +1 -1
  7. package/DataTable/utils/initializeHasuraWhereAndFilter.d.ts +0 -1
  8. package/DataTable/utils/queryParams.d.ts +16 -12
  9. package/DataTable/utils/tableQueryParamsToHasuraClauses.d.ts +1 -1
  10. package/README.md +1 -1
  11. package/index.cjs.js +1139 -1040
  12. package/index.d.ts +2 -0
  13. package/index.es.js +1139 -1040
  14. package/package.json +2 -2
  15. package/src/DataTable/Columns.js +2 -2
  16. package/src/DataTable/DisplayOptions.js +1 -1
  17. package/src/DataTable/EditabelCell.js +55 -0
  18. package/src/DataTable/FilterAndSortMenu.js +27 -30
  19. package/src/DataTable/defaultProps.js +45 -0
  20. package/src/DataTable/index.js +101 -84
  21. package/src/DataTable/style.css +1 -1
  22. package/src/DataTable/utils/computePresets.js +42 -0
  23. package/src/DataTable/utils/filterLocalEntitiesToHasura.js +128 -8
  24. package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +719 -21
  25. package/src/DataTable/utils/getAllRows.js +2 -6
  26. package/src/DataTable/utils/handleCopyColumn.js +2 -2
  27. package/src/DataTable/utils/handleCopyTable.js +2 -2
  28. package/src/DataTable/utils/initializeHasuraWhereAndFilter.js +1 -12
  29. package/src/DataTable/utils/queryParams.js +153 -770
  30. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +185 -168
  31. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.test.js +50 -11
  32. package/src/DataTable/utils/withTableParams.js +3 -16
  33. package/src/ExcelCell.js +38 -0
  34. package/src/FormComponents/Uploader.js +5 -1
  35. package/src/FormComponents/index.js +2 -2
  36. package/src/TgSelect/index.js +15 -0
  37. package/src/autoTooltip.js +1 -0
  38. package/src/index.js +2 -0
  39. package/src/utils/determineBlackOrWhiteTextColor.js +8 -1
  40. package/style.css +10537 -0
  41. package/ui.css +1 -1
  42. package/utils/determineBlackOrWhiteTextColor.d.ts +1 -2
@@ -1,21 +1,11 @@
1
1
  import queryString from "qs";
2
+ import { uniqBy, clone, camelCase, forEach } from "lodash-es";
2
3
  import {
3
- uniqBy,
4
- uniq,
5
- get,
6
- clone,
7
- camelCase,
8
- startsWith,
9
- endsWith,
10
- last,
11
- orderBy,
12
- take,
13
- drop,
14
- isEmpty,
15
- isInteger
16
- } from "lodash-es";
17
- import dayjs from "dayjs";
18
- import { flatMap } from "lodash-es";
4
+ getFieldsMappedByCCDisplayName,
5
+ tableQueryParamsToHasuraClauses
6
+ } from "./tableQueryParamsToHasuraClauses";
7
+ import { filterLocalEntitiesToHasura } from "./filterLocalEntitiesToHasura";
8
+ import { initializeHasuraWhereAndFilter } from "./initializeHasuraWhereAndFilter";
19
9
 
20
10
  const defaultPageSizes = [5, 10, 15, 25, 50, 100, 200, 400];
21
11
 
@@ -63,448 +53,6 @@ function safeParse(val) {
63
53
  return val;
64
54
  }
65
55
  }
66
-
67
- /**
68
- *
69
- * @param {object} field
70
- * @returns the camelCase display name of the field, to be used for filters, sorting, etc
71
- */
72
- export function getCCDisplayName(field) {
73
- return camelCase(
74
- typeof field.displayName === "string" ? field.displayName : field.path
75
- );
76
- }
77
-
78
- /**
79
- * Takes a schema and returns an object with the fields mapped by their camelCased display name.
80
- * If the displayName is not set or is a jsx element, the path is used instead.
81
- * The same conversion must be done when using the result of this method
82
- */
83
- function getFieldsMappedByCCDisplayName(schema) {
84
- return schema.fields.reduce((acc, field) => {
85
- const ccDisplayName = getCCDisplayName(field);
86
- acc[ccDisplayName] = field;
87
- return acc;
88
- }, {});
89
- }
90
-
91
- function orderEntitiesLocal(orderArray, entities, schema, ownProps) {
92
- if (orderArray?.length) {
93
- const orderFuncs = [];
94
- const ascOrDescArray = [];
95
- orderArray.forEach(order => {
96
- const ccDisplayName = order.replace(/^-/gi, ""); // "-updatedAt" => "updatedAt"
97
- const ccFields = getFieldsMappedByCCDisplayName(schema);
98
- const field = ccFields[ccDisplayName];
99
- if (!field) {
100
- throw new Error(
101
- "Ruh roh, there should have been a column to sort on for " +
102
- order +
103
- " but none was found in " +
104
- schema.fields
105
- );
106
- }
107
- const { path, getValueToFilterOn, sortFn } = field;
108
- if (field.type === "timestamp") {
109
- //with the timestamp logic below, make sure empty dates always end up on the bottom of the stack
110
- ascOrDescArray.push("desc");
111
- }
112
- ascOrDescArray.push(ccDisplayName === order ? "asc" : "desc");
113
- //push the actual sorting function
114
- if (field.type === "timestamp") {
115
- //with the timestamp logic above, make sure empty dates always end up on the bottom of the stack
116
- orderFuncs.push(r => {
117
- const val = get(r, path);
118
- return !!val;
119
- });
120
- }
121
- if (path && endsWith(path.toLowerCase(), "id")) {
122
- orderFuncs.push(o => {
123
- return parseInt(get(o, path), 10);
124
- });
125
- } else if (sortFn) {
126
- const toOrder = Array.isArray(sortFn) ? sortFn : [sortFn];
127
- orderFuncs.push(...toOrder);
128
- } else if (getValueToFilterOn) {
129
- orderFuncs.push(o => {
130
- return getValueToFilterOn(o, ownProps);
131
- });
132
- } else {
133
- orderFuncs.push(r => {
134
- const val = get(r, path);
135
- return val && val.toLowerCase ? val.toLowerCase() : val;
136
- });
137
- }
138
- });
139
- entities = orderBy(entities, orderFuncs, ascOrDescArray);
140
- }
141
- return entities;
142
- }
143
-
144
- function getAndAndOrFilters(allFilters) {
145
- const orFilters = [];
146
- const andFilters = [];
147
- const otherOrFilters = [];
148
-
149
- allFilters.forEach(filter => {
150
- if (
151
- filter.isOrFilter &&
152
- typeof filter.filterValue === "string" &&
153
- filter.filterValue.includes(",")
154
- ) {
155
- // handle comma separated filters by adding more orWheres
156
- const allFilterValues = filter.filterValue.split(",");
157
- allFilterValues.forEach((filterValue, i) => {
158
- filterValue = filterValue.trim();
159
- if (!filterValue) return;
160
- const newFilter = {
161
- ...filter,
162
- filterValue
163
- };
164
- if (i === 0) {
165
- orFilters.push(newFilter);
166
- } else {
167
- const iMinus = i - 1;
168
- if (!otherOrFilters[iMinus]) otherOrFilters[iMinus] = [];
169
- otherOrFilters[iMinus].push(newFilter);
170
- }
171
- });
172
- } else if (filter.isOrFilter) {
173
- orFilters.push(filter);
174
- } else {
175
- andFilters.push(filter);
176
- }
177
- });
178
- return {
179
- orFilters,
180
- andFilters,
181
- otherOrFilters
182
- };
183
- }
184
-
185
- function filterEntitiesLocal(
186
- filters = [],
187
- searchTerm,
188
- entities,
189
- schema,
190
- ownProps
191
- ) {
192
- const allFilters = getAllFilters(filters, searchTerm, schema);
193
-
194
- if (allFilters.length) {
195
- const ccFields = getFieldsMappedByCCDisplayName(schema);
196
- const { andFilters, orFilters, otherOrFilters } =
197
- getAndAndOrFilters(allFilters);
198
- //filter ands first
199
- andFilters.forEach(filter => {
200
- entities = getEntitiesForGivenFilter(
201
- entities,
202
- filter,
203
- ccFields,
204
- ownProps
205
- );
206
- });
207
- //then filter ors
208
- if (orFilters.length) {
209
- let orEntities = [];
210
- orFilters.concat(...otherOrFilters).forEach(filter => {
211
- orEntities = orEntities.concat(
212
- getEntitiesForGivenFilter(entities, filter, ccFields, ownProps)
213
- );
214
- });
215
- entities = uniq(orEntities);
216
- }
217
- }
218
- return entities;
219
- }
220
-
221
- function cleanFilterValue(_filterValue, type) {
222
- let filterValue = _filterValue;
223
- if (type === "number" || type === "integer") {
224
- filterValue = Array.isArray(filterValue)
225
- ? filterValue.map(val => Number(val))
226
- : Number(filterValue);
227
- }
228
- return filterValue;
229
- }
230
-
231
- function getEntitiesForGivenFilter(entities, filter, ccFields, ownProps) {
232
- const { filterOn, filterValue: _filterValue, selectedFilter } = filter;
233
- const field = ccFields[filterOn];
234
- const { path, getValueToFilterOn } = field;
235
- const filterValue = cleanFilterValue(_filterValue, field.type);
236
- const subFilter = getSubFilter(false, selectedFilter, filterValue);
237
- entities = entities.filter(entity => {
238
- const fieldVal = getValueToFilterOn
239
- ? getValueToFilterOn(entity, ownProps)
240
- : get(entity, path);
241
- const shouldKeep = subFilter(fieldVal);
242
- return shouldKeep;
243
- });
244
- return entities;
245
- }
246
-
247
- function getFiltersFromSearchTerm(searchTerm, schema) {
248
- const searchTermFilters = [];
249
- if (searchTerm) {
250
- const sharedFields = {
251
- isOrFilter: true,
252
- isSearchTermFilter: true
253
- };
254
- schema.fields.forEach(field => {
255
- const { type, searchDisabled } = field;
256
- if (searchDisabled || field.filterDisabled || type === "color") return;
257
- const ccDisplayName = getCCDisplayName(field);
258
- const filterValue = cleanFilterValue(searchTerm, type);
259
- if (type === "string" || type === "lookup") {
260
- searchTermFilters.push({
261
- ...sharedFields,
262
- filterOn: ccDisplayName,
263
- filterValue: searchTerm,
264
- selectedFilter: "contains"
265
- });
266
- } else if (type === "boolean") {
267
- let regex;
268
- try {
269
- regex = new RegExp("^" + searchTerm, "ig");
270
- } catch (error) {
271
- //ignore
272
- }
273
- if (regex) {
274
- if ("true".replace(regex, "") !== "true") {
275
- searchTermFilters.push({
276
- ...sharedFields,
277
- filterOn: ccDisplayName,
278
- filterValue: true,
279
- selectedFilter: "true"
280
- });
281
- } else if ("false".replace(regex, "") !== "false") {
282
- searchTermFilters.push({
283
- ...sharedFields,
284
- filterOn: ccDisplayName,
285
- filterValue: false,
286
- selectedFilter: "false"
287
- });
288
- }
289
- }
290
- } else if (
291
- (type === "number" || type === "integer") &&
292
- !isNaN(filterValue)
293
- ) {
294
- if (type === "integer" && !isInteger(filterValue)) {
295
- return;
296
- }
297
- searchTermFilters.push({
298
- ...sharedFields,
299
- filterOn: ccDisplayName,
300
- filterValue: filterValue,
301
- selectedFilter: "equalTo"
302
- });
303
- }
304
- });
305
- }
306
- return searchTermFilters;
307
- }
308
-
309
- function getSubFilter(
310
- qb, //if no qb is passed, it means we are filtering locally and want to get a function back that can be used in an array filter
311
- selectedFilter,
312
- filterValue
313
- ) {
314
- const ccSelectedFilter = camelCase(selectedFilter);
315
- let stringFilterValue =
316
- filterValue && filterValue.toString ? filterValue.toString() : filterValue;
317
- if (stringFilterValue === false) {
318
- // we still want to be able to search for the string "false" which will get parsed to false
319
- stringFilterValue = "false";
320
- } else {
321
- stringFilterValue = stringFilterValue || "";
322
- }
323
- const filterValLower =
324
- stringFilterValue.toLowerCase && stringFilterValue.toLowerCase();
325
- const arrayFilterValue = Array.isArray(filterValue)
326
- ? filterValue
327
- : stringFilterValue.split(";");
328
- if (ccSelectedFilter === "startsWith") {
329
- return qb
330
- ? qb.startsWith(stringFilterValue) //filter using qb (aka we're backend connected)
331
- : fieldVal => {
332
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
333
- if (!fieldVal || !fieldVal.toLowerCase) return false;
334
- return startsWith(fieldVal.toLowerCase(), filterValLower);
335
- };
336
- } else if (ccSelectedFilter === "endsWith") {
337
- return qb
338
- ? qb.endsWith(stringFilterValue) //filter using qb (aka we're backend connected)
339
- : fieldVal => {
340
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
341
- if (!fieldVal || !fieldVal.toLowerCase) return false;
342
- return endsWith(fieldVal.toLowerCase(), filterValLower);
343
- };
344
- } else if (
345
- ccSelectedFilter === "contains" ||
346
- ccSelectedFilter === "notContains"
347
- ) {
348
- return qb
349
- ? ccSelectedFilter === "contains"
350
- ? qb.contains(stringFilterValue.replace(/_/g, "\\_"))
351
- : qb.notContains(stringFilterValue.replace(/_/g, "\\_"))
352
- : fieldVal => {
353
- if (!fieldVal || !fieldVal.toLowerCase) return false;
354
- return ccSelectedFilter === "contains"
355
- ? fieldVal.toLowerCase().replace(filterValLower, "") !==
356
- fieldVal.toLowerCase()
357
- : fieldVal.toLowerCase().replace(filterValLower, "") ===
358
- fieldVal.toLowerCase();
359
- };
360
- } else if (ccSelectedFilter === "inList") {
361
- return qb
362
- ? qb.inList(arrayFilterValue) //filter using qb (aka we're backend connected)
363
- : fieldVal => {
364
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
365
- if (!fieldVal?.toString) return false;
366
- return (
367
- arrayFilterValue
368
- .map(val => {
369
- if (val) {
370
- if (val.toString) return val.toString().toLowerCase();
371
- return val.toLowerCase();
372
- }
373
- return undefined;
374
- })
375
- .indexOf(fieldVal.toString().toLowerCase()) > -1
376
- );
377
- };
378
- } else if (ccSelectedFilter === "notInList") {
379
- return qb
380
- ? qb.notInList(arrayFilterValue) //filter using qb (aka we're backend connected)
381
- : fieldVal => {
382
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
383
- if (!fieldVal?.toString) return false;
384
- return (
385
- arrayFilterValue
386
- .map(val => {
387
- if (val) {
388
- if (val.toString) return val.toString().toLowerCase();
389
- return val.toLowerCase();
390
- }
391
- return undefined;
392
- })
393
- .indexOf(fieldVal.toString().toLowerCase()) === -1
394
- );
395
- };
396
- } else if (ccSelectedFilter === "isEmpty") {
397
- return qb
398
- ? qb.isEmpty()
399
- : fieldVal => {
400
- return !fieldVal;
401
- };
402
- } else if (ccSelectedFilter === "notEmpty") {
403
- return qb
404
- ? [qb.notNull(), qb.notEquals("")]
405
- : fieldVal => {
406
- return !!fieldVal;
407
- };
408
- } else if (ccSelectedFilter === "isExactly") {
409
- return qb
410
- ? filterValue
411
- : fieldVal => {
412
- return fieldVal === filterValue;
413
- };
414
- } else if (ccSelectedFilter === "true") {
415
- return qb
416
- ? qb.equals(true) //filter using qb (aka we're backend connected)
417
- : fieldVal => {
418
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
419
- return !!fieldVal;
420
- };
421
- } else if (ccSelectedFilter === "false") {
422
- return qb
423
- ? qb.equals(false) //filter using qb (aka we're backend connected)
424
- : fieldVal => {
425
- //filter using plain old javascript (aka we've got a local table that isn't backend connected)
426
- return !fieldVal;
427
- };
428
- } else if (ccSelectedFilter === "isBetween") {
429
- return qb
430
- ? qb.between(
431
- new Date(arrayFilterValue[0]),
432
- new Date(new Date(arrayFilterValue[1]).setHours(23, 59)) // set end of day for more accurate filtering
433
- )
434
- : fieldVal => {
435
- return (
436
- dayjs(arrayFilterValue[0]).valueOf() <= dayjs(fieldVal).valueOf() &&
437
- dayjs(fieldVal).valueOf() <= dayjs(arrayFilterValue[1]).valueOf()
438
- );
439
- };
440
- } else if (ccSelectedFilter === "notBetween") {
441
- return qb
442
- ? qb.notBetween(
443
- new Date(arrayFilterValue[0]),
444
- new Date(new Date(arrayFilterValue[1]).setHours(23, 59)) // set end of day for more accurate filtering
445
- )
446
- : fieldVal => {
447
- return (
448
- dayjs(arrayFilterValue[0]).valueOf() > dayjs(fieldVal).valueOf() ||
449
- dayjs(fieldVal).valueOf() > dayjs(arrayFilterValue[1]).valueOf()
450
- );
451
- };
452
- } else if (ccSelectedFilter === "isBefore") {
453
- return qb
454
- ? qb.lessThan(new Date(filterValue))
455
- : fieldVal => {
456
- return dayjs(fieldVal).valueOf() < dayjs(filterValue).valueOf();
457
- };
458
- } else if (ccSelectedFilter === "isAfter") {
459
- return qb
460
- ? qb.greaterThan(new Date(new Date(filterValue).setHours(23, 59))) // set end of day for more accurate filtering
461
- : fieldVal => {
462
- return dayjs(fieldVal).valueOf() > dayjs(filterValue).valueOf();
463
- };
464
- } else if (ccSelectedFilter === "greaterThan") {
465
- return qb
466
- ? qb.greaterThan(filterValue)
467
- : fieldVal => {
468
- return fieldVal > filterValue;
469
- };
470
- } else if (ccSelectedFilter === "lessThan") {
471
- return qb
472
- ? qb.lessThan(filterValue)
473
- : fieldVal => {
474
- return fieldVal < filterValue;
475
- };
476
- } else if (ccSelectedFilter === "inRange") {
477
- return qb
478
- ? qb.between(filterValue[0], filterValue[1])
479
- : fieldVal => {
480
- return filterValue[0] <= fieldVal && fieldVal <= filterValue[1];
481
- };
482
- } else if (ccSelectedFilter === "outsideRange") {
483
- return qb
484
- ? qb.notBetween(filterValue[0], filterValue[1])
485
- : fieldVal => {
486
- return filterValue[0] > fieldVal || fieldVal > filterValue[1];
487
- };
488
- } else if (ccSelectedFilter === "equalTo") {
489
- return qb
490
- ? filterValue
491
- : fieldVal => {
492
- return fieldVal === filterValue;
493
- };
494
- } else if (ccSelectedFilter === "regex") {
495
- return qb
496
- ? qb.matchesRegex(filterValue)
497
- : fieldVal => {
498
- new RegExp(filterValue).test(fieldVal);
499
- return fieldVal;
500
- };
501
- }
502
-
503
- throw new Error(
504
- `Unsupported filter ${selectedFilter}. Please make a new filter if you need one`
505
- );
506
- }
507
-
508
56
  export function getCurrentParamsFromUrl(location, isSimple) {
509
57
  let { search } = location;
510
58
  if (isSimple) {
@@ -585,23 +133,6 @@ function parseFilters(newParams) {
585
133
  };
586
134
  }
587
135
 
588
- function buildRef(qb, reference, searchField, expression) {
589
- if (reference.reference) {
590
- // qb[reference.target] = {}
591
- return qb.related(reference.target).whereAny({
592
- [reference.sourceField]: buildRef(
593
- qb,
594
- reference.reference,
595
- searchField,
596
- expression
597
- )
598
- });
599
- }
600
- return qb.related(reference.target).whereAny({
601
- [searchField]: expression
602
- });
603
- }
604
-
605
136
  export function makeDataTableHandlers({
606
137
  setNewParams,
607
138
  defaults,
@@ -707,34 +238,25 @@ export function makeDataTableHandlers({
707
238
  };
708
239
  }
709
240
 
710
- // if an inList value only has two items like
711
- // 2.3 then it will get parsed to a number and
712
- // break, convert it back to a string here
713
- function cleanupFilter(filter) {
714
- let filterToUse = filter;
715
- if (
716
- filterToUse.selectedFilter === "inList" &&
717
- typeof filterToUse.filterValue === "number"
718
- ) {
719
- filterToUse = {
720
- ...filterToUse,
721
- filterValue: filterToUse.filterValue.toString()
722
- };
723
- }
724
- return filterToUse;
725
- }
726
-
727
- function getAllFilters(filters, searchTerm, schema) {
728
- let allFilters = [
729
- ...filters,
730
- ...getFiltersFromSearchTerm(searchTerm, schema)
731
- ];
732
-
733
- allFilters = allFilters.filter(val => {
734
- return val !== "";
735
- }); //get rid of erroneous filters
736
-
737
- return allFilters.map(cleanupFilter);
241
+ function cleanupFilters({ filters, ccFields }) {
242
+ (filters || []).forEach(filter => {
243
+ const { filterOn, filterValue } = filter;
244
+ const field = ccFields[filterOn];
245
+ if (field.type === "number" || field.type === "integer") {
246
+ filter.filterValue = Array.isArray(filterValue)
247
+ ? filterValue.map(val => Number(val))
248
+ : Number(filterValue);
249
+ }
250
+ if (
251
+ filter.selectedFilter === "inList" &&
252
+ typeof filter.filterValue === "number"
253
+ ) {
254
+ // if an inList value only has two items like
255
+ // 2.3 then it will get parsed to a number and
256
+ // break, convert it back to a string here
257
+ filter.filterValue = filter.filterValue.toString();
258
+ }
259
+ });
738
260
  }
739
261
 
740
262
  export function getQueryParams({
@@ -746,115 +268,53 @@ export function getQueryParams({
746
268
  entities,
747
269
  isLocalCall,
748
270
  additionalFilter,
749
- additionalOrFilter,
750
271
  doNotCoercePageSize,
751
272
  noOrderError,
752
273
  isCodeModel,
753
274
  ownProps
754
275
  }) {
755
- Object.keys(currentParams).forEach(function (key) {
756
- if (currentParams[key] === undefined) {
757
- delete currentParams[key]; //we want to use the default value if any of these are undefined
758
- }
759
- });
760
- const tableQueryParams = {
761
- ...defaults,
762
- ...currentParams
763
- };
764
- let { page, pageSize, searchTerm, filters, order } = tableQueryParams;
765
- if (page <= 0 || isNaN(page)) {
766
- page = undefined;
767
- }
768
- if (isInfinite) {
769
- page = undefined;
770
- pageSize = undefined;
771
- }
772
- if (pageSize !== undefined && !doNotCoercePageSize) {
773
- //pageSize might come in as an unexpected number so we coerce it to be one of the nums in our pageSizes array
774
- const closest = clone(window.tgPageSizes || defaultPageSizes).sort(
775
- (a, b) => Math.abs(pageSize - a) - Math.abs(pageSize - b)
776
- )[0];
777
- pageSize = closest;
778
- }
779
- const toReturn = {
780
- //these are values that might be generally useful for the wrapped component
781
- page,
782
- pageSize: ownProps.controlled_pageSize || pageSize,
783
- order,
784
- filters,
785
- searchTerm
786
- };
276
+ let errorParsingUrlString;
787
277
 
788
- if (isLocalCall) {
789
- let newEntities = entities;
790
- //if the table is local (aka not directly connected to a db) then we need to
791
- //handle filtering/paging/sorting all on the front end
792
- newEntities = filterEntitiesLocal(
793
- filters,
794
- searchTerm,
795
- newEntities,
796
- schema,
797
- ownProps
798
- );
799
- newEntities = orderEntitiesLocal(order, newEntities, schema, ownProps);
278
+ try {
279
+ Object.keys(currentParams).forEach(function (key) {
280
+ if (currentParams[key] === undefined) {
281
+ delete currentParams[key]; //we want to use the default value if any of these are undefined
282
+ }
283
+ });
284
+ const tableQueryParams = {
285
+ ...defaults,
286
+ ...currentParams
287
+ };
288
+ let { page, pageSize, searchTerm, filters, order } = tableQueryParams;
289
+ const ccFields = getFieldsMappedByCCDisplayName(schema);
800
290
 
801
- const entitiesAcrossPages = newEntities;
291
+ cleanupFilters({ filters, ccFields });
802
292
 
803
- const newEntityCount = newEntities.length;
804
- //calculate the sorted, filtered, paged entities for the local table
805
- if (!isInfinite && !ownProps.controlled_pageSize) {
806
- const offset = (page - 1) * pageSize;
807
- newEntities = take(drop(newEntities, offset), pageSize);
293
+ if (page <= 0 || isNaN(page)) {
294
+ page = undefined;
808
295
  }
809
- toReturn.entities = newEntities;
810
- toReturn.entitiesAcrossPages = entitiesAcrossPages;
811
- toReturn.entityCount = newEntityCount;
812
- //if this call is being made by a local-data only connected datatable component,
813
- //we don't want to do the following gql stuff
814
- return toReturn;
815
- } else {
816
- const graphqlQueryParams = {
817
- // need to make sure sort exists because of https://github.com/apollographql/apollo-client/issues/3077
818
- sort: []
819
- };
820
296
  if (isInfinite) {
821
- graphqlQueryParams.pageSize = 999;
822
- graphqlQueryParams.pageNumber = 1;
823
- } else {
824
- graphqlQueryParams.pageNumber = Number(page);
825
- graphqlQueryParams.pageSize =
826
- ownProps.controlled_pageSize || Number(pageSize);
297
+ page = undefined;
298
+ pageSize = undefined;
299
+ }
300
+ if (pageSize !== undefined && !doNotCoercePageSize) {
301
+ //pageSize might come in as an unexpected number so we coerce it to be one of the nums in our pageSizes array
302
+ const closest = clone(window.tgPageSizes || defaultPageSizes).sort(
303
+ (a, b) => Math.abs(pageSize - a) - Math.abs(pageSize - b)
304
+ )[0];
305
+ pageSize = closest;
827
306
  }
828
307
 
829
- const { model } = schema;
830
- if (!window.QueryBuilder) return toReturn;
831
- const qb = new window.QueryBuilder(model);
832
- // qb = qb.filter('user')
833
- // qb = qb.whereAny({
834
- // userStatus: qb.related('userStatus').whereAny({
835
- // code: qb.contains('pending')
836
- // })
837
- // })
838
- // qb = qb.andWhere({
839
- // age: qb.lessThan(12)
840
- // })
841
- // qb.toJSON()
842
- // let filterBuilder = qb.filter(model); //start filter on model
843
-
844
- const ccFields = getFieldsMappedByCCDisplayName(schema);
845
-
846
- if (tableQueryParams.order && tableQueryParams.order.length) {
847
- tableQueryParams.order.forEach(orderVal => {
308
+ const cleanedOrder = [];
309
+ if (order && order.length) {
310
+ order.forEach(orderVal => {
848
311
  const ccDisplayName = orderVal.replace(/^-/gi, "");
849
312
  const schemaForField = ccFields[ccDisplayName];
850
313
  if (schemaForField) {
851
314
  const { path } = schemaForField;
852
315
  const reversed = ccDisplayName !== orderVal;
853
316
  const prefix = reversed ? "-" : "";
854
- graphqlQueryParams.sort = [
855
- ...(graphqlQueryParams.sort || []),
856
- prefix + path
857
- ];
317
+ cleanedOrder.push(prefix + path);
858
318
  } else {
859
319
  !noOrderError &&
860
320
  console.error(
@@ -865,194 +325,117 @@ export function getQueryParams({
865
325
  }
866
326
  });
867
327
  }
328
+ let toRet = {
329
+ //these are values that might be generally useful for the wrapped component
330
+ page,
331
+ pageSize: ownProps.controlled_pageSize || pageSize,
332
+ order: cleanedOrder,
333
+ filters,
334
+ searchTerm
335
+ };
868
336
 
869
- let errorParsingUrlString;
870
-
871
- const additionalFilterToUse = additionalFilter(qb, currentParams);
872
- let additionalOrFilterToUse = additionalOrFilter(qb, currentParams);
873
- if (additionalOrFilterToUse && additionalOrFilterToUse.ignoreSearchTerm) {
874
- searchTerm = "";
875
- additionalOrFilterToUse = additionalOrFilterToUse.additionalOrFilterToUse;
876
- }
337
+ const { where, order_by, limit, offset } = tableQueryParamsToHasuraClauses({
338
+ page,
339
+ pageSize,
340
+ searchTerm,
341
+ filters,
342
+ order: cleanedOrder,
343
+ schema
344
+ });
345
+ initializeHasuraWhereAndFilter(additionalFilter, where, currentParams);
346
+ if (isLocalCall) {
347
+ //if the table is local (aka not directly connected to a db) then we need to
348
+ //handle filtering/paging/sorting all on the front end
349
+ const r = filterLocalEntitiesToHasura(
350
+ prepEntitiesForLocalFilter({ entities, ccFields }),
351
+ {
352
+ where,
353
+ order_by: (Array.isArray(order_by) ? order_by : [order_by]).map(
354
+ obj => {
355
+ const path = Object.keys(obj)[0];
356
+ return {
357
+ path,
358
+ direction: obj[path],
359
+ ownProps,
360
+ ...ccFields[path]
361
+ };
362
+ }
363
+ ),
364
+ limit,
365
+ offset,
366
+ isInfinite
367
+ }
368
+ );
877
369
 
878
- const allFilters = getAllFilters(filters, searchTerm, schema);
879
- const { andFilters, orFilters, otherOrFilters } =
880
- getAndAndOrFilters(allFilters);
881
- try {
882
- const flattenFilters = filterObj => {
883
- return flatMap(Object.keys(filterObj), key => {
884
- return filterObj[key].map(filter => ({
885
- [key]: filter
886
- }));
887
- });
370
+ toRet = {
371
+ ...toRet,
372
+ ...r
888
373
  };
889
- const orFiltersObject = getQueries(orFilters, qb, ccFields);
890
- let allOrFilters = flattenFilters(orFiltersObject);
891
-
892
- otherOrFilters.forEach(orFilters => {
893
- const otherOrFiltersObject = getQueries(orFilters, qb, ccFields);
894
- allOrFilters = allOrFilters.concat(
895
- flattenFilters(otherOrFiltersObject)
896
- );
897
- });
898
- allOrFilters.push(additionalOrFilterToUse);
899
- allOrFilters = allOrFilters.filter(obj => !isEmpty(obj));
900
-
901
- const unflattenedAndQueries = getQueries(andFilters, qb, ccFields);
902
- let allAndFilters = flattenFilters(unflattenedAndQueries);
903
- allAndFilters.push(additionalFilterToUse);
904
- allAndFilters = allAndFilters.filter(obj => !isEmpty(obj));
905
- if (allAndFilters.length) {
906
- qb.whereAll(...allAndFilters);
907
- }
908
- if (allOrFilters.length) {
909
- qb.andWhereAny(...allOrFilters);
374
+ return toRet;
375
+ } else {
376
+ if (!order_by.length) {
377
+ // if no order by is specified, we will default to sorting by updatedAt
378
+ // this is useful for models that do not have a code field
379
+ order_by.push({ updatedAt: "desc" });
910
380
  }
911
- const columnCustomFilters = getColumnCustomFilters(
912
- andFilters,
913
- qb,
914
- ccFields
381
+ // in case entries that have the same value in the column being sorted on
382
+ // fall back to id as a secondary sort to make sure ordering happens correctly
383
+ order_by.push(
384
+ isCodeModel ? { code: "asc" } : { [window.__sortId || "id"]: "asc" }
915
385
  );
916
- if (columnCustomFilters.length) {
917
- qb.whereAll(...columnCustomFilters);
918
- }
919
- } catch (e) {
920
- if (urlConnected) {
921
- errorParsingUrlString = e;
922
- console.error(
923
- "The following error occurred when trying to build the query params. This is probably due to a malformed URL:",
924
- e
925
- );
926
- } else {
927
- console.error("Error building query params from filter:");
928
- throw e;
929
- }
930
- }
931
386
 
932
- if (qb.query.filters.length) {
933
- graphqlQueryParams.filter = qb.toJSON();
934
- }
935
-
936
- // by default make sort by updated at
937
- if (!graphqlQueryParams.sort.length) {
938
- graphqlQueryParams.sort.push("-updatedAt");
387
+ return {
388
+ ...toRet,
389
+ variables: {
390
+ where,
391
+ order_by,
392
+ limit,
393
+ offset
394
+ }
395
+ };
939
396
  }
940
-
941
- // in case entries that have the same value in the column being sorted on
942
- // fall back to id as a secondary sort to make sure ordering happens correctly
943
- graphqlQueryParams.sort.push(
944
- isCodeModel ? "code" : window.__sortId || "id"
945
- );
946
-
947
- return {
948
- ...toReturn,
949
- //the query params will get passed directly to as variables to the graphql query
950
- variables: graphqlQueryParams,
951
- errorParsingUrlString
952
- };
953
- }
954
- }
955
-
956
- function getSubFiltersAndPath(filter, qb, ccFields) {
957
- const { selectedFilter, filterValue, filterOn } = filter;
958
- const fieldSchema = ccFields[filterOn];
959
- let filterValueToUse = filterValue;
960
-
961
- if (fieldSchema) {
962
- if (fieldSchema.normalizeFilter) {
963
- filterValueToUse = fieldSchema.normalizeFilter(
964
- filterValue,
965
- selectedFilter,
966
- filterOn
397
+ } catch (e) {
398
+ if (urlConnected) {
399
+ errorParsingUrlString = e;
400
+ console.error(
401
+ "The following error occurred when trying to build the query params. This is probably due to a malformed URL:",
402
+ e
967
403
  );
968
- }
969
- }
970
- const _subFilters = getSubFilter(qb, selectedFilter, filterValueToUse);
971
-
972
- let filterField;
973
- if (fieldSchema) {
974
- const { path, reference } = fieldSchema;
975
- if (reference) {
976
- filterField = reference.sourceField;
404
+ return {
405
+ errorParsingUrlString,
406
+ variables: {
407
+ where: {},
408
+ order_by: [],
409
+ limit: 0,
410
+ offset: 0
411
+ }
412
+ };
977
413
  } else {
978
- filterField = path;
414
+ console.error("Error building query params from filter:");
415
+ throw e;
979
416
  }
980
- } else if (filterOn === "id") {
981
- filterField = filterOn;
982
- } else {
983
- console.error("Trying to filter on unknown field");
984
417
  }
985
- const subFiltersToUse = [];
986
- const subFilters = Array.isArray(_subFilters) ? _subFilters : [_subFilters];
987
- subFilters.forEach(subFilter => {
988
- let subFilterToUse = subFilter;
989
- if (fieldSchema) {
990
- const { path, reference } = fieldSchema;
991
- if (reference) {
992
- subFilterToUse = buildRef(
993
- qb,
994
- reference,
995
- last(path.split(".")),
996
- subFilter
997
- );
998
- }
999
- }
1000
- subFiltersToUse.push(subFilterToUse);
1001
- });
1002
-
1003
- return {
1004
- path: filterField,
1005
- subFilters: subFiltersToUse
1006
- };
1007
418
  }
1008
419
 
1009
- function getQueries(filters, qb, ccFields) {
1010
- const subQueries = filters.reduce((acc, filter) => {
1011
- if (!filter) {
1012
- console.warn("We should always have a filter object!");
1013
- return acc;
1014
- }
1015
- const { filterOn } = filter;
1016
- const fieldSchema = ccFields[filterOn];
1017
- // will be handled below
1018
- if (!filter.isSearchTermFilter && fieldSchema?.additionalColumnFilter)
1019
- return acc;
1020
- const { path, subFilters } = getSubFiltersAndPath(filter, qb, ccFields);
1021
- acc[path] = subFilters;
1022
- return acc;
1023
- }, {});
1024
- return subQueries;
1025
- }
1026
-
1027
- function getColumnCustomFilters(filters, qb, ccFields) {
1028
- const subQueries = filters.reduce((acc, filter) => {
1029
- if (!filter) {
1030
- console.warn("We should always have a filter object!");
1031
- return acc;
1032
- }
1033
- const { filterOn } = filter;
1034
- const fieldSchema = ccFields[filterOn];
1035
- if (filter.isSearchTermFilter || !fieldSchema?.additionalColumnFilter) {
1036
- return acc;
1037
- }
1038
- const { path, subFilters } = getSubFiltersAndPath(filter, qb, ccFields);
1039
- /* the column filters need to have access to this sub filter but also be able to add additional
1040
- filter logic.
1041
- ex.
1042
- qb.whereAny({
1043
- id: qb.related("extendedStringValueView.buildSampleId").whereAll({
1044
- value: "something",
1045
- extendedPropertyId: "myId"
1046
- })
1047
- ...
1048
- })
1049
-
1050
- is possible because the returned accumulator will be passed to whereAny
1051
- */
1052
- subFilters.forEach(subFilter => {
1053
- acc.push(fieldSchema.additionalColumnFilter(qb, subFilter, path));
420
+ function prepEntitiesForLocalFilter({ entities, ccFields }) {
421
+ // Prepare entities for local filtering by mapping over them and applying necessary transformations
422
+ const r = entities.map(entity => {
423
+ const toSpread = {};
424
+ let hasChanged = false;
425
+ // Apply any necessary transformations using ccFields
426
+ forEach(ccFields, ({ getValueToFilterOn, path }) => {
427
+ if (getValueToFilterOn) {
428
+ hasChanged = true;
429
+ toSpread["___original___" + path] = entity[path];
430
+ const value = getValueToFilterOn(entity);
431
+ toSpread[path] = value;
432
+ }
1054
433
  });
1055
- return acc;
1056
- }, []);
1057
- return subQueries;
434
+ if (hasChanged) {
435
+ // If there are any transformations, spread them into the entity
436
+ return { ...entity, ...toSpread };
437
+ }
438
+ return entity;
439
+ });
440
+ return r;
1058
441
  }