@teselagen/ui 0.8.5 → 0.8.6
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/DataTable/utils/filterLocalEntitiesToHasura.d.ts +5 -0
- package/DataTable/utils/initializeHasuraWhereAndFilter.d.ts +2 -0
- package/DataTable/utils/tableQueryParamsToHasuraClauses.d.ts +26 -0
- package/package.json +1 -1
- package/src/DataTable/utils/filterLocalEntitiesToHasura.js +236 -0
- package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +587 -0
- package/src/DataTable/utils/initializeHasuraWhereAndFilter.js +26 -0
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +260 -0
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.test.js +206 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function tableQueryParamsToHasuraClauses({ page, pageSize, searchTerm, filters, order, schema, additionalFilter }: {
|
|
2
|
+
page: any;
|
|
3
|
+
pageSize: any;
|
|
4
|
+
searchTerm: any;
|
|
5
|
+
filters: any;
|
|
6
|
+
order: any;
|
|
7
|
+
schema: any;
|
|
8
|
+
additionalFilter: any;
|
|
9
|
+
}): {
|
|
10
|
+
where: {};
|
|
11
|
+
order_by: {};
|
|
12
|
+
limit: any;
|
|
13
|
+
offset: number;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Takes a schema and returns an object with the fields mapped by their camelCased display name.
|
|
17
|
+
* If the displayName is not set or is a jsx element, the path is used instead.
|
|
18
|
+
* The same conversion must be done when using the result of this method
|
|
19
|
+
*/
|
|
20
|
+
export function getFieldsMappedByCCDisplayName(schema: any): any;
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
* @param {object} field
|
|
24
|
+
* @returns the camelCase display name of the field, to be used for filters, sorting, etc
|
|
25
|
+
*/
|
|
26
|
+
export function getCCDisplayName(field: object): string;
|
package/package.json
CHANGED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isEmpty,
|
|
3
|
+
every,
|
|
4
|
+
some,
|
|
5
|
+
isEqual,
|
|
6
|
+
isString,
|
|
7
|
+
isNull,
|
|
8
|
+
isArray,
|
|
9
|
+
includes,
|
|
10
|
+
isObject,
|
|
11
|
+
has,
|
|
12
|
+
orderBy
|
|
13
|
+
} from "lodash-es";
|
|
14
|
+
|
|
15
|
+
export function filterLocalEntitiesToHasura(
|
|
16
|
+
records,
|
|
17
|
+
{ where, order_by, limit, offset, isInfinite } = {}
|
|
18
|
+
) {
|
|
19
|
+
let filteredRecords = [...records];
|
|
20
|
+
|
|
21
|
+
// Apply where clause if it exists
|
|
22
|
+
if (where) {
|
|
23
|
+
filteredRecords = applyWhereClause(filteredRecords, where);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Apply order_by if it exists
|
|
27
|
+
if (order_by) {
|
|
28
|
+
filteredRecords = applyOrderBy(filteredRecords, order_by);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Store the complete filtered and ordered records for pagination info
|
|
32
|
+
const allFilteredRecords = [...filteredRecords];
|
|
33
|
+
|
|
34
|
+
// Apply limit and offset
|
|
35
|
+
if (!isInfinite && offset !== undefined) {
|
|
36
|
+
filteredRecords = filteredRecords.slice(offset);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!isInfinite && limit !== undefined) {
|
|
40
|
+
filteredRecords = filteredRecords.slice(0, limit);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// For consistency, always return an object with entities, entitiesAcrossPages, and entityCount
|
|
44
|
+
return {
|
|
45
|
+
entities: filteredRecords,
|
|
46
|
+
entitiesAcrossPages: allFilteredRecords,
|
|
47
|
+
entityCount: allFilteredRecords.length
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function applyWhereClause(records, where) {
|
|
52
|
+
function applyFilter(record, filter) {
|
|
53
|
+
if (isEmpty(filter)) {
|
|
54
|
+
return true; // No filter, all records pass
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for (const key in filter) {
|
|
58
|
+
if (key === "_and") {
|
|
59
|
+
if (isEmpty(filter[key])) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (!every(filter[key], subFilter => applyFilter(record, subFilter))) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
} else if (key === "_or") {
|
|
66
|
+
if (isEmpty(filter[key])) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!some(filter[key], subFilter => applyFilter(record, subFilter))) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
} else if (key === "_not") {
|
|
73
|
+
if (applyFilter(record, filter[key])) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
const value = record[key];
|
|
78
|
+
const conditions = filter[key];
|
|
79
|
+
|
|
80
|
+
// Handle nested object properties
|
|
81
|
+
if (
|
|
82
|
+
isObject(value) &&
|
|
83
|
+
isObject(conditions) &&
|
|
84
|
+
!hasOperator(conditions)
|
|
85
|
+
) {
|
|
86
|
+
return applyFilter(value, conditions);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const operator in conditions) {
|
|
90
|
+
const conditionValue = conditions[operator];
|
|
91
|
+
|
|
92
|
+
// Handle range conditions (_gt/_lt or _gte/_lte combinations)
|
|
93
|
+
if (operator === "_gt" && conditions._lt) {
|
|
94
|
+
if (!(value > conditionValue && value < conditions._lt))
|
|
95
|
+
return false;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (operator === "_gte" && conditions._lte) {
|
|
99
|
+
if (!(value >= conditionValue && value <= conditions._lte))
|
|
100
|
+
return false;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
switch (operator) {
|
|
105
|
+
case "_eq":
|
|
106
|
+
if (!isEqual(value, conditionValue)) return false;
|
|
107
|
+
break;
|
|
108
|
+
case "_neq":
|
|
109
|
+
if (isEqual(value, conditionValue)) return false;
|
|
110
|
+
break;
|
|
111
|
+
case "_gt":
|
|
112
|
+
if (!(value > conditionValue)) return false;
|
|
113
|
+
break;
|
|
114
|
+
case "_gte":
|
|
115
|
+
if (!(value >= conditionValue)) return false;
|
|
116
|
+
break;
|
|
117
|
+
case "_lt":
|
|
118
|
+
if (!(value < conditionValue)) return false;
|
|
119
|
+
break;
|
|
120
|
+
case "_lte":
|
|
121
|
+
if (!(value <= conditionValue)) return false;
|
|
122
|
+
break;
|
|
123
|
+
case "_like":
|
|
124
|
+
if (
|
|
125
|
+
!isString(value) ||
|
|
126
|
+
!new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
|
|
127
|
+
)
|
|
128
|
+
return false;
|
|
129
|
+
break;
|
|
130
|
+
case "_ilike":
|
|
131
|
+
if (
|
|
132
|
+
!isString(value) ||
|
|
133
|
+
!new RegExp(conditionValue.replace(/%/g, ".*"), "i").test(value)
|
|
134
|
+
)
|
|
135
|
+
return false;
|
|
136
|
+
break;
|
|
137
|
+
case "_nlike":
|
|
138
|
+
if (
|
|
139
|
+
!isString(value) ||
|
|
140
|
+
new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
|
|
141
|
+
)
|
|
142
|
+
return false;
|
|
143
|
+
break;
|
|
144
|
+
case "_nilike":
|
|
145
|
+
if (
|
|
146
|
+
!isString(value) ||
|
|
147
|
+
new RegExp(conditionValue.replace(/%/g, ".*"), "i").test(value)
|
|
148
|
+
)
|
|
149
|
+
return false;
|
|
150
|
+
break;
|
|
151
|
+
case "_starts_with":
|
|
152
|
+
if (!isString(value) || !value.startsWith(conditionValue))
|
|
153
|
+
return false;
|
|
154
|
+
break;
|
|
155
|
+
case "_ends_with":
|
|
156
|
+
if (!isString(value) || !value.endsWith(conditionValue))
|
|
157
|
+
return false;
|
|
158
|
+
break;
|
|
159
|
+
case "_is_null":
|
|
160
|
+
if (
|
|
161
|
+
(conditionValue && !isNull(value)) ||
|
|
162
|
+
(!conditionValue && isNull(value))
|
|
163
|
+
)
|
|
164
|
+
return false;
|
|
165
|
+
break;
|
|
166
|
+
case "_contains":
|
|
167
|
+
if (
|
|
168
|
+
!isArray(value) ||
|
|
169
|
+
!every(conditionValue, item => includes(value, item))
|
|
170
|
+
)
|
|
171
|
+
return false;
|
|
172
|
+
break;
|
|
173
|
+
case "_contained_in":
|
|
174
|
+
if (
|
|
175
|
+
!isArray(value) ||
|
|
176
|
+
!every(value, item => includes(conditionValue, item))
|
|
177
|
+
)
|
|
178
|
+
return false;
|
|
179
|
+
break;
|
|
180
|
+
case "_has_key":
|
|
181
|
+
if (!isObject(value) || !has(value, conditionValue)) return false;
|
|
182
|
+
break;
|
|
183
|
+
case "_has_keys_any":
|
|
184
|
+
if (
|
|
185
|
+
!isObject(value) ||
|
|
186
|
+
!some(conditionValue, item => has(value, item))
|
|
187
|
+
)
|
|
188
|
+
return false;
|
|
189
|
+
break;
|
|
190
|
+
case "_has_keys_all":
|
|
191
|
+
if (
|
|
192
|
+
!isObject(value) ||
|
|
193
|
+
!every(conditionValue, item => has(value, item))
|
|
194
|
+
)
|
|
195
|
+
return false;
|
|
196
|
+
break;
|
|
197
|
+
case "_similar":
|
|
198
|
+
if (
|
|
199
|
+
!isString(value) ||
|
|
200
|
+
!new RegExp(conditionValue.replace(/%/g, ".*")).test(value)
|
|
201
|
+
)
|
|
202
|
+
return false;
|
|
203
|
+
break;
|
|
204
|
+
default:
|
|
205
|
+
if (operator.startsWith("_")) {
|
|
206
|
+
console.warn(`Unsupported operator: ${operator}`);
|
|
207
|
+
return false;
|
|
208
|
+
} else {
|
|
209
|
+
console.warn(`Unsupported operator: ${operator}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Helper to check if an object contains any Hasura operators
|
|
221
|
+
function hasOperator(obj) {
|
|
222
|
+
return Object.keys(obj).some(key => key.startsWith("_"));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return records.filter(record => applyFilter(record, where));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function applyOrderBy(records, order_by) {
|
|
229
|
+
const keys = Object.keys(order_by);
|
|
230
|
+
if (keys.length > 0) {
|
|
231
|
+
const field = keys[0];
|
|
232
|
+
const direction = order_by[field] === "asc" ? "asc" : "desc";
|
|
233
|
+
return orderBy(records, [field], [direction]);
|
|
234
|
+
}
|
|
235
|
+
return records;
|
|
236
|
+
}
|
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { filterLocalEntitiesToHasura } from "./filterLocalEntitiesToHasura";
|
|
2
|
+
|
|
3
|
+
describe("filterLocalEntitiesToHasura", () => {
|
|
4
|
+
const records = [
|
|
5
|
+
{
|
|
6
|
+
id: 123,
|
|
7
|
+
name: "John Doe",
|
|
8
|
+
age: 30,
|
|
9
|
+
is_active: true,
|
|
10
|
+
city: "London",
|
|
11
|
+
|
|
12
|
+
tags: ["programming", "javascript"],
|
|
13
|
+
email: "john@example.com",
|
|
14
|
+
data: { category: "A", type: "X" },
|
|
15
|
+
username: "john123"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 456,
|
|
19
|
+
name: "Jane Smith",
|
|
20
|
+
age: 25,
|
|
21
|
+
is_active: false,
|
|
22
|
+
city: "Paris",
|
|
23
|
+
tags: ["javascript", "python"],
|
|
24
|
+
email: null,
|
|
25
|
+
data: { category: "B", type: "Y" },
|
|
26
|
+
username: "jane456"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 789,
|
|
30
|
+
name: "Alice Johnson",
|
|
31
|
+
age: 35,
|
|
32
|
+
is_active: true,
|
|
33
|
+
city: "London",
|
|
34
|
+
tags: ["programming", "python", "java"],
|
|
35
|
+
email: "alice@example.com",
|
|
36
|
+
data: { category: "A", type: "Z" },
|
|
37
|
+
username: "alice789"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 101,
|
|
41
|
+
name: "Bob Williams",
|
|
42
|
+
age: 20,
|
|
43
|
+
is_active: false,
|
|
44
|
+
city: "New York",
|
|
45
|
+
tags: ["java"],
|
|
46
|
+
email: "bob@example.com",
|
|
47
|
+
data: { category: "C", type: "X" },
|
|
48
|
+
username: "bob101"
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
it("should filter by _eq", () => {
|
|
53
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
54
|
+
where: { id: { _eq: 123 } }
|
|
55
|
+
});
|
|
56
|
+
expect(result.entities).toEqual([records[0]]);
|
|
57
|
+
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
58
|
+
expect(result.entityCount).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should filter by _neq", () => {
|
|
62
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
63
|
+
where: { id: { _neq: 123 } }
|
|
64
|
+
});
|
|
65
|
+
expect(result.entities).toEqual(records.slice(1));
|
|
66
|
+
expect(result.entitiesAcrossPages).toEqual(records.slice(1));
|
|
67
|
+
expect(result.entityCount).toBe(3);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("should filter by _gt", () => {
|
|
71
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
72
|
+
where: { age: { _gt: 30 } }
|
|
73
|
+
});
|
|
74
|
+
expect(result.entities).toEqual([records[2]]);
|
|
75
|
+
expect(result.entitiesAcrossPages).toEqual([records[2]]);
|
|
76
|
+
expect(result.entityCount).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should filter by _gte", () => {
|
|
80
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
81
|
+
where: { age: { _gte: 30 } }
|
|
82
|
+
});
|
|
83
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
84
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
85
|
+
expect(result.entityCount).toBe(2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should filter by _lt", () => {
|
|
89
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
90
|
+
where: { age: { _lt: 25 } }
|
|
91
|
+
});
|
|
92
|
+
expect(result.entities).toEqual([records[3]]);
|
|
93
|
+
expect(result.entitiesAcrossPages).toEqual([records[3]]);
|
|
94
|
+
expect(result.entityCount).toBe(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("should filter by _lte", () => {
|
|
98
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
99
|
+
where: { age: { _lte: 25 } }
|
|
100
|
+
});
|
|
101
|
+
expect(result.entities).toEqual([records[1], records[3]]);
|
|
102
|
+
expect(result.entitiesAcrossPages).toEqual([records[1], records[3]]);
|
|
103
|
+
expect(result.entityCount).toBe(2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("should filter by _like", () => {
|
|
107
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
108
|
+
where: { name: { _like: "%John%" } }
|
|
109
|
+
});
|
|
110
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
111
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
112
|
+
expect(result.entityCount).toBe(2);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should filter by _ilike", () => {
|
|
116
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
117
|
+
where: { name: { _ilike: "%john%" } }
|
|
118
|
+
});
|
|
119
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
120
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
121
|
+
expect(result.entityCount).toBe(2);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("should filter by _nlike", () => {
|
|
125
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
126
|
+
where: { name: { _nlike: "%John%" } }
|
|
127
|
+
});
|
|
128
|
+
expect(result.entities).toEqual([records[1], records[3]]);
|
|
129
|
+
expect(result.entitiesAcrossPages).toEqual([records[1], records[3]]);
|
|
130
|
+
expect(result.entityCount).toBe(2);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("should filter by _nilike", () => {
|
|
134
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
135
|
+
where: { name: { _nilike: "%john%" } }
|
|
136
|
+
});
|
|
137
|
+
expect(result.entities).toEqual([records[1], records[3]]);
|
|
138
|
+
expect(result.entitiesAcrossPages).toEqual([records[1], records[3]]);
|
|
139
|
+
expect(result.entityCount).toBe(2);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("should filter by _starts_with", () => {
|
|
143
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
144
|
+
where: { name: { _starts_with: "John" } }
|
|
145
|
+
});
|
|
146
|
+
expect(result.entities).toEqual([records[0]]);
|
|
147
|
+
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
148
|
+
expect(result.entityCount).toBe(1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should filter by _ends_with", () => {
|
|
152
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
153
|
+
where: { name: { _ends_with: "Doe" } }
|
|
154
|
+
});
|
|
155
|
+
expect(result.entities).toEqual([records[0]]);
|
|
156
|
+
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
157
|
+
expect(result.entityCount).toBe(1);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("should filter by _is_null", () => {
|
|
161
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
162
|
+
where: { email: { _is_null: true } }
|
|
163
|
+
});
|
|
164
|
+
expect(result.entities).toEqual([records[1]]);
|
|
165
|
+
expect(result.entitiesAcrossPages).toEqual([records[1]]);
|
|
166
|
+
expect(result.entityCount).toBe(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should filter by _is_null false", () => {
|
|
170
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
171
|
+
where: { email: { _is_null: false } }
|
|
172
|
+
});
|
|
173
|
+
expect(result.entities).toEqual([records[0], records[2], records[3]]);
|
|
174
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
175
|
+
records[0],
|
|
176
|
+
records[2],
|
|
177
|
+
records[3]
|
|
178
|
+
]);
|
|
179
|
+
expect(result.entityCount).toBe(3);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should filter by _and", () => {
|
|
183
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
184
|
+
where: {
|
|
185
|
+
_and: [{ age: { _gt: 25 } }, { city: { _eq: "London" } }]
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
189
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
190
|
+
expect(result.entityCount).toBe(2);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("should filter by _or", () => {
|
|
194
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
195
|
+
where: {
|
|
196
|
+
_or: [{ city: { _eq: "London" } }, { city: { _eq: "Paris" } }]
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
expect(result.entities).toEqual([records[0], records[1], records[2]]);
|
|
200
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
201
|
+
records[0],
|
|
202
|
+
records[1],
|
|
203
|
+
records[2]
|
|
204
|
+
]);
|
|
205
|
+
expect(result.entityCount).toBe(3);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("should filter by _not", () => {
|
|
209
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
210
|
+
where: {
|
|
211
|
+
_not: { is_active: { _eq: true } }
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
expect(result.entities).toEqual([records[1], records[3]]);
|
|
215
|
+
expect(result.entitiesAcrossPages).toEqual([records[1], records[3]]);
|
|
216
|
+
expect(result.entityCount).toBe(2);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should filter by _contains", () => {
|
|
220
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
221
|
+
where: {
|
|
222
|
+
tags: { _contains: ["programming", "javascript"] }
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
expect(result.entities).toEqual([records[0]]);
|
|
226
|
+
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
227
|
+
expect(result.entityCount).toBe(1);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should filter by _contained_in", () => {
|
|
231
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
232
|
+
where: {
|
|
233
|
+
tags: { _contained_in: ["programming", "javascript", "python", "java"] }
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
expect(result.entities).toEqual(records);
|
|
237
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
238
|
+
expect(result.entityCount).toBe(4);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("should filter by _has_key", () => {
|
|
242
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
243
|
+
where: { data: { _has_key: "category" } }
|
|
244
|
+
});
|
|
245
|
+
expect(result.entities).toEqual(records);
|
|
246
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
247
|
+
expect(result.entityCount).toBe(4);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("should filter by _has_keys_any", () => {
|
|
251
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
252
|
+
where: {
|
|
253
|
+
data: { _has_keys_any: ["category", "missingKey"] }
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
expect(result.entities).toEqual(records);
|
|
257
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
258
|
+
expect(result.entityCount).toBe(4);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should filter by _has_keys_all", () => {
|
|
262
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
263
|
+
where: {
|
|
264
|
+
data: { _has_keys_all: ["category", "type"] }
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
expect(result.entities).toEqual(records);
|
|
268
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
269
|
+
expect(result.entityCount).toBe(4);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("should filter by _similar", () => {
|
|
273
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
274
|
+
where: {
|
|
275
|
+
username: { _similar: "(john|alice)%" }
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
279
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
280
|
+
expect(result.entityCount).toBe(2);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should filter by range _gte and _lte", () => {
|
|
284
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
285
|
+
where: { age: { _gte: 25, _lte: 30 } }
|
|
286
|
+
});
|
|
287
|
+
expect(result.entities).toEqual([records[0], records[1]]);
|
|
288
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[1]]);
|
|
289
|
+
expect(result.entityCount).toBe(2);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should filter by range _gt and _lt", () => {
|
|
293
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
294
|
+
where: { age: { _gt: 25, _lt: 35 } }
|
|
295
|
+
});
|
|
296
|
+
expect(result.entities).toEqual([records[0]]);
|
|
297
|
+
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
298
|
+
expect(result.entityCount).toBe(1);
|
|
299
|
+
});
|
|
300
|
+
it("should filter with nested filters in _and and _or properly ", () => {
|
|
301
|
+
const result = filterLocalEntitiesToHasura(
|
|
302
|
+
[
|
|
303
|
+
{
|
|
304
|
+
id: 1,
|
|
305
|
+
type: {
|
|
306
|
+
special: "01"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 2,
|
|
311
|
+
type: {
|
|
312
|
+
special: "02"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
{
|
|
317
|
+
where: {
|
|
318
|
+
_and: [
|
|
319
|
+
{
|
|
320
|
+
type: {
|
|
321
|
+
special: {
|
|
322
|
+
_ilike: "%01%"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
],
|
|
327
|
+
_or: []
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
expect(result.entities).toEqual([
|
|
332
|
+
{
|
|
333
|
+
id: 1,
|
|
334
|
+
type: {
|
|
335
|
+
special: "01"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
]);
|
|
339
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
340
|
+
{
|
|
341
|
+
id: 1,
|
|
342
|
+
type: {
|
|
343
|
+
special: "01"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
]);
|
|
347
|
+
expect(result.entityCount).toBe(1);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it("should handle empty where clause", () => {
|
|
351
|
+
const result = filterLocalEntitiesToHasura(records, {});
|
|
352
|
+
expect(result.entities).toEqual(records);
|
|
353
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
354
|
+
expect(result.entityCount).toBe(4);
|
|
355
|
+
});
|
|
356
|
+
it("should handle empty _and and _or clauses", () => {
|
|
357
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
358
|
+
where: { _and: [], _or: [] }
|
|
359
|
+
});
|
|
360
|
+
expect(result.entities).toEqual(records);
|
|
361
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
362
|
+
expect(result.entityCount).toBe(4);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("should handle nested _and and _or", () => {
|
|
366
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
367
|
+
where: {
|
|
368
|
+
_and: [
|
|
369
|
+
{ _or: [{ city: { _eq: "London" } }, { city: { _eq: "Paris" } }] },
|
|
370
|
+
{ age: { _gt: 20 } }
|
|
371
|
+
]
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
expect(result.entities).toEqual([records[0], records[1], records[2]]);
|
|
375
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
376
|
+
records[0],
|
|
377
|
+
records[1],
|
|
378
|
+
records[2]
|
|
379
|
+
]);
|
|
380
|
+
expect(result.entityCount).toBe(3);
|
|
381
|
+
});
|
|
382
|
+
it("should order by age ascending", () => {
|
|
383
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
384
|
+
order_by: { age: "asc" }
|
|
385
|
+
});
|
|
386
|
+
expect(result.entities).toEqual([
|
|
387
|
+
records[3],
|
|
388
|
+
records[1],
|
|
389
|
+
records[0],
|
|
390
|
+
records[2]
|
|
391
|
+
]);
|
|
392
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
393
|
+
records[3],
|
|
394
|
+
records[1],
|
|
395
|
+
records[0],
|
|
396
|
+
records[2]
|
|
397
|
+
]);
|
|
398
|
+
expect(result.entityCount).toBe(4);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("should order by age descending", () => {
|
|
402
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
403
|
+
order_by: { age: "desc" }
|
|
404
|
+
});
|
|
405
|
+
expect(result.entities).toEqual([
|
|
406
|
+
records[2],
|
|
407
|
+
records[0],
|
|
408
|
+
records[1],
|
|
409
|
+
records[3]
|
|
410
|
+
]);
|
|
411
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
412
|
+
records[2],
|
|
413
|
+
records[0],
|
|
414
|
+
records[1],
|
|
415
|
+
records[3]
|
|
416
|
+
]);
|
|
417
|
+
expect(result.entityCount).toBe(4);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("should order by name ascending", () => {
|
|
421
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
422
|
+
order_by: { name: "asc" }
|
|
423
|
+
});
|
|
424
|
+
expect(result.entities).toEqual([
|
|
425
|
+
records[2],
|
|
426
|
+
records[3],
|
|
427
|
+
records[1],
|
|
428
|
+
records[0]
|
|
429
|
+
]);
|
|
430
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
431
|
+
records[2],
|
|
432
|
+
records[3],
|
|
433
|
+
records[1],
|
|
434
|
+
records[0]
|
|
435
|
+
]);
|
|
436
|
+
expect(result.entityCount).toBe(4);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("should order by name descending", () => {
|
|
440
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
441
|
+
order_by: { name: "desc" }
|
|
442
|
+
});
|
|
443
|
+
expect(result.entities).toEqual([
|
|
444
|
+
records[0],
|
|
445
|
+
records[1],
|
|
446
|
+
records[3],
|
|
447
|
+
records[2]
|
|
448
|
+
]);
|
|
449
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
450
|
+
records[0],
|
|
451
|
+
records[1],
|
|
452
|
+
records[3],
|
|
453
|
+
records[2]
|
|
454
|
+
]);
|
|
455
|
+
expect(result.entityCount).toBe(4);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
it("should filter and order", () => {
|
|
459
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
460
|
+
where: { city: { _eq: "London" } },
|
|
461
|
+
order_by: { age: "desc" }
|
|
462
|
+
});
|
|
463
|
+
expect(result.entities).toEqual([records[2], records[0]]);
|
|
464
|
+
expect(result.entitiesAcrossPages).toEqual([records[2], records[0]]);
|
|
465
|
+
expect(result.entityCount).toBe(2);
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it("should handle empty order_by", () => {
|
|
469
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
470
|
+
where: { city: { _eq: "London" } }
|
|
471
|
+
});
|
|
472
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
473
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
474
|
+
expect(result.entityCount).toBe(2);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("should handle order_by with empty where", () => {
|
|
478
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
479
|
+
order_by: { age: "asc" }
|
|
480
|
+
});
|
|
481
|
+
expect(result.entities).toEqual([
|
|
482
|
+
records[3],
|
|
483
|
+
records[1],
|
|
484
|
+
records[0],
|
|
485
|
+
records[2]
|
|
486
|
+
]);
|
|
487
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
488
|
+
records[3],
|
|
489
|
+
records[1],
|
|
490
|
+
records[0],
|
|
491
|
+
records[2]
|
|
492
|
+
]);
|
|
493
|
+
expect(result.entityCount).toBe(4);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it("should apply limit", () => {
|
|
497
|
+
const result = filterLocalEntitiesToHasura(records, { limit: 2 });
|
|
498
|
+
expect(result.entities).toEqual([records[0], records[1]]);
|
|
499
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
500
|
+
expect(result.entityCount).toBe(4);
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it("should apply offset", () => {
|
|
504
|
+
const result = filterLocalEntitiesToHasura(records, { offset: 2 });
|
|
505
|
+
expect(result.entities).toEqual([records[2], records[3]]);
|
|
506
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
507
|
+
expect(result.entityCount).toBe(4);
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it("should apply limit and offset", () => {
|
|
511
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
512
|
+
limit: 1,
|
|
513
|
+
offset: 2
|
|
514
|
+
});
|
|
515
|
+
expect(result.entities).toEqual([records[2]]);
|
|
516
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
517
|
+
expect(result.entityCount).toBe(4);
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it("should apply limit to filtered results", () => {
|
|
521
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
522
|
+
where: { city: { _eq: "London" } },
|
|
523
|
+
limit: 1
|
|
524
|
+
});
|
|
525
|
+
expect(result.entities).toEqual([records[0]]);
|
|
526
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
527
|
+
expect(result.entityCount).toBe(2);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("should apply offset to filtered results", () => {
|
|
531
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
532
|
+
where: { city: { _eq: "London" } },
|
|
533
|
+
offset: 1
|
|
534
|
+
});
|
|
535
|
+
expect(result.entities).toEqual([records[2]]);
|
|
536
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
537
|
+
expect(result.entityCount).toBe(2);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it("should apply limit and offset to filtered and ordered results", () => {
|
|
541
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
542
|
+
where: { city: { _eq: "London" } },
|
|
543
|
+
order_by: { age: "desc" },
|
|
544
|
+
limit: 1,
|
|
545
|
+
offset: 1
|
|
546
|
+
});
|
|
547
|
+
expect(result.entities).toEqual([records[0]]);
|
|
548
|
+
expect(result.entitiesAcrossPages).toEqual([records[2], records[0]]);
|
|
549
|
+
expect(result.entityCount).toBe(2);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("should handle offset greater than array length", () => {
|
|
553
|
+
const result = filterLocalEntitiesToHasura(records, { offset: 10 });
|
|
554
|
+
expect(result.entities).toEqual([]);
|
|
555
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
556
|
+
expect(result.entityCount).toBe(4);
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it("should handle limit greater than array length", () => {
|
|
560
|
+
const result = filterLocalEntitiesToHasura(records, { limit: 10 });
|
|
561
|
+
expect(result.entities).toEqual(records);
|
|
562
|
+
expect(result.entitiesAcrossPages).toEqual(records);
|
|
563
|
+
expect(result.entityCount).toBe(4);
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
it("should handle isInfinite option with filtering", () => {
|
|
567
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
568
|
+
where: { city: { _eq: "London" } },
|
|
569
|
+
isInfinite: true
|
|
570
|
+
});
|
|
571
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
572
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
573
|
+
expect(result.entityCount).toBe(2);
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
it("should handle isInfinite option with filtering, limit, and offset", () => {
|
|
577
|
+
const result = filterLocalEntitiesToHasura(records, {
|
|
578
|
+
where: { city: { _eq: "London" } },
|
|
579
|
+
limit: 1,
|
|
580
|
+
offset: 1,
|
|
581
|
+
isInfinite: true
|
|
582
|
+
});
|
|
583
|
+
expect(result.entities).toEqual([records[0], records[2]]);
|
|
584
|
+
expect(result.entitiesAcrossPages).toEqual([records[0], records[2]]);
|
|
585
|
+
expect(result.entityCount).toBe(2);
|
|
586
|
+
});
|
|
587
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function initializeHasuraWhereAndFilter(
|
|
2
|
+
additionalFilter,
|
|
3
|
+
where = {},
|
|
4
|
+
currentParams
|
|
5
|
+
) {
|
|
6
|
+
where._and = where._and || [];
|
|
7
|
+
where._or = where._or || [];
|
|
8
|
+
if (typeof additionalFilter === "function") {
|
|
9
|
+
const newWhere = additionalFilter(where, currentParams);
|
|
10
|
+
if (newWhere) {
|
|
11
|
+
where = {
|
|
12
|
+
...where,
|
|
13
|
+
...newWhere
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
} else if (typeof additionalFilter === "object")
|
|
17
|
+
where._and.push(additionalFilter);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const addCustomColumnFilters = (where, fields, currentParams) => {
|
|
21
|
+
fields.forEach(field => {
|
|
22
|
+
const { customColumnFilter, filterDisabled } = field;
|
|
23
|
+
if (filterDisabled || !customColumnFilter) return;
|
|
24
|
+
customColumnFilter(where, currentParams);
|
|
25
|
+
});
|
|
26
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { camelCase, set } from "lodash-es";
|
|
2
|
+
|
|
3
|
+
export function tableQueryParamsToHasuraClauses({
|
|
4
|
+
page,
|
|
5
|
+
pageSize,
|
|
6
|
+
searchTerm,
|
|
7
|
+
filters,
|
|
8
|
+
order,
|
|
9
|
+
schema, // Add schema as a parameter
|
|
10
|
+
additionalFilter
|
|
11
|
+
}) {
|
|
12
|
+
const ccFields = getFieldsMappedByCCDisplayName(schema);
|
|
13
|
+
let where = {};
|
|
14
|
+
const order_by = {};
|
|
15
|
+
const limit = pageSize || 25;
|
|
16
|
+
const offset = page && pageSize ? (page - 1) * pageSize : 0;
|
|
17
|
+
|
|
18
|
+
if (searchTerm) {
|
|
19
|
+
const searchTermFilters = [];
|
|
20
|
+
schema.fields.forEach(field => {
|
|
21
|
+
const { type, path, searchDisabled } = field;
|
|
22
|
+
if (searchDisabled || field.filterDisabled || type === "color") return;
|
|
23
|
+
const filterValue = searchTerm; // No cleaning needed here, we're using _ilike
|
|
24
|
+
|
|
25
|
+
if (type === "string" || type === "lookup") {
|
|
26
|
+
const o = set({}, path, { _ilike: `%${filterValue}%` });
|
|
27
|
+
searchTermFilters.push(o);
|
|
28
|
+
} else if (type === "boolean") {
|
|
29
|
+
let regex;
|
|
30
|
+
try {
|
|
31
|
+
regex = new RegExp("^" + searchTerm, "ig");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
//ignore
|
|
34
|
+
}
|
|
35
|
+
if (regex) {
|
|
36
|
+
if ("true".replace(regex, "") !== "true") {
|
|
37
|
+
const o = set({}, path, { _eq: true });
|
|
38
|
+
searchTermFilters.push(o);
|
|
39
|
+
} else if ("false".replace(regex, "") !== "false") {
|
|
40
|
+
const o = set({}, path, { _eq: false });
|
|
41
|
+
searchTermFilters.push(o);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else if (
|
|
45
|
+
(type === "number" || type === "integer") &&
|
|
46
|
+
!isNaN(filterValue)
|
|
47
|
+
) {
|
|
48
|
+
const o = set({}, path, { _eq: parseFloat(filterValue) });
|
|
49
|
+
searchTermFilters.push(o);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (searchTermFilters.length > 0) {
|
|
53
|
+
if (Object.keys(where).length > 0) {
|
|
54
|
+
where = { _and: [where, { _or: searchTermFilters }] };
|
|
55
|
+
} else {
|
|
56
|
+
where = { _or: searchTermFilters };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (filters && filters.length > 0) {
|
|
62
|
+
const filterClauses = filters
|
|
63
|
+
.map(filter => {
|
|
64
|
+
let { selectedFilter, filterOn, filterValue } = filter;
|
|
65
|
+
const fieldSchema = ccFields[filterOn] || {};
|
|
66
|
+
|
|
67
|
+
const { path, reference, type } = fieldSchema;
|
|
68
|
+
let stringFilterValue =
|
|
69
|
+
filterValue && filterValue.toString
|
|
70
|
+
? filterValue.toString()
|
|
71
|
+
: filterValue;
|
|
72
|
+
if (stringFilterValue === false) {
|
|
73
|
+
// we still want to be able to search for the string "false" which will get parsed to false
|
|
74
|
+
stringFilterValue = "false";
|
|
75
|
+
} else {
|
|
76
|
+
stringFilterValue = stringFilterValue || "";
|
|
77
|
+
}
|
|
78
|
+
const arrayFilterValue = Array.isArray(filterValue)
|
|
79
|
+
? filterValue
|
|
80
|
+
: stringFilterValue.split(";");
|
|
81
|
+
|
|
82
|
+
if (type === "number" || type === "integer") {
|
|
83
|
+
filterValue = Array.isArray(filterValue)
|
|
84
|
+
? filterValue.map(val => Number(val))
|
|
85
|
+
: Number(filterValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (fieldSchema.normalizeFilter) {
|
|
89
|
+
filterValue = fieldSchema.normalizeFilter(
|
|
90
|
+
filterValue,
|
|
91
|
+
selectedFilter,
|
|
92
|
+
filterOn
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (reference) {
|
|
97
|
+
filterOn = reference.sourceField;
|
|
98
|
+
} else {
|
|
99
|
+
filterOn = path || filterOn;
|
|
100
|
+
}
|
|
101
|
+
switch (selectedFilter) {
|
|
102
|
+
case "none":
|
|
103
|
+
return {};
|
|
104
|
+
case "startsWith":
|
|
105
|
+
return { [filterOn]: { _ilike: `${filterValue}%` } };
|
|
106
|
+
case "endsWith":
|
|
107
|
+
return { [filterOn]: { _ilike: `%${filterValue}` } };
|
|
108
|
+
case "contains":
|
|
109
|
+
return { [filterOn]: { _ilike: `%${filterValue}%` } };
|
|
110
|
+
case "notContains":
|
|
111
|
+
return { [filterOn]: { _not_ilike: `%${filterValue}%` } };
|
|
112
|
+
case "isExactly":
|
|
113
|
+
return { [filterOn]: { _eq: filterValue } };
|
|
114
|
+
case "isEmpty":
|
|
115
|
+
return {
|
|
116
|
+
_or: [
|
|
117
|
+
{ [filterOn]: { _eq: "" } },
|
|
118
|
+
{ [filterOn]: { _is_null: true } }
|
|
119
|
+
]
|
|
120
|
+
};
|
|
121
|
+
case "notEmpty":
|
|
122
|
+
return {
|
|
123
|
+
_and: [
|
|
124
|
+
{ [filterOn]: { _neq: "" } },
|
|
125
|
+
{ [filterOn]: { _is_null: false } }
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
case "inList":
|
|
129
|
+
return { [filterOn]: { _in: filterValue } };
|
|
130
|
+
case "notInList":
|
|
131
|
+
return { [filterOn]: { _nin: filterValue } };
|
|
132
|
+
case "true":
|
|
133
|
+
return { [filterOn]: { _eq: true } };
|
|
134
|
+
case "false":
|
|
135
|
+
return { [filterOn]: { _eq: false } };
|
|
136
|
+
case "dateIs":
|
|
137
|
+
return { [filterOn]: { _eq: filterValue } };
|
|
138
|
+
case "notBetween":
|
|
139
|
+
return {
|
|
140
|
+
_or: [
|
|
141
|
+
{
|
|
142
|
+
[filterOn]: {
|
|
143
|
+
_lt: new Date(arrayFilterValue[0])
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
[filterOn]: {
|
|
148
|
+
_gt: new Date(
|
|
149
|
+
new Date(arrayFilterValue[1]).setHours(23, 59)
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
case "isBetween":
|
|
156
|
+
return {
|
|
157
|
+
[filterOn]: {
|
|
158
|
+
_gte: new Date(arrayFilterValue[0]),
|
|
159
|
+
_lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
case "isBefore":
|
|
163
|
+
return { [filterOn]: { _lt: new Date(filterValue) } };
|
|
164
|
+
case "isAfter":
|
|
165
|
+
return { [filterOn]: { _gt: new Date(filterValue) } };
|
|
166
|
+
case "greaterThan":
|
|
167
|
+
return { [filterOn]: { _gt: parseFloat(filterValue) } };
|
|
168
|
+
case "lessThan":
|
|
169
|
+
return { [filterOn]: { _lt: parseFloat(filterValue) } };
|
|
170
|
+
case "inRange":
|
|
171
|
+
return {
|
|
172
|
+
[filterOn]: {
|
|
173
|
+
_gte: parseFloat(arrayFilterValue[0]),
|
|
174
|
+
_lte: parseFloat(arrayFilterValue[1])
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
case "outsideRange":
|
|
178
|
+
return {
|
|
179
|
+
_or: [
|
|
180
|
+
{
|
|
181
|
+
[filterOn]: {
|
|
182
|
+
_lt: parseFloat(arrayFilterValue[0])
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
[filterOn]: {
|
|
187
|
+
_gt: parseFloat(arrayFilterValue[1])
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
]
|
|
191
|
+
};
|
|
192
|
+
case "equalTo":
|
|
193
|
+
return {
|
|
194
|
+
[filterOn]: {
|
|
195
|
+
_eq:
|
|
196
|
+
type === "number" || type === "integer"
|
|
197
|
+
? parseFloat(filterValue)
|
|
198
|
+
: filterValue
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
case "regex":
|
|
202
|
+
return { [filterOn]: { _regex: filterValue } };
|
|
203
|
+
default:
|
|
204
|
+
console.warn(`Unsupported filter type: ${selectedFilter}`);
|
|
205
|
+
return {};
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
.map(filter => {
|
|
209
|
+
const o = {};
|
|
210
|
+
set(o, Object.keys(filter)[0], filter[Object.keys(filter)[0]]);
|
|
211
|
+
return o;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (filterClauses.length > 0) {
|
|
215
|
+
if (Object.keys(where).length > 0) {
|
|
216
|
+
where = { _and: [where, ...filterClauses] };
|
|
217
|
+
} else {
|
|
218
|
+
where = { _and: filterClauses };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (order && order.length > 0) {
|
|
224
|
+
order.forEach(item => {
|
|
225
|
+
const field = item.startsWith("-") ? item.substring(1) : item;
|
|
226
|
+
const direction = item.startsWith("-") ? "desc" : "asc";
|
|
227
|
+
order_by[field] = direction;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (additionalFilter) {
|
|
232
|
+
where = { _and: [where, additionalFilter] };
|
|
233
|
+
}
|
|
234
|
+
return { where, order_by, limit, offset };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Takes a schema and returns an object with the fields mapped by their camelCased display name.
|
|
239
|
+
* If the displayName is not set or is a jsx element, the path is used instead.
|
|
240
|
+
* The same conversion must be done when using the result of this method
|
|
241
|
+
*/
|
|
242
|
+
export function getFieldsMappedByCCDisplayName(schema) {
|
|
243
|
+
if (!schema || !schema.fields) return {};
|
|
244
|
+
return schema.fields.reduce((acc, field) => {
|
|
245
|
+
const ccDisplayName = getCCDisplayName(field);
|
|
246
|
+
acc[ccDisplayName] = field;
|
|
247
|
+
return acc;
|
|
248
|
+
}, {});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
*
|
|
253
|
+
* @param {object} field
|
|
254
|
+
* @returns the camelCase display name of the field, to be used for filters, sorting, etc
|
|
255
|
+
*/
|
|
256
|
+
export function getCCDisplayName(field) {
|
|
257
|
+
return camelCase(
|
|
258
|
+
typeof field.displayName === "string" ? field.displayName : field.path
|
|
259
|
+
);
|
|
260
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { tableQueryParamsToHasuraClauses } from "./tableQueryParamsToHasuraClauses";
|
|
2
|
+
|
|
3
|
+
describe("tableQueryParamsToHasuraClauses", () => {
|
|
4
|
+
const schema = {
|
|
5
|
+
fields: [
|
|
6
|
+
{ path: "name", type: "string" },
|
|
7
|
+
{ path: "age", type: "number" },
|
|
8
|
+
{ path: "isActive", type: "boolean" },
|
|
9
|
+
{ path: "email", type: "string" }
|
|
10
|
+
]
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
it("should handle empty query params", () => {
|
|
14
|
+
const result = tableQueryParamsToHasuraClauses({});
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
where: {},
|
|
17
|
+
order_by: {},
|
|
18
|
+
limit: 25,
|
|
19
|
+
offset: 0
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should handle page and pageSize", () => {
|
|
24
|
+
const result = tableQueryParamsToHasuraClauses({ page: 2, pageSize: 10 });
|
|
25
|
+
expect(result).toEqual({
|
|
26
|
+
where: {},
|
|
27
|
+
order_by: {},
|
|
28
|
+
limit: 10,
|
|
29
|
+
offset: 10
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should handle searchTerm with string fields", () => {
|
|
34
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
35
|
+
searchTerm: "test",
|
|
36
|
+
schema
|
|
37
|
+
});
|
|
38
|
+
expect(result).toEqual({
|
|
39
|
+
where: {
|
|
40
|
+
_or: [{ name: { _ilike: "%test%" } }, { email: { _ilike: "%test%" } }]
|
|
41
|
+
},
|
|
42
|
+
order_by: {},
|
|
43
|
+
limit: 25,
|
|
44
|
+
offset: 0
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle searchTerm with number fields", () => {
|
|
49
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
50
|
+
searchTerm: "30",
|
|
51
|
+
schema
|
|
52
|
+
});
|
|
53
|
+
expect(result).toEqual({
|
|
54
|
+
where: {
|
|
55
|
+
_or: [
|
|
56
|
+
{ name: { _ilike: "%30%" } },
|
|
57
|
+
{ age: { _eq: 30 } },
|
|
58
|
+
{ email: { _ilike: "%30%" } }
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
order_by: {},
|
|
62
|
+
limit: 25,
|
|
63
|
+
offset: 0
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should handle searchTerm with boolean fields", () => {
|
|
68
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
69
|
+
searchTerm: "true",
|
|
70
|
+
schema
|
|
71
|
+
});
|
|
72
|
+
expect(result).toEqual({
|
|
73
|
+
where: {
|
|
74
|
+
_or: [
|
|
75
|
+
{ name: { _ilike: "%true%" } },
|
|
76
|
+
{ isActive: { _eq: true } },
|
|
77
|
+
{ email: { _ilike: "%true%" } }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
order_by: {},
|
|
81
|
+
limit: 25,
|
|
82
|
+
offset: 0
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle searchTerm with multiple field types", () => {
|
|
87
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
88
|
+
searchTerm: "test",
|
|
89
|
+
schema
|
|
90
|
+
});
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
where: {
|
|
93
|
+
_or: [{ name: { _ilike: "%test%" } }, { email: { _ilike: "%test%" } }]
|
|
94
|
+
},
|
|
95
|
+
order_by: {},
|
|
96
|
+
limit: 25,
|
|
97
|
+
offset: 0
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should handle contains filter", () => {
|
|
102
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
103
|
+
filters: [
|
|
104
|
+
{
|
|
105
|
+
selectedFilter: "contains",
|
|
106
|
+
filterOn: "name",
|
|
107
|
+
filterValue: "test"
|
|
108
|
+
}
|
|
109
|
+
]
|
|
110
|
+
});
|
|
111
|
+
expect(result).toEqual({
|
|
112
|
+
where: { _and: [{ name: { _ilike: "%test%" } }] },
|
|
113
|
+
order_by: {},
|
|
114
|
+
limit: 25,
|
|
115
|
+
offset: 0
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should handle equalTo filter for number", () => {
|
|
120
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
121
|
+
filters: [
|
|
122
|
+
{ selectedFilter: "equalTo", filterOn: "age", filterValue: "30" }
|
|
123
|
+
],
|
|
124
|
+
schema
|
|
125
|
+
});
|
|
126
|
+
expect(result).toEqual({
|
|
127
|
+
where: { _and: [{ age: { _eq: 30 } }] },
|
|
128
|
+
order_by: {},
|
|
129
|
+
limit: 25,
|
|
130
|
+
offset: 0
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should handle order", () => {
|
|
135
|
+
const result = tableQueryParamsToHasuraClauses({ order: ["name", "-age"] });
|
|
136
|
+
expect(result).toEqual({
|
|
137
|
+
where: {},
|
|
138
|
+
order_by: { name: "asc", age: "desc" },
|
|
139
|
+
limit: 25,
|
|
140
|
+
offset: 0
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should combine all params", () => {
|
|
145
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
146
|
+
page: 2,
|
|
147
|
+
pageSize: 10,
|
|
148
|
+
searchTerm: "test",
|
|
149
|
+
filters: [
|
|
150
|
+
{
|
|
151
|
+
selectedFilter: "greaterThan",
|
|
152
|
+
filterOn: "age",
|
|
153
|
+
filterValue: "30"
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
order: ["name"],
|
|
157
|
+
schema
|
|
158
|
+
});
|
|
159
|
+
expect(result).toEqual({
|
|
160
|
+
where: {
|
|
161
|
+
_and: [
|
|
162
|
+
{
|
|
163
|
+
_or: [
|
|
164
|
+
{ name: { _ilike: "%test%" } },
|
|
165
|
+
{ email: { _ilike: "%test%" } }
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
{ age: { _gt: 30 } }
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
order_by: { name: "asc" },
|
|
172
|
+
limit: 10,
|
|
173
|
+
offset: 10
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("should combine searchTerm and filters", () => {
|
|
178
|
+
const result = tableQueryParamsToHasuraClauses({
|
|
179
|
+
searchTerm: "test",
|
|
180
|
+
filters: [
|
|
181
|
+
{
|
|
182
|
+
selectedFilter: "greaterThan",
|
|
183
|
+
filterOn: "age",
|
|
184
|
+
filterValue: "30"
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
schema
|
|
188
|
+
});
|
|
189
|
+
expect(result).toEqual({
|
|
190
|
+
where: {
|
|
191
|
+
_and: [
|
|
192
|
+
{
|
|
193
|
+
_or: [
|
|
194
|
+
{ name: { _ilike: "%test%" } },
|
|
195
|
+
{ email: { _ilike: "%test%" } }
|
|
196
|
+
]
|
|
197
|
+
},
|
|
198
|
+
{ age: { _gt: 30 } }
|
|
199
|
+
]
|
|
200
|
+
},
|
|
201
|
+
order_by: {},
|
|
202
|
+
limit: 25,
|
|
203
|
+
offset: 0
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|