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={
|
dropdownComponents={
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer width="auto">
|
||||||
<SelectableList
|
<SelectableList
|
||||||
hotkeyScope={dropdownId}
|
hotkeyScope={dropdownId}
|
||||||
selectableItemIdArray={operandsForFilterType.map(
|
selectableItemIdArray={operandsForFilterType.map(
|
||||||
@ -125,6 +125,7 @@ export const AdvancedFilterRecordFilterOperandSelect = ({
|
|||||||
dropdownHotkeyScope={{ scope: dropdownId }}
|
dropdownHotkeyScope={{ scope: dropdownId }}
|
||||||
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET}
|
||||||
dropdownPlacement="bottom-start"
|
dropdownPlacement="bottom-start"
|
||||||
|
dropdownWidth={200}
|
||||||
/>
|
/>
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -83,17 +83,31 @@ export const AdvancedFilterValueInput = ({
|
|||||||
? ({ y: -33, x: 0 } satisfies DropdownOffset)
|
? ({ y: -33, x: 0 } satisfies DropdownOffset)
|
||||||
: DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET;
|
: DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET;
|
||||||
|
|
||||||
const showFilterTextInput =
|
const isFilterableByTextValue =
|
||||||
(isDefined(filterType) &&
|
isDefined(filterType) &&
|
||||||
(TEXT_FILTER_TYPES.includes(filterType) ||
|
(TEXT_FILTER_TYPES.includes(filterType) ||
|
||||||
NUMBER_FILTER_TYPES.includes(filterType))) ||
|
NUMBER_FILTER_TYPES.includes(filterType));
|
||||||
isExpectedSubFieldName(
|
|
||||||
FieldMetadataType.CURRENCY,
|
const isCurrencyAmountMicrosFilter = isExpectedSubFieldName(
|
||||||
'amountMicros',
|
FieldMetadataType.CURRENCY,
|
||||||
recordFilter.subFieldName,
|
'amountMicros',
|
||||||
) ||
|
recordFilter.subFieldName,
|
||||||
(filterType === 'ADDRESS' &&
|
);
|
||||||
subFieldNameUsedInDropdown !== 'addressCountry');
|
|
||||||
|
const isAddressFilterOnSubFieldOtherThanCountry =
|
||||||
|
filterType === 'ADDRESS' && subFieldNameUsedInDropdown !== 'addressCountry';
|
||||||
|
|
||||||
|
const isActorNameFilter = isExpectedSubFieldName(
|
||||||
|
FieldMetadataType.ACTOR,
|
||||||
|
'name',
|
||||||
|
recordFilter.subFieldName,
|
||||||
|
);
|
||||||
|
|
||||||
|
const showFilterTextInputInsteadOfDropdown =
|
||||||
|
isFilterableByTextValue ||
|
||||||
|
isCurrencyAmountMicrosFilter ||
|
||||||
|
isAddressFilterOnSubFieldOtherThanCountry ||
|
||||||
|
isActorNameFilter;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledValueDropdownContainer>
|
<StyledValueDropdownContainer>
|
||||||
@ -103,7 +117,7 @@ export const AdvancedFilterValueInput = ({
|
|||||||
<AdvancedFilterValueInputDropdownButtonClickableSelect
|
<AdvancedFilterValueInputDropdownButtonClickableSelect
|
||||||
recordFilterId={recordFilterId}
|
recordFilterId={recordFilterId}
|
||||||
/>
|
/>
|
||||||
) : showFilterTextInput ? (
|
) : showFilterTextInputInsteadOfDropdown ? (
|
||||||
<AdvancedFilterDropdownTextInput recordFilter={recordFilter} />
|
<AdvancedFilterDropdownTextInput recordFilter={recordFilter} />
|
||||||
) : (
|
) : (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
|
|||||||
@ -95,7 +95,7 @@ export type LinksFilter = {
|
|||||||
|
|
||||||
export type ActorFilter = {
|
export type ActorFilter = {
|
||||||
name?: StringFilter;
|
name?: StringFilter;
|
||||||
source?: IsFilter;
|
source?: SelectFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type EmailsFilter = {
|
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 { ObjectFilterDropdownRatingInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRatingInput';
|
||||||
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
import { ObjectFilterDropdownRecordSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownRecordSelect';
|
||||||
import { ObjectFilterDropdownSearchInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownSearchInput';
|
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 { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
|
||||||
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
import { ObjectFilterDropdownBooleanSelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownBooleanSelect';
|
||||||
import { ObjectFilterDropdownCurrencySelect } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownCurrencySelect';
|
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 { ObjectFilterDropdownTextInput } from '@/object-record/object-filter-dropdown/components/ObjectFilterDropdownTextInput';
|
||||||
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
import { DATE_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/DateFilterTypes';
|
||||||
import { NUMBER_FILTER_TYPES } from '@/object-record/object-filter-dropdown/constants/NumberFilterTypes';
|
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 { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
|
||||||
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState';
|
||||||
import { isExpectedSubFieldName } from '@/object-record/object-filter-dropdown/utils/isExpectedSubFieldName';
|
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -71,10 +70,6 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
fieldMetadataItemUsedInDropdown.type,
|
fieldMetadataItemUsedInDropdown.type,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isActorSourceCompositeFilter = isFilterOnActorSourceSubField(
|
|
||||||
subFieldNameUsedInDropdown,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isNotASubFieldFilter = !isDefined(subFieldNameUsedInDropdown);
|
const isNotASubFieldFilter = !isDefined(subFieldNameUsedInDropdown);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -100,16 +95,11 @@ export const ObjectFilterDropdownFilterInput = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{filterType === 'ACTOR' &&
|
{filterType === 'ACTOR' && (
|
||||||
(isActorSourceCompositeFilter || isNotASubFieldFilter ? (
|
<>
|
||||||
<>
|
<ObjectFilterDropdownSourceSelect />
|
||||||
<ObjectFilterDropdownSourceSelect />
|
</>
|
||||||
</>
|
)}
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ObjectFilterDropdownTextInput />
|
|
||||||
</>
|
|
||||||
))}
|
|
||||||
{filterType === 'ADDRESS' &&
|
{filterType === 'ADDRESS' &&
|
||||||
(isNotASubFieldFilter ? (
|
(isNotASubFieldFilter ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -5,4 +5,6 @@ export const ICON_NAME_BY_SUB_FIELD: Partial<
|
|||||||
> = {
|
> = {
|
||||||
currencyCode: 'IconCurrencyDollar',
|
currencyCode: 'IconCurrencyDollar',
|
||||||
amountMicros: 'IconNumber95Small',
|
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': {
|
case 'ACTOR': {
|
||||||
switch (filter.operand) {
|
if (isSubFieldFilter) {
|
||||||
case RecordFilterOperand.Is: {
|
switch (subFieldName) {
|
||||||
if (filter.value === '[]') {
|
case 'source': {
|
||||||
return;
|
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 {
|
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: {
|
|
||||||
[correspondingField.name]: {
|
[correspondingField.name]: {
|
||||||
name: {
|
source: {
|
||||||
ilike: `%${filter.value}%`,
|
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,
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
};
|
||||||
};
|
case RecordFilterOperand.DoesNotContain:
|
||||||
default:
|
return {
|
||||||
throw new Error(
|
and: [
|
||||||
`Unknown operand ${filter.operand} for ${filter.label} filter`,
|
{
|
||||||
);
|
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':
|
case 'EMAILS':
|
||||||
|
|||||||
@ -177,8 +177,8 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
label: 'Actor',
|
label: 'Actor',
|
||||||
Icon: IllustrationIconSetting,
|
Icon: IllustrationIconSetting,
|
||||||
category: 'Basic',
|
category: 'Basic',
|
||||||
subFields: ['source'],
|
subFields: ['source', 'name'],
|
||||||
filterableSubFields: ['source'],
|
filterableSubFields: ['source', 'name'],
|
||||||
labelBySubField: {
|
labelBySubField: {
|
||||||
source: 'Source',
|
source: 'Source',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
|||||||
@ -65,6 +65,7 @@ export const EditableFilterDropdownButton = ({
|
|||||||
dropdownOffset={{ y: 8, x: 0 }}
|
dropdownOffset={{ y: 8, x: 0 }}
|
||||||
dropdownPlacement="bottom-start"
|
dropdownPlacement="bottom-start"
|
||||||
onClickOutside={handleDropdownClickOutside}
|
onClickOutside={handleDropdownClickOutside}
|
||||||
|
dropdownWidth={200}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user