@teselagen/ui 0.8.6-beta.11 → 0.8.6-beta.13

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/index.cjs.js CHANGED
@@ -19422,34 +19422,37 @@ function tableQueryParamsToHasuraClauses({
19422
19422
  if (searchTerm) {
19423
19423
  const searchTermFilters = [];
19424
19424
  const uniqueFieldsByPath = {};
19425
+ const searchTerms = searchTerm.split(",");
19425
19426
  schema.fields.forEach((field) => {
19426
19427
  const { type: type2, path: path2, searchDisabled } = field;
19427
19428
  if (uniqueFieldsByPath[path2]) return;
19428
19429
  uniqueFieldsByPath[path2] = true;
19429
19430
  if (searchDisabled || field.filterDisabled || type2 === "color") return;
19430
- const filterValue = searchTerm;
19431
- if (type2 === "string" || type2 === "lookup") {
19432
- const o2 = set$1({}, path2, { _ilike: `%${filterValue}%` });
19433
- searchTermFilters.push(o2);
19434
- } else if (type2 === "boolean") {
19435
- let regex;
19436
- try {
19437
- regex = new RegExp("^" + searchTerm, "ig");
19438
- } catch (error) {
19439
- }
19440
- if (regex) {
19441
- if ("true".replace(regex, "") !== "true") {
19442
- const o2 = set$1({}, path2, { _eq: true });
19443
- searchTermFilters.push(o2);
19444
- } else if ("false".replace(regex, "") !== "false") {
19445
- const o2 = set$1({}, path2, { _eq: false });
19446
- searchTermFilters.push(o2);
19431
+ searchTerms.forEach((term) => {
19432
+ const filterValue = term.trim();
19433
+ if (type2 === "string" || type2 === "lookup") {
19434
+ const o2 = set$1({}, path2, { _ilike: `%${filterValue}%` });
19435
+ searchTermFilters.push(o2);
19436
+ } else if (type2 === "boolean") {
19437
+ let regex;
19438
+ try {
19439
+ regex = new RegExp("^" + filterValue, "ig");
19440
+ } catch (error) {
19441
+ }
19442
+ if (regex) {
19443
+ if ("true".replace(regex, "") !== "true") {
19444
+ const o2 = set$1({}, path2, { _eq: true });
19445
+ searchTermFilters.push(o2);
19446
+ } else if ("false".replace(regex, "") !== "false") {
19447
+ const o2 = set$1({}, path2, { _eq: false });
19448
+ searchTermFilters.push(o2);
19449
+ }
19447
19450
  }
19451
+ } else if ((type2 === "number" || type2 === "integer") && !isNaN(filterValue)) {
19452
+ const o2 = set$1({}, path2, { _eq: parseFloat(filterValue) });
19453
+ searchTermFilters.push(o2);
19448
19454
  }
19449
- } else if ((type2 === "number" || type2 === "integer") && !isNaN(filterValue)) {
19450
- const o2 = set$1({}, path2, { _eq: parseFloat(filterValue) });
19451
- searchTermFilters.push(o2);
19452
- }
19455
+ });
19453
19456
  });
19454
19457
  if (searchTermFilters.length > 0) {
19455
19458
  if (Object.keys(where).length > 0) {
@@ -19543,9 +19546,7 @@ function tableQueryParamsToHasuraClauses({
19543
19546
  },
19544
19547
  {
19545
19548
  [filterOn]: {
19546
- _gt: new Date(
19547
- new Date(arrayFilterValue[1]).setHours(23, 59)
19548
- )
19549
+ _gt: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
19549
19550
  }
19550
19551
  }
19551
19552
  ]
@@ -22227,13 +22228,27 @@ function applyOrderBy(records, _order_by) {
22227
22228
  directions.push(direction);
22228
22229
  iteratees.push((record) => {
22229
22230
  const value = record[field];
22231
+ if (isNull(value) || value === void 0) {
22232
+ return direction === "asc" ? -Infinity : -Infinity;
22233
+ }
22230
22234
  if (isString$1(value) && /\d/.test(value)) {
22231
22235
  return value.replace(/(\d+)/g, (num) => num.padStart(10, "0"));
22232
22236
  }
22233
22237
  return value;
22234
22238
  });
22235
22239
  });
22236
- records = orderBy$1(records, iteratees, directions);
22240
+ let sortedRecords = orderBy$1(records, iteratees, directions);
22241
+ order_by.forEach((item) => {
22242
+ const field = Object.keys(item)[0];
22243
+ const direction = item[field];
22244
+ if (direction === "desc") {
22245
+ sortedRecords = [
22246
+ ...sortedRecords.filter((record) => !isNull(record[field]) && record[field] !== void 0),
22247
+ ...sortedRecords.filter((record) => isNull(record[field]) || record[field] === void 0)
22248
+ ];
22249
+ }
22250
+ });
22251
+ records = sortedRecords;
22237
22252
  }
22238
22253
  return records;
22239
22254
  }
package/index.es.js CHANGED
@@ -19404,34 +19404,37 @@ function tableQueryParamsToHasuraClauses({
19404
19404
  if (searchTerm) {
19405
19405
  const searchTermFilters = [];
19406
19406
  const uniqueFieldsByPath = {};
19407
+ const searchTerms = searchTerm.split(",");
19407
19408
  schema.fields.forEach((field) => {
19408
19409
  const { type: type2, path: path2, searchDisabled } = field;
19409
19410
  if (uniqueFieldsByPath[path2]) return;
19410
19411
  uniqueFieldsByPath[path2] = true;
19411
19412
  if (searchDisabled || field.filterDisabled || type2 === "color") return;
19412
- const filterValue = searchTerm;
19413
- if (type2 === "string" || type2 === "lookup") {
19414
- const o2 = set$1({}, path2, { _ilike: `%${filterValue}%` });
19415
- searchTermFilters.push(o2);
19416
- } else if (type2 === "boolean") {
19417
- let regex;
19418
- try {
19419
- regex = new RegExp("^" + searchTerm, "ig");
19420
- } catch (error) {
19421
- }
19422
- if (regex) {
19423
- if ("true".replace(regex, "") !== "true") {
19424
- const o2 = set$1({}, path2, { _eq: true });
19425
- searchTermFilters.push(o2);
19426
- } else if ("false".replace(regex, "") !== "false") {
19427
- const o2 = set$1({}, path2, { _eq: false });
19428
- searchTermFilters.push(o2);
19413
+ searchTerms.forEach((term) => {
19414
+ const filterValue = term.trim();
19415
+ if (type2 === "string" || type2 === "lookup") {
19416
+ const o2 = set$1({}, path2, { _ilike: `%${filterValue}%` });
19417
+ searchTermFilters.push(o2);
19418
+ } else if (type2 === "boolean") {
19419
+ let regex;
19420
+ try {
19421
+ regex = new RegExp("^" + filterValue, "ig");
19422
+ } catch (error) {
19423
+ }
19424
+ if (regex) {
19425
+ if ("true".replace(regex, "") !== "true") {
19426
+ const o2 = set$1({}, path2, { _eq: true });
19427
+ searchTermFilters.push(o2);
19428
+ } else if ("false".replace(regex, "") !== "false") {
19429
+ const o2 = set$1({}, path2, { _eq: false });
19430
+ searchTermFilters.push(o2);
19431
+ }
19429
19432
  }
19433
+ } else if ((type2 === "number" || type2 === "integer") && !isNaN(filterValue)) {
19434
+ const o2 = set$1({}, path2, { _eq: parseFloat(filterValue) });
19435
+ searchTermFilters.push(o2);
19430
19436
  }
19431
- } else if ((type2 === "number" || type2 === "integer") && !isNaN(filterValue)) {
19432
- const o2 = set$1({}, path2, { _eq: parseFloat(filterValue) });
19433
- searchTermFilters.push(o2);
19434
- }
19437
+ });
19435
19438
  });
19436
19439
  if (searchTermFilters.length > 0) {
19437
19440
  if (Object.keys(where).length > 0) {
@@ -19525,9 +19528,7 @@ function tableQueryParamsToHasuraClauses({
19525
19528
  },
19526
19529
  {
19527
19530
  [filterOn]: {
19528
- _gt: new Date(
19529
- new Date(arrayFilterValue[1]).setHours(23, 59)
19530
- )
19531
+ _gt: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
19531
19532
  }
19532
19533
  }
19533
19534
  ]
@@ -22209,13 +22210,27 @@ function applyOrderBy(records, _order_by) {
22209
22210
  directions.push(direction);
22210
22211
  iteratees.push((record) => {
22211
22212
  const value = record[field];
22213
+ if (isNull(value) || value === void 0) {
22214
+ return direction === "asc" ? -Infinity : -Infinity;
22215
+ }
22212
22216
  if (isString$1(value) && /\d/.test(value)) {
22213
22217
  return value.replace(/(\d+)/g, (num) => num.padStart(10, "0"));
22214
22218
  }
22215
22219
  return value;
22216
22220
  });
22217
22221
  });
22218
- records = orderBy$1(records, iteratees, directions);
22222
+ let sortedRecords = orderBy$1(records, iteratees, directions);
22223
+ order_by.forEach((item) => {
22224
+ const field = Object.keys(item)[0];
22225
+ const direction = item[field];
22226
+ if (direction === "desc") {
22227
+ sortedRecords = [
22228
+ ...sortedRecords.filter((record) => !isNull(record[field]) && record[field] !== void 0),
22229
+ ...sortedRecords.filter((record) => isNull(record[field]) || record[field] === void 0)
22230
+ ];
22231
+ }
22232
+ });
22233
+ records = sortedRecords;
22219
22234
  }
22220
22235
  return records;
22221
22236
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teselagen/ui",
3
- "version": "0.8.6-beta.11",
3
+ "version": "0.8.6-beta.13",
4
4
  "main": "./src/index.js",
5
5
  "type": "module",
6
6
  "exports": {
@@ -17,7 +17,6 @@
17
17
  "@dnd-kit/core": "^6.1.0",
18
18
  "@dnd-kit/modifiers": "^7.0.0",
19
19
  "@dnd-kit/sortable": "^8.0.0",
20
- "@teselagen/react-table": "6.10.16",
21
20
  "classnames": "^2.3.2",
22
21
  "color": "^3.2.1",
23
22
  "copy-to-clipboard": "^3.3.1",
@@ -230,6 +230,8 @@ function applyWhereClause(records, where) {
230
230
  // order_by looks like this: [{ some_field: "asc" }, { some_other_field: "desc" }] or {some_field: "asc"}
231
231
  // returns the records sorted by the order_by clause
232
232
  function applyOrderBy(records, _order_by) {
233
+ // console.log('records start:', records)
234
+ // console.log('_order_by:', _order_by)
233
235
  const order_by = isArray(_order_by)
234
236
  ? _order_by
235
237
  : isEmpty(_order_by)
@@ -251,6 +253,10 @@ function applyOrderBy(records, _order_by) {
251
253
  // Create a custom iteratee function for natural sorting
252
254
  iteratees.push(record => {
253
255
  const value = record[field];
256
+ // Handle null values based on sort direction
257
+ if (isNull(value) || value === undefined) {
258
+ return direction === 'asc' ? -Infinity : -Infinity;
259
+ }
254
260
  // Use natural sorting only for strings that contain numbers
255
261
  if (isString(value) && /\d/.test(value)) {
256
262
  // Return the value in a format that can be naturally sorted
@@ -260,9 +266,26 @@ function applyOrderBy(records, _order_by) {
260
266
  return value;
261
267
  });
262
268
  });
263
-
264
- // Use the custom iteratees for natural sorting
265
- records = orderBy(records, iteratees, directions);
269
+
270
+ // First sort normally
271
+ let sortedRecords = orderBy(records, iteratees, directions);
272
+
273
+ // Then ensure entries with a value for the field come before entries without a value
274
+ // for any field sorted in descending order
275
+ order_by.forEach((item) => {
276
+ const field = Object.keys(item)[0];
277
+ const direction = item[field];
278
+
279
+ if (direction === 'desc') {
280
+ // For descending sorts, we want entries with values to appear before entries without values
281
+ sortedRecords = [
282
+ ...sortedRecords.filter(record => !isNull(record[field]) && record[field] !== undefined),
283
+ ...sortedRecords.filter(record => isNull(record[field]) || record[field] === undefined)
284
+ ];
285
+ }
286
+ });
287
+
288
+ records = sortedRecords;
266
289
  }
267
290
  return records;
268
291
  }
@@ -379,42 +379,152 @@ describe("filterLocalEntitiesToHasura", () => {
379
379
  ]);
380
380
  expect(result.entityCount).toBe(3);
381
381
  });
382
- it("should order by age ascending", () => {
383
- const result = filterLocalEntitiesToHasura(records, {
384
- order_by: { age: "asc" }
385
- });
382
+ it("should order by age ascending and put null vals last by default", () => {
383
+ const result = filterLocalEntitiesToHasura(
384
+ [{ id: 111, name: "Null Age", age: null, city: "Unknown" }, ...records],
385
+ {
386
+ order_by: { age: "asc" }
387
+ }
388
+ );
386
389
  expect(result.entities).toEqual([
387
390
  records[3],
388
391
  records[1],
389
392
  records[0],
390
- records[2]
393
+ records[2],
394
+ { id: 111, name: "Null Age", age: null, city: "Unknown" }
391
395
  ]);
392
396
  expect(result.entitiesAcrossPages).toEqual([
393
397
  records[3],
394
398
  records[1],
395
399
  records[0],
396
- records[2]
400
+ records[2],
401
+ { id: 111, name: "Null Age", age: null, city: "Unknown" }
397
402
  ]);
398
- expect(result.entityCount).toBe(4);
403
+ expect(result.entityCount).toBe(5);
399
404
  });
400
-
401
- it("should order by age descending", () => {
402
- const result = filterLocalEntitiesToHasura(records, {
403
- order_by: { age: "desc" }
404
- });
405
+ it("should order by age desc and put null/undefined vals last by default", () => {
406
+ const result = filterLocalEntitiesToHasura(
407
+ [
408
+ { id: 111, name: "Null Age", age: null, city: "Unknown" },
409
+ { id: 1121, name: "Undefined Age", age: undefined, city: "Unknown" },
410
+ ...records
411
+ ],
412
+ {
413
+ order_by: { age: "desc" }
414
+ }
415
+ );
405
416
  expect(result.entities).toEqual([
406
417
  records[2],
407
418
  records[0],
408
419
  records[1],
409
- records[3]
420
+ records[3],
421
+ { id: 111, name: "Null Age", age: null, city: "Unknown" },
422
+ { id: 1121, name: "Undefined Age", age: undefined, city: "Unknown" }
410
423
  ]);
411
- expect(result.entitiesAcrossPages).toEqual([
424
+ expect(result.entityCount).toBe(6);
425
+ });
426
+
427
+ it.only("should order by updatedAt descending, putting null/undefined vals last ", () => {
428
+ const result = filterLocalEntitiesToHasura(
429
+ [
430
+ {
431
+ name: "-10_signal",
432
+ color: "#4ECDC4",
433
+ isGenbankStandardType: true,
434
+ __typename: "featureTypeOverride"
435
+ },
436
+ {
437
+ name: "-35_signal",
438
+ color: "#F7FFF7",
439
+ isGenbankStandardType: true,
440
+ __typename: "featureTypeOverride"
441
+ },
442
+ {
443
+ name: "3'clip",
444
+ color: "#FF6B6B",
445
+ isGenbankStandardType: true,
446
+ __typename: "featureTypeOverride"
447
+ },
448
+ {
449
+ name: "3'UTR",
450
+ color: "#FFE66D",
451
+ isGenbankStandardType: true,
452
+ __typename: "featureTypeOverride"
453
+ },
454
+
455
+ {
456
+ __typename: "featureTypeOverride",
457
+ id: "33a90fcb-fc26-406f-a6d5-41ac4ba8ea64",
458
+ name: "lalala",
459
+ description: null,
460
+ color: "#81bb41",
461
+ genbankEquivalentType: null,
462
+ updatedAt: "2025-06-03T01:02:24.737499+00:00",
463
+ isHidden: false,
464
+ isCustomType: true
465
+ }
466
+ ],
467
+ {
468
+ order_by: [{ updatedAt: "desc" }]
469
+ }
470
+ );
471
+ expect(result.entities).toEqual([
472
+ {
473
+ __typename: "featureTypeOverride",
474
+ id: "33a90fcb-fc26-406f-a6d5-41ac4ba8ea64",
475
+ name: "lalala",
476
+ description: null,
477
+ color: "#81bb41",
478
+ genbankEquivalentType: null,
479
+ updatedAt: "2025-06-03T01:02:24.737499+00:00",
480
+ isHidden: false,
481
+ isCustomType: true
482
+ },
483
+ {
484
+ name: "-10_signal",
485
+ color: "#4ECDC4",
486
+ isGenbankStandardType: true,
487
+ __typename: "featureTypeOverride"
488
+ },
489
+ {
490
+ name: "-35_signal",
491
+ color: "#F7FFF7",
492
+ isGenbankStandardType: true,
493
+ __typename: "featureTypeOverride"
494
+ },
495
+ {
496
+ name: "3'clip",
497
+ color: "#FF6B6B",
498
+ isGenbankStandardType: true,
499
+ __typename: "featureTypeOverride"
500
+ },
501
+ {
502
+ name: "3'UTR",
503
+ color: "#FFE66D",
504
+ isGenbankStandardType: true,
505
+ __typename: "featureTypeOverride"
506
+ }
507
+ ]);
508
+ });
509
+ it("should order by age descending, putting null/undefined vals last ", () => {
510
+ const result = filterLocalEntitiesToHasura(
511
+ [
512
+ { id: 111, name: "Null Age", age: null, city: "Unknown" },
513
+ { id: 1121, name: "Undefined Age", age: undefined, city: "Unknown" },
514
+ ...records
515
+ ],
516
+ {
517
+ order_by: { age: "desc" }
518
+ }
519
+ );
520
+ expect(result.entities).toEqual([
412
521
  records[2],
413
522
  records[0],
414
523
  records[1],
415
- records[3]
524
+ records[3],
525
+ { id: 111, name: "Null Age", age: null, city: "Unknown" },
526
+ { id: 1121, name: "Undefined Age", age: undefined, city: "Unknown" }
416
527
  ]);
417
- expect(result.entityCount).toBe(4);
418
528
  });
419
529
 
420
530
  it("should order by name ascending", () => {
@@ -12,4 +12,4 @@ export function initializeHasuraWhereAndFilter(
12
12
  }
13
13
  } else if (typeof additionalFilter === "object")
14
14
  where._and.push(additionalFilter);
15
- }
15
+ }
@@ -5,9 +5,7 @@ import {
5
5
  tableQueryParamsToHasuraClauses
6
6
  } from "./tableQueryParamsToHasuraClauses";
7
7
  import { filterLocalEntitiesToHasura } from "./filterLocalEntitiesToHasura";
8
- import {
9
- initializeHasuraWhereAndFilter
10
- } from "./initializeHasuraWhereAndFilter";
8
+ import { initializeHasuraWhereAndFilter } from "./initializeHasuraWhereAndFilter";
11
9
 
12
10
  const defaultPageSizes = [5, 10, 15, 25, 50, 100, 200, 400];
13
11
 
@@ -20,39 +20,46 @@ export function tableQueryParamsToHasuraClauses({
20
20
  // Create a map to deduplicate fields by path
21
21
  const uniqueFieldsByPath = {};
22
22
 
23
+ // Split the search term by comma to support multi-term searching
24
+ const searchTerms = searchTerm.split(",");
25
+
23
26
  schema.fields.forEach(field => {
24
27
  const { type, path, searchDisabled } = field;
25
28
  if (uniqueFieldsByPath[path]) return; // Skip if already added
26
29
  uniqueFieldsByPath[path] = true;
27
30
  if (searchDisabled || field.filterDisabled || type === "color") return;
28
- const filterValue = searchTerm; // No cleaning needed here, we're using _ilike
29
31
 
30
- if (type === "string" || type === "lookup") {
31
- const o = set({}, path, { _ilike: `%${filterValue}%` });
32
- searchTermFilters.push(o);
33
- } else if (type === "boolean") {
34
- let regex;
35
- try {
36
- regex = new RegExp("^" + searchTerm, "ig");
37
- } catch (error) {
38
- //ignore
39
- }
40
- if (regex) {
41
- if ("true".replace(regex, "") !== "true") {
42
- const o = set({}, path, { _eq: true });
43
- searchTermFilters.push(o);
44
- } else if ("false".replace(regex, "") !== "false") {
45
- const o = set({}, path, { _eq: false });
46
- searchTermFilters.push(o);
32
+ // Process each search term
33
+ searchTerms.forEach(term => {
34
+ const filterValue = term.trim(); // Trim the term to handle spaces after commas
35
+
36
+ if (type === "string" || type === "lookup") {
37
+ const o = set({}, path, { _ilike: `%${filterValue}%` });
38
+ searchTermFilters.push(o);
39
+ } else if (type === "boolean") {
40
+ let regex;
41
+ try {
42
+ regex = new RegExp("^" + filterValue, "ig");
43
+ } catch (error) {
44
+ //ignore
47
45
  }
46
+ if (regex) {
47
+ if ("true".replace(regex, "") !== "true") {
48
+ const o = set({}, path, { _eq: true });
49
+ searchTermFilters.push(o);
50
+ } else if ("false".replace(regex, "") !== "false") {
51
+ const o = set({}, path, { _eq: false });
52
+ searchTermFilters.push(o);
53
+ }
54
+ }
55
+ } else if (
56
+ (type === "number" || type === "integer") &&
57
+ !isNaN(filterValue)
58
+ ) {
59
+ const o = set({}, path, { _eq: parseFloat(filterValue) });
60
+ searchTermFilters.push(o);
48
61
  }
49
- } else if (
50
- (type === "number" || type === "integer") &&
51
- !isNaN(filterValue)
52
- ) {
53
- const o = set({}, path, { _eq: parseFloat(filterValue) });
54
- searchTermFilters.push(o);
55
- }
62
+ });
56
63
  });
57
64
 
58
65
  if (searchTermFilters.length > 0) {
@@ -65,164 +72,161 @@ export function tableQueryParamsToHasuraClauses({
65
72
  }
66
73
 
67
74
  if (filters && filters.length > 0) {
68
- const filterClauses = filters
69
- .map(filter => {
70
- let { selectedFilter, filterOn, filterValue } = filter;
71
- const fieldSchema = ccFields[filterOn] || {};
75
+ const filterClauses = filters.map(filter => {
76
+ let { selectedFilter, filterOn, filterValue } = filter;
77
+ const fieldSchema = ccFields[filterOn] || {};
72
78
 
73
- const { path, reference, type, customColumnFilter } = fieldSchema;
74
- if (customColumnFilter) {
75
- return customColumnFilter(filterValue);
76
- }
77
- let stringFilterValue =
78
- filterValue && filterValue.toString
79
- ? filterValue.toString()
80
- : filterValue;
81
- if (stringFilterValue === false) {
82
- // we still want to be able to search for the string "false" which will get parsed to false
83
- stringFilterValue = "false";
84
- } else {
85
- stringFilterValue = stringFilterValue || "";
86
- }
87
- const arrayFilterValue = Array.isArray(filterValue)
88
- ? filterValue
89
- : stringFilterValue.split(";");
79
+ const { path, reference, type, customColumnFilter } = fieldSchema;
80
+ if (customColumnFilter) {
81
+ return customColumnFilter(filterValue);
82
+ }
83
+ let stringFilterValue =
84
+ filterValue && filterValue.toString
85
+ ? filterValue.toString()
86
+ : filterValue;
87
+ if (stringFilterValue === false) {
88
+ // we still want to be able to search for the string "false" which will get parsed to false
89
+ stringFilterValue = "false";
90
+ } else {
91
+ stringFilterValue = stringFilterValue || "";
92
+ }
93
+ const arrayFilterValue = Array.isArray(filterValue)
94
+ ? filterValue
95
+ : stringFilterValue.split(";");
90
96
 
91
- if (type === "number" || type === "integer") {
92
- filterValue = Array.isArray(filterValue)
93
- ? filterValue.map(val => Number(val))
94
- : Number(filterValue);
95
- }
97
+ if (type === "number" || type === "integer") {
98
+ filterValue = Array.isArray(filterValue)
99
+ ? filterValue.map(val => Number(val))
100
+ : Number(filterValue);
101
+ }
96
102
 
97
- if (fieldSchema.normalizeFilter) {
98
- filterValue = fieldSchema.normalizeFilter(
99
- filterValue,
100
- selectedFilter,
101
- filterOn
102
- );
103
- }
103
+ if (fieldSchema.normalizeFilter) {
104
+ filterValue = fieldSchema.normalizeFilter(
105
+ filterValue,
106
+ selectedFilter,
107
+ filterOn
108
+ );
109
+ }
104
110
 
105
- if (reference) {
106
- filterOn = reference.sourceField;
107
- } else {
108
- filterOn = path || filterOn;
109
- }
110
- switch (selectedFilter) {
111
- case "none":
112
- return {};
113
- case "startsWith":
114
- return { [filterOn]: { _ilike: `${filterValue}%` } };
115
- case "endsWith":
116
- return { [filterOn]: { _ilike: `%${filterValue}` } };
117
- case "contains":
118
- return { [filterOn]: { _ilike: `%${filterValue}%` } };
119
- case "notContains":
120
- return { [filterOn]: { _nilike: `%${filterValue}%` } };
121
- case "isExactly":
122
- return { [filterOn]: { _eq: filterValue } };
123
- case "isEmpty":
124
- if (filterOn.includes(".")) {
125
- // if we're filtering on a nested field, like a sequence table with parts.name
126
- // we really want to just query on the top level field's existence
127
- return {
128
- _not: {
129
- [filterOn.split(".")[0]]: {}
130
- }
131
- };
132
- }
133
- return {
134
- _or: [
135
- { [filterOn]: { _eq: "" } },
136
- { [filterOn]: { _is_null: true } }
137
- ]
138
- };
139
- case "notEmpty":
140
- return {
141
- _and: [
142
- { [filterOn]: { _neq: "" } },
143
- { [filterOn]: { _is_null: false } }
144
- ]
145
- };
146
- case "inList":
147
- return { [filterOn]: { _in: filterValue } };
148
- case "notInList":
149
- return { [filterOn]: { _nin: filterValue } };
150
- case "true":
151
- return { [filterOn]: { _eq: true } };
152
- case "false":
153
- return { [filterOn]: { _eq: false } };
154
- case "dateIs":
155
- return { [filterOn]: { _eq: filterValue } };
156
- case "notBetween":
157
- return {
158
- _or: [
159
- {
160
- [filterOn]: {
161
- _lt: new Date(arrayFilterValue[0])
162
- }
163
- },
164
- {
165
- [filterOn]: {
166
- _gt: new Date(
167
- new Date(arrayFilterValue[1]).setHours(23, 59)
168
- )
169
- }
170
- }
171
- ]
172
- };
173
- case "isBetween":
111
+ if (reference) {
112
+ filterOn = reference.sourceField;
113
+ } else {
114
+ filterOn = path || filterOn;
115
+ }
116
+ switch (selectedFilter) {
117
+ case "none":
118
+ return {};
119
+ case "startsWith":
120
+ return { [filterOn]: { _ilike: `${filterValue}%` } };
121
+ case "endsWith":
122
+ return { [filterOn]: { _ilike: `%${filterValue}` } };
123
+ case "contains":
124
+ return { [filterOn]: { _ilike: `%${filterValue}%` } };
125
+ case "notContains":
126
+ return { [filterOn]: { _nilike: `%${filterValue}%` } };
127
+ case "isExactly":
128
+ return { [filterOn]: { _eq: filterValue } };
129
+ case "isEmpty":
130
+ if (filterOn.includes(".")) {
131
+ // if we're filtering on a nested field, like a sequence table with parts.name
132
+ // we really want to just query on the top level field's existence
174
133
  return {
175
- [filterOn]: {
176
- _gte: new Date(arrayFilterValue[0]),
177
- _lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
134
+ _not: {
135
+ [filterOn.split(".")[0]]: {}
178
136
  }
179
137
  };
180
- case "isBefore":
181
- return { [filterOn]: { _lt: new Date(filterValue) } };
182
- case "isAfter":
183
- return { [filterOn]: { _gt: new Date(filterValue) } };
184
- case "greaterThan":
185
- return { [filterOn]: { _gt: parseFloat(filterValue) } };
186
- case "lessThan":
187
- return { [filterOn]: { _lt: parseFloat(filterValue) } };
188
- case "inRange":
189
- return {
190
- [filterOn]: {
191
- _gte: parseFloat(arrayFilterValue[0]),
192
- _lte: parseFloat(arrayFilterValue[1])
138
+ }
139
+ return {
140
+ _or: [
141
+ { [filterOn]: { _eq: "" } },
142
+ { [filterOn]: { _is_null: true } }
143
+ ]
144
+ };
145
+ case "notEmpty":
146
+ return {
147
+ _and: [
148
+ { [filterOn]: { _neq: "" } },
149
+ { [filterOn]: { _is_null: false } }
150
+ ]
151
+ };
152
+ case "inList":
153
+ return { [filterOn]: { _in: filterValue } };
154
+ case "notInList":
155
+ return { [filterOn]: { _nin: filterValue } };
156
+ case "true":
157
+ return { [filterOn]: { _eq: true } };
158
+ case "false":
159
+ return { [filterOn]: { _eq: false } };
160
+ case "dateIs":
161
+ return { [filterOn]: { _eq: filterValue } };
162
+ case "notBetween":
163
+ return {
164
+ _or: [
165
+ {
166
+ [filterOn]: {
167
+ _lt: new Date(arrayFilterValue[0])
168
+ }
169
+ },
170
+ {
171
+ [filterOn]: {
172
+ _gt: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
173
+ }
193
174
  }
194
- };
195
- case "outsideRange":
196
- return {
197
- _or: [
198
- {
199
- [filterOn]: {
200
- _lt: parseFloat(arrayFilterValue[0])
201
- }
202
- },
203
- {
204
- [filterOn]: {
205
- _gt: parseFloat(arrayFilterValue[1])
206
- }
175
+ ]
176
+ };
177
+ case "isBetween":
178
+ return {
179
+ [filterOn]: {
180
+ _gte: new Date(arrayFilterValue[0]),
181
+ _lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
182
+ }
183
+ };
184
+ case "isBefore":
185
+ return { [filterOn]: { _lt: new Date(filterValue) } };
186
+ case "isAfter":
187
+ return { [filterOn]: { _gt: new Date(filterValue) } };
188
+ case "greaterThan":
189
+ return { [filterOn]: { _gt: parseFloat(filterValue) } };
190
+ case "lessThan":
191
+ return { [filterOn]: { _lt: parseFloat(filterValue) } };
192
+ case "inRange":
193
+ return {
194
+ [filterOn]: {
195
+ _gte: parseFloat(arrayFilterValue[0]),
196
+ _lte: parseFloat(arrayFilterValue[1])
197
+ }
198
+ };
199
+ case "outsideRange":
200
+ return {
201
+ _or: [
202
+ {
203
+ [filterOn]: {
204
+ _lt: parseFloat(arrayFilterValue[0])
205
+ }
206
+ },
207
+ {
208
+ [filterOn]: {
209
+ _gt: parseFloat(arrayFilterValue[1])
207
210
  }
208
- ]
209
- };
210
- case "equalTo":
211
- return {
212
- [filterOn]: {
213
- _eq:
214
- type === "number" || type === "integer"
215
- ? parseFloat(filterValue)
216
- : filterValue
217
211
  }
218
- };
219
- case "regex":
220
- return { [filterOn]: { _regex: filterValue } };
221
- default:
222
- console.warn(`Unsupported filter type: ${selectedFilter}`);
223
- return {};
224
- }
225
- })
212
+ ]
213
+ };
214
+ case "equalTo":
215
+ return {
216
+ [filterOn]: {
217
+ _eq:
218
+ type === "number" || type === "integer"
219
+ ? parseFloat(filterValue)
220
+ : filterValue
221
+ }
222
+ };
223
+ case "regex":
224
+ return { [filterOn]: { _regex: filterValue } };
225
+ default:
226
+ console.warn(`Unsupported filter type: ${selectedFilter}`);
227
+ return {};
228
+ }
229
+ });
226
230
 
227
231
  if (filterClauses.length > 0) {
228
232
  if (Object.keys(where).length > 0) {
@@ -44,6 +44,25 @@ describe("tableQueryParamsToHasuraClauses", () => {
44
44
  offset: 0
45
45
  });
46
46
  });
47
+ it("should handle searchTerm with string fields with a comma in them", () => {
48
+ const result = tableQueryParamsToHasuraClauses({
49
+ searchTerm: "test,test2",
50
+ schema
51
+ });
52
+ expect(result).toEqual({
53
+ where: {
54
+ _or: [
55
+ { name: { _ilike: "%test%" } },
56
+ { name: { _ilike: "%test2%" } },
57
+ { email: { _ilike: "%test%" } },
58
+ { email: { _ilike: "%test2%" } }
59
+ ]
60
+ },
61
+ order_by: [],
62
+ limit: 25,
63
+ offset: 0
64
+ });
65
+ });
47
66
  it("should flatten queries with dup paths", () => {
48
67
  const result = tableQueryParamsToHasuraClauses({
49
68
  searchTerm: "test",