Skip to content

Commit 6454b7e

Browse files
authored
Add Array filter types (#204)
1 parent 18bb5df commit 6454b7e

5 files changed

Lines changed: 190 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
### 1.45.0 - 2026-01-20
2+
- Add Array filter types
3+
- ARRAY_CONTAINS_ALL, ARRAY_CONTAINS_ANY, ARRAY_CONTAINS_EXACT, ARRAY_CONTAINS_NOT_EXACT, ARRAY_ISEMPTY, ARRAY_ISNOTEMPTY
4+
15
### 1.44.1 - 2026-01-07
26
- Lineage: add "restricted" property
37

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@labkey/api",
3-
"version": "1.44.1",
3+
"version": "1.45.0",
44
"description": "JavaScript client API for LabKey Server",
55
"scripts": {
66
"build": "npm run build:dist && npm run build:docs",

src/labkey/filter/Types.ts

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export interface IFilterType {
4545
* Split a filter String or Array value appropriately for this filter type.
4646
* @return For multi-valued filter types, an Array of values, otherwise the original filter value.
4747
*/
48-
parseValue: (value: string | FilterValue[]) => FilterValue | FilterValue[];
48+
parseValue: (value: FilterValue[] | string) => FilterValue | FilterValue[];
4949
validate: (value: FilterValue, jsonType: string, columnName: string) => any;
5050
}
5151

@@ -128,6 +128,33 @@ export const Types: Record<string, IFilterType> = {
128128
// These operators require a data value
129129
//
130130

131+
ARRAY_CONTAINS_ALL: registerFilterType('Contains All', null, 'arraycontainsall', true, ',', 'Contains All Of'),
132+
ARRAY_CONTAINS_ANY: registerFilterType(
133+
'Contains Any',
134+
null,
135+
'arraycontainsany',
136+
true,
137+
',',
138+
'Contains At Least One Of'
139+
),
140+
ARRAY_CONTAINS_EXACT: registerFilterType(
141+
'Contains Exactly',
142+
null,
143+
'arraymatches',
144+
true,
145+
',',
146+
'Contains Exactly the Selected Values'
147+
),
148+
ARRAY_CONTAINS_NOT_EXACT: registerFilterType(
149+
'Does Not Contain Exactly',
150+
null,
151+
'arraynotmatches',
152+
true,
153+
',',
154+
'Does Not Contains Exactly the Selected Values'
155+
),
156+
ARRAY_CONTAINS_NONE: registerFilterType('Contains None', null, 'arraycontainsnone', true, ',', 'Contains None Of'),
157+
131158
EQUAL,
132159
DATE_EQUAL: registerFilterType(
133160
EQUAL.getDisplayText(),
@@ -271,6 +298,29 @@ export const Types: Record<string, IFilterType> = {
271298
// These are the 'no data value' operators
272299
//
273300

301+
ARRAY_ISEMPTY: registerFilterType(
302+
'Is Empty',
303+
null,
304+
'arrayisempty',
305+
false,
306+
undefined,
307+
undefined,
308+
undefined,
309+
undefined,
310+
false
311+
),
312+
ARRAY_ISNOTEMPTY: registerFilterType(
313+
'Is Not Empty',
314+
null,
315+
'arrayisnotempty',
316+
false,
317+
undefined,
318+
undefined,
319+
undefined,
320+
undefined,
321+
false
322+
),
323+
274324
// NOTE: This type, for better or worse, uses empty string as it's urlSuffix.
275325
// The result is a filter that is encoded as "<dataRegionName>.<columnName>~=".
276326
HAS_ANY_VALUE: registerFilterType('Has Any Value', null, ''),
@@ -359,9 +409,18 @@ export const Types: Record<string, IFilterType> = {
359409
EXP_LINEAGE_OF: registerFilterType('In The Lineage Of', null, 'exp:lineageof', true, ',', ' in the lineage of'),
360410
};
361411

362-
export type JsonType = 'boolean' | 'date' | 'float' | 'int' | 'string' | 'time';
412+
export type JsonType = 'array' | 'boolean' | 'date' | 'float' | 'int' | 'string' | 'time';
363413

364414
export const TYPES_BY_JSON_TYPE: Record<string, IFilterType[]> = {
415+
array: [
416+
Types.ARRAY_CONTAINS_ALL,
417+
Types.ARRAY_CONTAINS_ANY,
418+
Types.ARRAY_CONTAINS_EXACT,
419+
Types.ARRAY_CONTAINS_NONE,
420+
Types.ARRAY_CONTAINS_NOT_EXACT,
421+
Types.ARRAY_ISEMPTY,
422+
Types.ARRAY_ISNOTEMPTY,
423+
],
365424
boolean: [Types.HAS_ANY_VALUE, Types.EQUAL, Types.NEQ_OR_NULL, Types.ISBLANK, Types.NONBLANK],
366425
date: [
367426
Types.DATE_EQUAL,
@@ -440,6 +499,7 @@ export const TYPES_BY_JSON_TYPE: Record<string, IFilterType[]> = {
440499

441500
// TODO: Update to Record<JsonType, IFilterType[]>
442501
export const TYPES_BY_JSON_TYPE_DEFAULT: Record<string, IFilterType> = {
502+
array: Types.ARRAY_CONTAINS_ALL,
443503
boolean: Types.EQUAL,
444504
date: Types.DATE_EQUAL,
445505
float: Types.EQUAL,
@@ -593,7 +653,7 @@ export function registerFilterType(
593653
return value;
594654
},
595655

596-
validate: (value: FilterValue, jsonType: JsonType, columnName: string): string | boolean | undefined => {
656+
validate: (value: FilterValue, jsonType: JsonType, columnName: string): boolean | string | undefined => {
597657
if (!isDataValueRequired()) {
598658
return true; // TODO: This method is all over the place with it's return type. WTB sanity...
599659
}
@@ -680,7 +740,7 @@ function validate(jsonType: JsonType, value: FilterValue, columnName: string): s
680740
return undefined;
681741
}
682742
} else if (jsonType === 'date') {
683-
let year: number, month: number, day: number, hour: number, minute: number;
743+
let day: number, hour: number, minute: number, month: number, year: number;
684744
hour = 0;
685745
minute = 0;
686746

src/test/data/filter_types_snapshot.json

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,123 @@
11
{
2+
"ARRAY_CONTAINS_ALL": {
3+
"getDisplaySymbol": null,
4+
"getDisplayText": "Contains All",
5+
"getLabKeySqlOperator": "undefined",
6+
"getLongDisplayText": "Contains All Of",
7+
"getMultiValueFilter": null,
8+
"getMultiValueMaxOccurs": "undefined",
9+
"getMultiValueMinOccurs": "undefined",
10+
"getMultiValueSeparator": ",",
11+
"getOpposite": null,
12+
"getSingleValueFilter": "undefined",
13+
"getURLParameterValue": "undefined",
14+
"getURLSuffix": "arraycontainsall",
15+
"isDataValueRequired": true,
16+
"isMultiValued": true,
17+
"isTableWise": false
18+
},
19+
"ARRAY_CONTAINS_ANY": {
20+
"getDisplaySymbol": null,
21+
"getDisplayText": "Contains Any",
22+
"getLabKeySqlOperator": "undefined",
23+
"getLongDisplayText": "Contains At Least One Of",
24+
"getMultiValueFilter": null,
25+
"getMultiValueMaxOccurs": "undefined",
26+
"getMultiValueMinOccurs": "undefined",
27+
"getMultiValueSeparator": ",",
28+
"getOpposite": null,
29+
"getSingleValueFilter": "undefined",
30+
"getURLParameterValue": "undefined",
31+
"getURLSuffix": "arraycontainsany",
32+
"isDataValueRequired": true,
33+
"isMultiValued": true,
34+
"isTableWise": false
35+
},
36+
"ARRAY_CONTAINS_EXACT": {
37+
"getDisplaySymbol": null,
38+
"getDisplayText": "Contains Exactly",
39+
"getLabKeySqlOperator": "undefined",
40+
"getLongDisplayText": "Contains Exactly the Selected Values",
41+
"getMultiValueFilter": null,
42+
"getMultiValueMaxOccurs": "undefined",
43+
"getMultiValueMinOccurs": "undefined",
44+
"getMultiValueSeparator": ",",
45+
"getOpposite": null,
46+
"getSingleValueFilter": "undefined",
47+
"getURLParameterValue": "undefined",
48+
"getURLSuffix": "arraymatches",
49+
"isDataValueRequired": true,
50+
"isMultiValued": true,
51+
"isTableWise": false
52+
},
53+
"ARRAY_CONTAINS_NONE": {
54+
"getDisplaySymbol": null,
55+
"getDisplayText": "Contains None",
56+
"getLabKeySqlOperator": "undefined",
57+
"getLongDisplayText": "Contains None Of",
58+
"getMultiValueFilter": null,
59+
"getMultiValueMaxOccurs": "undefined",
60+
"getMultiValueMinOccurs": "undefined",
61+
"getMultiValueSeparator": ",",
62+
"getOpposite": null,
63+
"getSingleValueFilter": "undefined",
64+
"getURLParameterValue": "undefined",
65+
"getURLSuffix": "arraycontainsnone",
66+
"isDataValueRequired": true,
67+
"isMultiValued": true,
68+
"isTableWise": false
69+
},
70+
"ARRAY_CONTAINS_NOT_EXACT": {
71+
"getDisplaySymbol": null,
72+
"getDisplayText": "Does Not Contain Exactly",
73+
"getLabKeySqlOperator": "undefined",
74+
"getLongDisplayText": "Does Not Contains Exactly the Selected Values",
75+
"getMultiValueFilter": null,
76+
"getMultiValueMaxOccurs": "undefined",
77+
"getMultiValueMinOccurs": "undefined",
78+
"getMultiValueSeparator": ",",
79+
"getOpposite": null,
80+
"getSingleValueFilter": "undefined",
81+
"getURLParameterValue": "undefined",
82+
"getURLSuffix": "arraynotmatches",
83+
"isDataValueRequired": true,
84+
"isMultiValued": true,
85+
"isTableWise": false
86+
},
87+
"ARRAY_ISEMPTY": {
88+
"getDisplaySymbol": null,
89+
"getDisplayText": "Is Empty",
90+
"getLabKeySqlOperator": "undefined",
91+
"getLongDisplayText": "Is Empty",
92+
"getMultiValueFilter": "undefined",
93+
"getMultiValueMaxOccurs": "undefined",
94+
"getMultiValueMinOccurs": "undefined",
95+
"getMultiValueSeparator": null,
96+
"getOpposite": null,
97+
"getSingleValueFilter": "arrayisempty",
98+
"getURLParameterValue": "",
99+
"getURLSuffix": "arrayisempty",
100+
"isDataValueRequired": false,
101+
"isMultiValued": false,
102+
"isTableWise": false
103+
},
104+
"ARRAY_ISNOTEMPTY": {
105+
"getDisplaySymbol": null,
106+
"getDisplayText": "Is Not Empty",
107+
"getLabKeySqlOperator": "undefined",
108+
"getLongDisplayText": "Is Not Empty",
109+
"getMultiValueFilter": "undefined",
110+
"getMultiValueMaxOccurs": "undefined",
111+
"getMultiValueMinOccurs": "undefined",
112+
"getMultiValueSeparator": null,
113+
"getOpposite": null,
114+
"getSingleValueFilter": "arrayisnotempty",
115+
"getURLParameterValue": "",
116+
"getURLSuffix": "arrayisnotempty",
117+
"isDataValueRequired": false,
118+
"isMultiValued": false,
119+
"isTableWise": false
120+
},
2121
"BETWEEN": {
3122
"getDisplaySymbol": null,
4123
"getDisplayText": "Between",

0 commit comments

Comments
 (0)