diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/__snapshots__/filter-arg-processor.service.spec.ts.snap b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/__snapshots__/filter-arg-processor.service.spec.ts.snap index 49cba9e02d..7cabfea883 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/__snapshots__/filter-arg-processor.service.spec.ts.snap +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/__snapshots__/filter-arg-processor.service.spec.ts.snap @@ -1,8 +1,8 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`FilterArgProcessorService failing filter inputs validation ACTOR should throw for invalid filter #1: {"actorField":{"invalidSubField":{"eq":"test"}}} 1`] = `"Sub field "invalidSubField" not found for composite type: ACTOR"`; -exports[`FilterArgProcessorService failing filter inputs validation ACTOR should throw for invalid filter #2: {"actorField":{"source":{"invalidOperator":"test"}}} 1`] = `"Operator "invalidOperator" is not valid for field "actorField.source" of type SELECT - Allowed operators: eq, neq, in, containsAny, is, isEmptyArray"`; +exports[`FilterArgProcessorService failing filter inputs validation ACTOR should throw for invalid filter #2: {"actorField":{"source":{"invalidOperator":"test"}}} 1`] = `"Operator "invalidOperator" is not valid for field "actorField.source" of type SELECT - Allowed operators: eq, neq, gt, gte, lt, lte, in, containsAny, is, isEmptyArray"`; exports[`FilterArgProcessorService failing filter inputs validation ACTOR should throw for invalid filter #3: {"actorField":{"workspaceMemberId":{"eq":"invalid-uuid"}}} 1`] = `"Invalid UUID value 'invalid-uuid' for field "actorField.workspaceMemberId""`; @@ -124,7 +124,7 @@ exports[`FilterArgProcessorService failing filter inputs validation POSITION sho exports[`FilterArgProcessorService failing filter inputs validation POSITION should throw for invalid filter #4: {"position":{}} 1`] = `"Filter for field "position" must have exactly one operator"`; -exports[`FilterArgProcessorService failing filter inputs validation RATING should throw for invalid filter #1: {"ratingField":{"invalidOperator":"RATING_1"}} 1`] = `"Operator "invalidOperator" is not valid for field "ratingField" of type RATING - Allowed operators: eq, neq, in, containsAny, is, isEmptyArray"`; +exports[`FilterArgProcessorService failing filter inputs validation RATING should throw for invalid filter #1: {"ratingField":{"invalidOperator":"RATING_1"}} 1`] = `"Operator "invalidOperator" is not valid for field "ratingField" of type RATING - Allowed operators: eq, neq, gt, gte, lt, lte, in, containsAny, is, isEmptyArray"`; exports[`FilterArgProcessorService failing filter inputs validation RATING should throw for invalid filter #2: {"ratingField":{}} 1`] = `"Filter for field "ratingField" must have exactly one operator"`; @@ -144,7 +144,7 @@ exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT sh exports[`FilterArgProcessorService failing filter inputs validation RICH_TEXT should throw for invalid filter #2: {"richTextField":{"markdown":{"invalidOperator":"test"}}} 1`] = `"Operator "invalidOperator" is not valid for field "richTextField.markdown" of type TEXT - Allowed operators: eq, neq, gt, gte, lt, lte, in, is, like, ilike, startsWith, endsWith"`; -exports[`FilterArgProcessorService failing filter inputs validation SELECT should throw for invalid filter #1: {"selectField":{"invalidOperator":"OPTION_1"}} 1`] = `"Operator "invalidOperator" is not valid for field "selectField" of type SELECT - Allowed operators: eq, neq, in, containsAny, is, isEmptyArray"`; +exports[`FilterArgProcessorService failing filter inputs validation SELECT should throw for invalid filter #1: {"selectField":{"invalidOperator":"OPTION_1"}} 1`] = `"Operator "invalidOperator" is not valid for field "selectField" of type SELECT - Allowed operators: eq, neq, gt, gte, lt, lte, in, containsAny, is, isEmptyArray"`; exports[`FilterArgProcessorService failing filter inputs validation SELECT should throw for invalid filter #2: {"selectField":{}} 1`] = `"Filter for field "selectField" must have exactly one operator"`; diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/constants/successful-filter-inputs-by-field-metadata-type.constant.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/constants/successful-filter-inputs-by-field-metadata-type.constant.ts index 6b11ee5715..bd5cd2bb2c 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/constants/successful-filter-inputs-by-field-metadata-type.constant.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/__tests__/constants/successful-filter-inputs-by-field-metadata-type.constant.ts @@ -110,6 +110,10 @@ export const successfulFilterInputsByFieldMetadataType: { { filter: { selectField: { in: ['OPTION_1', 'OPTION_2'] } } }, { filter: { selectField: { is: 'NULL' } } }, { filter: { selectField: { is: 'NOT_NULL' } } }, + { filter: { selectField: { gt: 'OPTION_1' } } }, + { filter: { selectField: { gte: 'OPTION_1' } } }, + { filter: { selectField: { lt: 'OPTION_1' } } }, + { filter: { selectField: { lte: 'OPTION_1' } } }, ], [FieldMetadataType.RATING]: [ { filter: { ratingField: { eq: 'RATING_1' } } }, @@ -117,6 +121,10 @@ export const successfulFilterInputsByFieldMetadataType: { { filter: { ratingField: { in: ['RATING_1', 'RATING_2'] } } }, { filter: { ratingField: { is: 'NULL' } } }, { filter: { ratingField: { is: 'NOT_NULL' } } }, + { filter: { ratingField: { gt: 'RATING_1' } } }, + { filter: { ratingField: { gte: 'RATING_1' } } }, + { filter: { ratingField: { lt: 'RATING_1' } } }, + { filter: { ratingField: { lte: 'RATING_1' } } }, ], [FieldMetadataType.MULTI_SELECT]: [ { filter: { multiSelectField: { containsAny: ['OPTION_1'] } } }, diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/constants/filter-operators.constant.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/constants/filter-operators.constant.ts index 1baada3880..cec02355c6 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/constants/filter-operators.constant.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/constants/filter-operators.constant.ts @@ -65,6 +65,10 @@ export const MULTI_SELECT_FILTER_OPERATORS: FilterOperator[] = [ export const ENUM_FILTER_OPERATORS: FilterOperator[] = [ 'eq', 'neq', + 'gt', + 'gte', + 'lt', + 'lte', 'in', 'containsAny', 'is', diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/get-operators-for-field-type.util.spec.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/get-operators-for-field-type.util.spec.ts index 4a054bd22f..942c377fc2 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/get-operators-for-field-type.util.spec.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/get-operators-for-field-type.util.spec.ts @@ -31,4 +31,18 @@ describe('getOperatorsForFieldType', () => { expect(result).toContain('containsIlike'); expect(result).toContain('isEmptyArray'); }); + + it.each([FieldMetadataType.SELECT, FieldMetadataType.RATING])( + 'should allow enum operators and ordering operators for %s', + (fieldType) => { + const result = getOperatorsForFieldType(fieldType); + + expect(result).toContain('eq'); + expect(result).toContain('in'); + expect(result).toContain('gt'); + expect(result).toContain('gte'); + expect(result).toContain('lt'); + expect(result).toContain('lte'); + }, + ); }); diff --git a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/validate-operator-for-field-type-or-throw.util.spec.ts b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/validate-operator-for-field-type-or-throw.util.spec.ts index cffa6b1b5f..89b0e2e5da 100644 --- a/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/validate-operator-for-field-type-or-throw.util.spec.ts +++ b/packages/twenty-server/src/engine/api/common/common-args-processors/filter-arg-processor/utils/__tests__/validate-operator-for-field-type-or-throw.util.spec.ts @@ -46,4 +46,19 @@ describe('validateOperatorForFieldTypeOrThrow', () => { validateOperatorForFieldTypeOrThrow('like', fieldMetadata, 'testField'), ).toThrow(CommonQueryRunnerException); }); + + it.each(['gt', 'gte', 'lt', 'lte'] as const)( + 'should not throw when ordering operator "%s" is used on a SELECT field', + (operator) => { + const fieldMetadata = createFieldMetadata(FieldMetadataType.SELECT); + + expect(() => + validateOperatorForFieldTypeOrThrow( + operator, + fieldMetadata, + 'severity', + ), + ).not.toThrow(); + }, + ); });