diff --git a/CHANGELOG.md b/CHANGELOG.md index 7edd6caa..5253ef59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.X - 2026-01-X +- Add Array filter types + - ARRAY_CONTAINS_ALL, ARRAY_CONTAINS_ANY, ARRAY_CONTAINS_EXACT, ARRAY_CONTAINS_NOT_EXACT, ARRAY_ISEMPTY, ARRAY_ISNOTEMPTY + ### 1.44.1 - 2026-01-07 - Lineage: add "restricted" property diff --git a/package-lock.json b/package-lock.json index 45b738ad..1f03519f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@labkey/api", - "version": "1.44.1", + "version": "1.45.0-fb-mvtc.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@labkey/api", - "version": "1.44.1", + "version": "1.45.0-fb-mvtc.4", "license": "Apache-2.0", "devDependencies": { "@babel/core": "7.28.5", diff --git a/package.json b/package.json index 17a36fca..f852463c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@labkey/api", - "version": "1.44.1", + "version": "1.45.0-fb-mvtc.5", "description": "JavaScript client API for LabKey Server", "scripts": { "build": "npm run build:dist && npm run build:docs", diff --git a/src/labkey/filter/Types.ts b/src/labkey/filter/Types.ts index 60f14e12..4bfff098 100644 --- a/src/labkey/filter/Types.ts +++ b/src/labkey/filter/Types.ts @@ -45,7 +45,7 @@ export interface IFilterType { * Split a filter String or Array value appropriately for this filter type. * @return For multi-valued filter types, an Array of values, otherwise the original filter value. */ - parseValue: (value: string | FilterValue[]) => FilterValue | FilterValue[]; + parseValue: (value: FilterValue[] | string) => FilterValue | FilterValue[]; validate: (value: FilterValue, jsonType: string, columnName: string) => any; } @@ -128,6 +128,33 @@ export const Types: Record = { // These operators require a data value // + ARRAY_CONTAINS_ALL: registerFilterType('Contains All', null, 'arraycontainsall', true, ',', 'Contains All Of'), + ARRAY_CONTAINS_ANY: registerFilterType( + 'Contains Any', + null, + 'arraycontainsany', + true, + ',', + 'Contains At Least One Of' + ), + ARRAY_CONTAINS_EXACT: registerFilterType( + 'Contains Exactly', + null, + 'arraymatches', + true, + ',', + 'Contains Exactly the Selected Values' + ), + ARRAY_CONTAINS_NOT_EXACT: registerFilterType( + 'Does Not Contain Exactly', + null, + 'arraynotmatches', + true, + ',', + 'Does Not Contains Exactly the Selected Values' + ), + ARRAY_CONTAINS_NONE: registerFilterType('Contains None', null, 'arraycontainsnone', true, ',', 'Contains None Of'), + EQUAL, DATE_EQUAL: registerFilterType( EQUAL.getDisplayText(), @@ -271,6 +298,29 @@ export const Types: Record = { // These are the 'no data value' operators // + ARRAY_ISEMPTY: registerFilterType( + 'Is Empty', + null, + 'arrayisempty', + false, + undefined, + undefined, + undefined, + undefined, + false + ), + ARRAY_ISNOTEMPTY: registerFilterType( + 'Is Not Empty', + null, + 'arrayisnotempty', + false, + undefined, + undefined, + undefined, + undefined, + false + ), + // NOTE: This type, for better or worse, uses empty string as it's urlSuffix. // The result is a filter that is encoded as ".~=". HAS_ANY_VALUE: registerFilterType('Has Any Value', null, ''), @@ -359,9 +409,18 @@ export const Types: Record = { EXP_LINEAGE_OF: registerFilterType('In The Lineage Of', null, 'exp:lineageof', true, ',', ' in the lineage of'), }; -export type JsonType = 'boolean' | 'date' | 'float' | 'int' | 'string' | 'time'; +export type JsonType = 'array' | 'boolean' | 'date' | 'float' | 'int' | 'string' | 'time'; export const TYPES_BY_JSON_TYPE: Record = { + array: [ + Types.ARRAY_CONTAINS_ALL, + Types.ARRAY_CONTAINS_ANY, + Types.ARRAY_CONTAINS_EXACT, + Types.ARRAY_CONTAINS_NONE, + Types.ARRAY_CONTAINS_NOT_EXACT, + Types.ARRAY_ISEMPTY, + Types.ARRAY_ISNOTEMPTY, + ], boolean: [Types.HAS_ANY_VALUE, Types.EQUAL, Types.NEQ_OR_NULL, Types.ISBLANK, Types.NONBLANK], date: [ Types.DATE_EQUAL, @@ -440,6 +499,7 @@ export const TYPES_BY_JSON_TYPE: Record = { // TODO: Update to Record export const TYPES_BY_JSON_TYPE_DEFAULT: Record = { + array: Types.ARRAY_CONTAINS_ALL, boolean: Types.EQUAL, date: Types.DATE_EQUAL, float: Types.EQUAL, @@ -593,7 +653,7 @@ export function registerFilterType( return value; }, - validate: (value: FilterValue, jsonType: JsonType, columnName: string): string | boolean | undefined => { + validate: (value: FilterValue, jsonType: JsonType, columnName: string): boolean | string | undefined => { if (!isDataValueRequired()) { return true; // TODO: This method is all over the place with it's return type. WTB sanity... } @@ -680,7 +740,7 @@ function validate(jsonType: JsonType, value: FilterValue, columnName: string): s return undefined; } } else if (jsonType === 'date') { - let year: number, month: number, day: number, hour: number, minute: number; + let day: number, hour: number, minute: number, month: number, year: number; hour = 0; minute = 0; diff --git a/src/test/data/filter_types_snapshot.json b/src/test/data/filter_types_snapshot.json index cd3e6334..103b7a3d 100644 --- a/src/test/data/filter_types_snapshot.json +++ b/src/test/data/filter_types_snapshot.json @@ -1,4 +1,123 @@ { + "ARRAY_CONTAINS_ALL": { + "getDisplaySymbol": null, + "getDisplayText": "Contains All", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Contains All Of", + "getMultiValueFilter": null, + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": ",", + "getOpposite": null, + "getSingleValueFilter": "undefined", + "getURLParameterValue": "undefined", + "getURLSuffix": "arraycontainsall", + "isDataValueRequired": true, + "isMultiValued": true, + "isTableWise": false + }, + "ARRAY_CONTAINS_ANY": { + "getDisplaySymbol": null, + "getDisplayText": "Contains Any", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Contains At Least One Of", + "getMultiValueFilter": null, + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": ",", + "getOpposite": null, + "getSingleValueFilter": "undefined", + "getURLParameterValue": "undefined", + "getURLSuffix": "arraycontainsany", + "isDataValueRequired": true, + "isMultiValued": true, + "isTableWise": false + }, + "ARRAY_CONTAINS_EXACT": { + "getDisplaySymbol": null, + "getDisplayText": "Contains Exactly", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Contains Exactly the Selected Values", + "getMultiValueFilter": null, + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": ",", + "getOpposite": null, + "getSingleValueFilter": "undefined", + "getURLParameterValue": "undefined", + "getURLSuffix": "arraymatches", + "isDataValueRequired": true, + "isMultiValued": true, + "isTableWise": false + }, + "ARRAY_CONTAINS_NONE": { + "getDisplaySymbol": null, + "getDisplayText": "Contains None", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Contains None Of", + "getMultiValueFilter": null, + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": ",", + "getOpposite": null, + "getSingleValueFilter": "undefined", + "getURLParameterValue": "undefined", + "getURLSuffix": "arraycontainsnone", + "isDataValueRequired": true, + "isMultiValued": true, + "isTableWise": false + }, + "ARRAY_CONTAINS_NOT_EXACT": { + "getDisplaySymbol": null, + "getDisplayText": "Does Not Contain Exactly", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Does Not Contains Exactly the Selected Values", + "getMultiValueFilter": null, + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": ",", + "getOpposite": null, + "getSingleValueFilter": "undefined", + "getURLParameterValue": "undefined", + "getURLSuffix": "arraynotmatches", + "isDataValueRequired": true, + "isMultiValued": true, + "isTableWise": false + }, + "ARRAY_ISEMPTY": { + "getDisplaySymbol": null, + "getDisplayText": "Is Empty", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Is Empty", + "getMultiValueFilter": "undefined", + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": null, + "getOpposite": null, + "getSingleValueFilter": "arrayisempty", + "getURLParameterValue": "", + "getURLSuffix": "arrayisempty", + "isDataValueRequired": false, + "isMultiValued": false, + "isTableWise": false + }, + "ARRAY_ISNOTEMPTY": { + "getDisplaySymbol": null, + "getDisplayText": "Is Not Empty", + "getLabKeySqlOperator": "undefined", + "getLongDisplayText": "Is Not Empty", + "getMultiValueFilter": "undefined", + "getMultiValueMaxOccurs": "undefined", + "getMultiValueMinOccurs": "undefined", + "getMultiValueSeparator": null, + "getOpposite": null, + "getSingleValueFilter": "arrayisnotempty", + "getURLParameterValue": "", + "getURLSuffix": "arrayisnotempty", + "isDataValueRequired": false, + "isMultiValued": false, + "isTableWise": false + }, "BETWEEN": { "getDisplaySymbol": null, "getDisplayText": "Between",