diff --git a/packages/agent/src/utils/condition-tree-parser.ts b/packages/agent/src/utils/condition-tree-parser.ts index 29a5ac4c5a..346bbdbc64 100644 --- a/packages/agent/src/utils/condition-tree-parser.ts +++ b/packages/agent/src/utils/condition-tree-parser.ts @@ -76,7 +76,7 @@ export default class ConditionTreeParser { return 'Number'; } - if (filter.operator === 'In') { + if (filter.operator === 'In' || filter.operator === 'NotIn') { return [fieldSchema.columnType]; } diff --git a/packages/agent/src/utils/forest-schema/filterable.ts b/packages/agent/src/utils/forest-schema/filterable.ts index 603739fc62..e90a0d62ee 100644 --- a/packages/agent/src/utils/forest-schema/filterable.ts +++ b/packages/agent/src/utils/forest-schema/filterable.ts @@ -28,18 +28,19 @@ export default class FrontendFilterableUtils { Boolean: FrontendFilterableUtils.baseOperators, Date: FrontendFilterableUtils.dateOperators, Dateonly: FrontendFilterableUtils.dateOperators, - Enum: [...FrontendFilterableUtils.baseOperators, 'In'], - Number: [...FrontendFilterableUtils.baseOperators, 'In', 'GreaterThan', 'LessThan'], + Enum: [...FrontendFilterableUtils.baseOperators, 'In', 'NotIn'], + Number: [...FrontendFilterableUtils.baseOperators, 'In', 'NotIn', 'GreaterThan', 'LessThan'], String: [ ...FrontendFilterableUtils.baseOperators, 'In', + 'NotIn', 'StartsWith', 'EndsWith', 'Contains', 'NotContains', ], Time: [...FrontendFilterableUtils.baseOperators, 'GreaterThan', 'LessThan'], - Uuid: FrontendFilterableUtils.baseOperators, + Uuid: [...FrontendFilterableUtils.baseOperators, 'In', 'NotIn'], }; /** diff --git a/packages/agent/test/utils/condition-tree-parser.test.ts b/packages/agent/test/utils/condition-tree-parser.test.ts index de70ae7ca7..f126dcf4e6 100644 --- a/packages/agent/test/utils/condition-tree-parser.test.ts +++ b/packages/agent/test/utils/condition-tree-parser.test.ts @@ -56,6 +56,14 @@ describe('ConditionTreeParser', () => { }); expect(tree).toStrictEqual(new ConditionTreeLeaf('id', 'In', ['id1', 'id2', 'id3'])); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'id', + operator: 'in', + value: ['id1', 'id2', 'id3'], + }); + + expect(treeWithArray).toStrictEqual(new ConditionTreeLeaf('id', 'In', ['id1', 'id2', 'id3'])); }); test('should work with in and number', () => { @@ -66,6 +74,14 @@ describe('ConditionTreeParser', () => { }); expect(tree).toStrictEqual(new ConditionTreeLeaf('number', 'In', [1, 2, 3])); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'number', + operator: 'in', + value: [1, 2, 3, 'invalid'], + }); + + expect(treeWithArray).toStrictEqual(new ConditionTreeLeaf('number', 'In', [1, 2, 3])); }); test('should work with in and boolean', () => { @@ -78,6 +94,76 @@ describe('ConditionTreeParser', () => { expect(tree).toStrictEqual( new ConditionTreeLeaf('boolean', 'In', [true, false, false, true, false]), ); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'boolean', + operator: 'in', + value: [true, 0, false, 'yes', 'no'], + }); + + expect(treeWithArray).toStrictEqual( + new ConditionTreeLeaf('boolean', 'In', [true, false, false, true, false]), + ); + }); + + test('should work with not_in and string', () => { + const tree = ConditionTreeParser.fromPlainObject(collection, { + field: 'id', + operator: 'not_in', + value: 'id1,id2 , id3', + }); + + expect(tree).toStrictEqual(new ConditionTreeLeaf('id', 'NotIn', ['id1', 'id2', 'id3'])); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'id', + operator: 'not_in', + value: ['id1', 'id2', 'id3'], + }); + + expect(treeWithArray).toStrictEqual( + new ConditionTreeLeaf('id', 'NotIn', ['id1', 'id2', 'id3']), + ); + }); + + test('should work with not_in and number', () => { + const tree = ConditionTreeParser.fromPlainObject(collection, { + field: 'number', + operator: 'not_in', + value: '1, 2, 3 , invalid', + }); + + expect(tree).toStrictEqual(new ConditionTreeLeaf('number', 'NotIn', [1, 2, 3])); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'number', + operator: 'not_in', + value: [1, 2, 3, 'invalid'], + }); + + expect(treeWithArray).toStrictEqual(new ConditionTreeLeaf('number', 'NotIn', [1, 2, 3])); + }); + + test('should work with not_in and boolean', () => { + const tree = ConditionTreeParser.fromPlainObject(collection, { + field: 'boolean', + operator: 'not_in', + value: 'true, 0, false, yes, no', + }); + + expect(tree).toStrictEqual( + new ConditionTreeLeaf('boolean', 'NotIn', [true, false, false, true, false]), + ); + + const treeWithArray = ConditionTreeParser.fromPlainObject(collection, { + field: 'boolean', + operator: 'not_in', + value: [true, 0, false, 'yes', 'no'], + }); + + expect(treeWithArray).toStrictEqual( + new ConditionTreeLeaf('boolean', 'NotIn', [true, false, false, true, false]), + ); }); test('should work with ShorterThan and string', () => { diff --git a/packages/agent/test/utils/forest-schema/filterable.test.ts b/packages/agent/test/utils/forest-schema/filterable.test.ts index 1b426add91..d104f5624f 100644 --- a/packages/agent/test/utils/forest-schema/filterable.test.ts +++ b/packages/agent/test/utils/forest-schema/filterable.test.ts @@ -23,6 +23,7 @@ describe('FrontendFilterableUtils', () => { 'Present', 'Blank', 'In', + 'NotIn', 'StartsWith', 'EndsWith', 'Contains', diff --git a/packages/agent/test/utils/forest-schema/generator-fields_column.test.ts b/packages/agent/test/utils/forest-schema/generator-fields_column.test.ts index 9509ae56c4..c34bcb9e85 100644 --- a/packages/agent/test/utils/forest-schema/generator-fields_column.test.ts +++ b/packages/agent/test/utils/forest-schema/generator-fields_column.test.ts @@ -58,6 +58,7 @@ describe('SchemaGeneratorFields > Column', () => { 'Present', 'Blank', 'In', + 'NotIn', 'StartsWith', 'EndsWith', 'Contains', diff --git a/packages/datasource-customizer/src/decorators/empty/collection.ts b/packages/datasource-customizer/src/decorators/empty/collection.ts index df091ef53b..807b1d3b68 100644 --- a/packages/datasource-customizer/src/decorators/empty/collection.ts +++ b/packages/datasource-customizer/src/decorators/empty/collection.ts @@ -105,6 +105,8 @@ export default class EmptyCollectionDecorator extends CollectionDecorator { valuesByField[field] = valuesByField[field].includes(value) ? [value] : []; } else if (valuesByField[field] && operator === 'In') { valuesByField[field] = valuesByField[field].filter(v => (value as unknown[]).includes(v)); + } else if (valuesByField[field] && operator === 'NotIn') { + valuesByField[field] = valuesByField[field].filter(v => !(value as unknown[]).includes(v)); } } diff --git a/packages/datasource-mongoose/src/utils/pipeline/filter.ts b/packages/datasource-mongoose/src/utils/pipeline/filter.ts index a548b7b5d7..f275995c2e 100644 --- a/packages/datasource-mongoose/src/utils/pipeline/filter.ts +++ b/packages/datasource-mongoose/src/utils/pipeline/filter.ts @@ -181,6 +181,8 @@ export default class FilterGenerator { return { $ne: formattedLeafValue }; case 'In': return { $in: formattedLeafValue }; + case 'NotIn': + return { $nin: formattedLeafValue }; case 'IncludesAll': return { $all: formattedLeafValue }; case 'IncludesNone':