Fixed error with previous filters on ACTOR with new sub-field filtering (#12050)

Fixes https://github.com/twentyhq/core-team-issues/issues/969 that
appeared since the new ACTOR sub-field filtering that changed the
default sub-field filtering from name to source.

Now we apply any existing filter value on an ACTOR field, whether for a
sub-field or not, to the name sub-field by default.

If the user wants to create a sub-field filter on source, he has to
create a new advanced filter.

Fixes https://github.com/twentyhq/core-team-issues/issues/967

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Lucas Bordeau
2025-05-15 13:58:22 +02:00
committed by GitHub
parent 93c91c88dc
commit 3a494905ec
5 changed files with 64 additions and 126 deletions

View File

@ -10,7 +10,6 @@ import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
import { ObjectFilterDropdownCurrencySelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownCurrencySelect';
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
import { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
@ -97,7 +96,7 @@ export const ObjectFilterDropdownFilterInput = ({
)}
{filterType === 'ACTOR' && (
<>
<ObjectFilterDropdownSourceSelect />
<ObjectFilterDropdownTextInput />
</>
)}
{filterType === 'ADDRESS' &&

View File

@ -59,7 +59,7 @@ describe('getOperandsForFilterType', () => {
['LINKS', [...containsOperands, ...emptyOperands], 'secondaryLinks'],
['ACTOR', [...containsOperands, ...emptyOperands], 'name'],
['ACTOR', [...actorSourceOperands, ...emptyOperands], 'source'],
['ACTOR', [...actorSourceOperands, ...emptyOperands]],
['ACTOR', [...containsOperands, ...emptyOperands]],
[
'CURRENCY',
[...currencyCurrencyCodeOperands, ...emptyOperands],

View File

@ -886,139 +886,81 @@ export const turnRecordFilterIntoRecordGqlOperationFilter = ({
}
}
case 'ACTOR': {
if (isSubFieldFilter) {
switch (subFieldName) {
case 'source': {
switch (recordFilter.operand) {
case RecordFilterOperand.Is: {
if (recordFilter.value === '[]') {
return;
}
const parsedSources = JSON.parse(
recordFilter.value,
) as string[];
return {
[correspondingFieldMetadataItem.name]: {
source: {
in: parsedSources,
} satisfies RelationFilter,
},
};
}
case RecordFilterOperand.IsNot: {
if (recordFilter.value === '[]') {
return;
}
const parsedSources = JSON.parse(
recordFilter.value,
) as string[];
if (parsedSources.length === 0) return;
return {
not: {
[correspondingFieldMetadataItem.name]: {
source: {
in: parsedSources,
} satisfies RelationFilter,
},
},
};
}
default:
throw new Error(
`Unknown operand ${recordFilter.operand} for ${recordFilter.label} filter`,
);
}
}
case 'name': {
switch (recordFilter.operand) {
case RecordFilterOperand.Contains:
return {
or: [
{
[correspondingFieldMetadataItem.name]: {
name: {
ilike: `%${recordFilter.value}%`,
},
} satisfies ActorFilter,
},
],
};
case RecordFilterOperand.DoesNotContain:
return {
and: [
{
not: {
[correspondingFieldMetadataItem.name]: {
name: {
ilike: `%${recordFilter.value}%`,
},
} satisfies ActorFilter,
},
},
],
};
default:
throw new Error(
`Unknown operand ${recordFilter.operand} for ${recordFilter.label} filter`,
);
}
}
}
break;
} else {
if (recordFilter.value === '[]') {
return;
}
const parsedSources = JSON.parse(recordFilter.value) as string[];
if (parsedSources.length === 0) return;
if (subFieldName === 'source') {
switch (recordFilter.operand) {
case RecordFilterOperand.Is:
case RecordFilterOperand.Is: {
if (recordFilter.value === '[]') {
return;
}
const parsedSources = JSON.parse(recordFilter.value) as string[];
return {
[correspondingFieldMetadataItem.name]: {
source: {
in: parsedSources,
},
} satisfies ActorFilter,
} satisfies RelationFilter,
},
};
case RecordFilterOperand.IsNot:
}
case RecordFilterOperand.IsNot: {
if (recordFilter.value === '[]') {
return;
}
const parsedSources = JSON.parse(recordFilter.value) as string[];
if (parsedSources.length === 0) return;
return {
and: [
{
or: [
{
not: {
[correspondingFieldMetadataItem.name]: {
source: {
in: parsedSources,
},
} satisfies ActorFilter,
},
},
{
[correspondingFieldMetadataItem.name]: {
source: {
is: 'NULL',
},
} satisfies ActorFilter,
},
],
not: {
[correspondingFieldMetadataItem.name]: {
source: {
in: parsedSources,
} satisfies RelationFilter,
},
],
},
};
}
default:
throw new Error(
`Unknown operand ${recordFilter.operand} for ${recordFilter.label} filter`,
);
}
}
switch (recordFilter.operand) {
case RecordFilterOperand.Contains:
return {
or: [
{
[correspondingFieldMetadataItem.name]: {
name: {
ilike: `%${recordFilter.value}%`,
},
} satisfies ActorFilter,
},
],
};
case RecordFilterOperand.DoesNotContain:
return {
and: [
{
not: {
[correspondingFieldMetadataItem.name]: {
name: {
ilike: `%${recordFilter.value}%`,
},
} satisfies ActorFilter,
},
},
],
};
default:
throw new Error(
`Unknown operand ${recordFilter.operand} for ${recordFilter.label} filter`,
);
}
}
case 'EMAILS': {
return computeGqlOperationFilterForEmails({

View File

@ -25,7 +25,7 @@ export const getDefaultSubFieldNameForCompositeFilterableFieldType = (
case 'ADDRESS':
return undefined;
case 'ACTOR':
return 'source';
return 'name';
case 'FULL_NAME':
return undefined;
default:

View File

@ -3,7 +3,6 @@ import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dro
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
import { isNonEmptyString } from '@sniptt/guards';
import { FieldMetadataType } from 'twenty-shared/types';
import { assertUnreachable } from 'twenty-shared/utils';
@ -148,8 +147,6 @@ export const getRecordFilterOperands = ({
filterType,
subFieldName,
}: GetRecordFilterOperandsParams) => {
const isFilterOnSubField = isNonEmptyString(subFieldName);
switch (filterType) {
case 'TEXT':
case 'EMAILS':
@ -187,7 +184,7 @@ export const getRecordFilterOperands = ({
case 'SELECT':
return FILTER_OPERANDS_MAP.SELECT;
case 'ACTOR': {
if (isFilterOnActorSourceSubField(subFieldName) || !isFilterOnSubField) {
if (isFilterOnActorSourceSubField(subFieldName)) {
return [
RecordFilterOperand.Is,
RecordFilterOperand.IsNot,