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
This commit is contained in:
@ -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,
|
||||
|
||||
@ -15,14 +15,14 @@ const convertFieldRatingValueToNumber = (
|
||||
rating: Exclude<FieldRatingValue, null>,
|
||||
): 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(
|
||||
|
||||
@ -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
|
||||
];
|
||||
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -4,8 +4,8 @@ export const configurableViewFilterOperands = new Set<ViewFilterOperand>([
|
||||
ViewFilterOperand.Is,
|
||||
ViewFilterOperand.IsNotNull,
|
||||
ViewFilterOperand.IsNot,
|
||||
ViewFilterOperand.LessThan,
|
||||
ViewFilterOperand.GreaterThan,
|
||||
ViewFilterOperand.LessThanOrEqual,
|
||||
ViewFilterOperand.GreaterThanOrEqual,
|
||||
ViewFilterOperand.IsBefore,
|
||||
ViewFilterOperand.IsAfter,
|
||||
ViewFilterOperand.Contains,
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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 },
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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',
|
||||
},
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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',
|
||||
|
||||
Reference in New Issue
Block a user