@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 +40 -25
- package/index.es.js +40 -25
- package/package.json +1 -2
- package/src/DataTable/utils/filterLocalEntitiesToHasura.js +26 -3
- package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +126 -16
- package/src/DataTable/utils/initializeHasuraWhereAndFilter.js +1 -1
- package/src/DataTable/utils/queryParams.js +1 -3
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +177 -173
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.test.js +19 -0
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
|
-
|
|
19431
|
-
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
19440
|
-
|
|
19441
|
-
if (
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
19413
|
-
|
|
19414
|
-
|
|
19415
|
-
|
|
19416
|
-
|
|
19417
|
-
|
|
19418
|
-
|
|
19419
|
-
|
|
19420
|
-
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
if (
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
|
|
19427
|
-
|
|
19428
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
265
|
-
|
|
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(
|
|
384
|
-
|
|
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(
|
|
403
|
+
expect(result.entityCount).toBe(5);
|
|
399
404
|
});
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
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.
|
|
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", () => {
|
|
@@ -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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
if (fieldSchema.normalizeFilter) {
|
|
104
|
+
filterValue = fieldSchema.normalizeFilter(
|
|
105
|
+
filterValue,
|
|
106
|
+
selectedFilter,
|
|
107
|
+
filterOn
|
|
108
|
+
);
|
|
109
|
+
}
|
|
104
110
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
_lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
|
|
134
|
+
_not: {
|
|
135
|
+
[filterOn.split(".")[0]]: {}
|
|
178
136
|
}
|
|
179
137
|
};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
[filterOn]: {
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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",
|