From 0dcfca2ba3cb5ca0e4228a128ce5f35391694bba Mon Sep 17 00:00:00 2001 From: Lucas Bordeau Date: Fri, 4 Jul 2025 16:26:19 +0200 Subject: [PATCH] Fixes greater than or equal and less than and equal filters (#13033) This PR fixes a mismatch between the filter operation we have on NUMBER and RATING field types, and the labels we use for those filters in the application. What is actually used is : - Greater than or equal - Less than or equal But unfortunately, until now we display "less than" and "greater than" everywhere. This PR fixes that. We would still have to change the value that is saved in viewFilter table from `greaterThan` to `greaterThanOrEqual` and likewise for less than, but it would require a careful migration, and for now just changing the display labels is enough. See follow-up issue for migration of the DB values : https://github.com/twentyhq/core-team-issues/issues/1196 Fixes https://github.com/twentyhq/twenty/issues/13000 --- .../ObjectFilterDropdownFilterInput.tsx | 4 ++-- .../ObjectFilterDropdownRatingInput.tsx | 4 ++-- .../utils/__tests__/getOperandLabel.test.ts | 10 +++++----- .../getOperandsForFilterType.test.ts | 8 ++++---- .../isFilterOperandExpectingValue.test.ts | 4 ++-- .../utils/configurableViewFilterOperands.ts | 4 ++-- .../utils/getOperandLabel.ts | 16 +++++++-------- .../utils/isFilterOperandExpectingValue.ts | 4 ++-- ...omputeViewRecordGqlOperationFilter.test.ts | 10 +++++----- .../turnRecordFilterIntoGqlOperationFilter.ts | 20 +++++++++---------- .../utils/getRecordFilterOperands.ts | 16 +++++++-------- .../utils/buildRecordInputFromFilter.spec.ts | 8 ++++---- .../utils/buildRecordInputFromFilter.ts | 9 +++++---- .../modules/views/types/ViewFilterOperand.ts | 4 ++-- 14 files changed, 61 insertions(+), 60 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx index 5df28eb2e..295f62142 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownFilterInput.tsx @@ -49,8 +49,8 @@ export const ObjectFilterDropdownFilterInput = ({ ViewFilterOperand.Is, ViewFilterOperand.IsNotNull, ViewFilterOperand.IsNot, - ViewFilterOperand.LessThan, - ViewFilterOperand.GreaterThan, + ViewFilterOperand.LessThanOrEqual, + ViewFilterOperand.GreaterThanOrEqual, ViewFilterOperand.IsBefore, ViewFilterOperand.IsAfter, ViewFilterOperand.Contains, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx index 1c9db8ef4..a078dc8d9 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput.tsx @@ -15,14 +15,14 @@ const convertFieldRatingValueToNumber = ( rating: Exclude, ): string => rating.split('_')[1]; -export const convertGreaterThanRatingToArrayOfRatingValues = ( +export const convertGreaterThanOrEqualRatingToArrayOfRatingValues = ( greaterThanValue: number, ) => RATING_VALUES.filter( (ratingValue) => +ratingValue.split('_')[1] >= greaterThanValue, ); -export const convertLessThanRatingToArrayOfRatingValues = ( +export const convertLessThanOrEqualRatingToArrayOfRatingValues = ( lessThanValue: number, ) => RATING_VALUES.filter( diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts index a20f31ee5..2f2a96171 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandLabel.test.ts @@ -1,14 +1,14 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand'; -import { getOperandLabel, getOperandLabelShort } from '../getOperandLabel'; import { capitalize } from 'twenty-shared/utils'; +import { getOperandLabel, getOperandLabelShort } from '../getOperandLabel'; describe('getOperandLabel', () => { const testCases = [ [ViewFilterOperand.Contains, 'Contains'], [ViewFilterOperand.DoesNotContain, "Doesn't contain"], - [ViewFilterOperand.GreaterThan, 'Greater than'], - [ViewFilterOperand.LessThan, 'Less than'], + [ViewFilterOperand.GreaterThanOrEqual, 'Greater than or equal'], + [ViewFilterOperand.LessThanOrEqual, 'Less than or equal'], [ViewFilterOperand.Is, 'Is'], [ViewFilterOperand.IsNot, 'Is not'], [ViewFilterOperand.IsNotNull, 'Is not null'], @@ -32,8 +32,8 @@ describe('getOperandLabelShort', () => { [ViewFilterOperand.IsNot, ': Not'], [ViewFilterOperand.DoesNotContain, ': Not'], [ViewFilterOperand.IsNotNull, ': NotNull'], - [ViewFilterOperand.GreaterThan, '\u00A0> '], - [ViewFilterOperand.LessThan, '\u00A0< '], + [ViewFilterOperand.GreaterThanOrEqual, '\u00A0≥ '], + [ViewFilterOperand.LessThanOrEqual, '\u00A0≤ '], [undefined, ': '], // undefined operand ]; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts index a97e2e8ff..7f4a245d1 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/getOperandsForFilterType.test.ts @@ -16,13 +16,13 @@ describe('getOperandsForFilterType', () => { ]; const numberOperands = [ - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, ]; const currencyAmountMicrosOperands = [ - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, RecordFilterOperand.Is, RecordFilterOperand.IsNot, ]; diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts index 62246c736..7f1e8b26b 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/__tests__/isFilterOperandExpectingValue.test.ts @@ -6,8 +6,8 @@ describe('isFilterOperandExpectingValue', () => { const testCases = [ { operand: ViewFilterOperand.Contains, expectedResult: true }, { operand: ViewFilterOperand.DoesNotContain, expectedResult: true }, - { operand: ViewFilterOperand.GreaterThan, expectedResult: true }, - { operand: ViewFilterOperand.LessThan, expectedResult: true }, + { operand: ViewFilterOperand.GreaterThanOrEqual, expectedResult: true }, + { operand: ViewFilterOperand.LessThanOrEqual, expectedResult: true }, { operand: ViewFilterOperand.Is, expectedResult: true }, { operand: ViewFilterOperand.IsNot, expectedResult: true }, { operand: ViewFilterOperand.IsRelative, expectedResult: true }, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts index 1b85debc7..aaafc9e8d 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/configurableViewFilterOperands.ts @@ -4,8 +4,8 @@ export const configurableViewFilterOperands = new Set([ ViewFilterOperand.Is, ViewFilterOperand.IsNotNull, ViewFilterOperand.IsNot, - ViewFilterOperand.LessThan, - ViewFilterOperand.GreaterThan, + ViewFilterOperand.LessThanOrEqual, + ViewFilterOperand.GreaterThanOrEqual, ViewFilterOperand.IsBefore, ViewFilterOperand.IsAfter, ViewFilterOperand.Contains, diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts index 1e0a25190..4f63f9252 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/getOperandLabel.ts @@ -9,10 +9,10 @@ export const getOperandLabel = ( return t`Contains`; case ViewFilterOperand.DoesNotContain: return t`Doesn't contain`; - case ViewFilterOperand.GreaterThan: - return t`Greater than`; - case ViewFilterOperand.LessThan: - return t`Less than`; + case ViewFilterOperand.GreaterThanOrEqual: + return t`Greater than or equal`; + case ViewFilterOperand.LessThanOrEqual: + return t`Less than or equal`; case ViewFilterOperand.IsBefore: return t`Is before`; case ViewFilterOperand.IsAfter: @@ -56,10 +56,10 @@ export const getOperandLabelShort = ( return t`: NotEmpty`; case ViewFilterOperand.IsEmpty: return t`: Empty`; - case ViewFilterOperand.GreaterThan: - return '\u00A0> '; - case ViewFilterOperand.LessThan: - return '\u00A0< '; + case ViewFilterOperand.GreaterThanOrEqual: + return '\u00A0≥ '; + case ViewFilterOperand.LessThanOrEqual: + return '\u00A0≤ '; case ViewFilterOperand.IsBefore: return '\u00A0< '; case ViewFilterOperand.IsAfter: diff --git a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts index 668742946..cdf0623b7 100644 --- a/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts +++ b/packages/twenty-front/src/modules/object-record/object-filter-dropdown/utils/isFilterOperandExpectingValue.ts @@ -12,8 +12,8 @@ export const isFilterOperandExpectingValue = (operand: ViewFilterOperand) => { case ViewFilterOperand.IsNot: case ViewFilterOperand.Contains: case ViewFilterOperand.DoesNotContain: - case ViewFilterOperand.GreaterThan: - case ViewFilterOperand.LessThan: + case ViewFilterOperand.GreaterThanOrEqual: + case ViewFilterOperand.LessThanOrEqual: case ViewFilterOperand.IsBefore: case ViewFilterOperand.IsAfter: case ViewFilterOperand.Is: diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts index 71d85e9bd..673a5f770 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/__tests__/computeViewRecordGqlOperationFilter.test.ts @@ -85,7 +85,7 @@ describe('computeViewRecordGqlOperationFilter', () => { value: '1000', fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, displayValue: '1000', - operand: ViewFilterOperand.GreaterThan, + operand: ViewFilterOperand.GreaterThanOrEqual, type: FieldMetadataType.NUMBER, label: 'Employees', }; @@ -1119,7 +1119,7 @@ describe('should work as expected for the different field types', () => { value: '1000', fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, displayValue: '1000', - operand: ViewFilterOperand.GreaterThan, + operand: ViewFilterOperand.GreaterThanOrEqual, label: 'Employees', type: FieldMetadataType.NUMBER, }; @@ -1129,7 +1129,7 @@ describe('should work as expected for the different field types', () => { value: '1000', fieldMetadataId: companyMockEmployeesFieldMetadataId?.id, displayValue: '1000', - operand: ViewFilterOperand.LessThan, + operand: ViewFilterOperand.LessThanOrEqual, label: 'Employees', type: FieldMetadataType.NUMBER, }; @@ -1205,7 +1205,7 @@ describe('should work as expected for the different field types', () => { value: '1000', fieldMetadataId: companyMockARRFieldMetadataId?.id, displayValue: '1000', - operand: RecordFilterOperand.GreaterThan, + operand: RecordFilterOperand.GreaterThanOrEqual, subFieldName: 'amountMicros' satisfies Extract< keyof FieldCurrencyValue, 'amountMicros' @@ -1219,7 +1219,7 @@ describe('should work as expected for the different field types', () => { value: '1000', fieldMetadataId: companyMockARRFieldMetadataId?.id, displayValue: '1000', - operand: RecordFilterOperand.LessThan, + operand: RecordFilterOperand.LessThanOrEqual, subFieldName: 'amountMicros' satisfies Extract< keyof FieldCurrencyValue, 'amountMicros' diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts index cabf6e83c..c12457e11 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter.ts @@ -24,8 +24,8 @@ import { generateILikeFiltersForCompositeFields } from '~/utils/array/generateIL import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions'; import { - convertGreaterThanRatingToArrayOfRatingValues, - convertLessThanRatingToArrayOfRatingValues, + convertGreaterThanOrEqualRatingToArrayOfRatingValues, + convertLessThanOrEqualRatingToArrayOfRatingValues, convertRatingToRatingValue, } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput'; import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; @@ -268,18 +268,18 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({ eq: convertRatingToRatingValue(parseFloat(recordFilter.value)), } as RatingFilter, }; - case RecordFilterOperand.GreaterThan: + case RecordFilterOperand.GreaterThanOrEqual: return { [correspondingFieldMetadataItem.name]: { - in: convertGreaterThanRatingToArrayOfRatingValues( + in: convertGreaterThanOrEqualRatingToArrayOfRatingValues( parseFloat(recordFilter.value), ), } as RatingFilter, }; - case RecordFilterOperand.LessThan: + case RecordFilterOperand.LessThanOrEqual: return { [correspondingFieldMetadataItem.name]: { - in: convertLessThanRatingToArrayOfRatingValues( + in: convertLessThanOrEqualRatingToArrayOfRatingValues( parseFloat(recordFilter.value), ), } as RatingFilter, @@ -291,13 +291,13 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({ } case 'NUMBER': switch (recordFilter.operand) { - case RecordFilterOperand.GreaterThan: + case RecordFilterOperand.GreaterThanOrEqual: return { [correspondingFieldMetadataItem.name]: { gte: parseFloat(recordFilter.value), } as FloatFilter, }; - case RecordFilterOperand.LessThan: + case RecordFilterOperand.LessThanOrEqual: return { [correspondingFieldMetadataItem.name]: { lte: parseFloat(recordFilter.value), @@ -401,13 +401,13 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({ !isSubFieldFilter ) { switch (recordFilter.operand) { - case RecordFilterOperand.GreaterThan: + case RecordFilterOperand.GreaterThanOrEqual: return { [correspondingFieldMetadataItem.name]: { amountMicros: { gte: parseFloat(recordFilter.value) * 1000000 }, } as CurrencyFilter, }; - case RecordFilterOperand.LessThan: + case RecordFilterOperand.LessThanOrEqual: return { [correspondingFieldMetadataItem.name]: { amountMicros: { lte: parseFloat(recordFilter.value) * 1000000 }, diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts index 315708315..82af90d34 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/getRecordFilterOperands.ts @@ -69,13 +69,13 @@ export const FILTER_OPERANDS_MAP = { ...emptyOperands, ], CURRENCY: [ - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, ...emptyOperands, ], NUMBER: [ - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, ...emptyOperands, ], RAW_JSON: [ @@ -105,8 +105,8 @@ export const FILTER_OPERANDS_MAP = { ], RATING: [ RecordFilterOperand.Is, - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, ...emptyOperands, ], RELATION: [...relationOperands, ...emptyOperands], @@ -139,8 +139,8 @@ export const COMPOSITE_FIELD_FILTER_OPERANDS_MAP = { ...emptyOperands, ], amountMicros: [ - RecordFilterOperand.GreaterThan, - RecordFilterOperand.LessThan, + RecordFilterOperand.GreaterThanOrEqual, + RecordFilterOperand.LessThanOrEqual, RecordFilterOperand.Is, RecordFilterOperand.IsNot, ...emptyOperands, diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts index 4f01b64ce..cecbb407a 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.spec.ts @@ -133,12 +133,12 @@ describe('buildValueFromFilter', () => { describe('NUMBER field type', () => { const testCases = [ { - operand: ViewFilterOperand.GreaterThan, + operand: ViewFilterOperand.GreaterThanOrEqual, value: '5', expected: 6, }, { - operand: ViewFilterOperand.LessThan, + operand: ViewFilterOperand.LessThanOrEqual, value: '5', expected: 4, }, @@ -359,12 +359,12 @@ describe('buildValueFromFilter', () => { expected: undefined, }, { - operand: ViewFilterOperand.GreaterThan, + operand: ViewFilterOperand.GreaterThanOrEqual, value: 'Rating 1', expected: 'RATING_2', }, { - operand: ViewFilterOperand.LessThan, + operand: ViewFilterOperand.LessThanOrEqual, value: 'Rating 2', expected: 'RATING_1', }, diff --git a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts index 24d814a2b..924617586 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-table/utils/buildRecordInputFromFilter.ts @@ -150,9 +150,10 @@ const computeValueFromFilterNumber = ( value: string, ) => { switch (operand) { - case ViewFilterOperand.GreaterThan: + //TODO: we shouln't create values from those filters as it makes no sense for the user + case ViewFilterOperand.GreaterThanOrEqual: return Number(value) + 1; - case ViewFilterOperand.LessThan: + case ViewFilterOperand.LessThanOrEqual: return Number(value) - 1; case ViewFilterOperand.IsNotEmpty: return Number(value); @@ -205,13 +206,13 @@ const computeValueFromFilterRating = ( case ViewFilterOperand.Is: case ViewFilterOperand.IsNotEmpty: return option.value; - case ViewFilterOperand.GreaterThan: { + case ViewFilterOperand.GreaterThanOrEqual: { const plusOne = options?.find( (opt) => opt.position === option.position + 1, )?.value; return plusOne ? plusOne : option.value; } - case ViewFilterOperand.LessThan: { + case ViewFilterOperand.LessThanOrEqual: { const minusOne = options?.find( (opt) => opt.position === option.position - 1, )?.value; diff --git a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts index 7ea686348..e67700271 100644 --- a/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts +++ b/packages/twenty-front/src/modules/views/types/ViewFilterOperand.ts @@ -2,8 +2,8 @@ export enum ViewFilterOperand { Is = 'is', IsNotNull = 'isNotNull', IsNot = 'isNot', - LessThan = 'lessThan', - GreaterThan = 'greaterThan', + LessThanOrEqual = 'lessThan', // TODO: we could change this to 'lessThanOrEqual' for consistency but it would require a migration + GreaterThanOrEqual = 'greaterThan', // TODO: we could change this to 'greaterThanOrEqual' for consistency but it would require a migration IsBefore = 'isBefore', IsAfter = 'isAfter', Contains = 'contains',