Keep leading + when filtering phones by calling code (#23546)

Fixes #23528

Filtering a PHONES field with `CONTAINS` / `DOES_NOT_CONTAIN` stripped
every non-digit character from the filter value, so `+33` became `33`
and the generated `ilike`/`like` predicates could not distinguish an
international calling code from any number containing those digits.

`turnRecordFilterIntoGqlOperationFilter` now preserves a leading `+`
while still removing other formatting characters (spaces, dashes,
parentheses). `+33 6 12` becomes `+33612`; values without a `+` are
unchanged.

Added a regression test in `computeViewRecordGqlOperationFilter.test.ts`
for a `+`-prefixed value.

Lint and typecheck pass on `twenty-shared` and `twenty-front`; the
filter test suites pass in both packages.

---------

Co-authored-by: Thomas Trompette <tom@twenty.com>
This commit is contained in:
Thomas Trompette
2026-07-31 11:14:19 +02:00
committed by GitHub
parent 8f9f2f390e
commit 3ed11054a0
2 changed files with 62 additions and 2 deletions
@@ -799,6 +799,60 @@ describe('should work as expected for the different field types', () => {
});
});
it('phones field type with an international calling code prefix', () => {
const personMockPhonesFieldMetadataItem =
personMockObjectMetadataItem.fields.find(
(field) => field.name === 'phones',
);
if (!isDefined(personMockPhonesFieldMetadataItem)) {
throw new Error('Person mock phones field metadata ID is undefined');
}
const phonesFilterContains: RecordFilter = {
id: 'person-phones-filter-contains-calling-code',
value: '+33 6 12',
fieldMetadataId: personMockPhonesFieldMetadataItem.id,
displayValue: '+33 6 12',
operand: ViewFilterOperand.CONTAINS,
label: 'Phones',
type: FieldMetadataType.PHONES,
};
const result = computeRecordGqlOperationFilter({
filterValueDependencies: mockFilterValueDependencies,
recordFilters: [phonesFilterContains],
recordFilterGroups: [],
fieldMetadataItems: personFields,
});
expect(result).toEqual({
or: [
{
phones: {
primaryPhoneNumber: {
ilike: '%+33612%',
},
},
},
{
phones: {
primaryPhoneCallingCode: {
ilike: '%+33612%',
},
},
},
{
phones: {
additionalPhones: {
like: '%+33612%',
},
},
},
],
});
});
it('emails field type', () => {
const personMockEmailFieldMetadataId = getMockFieldMetadataItemOrThrow({
objectMetadataItem: personMockObjectMetadataItem,
@@ -60,6 +60,10 @@ import {
computeRelationGqlFieldJoinColumnName,
} from '@/utils/fieldMetadata/compute-relation-gql-field-join-column-name';
const PHONE_FILTER_NON_SIGNIFICANT_CHARS = /(?!^)\+|[^0-9+]/g;
const CONTAINS_DIGIT = /[0-9]/;
type FieldSharedMorphRelation = {
type: RelationType;
targetObjectMetadata: {
@@ -1403,9 +1407,11 @@ const buildDirectFieldGqlOperationFilter = ({
}
case 'PHONES': {
if (!isSubFieldFilter) {
const filterValue = recordFilter.value.replace(/[^0-9]/g, '');
const filterValue = recordFilter.value
.trim()
.replace(PHONE_FILTER_NON_SIGNIFICANT_CHARS, '');
if (!isNonEmptyString(filterValue)) {
if (!CONTAINS_DIGIT.test(filterValue)) {
return;
}