Implemented ACTOR sub-field filtering (#11957)
This PR implements what's missing for ACTOR sub-field filtering, filtering on the source sub-field was already working. We can now filter on name sub-field. Since the sub-fields are different types and cannot be filtered both by text, we consider that a simple filter on ACTOR is filtering on the source, we have to go to advanced filter to have the name filter sub-field.
This commit is contained in:
@ -94,7 +94,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownMenuItemsContainer>
|
||||
<DropdownMenuItemsContainer width="auto">
|
||||
<SelectableList
|
||||
hotkeyScope={dropdownId}
|
||||
selectableItemIdArray={operandsForFilterType.map(
|
||||
@ -125,6 +125,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||
dropdownPlacement="bottom-start"
|
||||
dropdownWidth={200}
|
||||
/>
|
||||
</StyledContainer>
|
||||
);
|
||||
|
||||
@ -83,17 +83,31 @@ export const AdvancedFilterValueInput = ({
|
||||
? ({ y: -33, x: 0 } satisfies DropdownOffset)
|
||||
: DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET;
|
||||
|
||||
const showFilterTextInput =
|
||||
(isDefined(filterType) &&
|
||||
(TEXT_FILTER_TYPES.includes(filterType) ||
|
||||
NUMBER_FILTER_TYPES.includes(filterType))) ||
|
||||
isExpectedSubFieldName(
|
||||
FieldMetadataType.CURRENCY,
|
||||
'amountMicros',
|
||||
recordFilter.subFieldName,
|
||||
) ||
|
||||
(filterType === 'ADDRESS' &&
|
||||
subFieldNameUsedInDropdown !== 'addressCountry');
|
||||
const isFilterableByTextValue =
|
||||
isDefined(filterType) &&
|
||||
(TEXT_FILTER_TYPES.includes(filterType) ||
|
||||
NUMBER_FILTER_TYPES.includes(filterType));
|
||||
|
||||
const isCurrencyAmountMicrosFilter = isExpectedSubFieldName(
|
||||
FieldMetadataType.CURRENCY,
|
||||
'amountMicros',
|
||||
recordFilter.subFieldName,
|
||||
);
|
||||
|
||||
const isAddressFilterOnSubFieldOtherThanCountry =
|
||||
filterType === 'ADDRESS' && subFieldNameUsedInDropdown !== 'addressCountry';
|
||||
|
||||
const isActorNameFilter = isExpectedSubFieldName(
|
||||
FieldMetadataType.ACTOR,
|
||||
'name',
|
||||
recordFilter.subFieldName,
|
||||
);
|
||||
|
||||
const showFilterTextInputInsteadOfDropdown =
|
||||
isFilterableByTextValue ||
|
||||
isCurrencyAmountMicrosFilter ||
|
||||
isAddressFilterOnSubFieldOtherThanCountry ||
|
||||
isActorNameFilter;
|
||||
|
||||
return (
|
||||
<StyledValueDropdownContainer>
|
||||
@ -103,7 +117,7 @@ export const AdvancedFilterValueInput = ({
|
||||
<AdvancedFilterValueInputDropdownButtonClickableSelect
|
||||
recordFilterId={recordFilterId}
|
||||
/>
|
||||
) : showFilterTextInput ? (
|
||||
) : showFilterTextInputInsteadOfDropdown ? (
|
||||
<AdvancedFilterDropdownTextInput recordFilter={recordFilter} />
|
||||
) : (
|
||||
<Dropdown
|
||||
|
||||
@ -95,7 +95,7 @@ export type LinksFilter = {
|
||||
|
||||
export type ActorFilter = {
|
||||
name?: StringFilter;
|
||||
source?: IsFilter;
|
||||
source?: SelectFilter;
|
||||
};
|
||||
|
||||
export type EmailsFilter = {
|
||||
|
||||
@ -4,13 +4,13 @@ import { ObjectFilterDropdownOptionSelect } from '@/object-record/object-filter-
|
||||
import { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
||||
import { ObjectFilterDropdownSourceSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSourceSelect';
|
||||
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||
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';
|
||||
@ -19,7 +19,6 @@ import { fieldMetadataItemUsedInDropdownComponentSelector } from '@/object-recor
|
||||
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||
import { isExpectedSubFieldName } from '@/object-record/object-filter-dropdown/utils/isExpectedSubFieldName';
|
||||
import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dropdown/utils/isFilterOnActorSourceSubField';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@ -71,10 +70,6 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
fieldMetadataItemUsedInDropdown.type,
|
||||
);
|
||||
|
||||
const isActorSourceCompositeFilter = isFilterOnActorSourceSubField(
|
||||
subFieldNameUsedInDropdown,
|
||||
);
|
||||
|
||||
const isNotASubFieldFilter = !isDefined(subFieldNameUsedInDropdown);
|
||||
|
||||
return (
|
||||
@ -100,16 +95,11 @@ export const ObjectFilterDropdownFilterInput = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ACTOR' &&
|
||||
(isActorSourceCompositeFilter || isNotASubFieldFilter ? (
|
||||
<>
|
||||
<ObjectFilterDropdownSourceSelect />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<ObjectFilterDropdownTextInput />
|
||||
</>
|
||||
))}
|
||||
{filterType === 'ACTOR' && (
|
||||
<>
|
||||
<ObjectFilterDropdownSourceSelect />
|
||||
</>
|
||||
)}
|
||||
{filterType === 'ADDRESS' &&
|
||||
(isNotASubFieldFilter ? (
|
||||
<>
|
||||
|
||||
@ -5,4 +5,6 @@ export const ICON_NAME_BY_SUB_FIELD: Partial<
|
||||
> = {
|
||||
currencyCode: 'IconCurrencyDollar',
|
||||
amountMicros: 'IconNumber95Small',
|
||||
name: 'IconAlignJustified',
|
||||
source: 'IconFileArrowLeft',
|
||||
};
|
||||
|
||||
@ -927,73 +927,139 @@ export const computeFilterRecordGqlOperationFilter = ({
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO: fix this with a new composite field in ViewFilter entity
|
||||
case 'ACTOR': {
|
||||
switch (filter.operand) {
|
||||
case RecordFilterOperand.Is: {
|
||||
if (filter.value === '[]') {
|
||||
return;
|
||||
}
|
||||
if (isSubFieldFilter) {
|
||||
switch (subFieldName) {
|
||||
case 'source': {
|
||||
switch (filter.operand) {
|
||||
case RecordFilterOperand.Is: {
|
||||
if (filter.value === '[]') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||
const parsedSources = JSON.parse(filter.value) as string[];
|
||||
|
||||
return {
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
in: parsedRecordIds,
|
||||
} as RelationFilter,
|
||||
},
|
||||
};
|
||||
}
|
||||
case RecordFilterOperand.IsNot: {
|
||||
if (filter.value === '[]') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedRecordIds = JSON.parse(filter.value) as string[];
|
||||
|
||||
if (parsedRecordIds.length === 0) return;
|
||||
|
||||
return {
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
in: parsedRecordIds,
|
||||
} as RelationFilter,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
case RecordFilterOperand.Contains:
|
||||
return {
|
||||
or: [
|
||||
{
|
||||
[correspondingField.name]: {
|
||||
name: {
|
||||
ilike: `%${filter.value}%`,
|
||||
},
|
||||
} as ActorFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
case RecordFilterOperand.DoesNotContain:
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
not: {
|
||||
return {
|
||||
[correspondingField.name]: {
|
||||
name: {
|
||||
ilike: `%${filter.value}%`,
|
||||
source: {
|
||||
in: parsedSources,
|
||||
} satisfies RelationFilter,
|
||||
},
|
||||
};
|
||||
}
|
||||
case RecordFilterOperand.IsNot: {
|
||||
if (filter.value === '[]') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedSources = JSON.parse(filter.value) as string[];
|
||||
|
||||
if (parsedSources.length === 0) return;
|
||||
|
||||
return {
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
in: parsedSources,
|
||||
} satisfies RelationFilter,
|
||||
},
|
||||
} as ActorFilter,
|
||||
},
|
||||
};
|
||||
}
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
case 'name': {
|
||||
switch (filter.operand) {
|
||||
case RecordFilterOperand.Contains:
|
||||
return {
|
||||
or: [
|
||||
{
|
||||
[correspondingField.name]: {
|
||||
name: {
|
||||
ilike: `%${filter.value}%`,
|
||||
},
|
||||
} satisfies ActorFilter,
|
||||
},
|
||||
],
|
||||
};
|
||||
case RecordFilterOperand.DoesNotContain:
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
name: {
|
||||
ilike: `%${filter.value}%`,
|
||||
},
|
||||
} satisfies ActorFilter,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
if (filter.value === '[]') {
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedSources = JSON.parse(filter.value) as string[];
|
||||
|
||||
if (parsedSources.length === 0) return;
|
||||
|
||||
switch (filter.operand) {
|
||||
case RecordFilterOperand.Contains:
|
||||
return {
|
||||
or: [
|
||||
{
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
in: parsedSources,
|
||||
},
|
||||
} satisfies ActorFilter,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
||||
);
|
||||
],
|
||||
};
|
||||
case RecordFilterOperand.DoesNotContain:
|
||||
return {
|
||||
and: [
|
||||
{
|
||||
or: [
|
||||
{
|
||||
not: {
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
in: parsedSources,
|
||||
},
|
||||
} satisfies ActorFilter,
|
||||
},
|
||||
},
|
||||
{
|
||||
[correspondingField.name]: {
|
||||
source: {
|
||||
is: 'NULL',
|
||||
},
|
||||
} satisfies ActorFilter,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
default:
|
||||
throw new Error(
|
||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
case 'EMAILS':
|
||||
|
||||
@ -177,8 +177,8 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
||||
label: 'Actor',
|
||||
Icon: IllustrationIconSetting,
|
||||
category: 'Basic',
|
||||
subFields: ['source'],
|
||||
filterableSubFields: ['source'],
|
||||
subFields: ['source', 'name'],
|
||||
filterableSubFields: ['source', 'name'],
|
||||
labelBySubField: {
|
||||
source: 'Source',
|
||||
name: 'Name',
|
||||
|
||||
@ -65,6 +65,7 @@ export const EditableFilterDropdownButton = ({
|
||||
dropdownOffset={{ y: 8, x: 0 }}
|
||||
dropdownPlacement="bottom-start"
|
||||
onClickOutside={handleDropdownClickOutside}
|
||||
dropdownWidth={200}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user