Implemented LINKS and EMAILS sub-field fitering (#11984)
This PR introduces LINKS and EMAILS sub-field filtering. It's mainly about the implementation of secondaryLinks and additionalEmails sub-fields, which are treated like additionalPhones. There's also a refactor on the computeViewRecordGqlOperationFilter, a big file that becomes very difficult to read and maintain. This PR breaks it down into multiple smaller utils. There's still work to be done to clean it as it is a central part of the record filter module, this PR lays the foundation.
This commit is contained in:
@ -3,7 +3,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
import { makeAndFilterVariables } from '@/object-record/utils/makeAndFilterVariables';
|
||||||
|
|
||||||
export const computeContextStoreFilters = (
|
export const computeContextStoreFilters = (
|
||||||
|
|||||||
@ -90,9 +90,9 @@ export const AdvancedFilterSubFieldSelectMenu = ({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subFieldNames = SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[
|
const subFieldNames =
|
||||||
objectFilterDropdownSubMenuFieldType
|
SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS[objectFilterDropdownSubMenuFieldType]
|
||||||
].filterableSubFields.sort((a, b) => a.localeCompare(b));
|
.filterableSubFields;
|
||||||
|
|
||||||
const subFieldsAreFilterable =
|
const subFieldsAreFilterable =
|
||||||
isDefined(fieldMetadataItemUsedInDropdown) &&
|
isDefined(fieldMetadataItemUsedInDropdown) &&
|
||||||
|
|||||||
@ -91,6 +91,7 @@ export type AddressFilter = {
|
|||||||
export type LinksFilter = {
|
export type LinksFilter = {
|
||||||
primaryLinkUrl?: StringFilter;
|
primaryLinkUrl?: StringFilter;
|
||||||
primaryLinkLabel?: StringFilter;
|
primaryLinkLabel?: StringFilter;
|
||||||
|
secondaryLinks?: RawJsonFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActorFilter = {
|
export type ActorFilter = {
|
||||||
@ -100,6 +101,7 @@ export type ActorFilter = {
|
|||||||
|
|
||||||
export type EmailsFilter = {
|
export type EmailsFilter = {
|
||||||
primaryEmail?: StringFilter;
|
primaryEmail?: StringFilter;
|
||||||
|
additionalEmails?: RawJsonFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PhonesFilter = {
|
export type PhonesFilter = {
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export const ObjectFilterDropdownTextInput = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer width="auto">
|
||||||
<DropdownMenuInput
|
<DropdownMenuInput
|
||||||
ref={handleInputRef}
|
ref={handleInputRef}
|
||||||
value={objectFilterDropdownFilterValue}
|
value={objectFilterDropdownFilterValue}
|
||||||
|
|||||||
@ -32,6 +32,11 @@ describe('getOperandsForFilterType', () => {
|
|||||||
RecordFilterOperand.IsNot,
|
RecordFilterOperand.IsNot,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const actorSourceOperands = [
|
||||||
|
RecordFilterOperand.Is,
|
||||||
|
RecordFilterOperand.IsNot,
|
||||||
|
];
|
||||||
|
|
||||||
const dateOperands = [
|
const dateOperands = [
|
||||||
RecordFilterOperand.Is,
|
RecordFilterOperand.Is,
|
||||||
RecordFilterOperand.IsRelative,
|
RecordFilterOperand.IsRelative,
|
||||||
@ -49,7 +54,12 @@ describe('getOperandsForFilterType', () => {
|
|||||||
['FULL_NAME', [...containsOperands, ...emptyOperands]],
|
['FULL_NAME', [...containsOperands, ...emptyOperands]],
|
||||||
['ADDRESS', [...containsOperands, ...emptyOperands]],
|
['ADDRESS', [...containsOperands, ...emptyOperands]],
|
||||||
['LINKS', [...containsOperands, ...emptyOperands]],
|
['LINKS', [...containsOperands, ...emptyOperands]],
|
||||||
['ACTOR', [...containsOperands, ...emptyOperands]],
|
['LINKS', [...containsOperands, ...emptyOperands], 'primaryLinkUrl'],
|
||||||
|
['LINKS', [...containsOperands, ...emptyOperands], 'primaryLinkLabel'],
|
||||||
|
['LINKS', [...containsOperands, ...emptyOperands], 'secondaryLinks'],
|
||||||
|
['ACTOR', [...containsOperands, ...emptyOperands], 'name'],
|
||||||
|
['ACTOR', [...actorSourceOperands, ...emptyOperands], 'source'],
|
||||||
|
['ACTOR', [...actorSourceOperands, ...emptyOperands]],
|
||||||
[
|
[
|
||||||
'CURRENCY',
|
'CURRENCY',
|
||||||
[...currencyCurrencyCodeOperands, ...emptyOperands],
|
[...currencyCurrencyCodeOperands, ...emptyOperands],
|
||||||
|
|||||||
@ -7,4 +7,11 @@ export const ICON_NAME_BY_SUB_FIELD: Partial<
|
|||||||
amountMicros: 'IconNumber95Small',
|
amountMicros: 'IconNumber95Small',
|
||||||
name: 'IconAlignJustified',
|
name: 'IconAlignJustified',
|
||||||
source: 'IconFileArrowLeft',
|
source: 'IconFileArrowLeft',
|
||||||
|
primaryEmail: 'IconMail',
|
||||||
|
additionalEmails: 'IconList',
|
||||||
|
primaryLinkLabel: 'IconLabel',
|
||||||
|
primaryLinkUrl: 'IconLink',
|
||||||
|
secondaryLinks: 'IconList',
|
||||||
|
primaryPhoneCallingCode: 'IconPlus',
|
||||||
|
additionalPhones: 'IconList',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMeta
|
|||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
import { getCompaniesMock } from '~/testing/mock-data/companies';
|
||||||
@ -682,7 +682,7 @@ describe('should work as expected for the different field types', () => {
|
|||||||
not: {
|
not: {
|
||||||
phones: {
|
phones: {
|
||||||
additionalPhones: {
|
additionalPhones: {
|
||||||
like: `%1234567890%`,
|
like: '%1234567890%',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -865,6 +865,13 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: '%test@test.com%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -878,42 +885,106 @@ describe('should work as expected for the different field types', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: '%test@test.com%',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
or: [
|
and: [
|
||||||
{
|
{
|
||||||
emails: {
|
or: [
|
||||||
primaryEmail: {
|
{
|
||||||
ilike: '',
|
emails: {
|
||||||
|
primaryEmail: {
|
||||||
|
eq: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
emails: {
|
||||||
|
primaryEmail: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
emails: {
|
or: [
|
||||||
primaryEmail: {
|
{
|
||||||
is: 'NULL',
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: '[]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
not: {
|
not: {
|
||||||
or: [
|
and: [
|
||||||
{
|
{
|
||||||
emails: {
|
or: [
|
||||||
primaryEmail: {
|
{
|
||||||
ilike: '',
|
emails: {
|
||||||
|
primaryEmail: {
|
||||||
|
eq: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
emails: {
|
||||||
|
primaryEmail: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
emails: {
|
or: [
|
||||||
primaryEmail: {
|
{
|
||||||
is: 'NULL',
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
|
emails: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: '[]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -6,6 +6,8 @@ const COMPOSITE_TYPES_FILTERABLE = [
|
|||||||
'CURRENCY',
|
'CURRENCY',
|
||||||
'ADDRESS',
|
'ADDRESS',
|
||||||
'PHONES',
|
'PHONES',
|
||||||
|
'LINKS',
|
||||||
|
'EMAILS',
|
||||||
] satisfies FieldType[];
|
] satisfies FieldType[];
|
||||||
|
|
||||||
type FilterableCompositeFieldType = (typeof COMPOSITE_TYPES_FILTERABLE)[number];
|
type FilterableCompositeFieldType = (typeof COMPOSITE_TYPES_FILTERABLE)[number];
|
||||||
|
|||||||
@ -0,0 +1,92 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import {
|
||||||
|
EmailsFilter,
|
||||||
|
RecordGqlOperationFilter,
|
||||||
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const computeEmptyGqlOperationFilterForEmails = ({
|
||||||
|
recordFilter,
|
||||||
|
correspondingFieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
correspondingFieldMetadataItem: Pick<FieldMetadataItem, 'name' | 'type'>;
|
||||||
|
}): RecordGqlOperationFilter => {
|
||||||
|
const subFieldName = recordFilter.subFieldName;
|
||||||
|
const isSubFieldFilter = isNonEmptyString(subFieldName);
|
||||||
|
|
||||||
|
if (isSubFieldFilter) {
|
||||||
|
switch (subFieldName) {
|
||||||
|
case 'primaryEmail': {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: { eq: '' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: { is: 'NULL' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'additionalEmails': {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: { is: 'NULL' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: { like: '[]' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unknown subfield name ${subFieldName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: { eq: '' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: { is: 'NULL' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: { is: 'NULL' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: { like: '[]' },
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,122 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import {
|
||||||
|
LinksFilter,
|
||||||
|
RecordGqlOperationFilter,
|
||||||
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const computeEmptyGqlOperationFilterForLinks = ({
|
||||||
|
recordFilter,
|
||||||
|
correspondingFieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
correspondingFieldMetadataItem: Pick<FieldMetadataItem, 'name' | 'type'>;
|
||||||
|
}): RecordGqlOperationFilter => {
|
||||||
|
const subFieldName = recordFilter.subFieldName;
|
||||||
|
const isSubFieldFilter = isNonEmptyString(subFieldName);
|
||||||
|
|
||||||
|
if (isSubFieldFilter) {
|
||||||
|
switch (subFieldName) {
|
||||||
|
case 'primaryLinkLabel': {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: { eq: '' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'primaryLinkUrl': {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: { eq: '' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'secondaryLinks': {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: { like: '[]' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unknown subfield name ${subFieldName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: { eq: '' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: { eq: '' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: { is: 'NULL' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: { like: '[]' },
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { getFilterTypeFromFieldType } from '@/object-metadata/utils/formatFieldMetadataItemsAsFilterDefinitions';
|
||||||
|
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { isEmptinessOperand } from '@/object-record/record-filter/utils/isEmptinessOperand';
|
||||||
|
|
||||||
|
export const checkIfShouldComputeEmptinessFilter = ({
|
||||||
|
recordFilter,
|
||||||
|
correspondingFieldMetadataItem,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
correspondingFieldMetadataItem: Pick<FieldMetadataItem, 'type'>;
|
||||||
|
}) => {
|
||||||
|
const isAnEmptinessOperand = isEmptinessOperand(recordFilter.operand);
|
||||||
|
|
||||||
|
const filterTypesThatHaveNoEmptinessOperand: FilterableFieldType[] = [
|
||||||
|
'BOOLEAN',
|
||||||
|
];
|
||||||
|
|
||||||
|
const filterType = getFilterTypeFromFieldType(
|
||||||
|
correspondingFieldMetadataItem.type,
|
||||||
|
);
|
||||||
|
|
||||||
|
const filterHasEmptinessOperands =
|
||||||
|
!filterTypesThatHaveNoEmptinessOperand.includes(filterType);
|
||||||
|
|
||||||
|
const shouldComputeEmptinessFilter =
|
||||||
|
filterHasEmptinessOperands && isAnEmptinessOperand;
|
||||||
|
|
||||||
|
return shouldComputeEmptinessFilter;
|
||||||
|
};
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
|
import { isEmptinessOperand } from '@/object-record/record-filter/utils/isEmptinessOperand';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const checkIfShouldSkipFiltering = ({
|
||||||
|
recordFilter,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
}) => {
|
||||||
|
const isAnEmptinessOperand = isEmptinessOperand(recordFilter.operand);
|
||||||
|
|
||||||
|
const isDateOperandWithoutValue = [
|
||||||
|
RecordFilterOperand.IsInPast,
|
||||||
|
RecordFilterOperand.IsInFuture,
|
||||||
|
RecordFilterOperand.IsToday,
|
||||||
|
].includes(recordFilter.operand);
|
||||||
|
|
||||||
|
const isFilterValueEmpty =
|
||||||
|
!isDefined(recordFilter.value) || recordFilter.value === '';
|
||||||
|
|
||||||
|
const shouldSkipFiltering =
|
||||||
|
!isAnEmptinessOperand && !isDateOperandWithoutValue && isFilterValueEmpty;
|
||||||
|
|
||||||
|
return shouldSkipFiltering;
|
||||||
|
};
|
||||||
@ -0,0 +1,153 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import {
|
||||||
|
EmailsFilter,
|
||||||
|
RecordGqlOperationFilter,
|
||||||
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
|
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const computeGqlOperationFilterForEmails = ({
|
||||||
|
recordFilter,
|
||||||
|
correspondingFieldMetadataItem,
|
||||||
|
subFieldName,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
correspondingFieldMetadataItem: Pick<FieldMetadataItem, 'name' | 'type'>;
|
||||||
|
subFieldName: CompositeFieldSubFieldName | null | undefined;
|
||||||
|
}): RecordGqlOperationFilter => {
|
||||||
|
const isSubFieldFilter = isNonEmptyString(subFieldName);
|
||||||
|
|
||||||
|
if (isSubFieldFilter) {
|
||||||
|
switch (subFieldName) {
|
||||||
|
case 'primaryEmail': {
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'additionalEmails': {
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unknown subfield name ${subFieldName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryEmail: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
additionalEmails: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
} satisfies EmailsFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,166 @@
|
|||||||
|
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||||
|
import { LinksFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterOperand } from '@/object-record/record-filter/types/RecordFilterOperand';
|
||||||
|
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const computeGqlOperationFilterForLinks = ({
|
||||||
|
recordFilter,
|
||||||
|
correspondingFieldMetadataItem,
|
||||||
|
subFieldName,
|
||||||
|
}: {
|
||||||
|
recordFilter: RecordFilter;
|
||||||
|
correspondingFieldMetadataItem: Pick<FieldMetadataItem, 'name' | 'type'>;
|
||||||
|
subFieldName: CompositeFieldSubFieldName | null | undefined;
|
||||||
|
}) => {
|
||||||
|
const isSubFieldFilter = isNonEmptyString(subFieldName);
|
||||||
|
|
||||||
|
if (isSubFieldFilter) {
|
||||||
|
switch (subFieldName) {
|
||||||
|
case 'primaryLinkLabel':
|
||||||
|
case 'primaryLinkUrl': {
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
[subFieldName]: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
[subFieldName]: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'secondaryLinks': {
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error(`Unknown subfield name ${subFieldName}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (recordFilter.operand) {
|
||||||
|
case RecordFilterOperand.Contains:
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
case RecordFilterOperand.DoesNotContain:
|
||||||
|
return {
|
||||||
|
and: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkLabel: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
primaryLinkUrl: {
|
||||||
|
ilike: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
or: [
|
||||||
|
{
|
||||||
|
not: {
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
like: `%${recordFilter.value}%`,
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
[correspondingFieldMetadataItem.name]: {
|
||||||
|
secondaryLinks: {
|
||||||
|
is: 'NULL',
|
||||||
|
},
|
||||||
|
} satisfies LinksFilter,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Unknown operand ${recordFilter.operand} for ${correspondingFieldMetadataItem.type} filter`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { Field } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
|
|
||||||
|
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||||
|
import { RecordFilterGroupLogicalOperator } from '@/object-record/record-filter-group/types/RecordFilterGroupLogicalOperator';
|
||||||
|
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const turnRecordFilterGroupsIntoGqlOperationFilter = (
|
||||||
|
filterValueDependencies: RecordFilterValueDependencies,
|
||||||
|
filters: RecordFilter[],
|
||||||
|
fields: Pick<Field, 'id' | 'name' | 'type'>[],
|
||||||
|
recordFilterGroups: RecordFilterGroup[],
|
||||||
|
currentRecordFilterGroupId?: string,
|
||||||
|
): RecordGqlOperationFilter | undefined => {
|
||||||
|
const currentRecordFilterGroup = recordFilterGroups.find(
|
||||||
|
(recordFilterGroup) => recordFilterGroup.id === currentRecordFilterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(currentRecordFilterGroup)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordFiltersInGroup = filters.filter(
|
||||||
|
(filter) => filter.recordFilterGroupId === currentRecordFilterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const groupRecordGqlOperationFilters = recordFiltersInGroup
|
||||||
|
.map((recordFilter) =>
|
||||||
|
turnRecordFilterIntoRecordGqlOperationFilter({
|
||||||
|
filterValueDependencies,
|
||||||
|
recordFilter: recordFilter,
|
||||||
|
fieldMetadataItems: fields,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
const subGroupRecordGqlOperationFilters = recordFilterGroups
|
||||||
|
.filter(
|
||||||
|
(recordFilterGroup) =>
|
||||||
|
recordFilterGroup.parentRecordFilterGroupId ===
|
||||||
|
currentRecordFilterGroupId,
|
||||||
|
)
|
||||||
|
.map((subRecordFilterGroup) =>
|
||||||
|
turnRecordFilterGroupsIntoGqlOperationFilter(
|
||||||
|
filterValueDependencies,
|
||||||
|
filters,
|
||||||
|
fields,
|
||||||
|
recordFilterGroups,
|
||||||
|
subRecordFilterGroup.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
if (
|
||||||
|
currentRecordFilterGroup.logicalOperator ===
|
||||||
|
RecordFilterGroupLogicalOperator.AND
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
and: [
|
||||||
|
...groupRecordGqlOperationFilters,
|
||||||
|
...subGroupRecordGqlOperationFilters,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else if (
|
||||||
|
currentRecordFilterGroup.logicalOperator ===
|
||||||
|
RecordFilterGroupLogicalOperator.OR
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
or: [
|
||||||
|
...groupRecordGqlOperationFilters,
|
||||||
|
...subGroupRecordGqlOperationFilters,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new Error(
|
||||||
|
`Unknown logical operator ${currentRecordFilterGroup.logicalOperator}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,66 @@
|
|||||||
|
import { RecordGqlOperationFilter } from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
|
import { Field } from '~/generated/graphql';
|
||||||
|
|
||||||
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
|
|
||||||
|
import { RecordFilterGroup } from '@/object-record/record-filter-group/types/RecordFilterGroup';
|
||||||
|
import { turnRecordFilterGroupsIntoGqlOperationFilter } from '@/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterGroupIntoGqlOperationFilter';
|
||||||
|
import { turnRecordFilterIntoRecordGqlOperationFilter } from '@/object-record/record-filter/utils/compute-record-gql-operation-filter/turnRecordFilterIntoGqlOperationFilter';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const computeRecordGqlOperationFilter = ({
|
||||||
|
fields,
|
||||||
|
filterValueDependencies,
|
||||||
|
recordFilters,
|
||||||
|
recordFilterGroups,
|
||||||
|
}: {
|
||||||
|
filterValueDependencies: RecordFilterValueDependencies;
|
||||||
|
recordFilters: RecordFilter[];
|
||||||
|
fields: Pick<Field, 'id' | 'name' | 'type'>[];
|
||||||
|
recordFilterGroups: RecordFilterGroup[];
|
||||||
|
}): RecordGqlOperationFilter => {
|
||||||
|
const regularRecordGqlOperationFilter: RecordGqlOperationFilter[] =
|
||||||
|
recordFilters
|
||||||
|
.filter((filter) => !isDefined(filter.recordFilterGroupId))
|
||||||
|
.map((regularFilter) =>
|
||||||
|
turnRecordFilterIntoRecordGqlOperationFilter({
|
||||||
|
filterValueDependencies,
|
||||||
|
recordFilter: regularFilter,
|
||||||
|
fieldMetadataItems: fields,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.filter(isDefined);
|
||||||
|
|
||||||
|
const outermostFilterGroupId = recordFilterGroups.find(
|
||||||
|
(recordFilterGroup) => !recordFilterGroup.parentRecordFilterGroupId,
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
const advancedRecordGqlOperationFilter =
|
||||||
|
turnRecordFilterGroupsIntoGqlOperationFilter(
|
||||||
|
filterValueDependencies,
|
||||||
|
recordFilters,
|
||||||
|
fields,
|
||||||
|
recordFilterGroups,
|
||||||
|
outermostFilterGroupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordGqlOperationFilters = [
|
||||||
|
...regularRecordGqlOperationFilter,
|
||||||
|
advancedRecordGqlOperationFilter,
|
||||||
|
].filter(isDefined);
|
||||||
|
|
||||||
|
if (recordGqlOperationFilters.length === 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recordGqlOperationFilters.length === 1) {
|
||||||
|
return recordGqlOperationFilters[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const recordGqlOperationFilter = {
|
||||||
|
and: recordGqlOperationFilters,
|
||||||
|
};
|
||||||
|
|
||||||
|
return recordGqlOperationFilter;
|
||||||
|
};
|
||||||
@ -5,7 +5,6 @@ import {
|
|||||||
ArrayFilter,
|
ArrayFilter,
|
||||||
CurrencyFilter,
|
CurrencyFilter,
|
||||||
DateFilter,
|
DateFilter,
|
||||||
EmailsFilter,
|
|
||||||
FloatFilter,
|
FloatFilter,
|
||||||
MultiSelectFilter,
|
MultiSelectFilter,
|
||||||
PhonesFilter,
|
PhonesFilter,
|
||||||
@ -15,9 +14,10 @@ import {
|
|||||||
RelationFilter,
|
RelationFilter,
|
||||||
SelectFilter,
|
SelectFilter,
|
||||||
StringFilter,
|
StringFilter,
|
||||||
URLFilter,
|
|
||||||
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
} from '@/object-record/graphql/types/RecordGqlOperationFilter';
|
||||||
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
import { RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
|
||||||
|
import { computeEmptyGqlOperationFilterForEmails } from '@/object-record/record-filter/utils/compute-empty-record-gql-operation-filter/for-composite-field/computeEmptyGqlOperationFilterForEmails';
|
||||||
|
import { computeEmptyGqlOperationFilterForLinks } from '@/object-record/record-filter/utils/compute-empty-record-gql-operation-filter/for-composite-field/computeEmptyGqlOperationFilterForLinks';
|
||||||
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { Field } from '~/generated/graphql';
|
import { Field } from '~/generated/graphql';
|
||||||
@ -187,33 +187,10 @@ export const getEmptyRecordGqlOperationFilter = ({
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'LINKS': {
|
case 'LINKS': {
|
||||||
if (!isSubFieldFilter) {
|
emptyRecordFilter = computeEmptyGqlOperationFilterForLinks({
|
||||||
const linksFilters = generateILikeFiltersForCompositeFields(
|
correspondingFieldMetadataItem: correspondingField,
|
||||||
'',
|
recordFilter,
|
||||||
correspondingField.name,
|
});
|
||||||
['primaryLinkLabel', 'primaryLinkUrl'],
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
emptyRecordFilter = {
|
|
||||||
and: linksFilters,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
emptyRecordFilter = {
|
|
||||||
or: [
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
[compositeFieldName]: { ilike: '' },
|
|
||||||
} as URLFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
[compositeFieldName]: { is: 'NULL' },
|
|
||||||
} as URLFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ADDRESS':
|
case 'ADDRESS':
|
||||||
@ -401,20 +378,10 @@ export const getEmptyRecordGqlOperationFilter = ({
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case 'EMAILS':
|
case 'EMAILS':
|
||||||
emptyRecordFilter = {
|
emptyRecordFilter = computeEmptyGqlOperationFilterForEmails({
|
||||||
or: [
|
correspondingFieldMetadataItem: correspondingField,
|
||||||
{
|
recordFilter,
|
||||||
[correspondingField.name]: {
|
});
|
||||||
primaryEmail: { ilike: '' },
|
|
||||||
} as EmailsFilter,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[correspondingField.name]: {
|
|
||||||
primaryEmail: { is: 'NULL' },
|
|
||||||
} as EmailsFilter,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported empty filter type ${filterType}`);
|
throw new Error(`Unsupported empty filter type ${filterType}`);
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { isFilterOnActorSourceSubField } from '@/object-record/object-filter-dro
|
|||||||
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
import { FilterableFieldType } from '@/object-record/record-filter/types/FilterableFieldType';
|
||||||
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
import { CompositeFieldSubFieldName } from '@/settings/data-model/types/CompositeFieldSubFieldName';
|
||||||
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
|
import { ViewFilterOperand as RecordFilterOperand } from '@/views/types/ViewFilterOperand';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { assertUnreachable } from 'twenty-shared/utils';
|
import { assertUnreachable } from 'twenty-shared/utils';
|
||||||
|
|
||||||
@ -147,6 +148,8 @@ export const getRecordFilterOperands = ({
|
|||||||
filterType,
|
filterType,
|
||||||
subFieldName,
|
subFieldName,
|
||||||
}: GetRecordFilterOperandsParams) => {
|
}: GetRecordFilterOperandsParams) => {
|
||||||
|
const isFilterOnSubField = isNonEmptyString(subFieldName);
|
||||||
|
|
||||||
switch (filterType) {
|
switch (filterType) {
|
||||||
case 'TEXT':
|
case 'TEXT':
|
||||||
case 'EMAILS':
|
case 'EMAILS':
|
||||||
@ -184,7 +187,7 @@ export const getRecordFilterOperands = ({
|
|||||||
case 'SELECT':
|
case 'SELECT':
|
||||||
return FILTER_OPERANDS_MAP.SELECT;
|
return FILTER_OPERANDS_MAP.SELECT;
|
||||||
case 'ACTOR': {
|
case 'ACTOR': {
|
||||||
if (isFilterOnActorSourceSubField(subFieldName)) {
|
if (isFilterOnActorSourceSubField(subFieldName) || !isFilterOnSubField) {
|
||||||
return [
|
return [
|
||||||
RecordFilterOperand.Is,
|
RecordFilterOperand.Is,
|
||||||
RecordFilterOperand.IsNot,
|
RecordFilterOperand.IsNot,
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils
|
|||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
import { useCurrentRecordGroupDefinition } from '@/object-record/record-group/hooks/useCurrentRecordGroupDefinition';
|
||||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState';
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import { useSetRecordIdsForColumn } from '@/object-record/record-board/hooks/use
|
|||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
import { recordGroupDefinitionFamilyState } from '@/object-record/record-group/states/recordGroupDefinitionFamilyState';
|
||||||
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
import { useRecordBoardRecordGqlFields } from '@/object-record/record-index/hooks/useRecordBoardRecordGqlFields';
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { computeAggregateValueAndLabel } from '@/object-record/record-board/reco
|
|||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
import { recordIndexKanbanAggregateOperationState } from '@/object-record/record-index/states/recordIndexKanbanAggregateOperationState';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { UserContext } from '@/users/contexts/UserContext';
|
import { UserContext } from '@/users/contexts/UserContext';
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { computeAggregateValueAndLabel } from '@/object-record/record-board/reco
|
|||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
import { useRecordGroupFilter } from '@/object-record/record-group/hooks/useRecordGroupFilter';
|
||||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext';
|
||||||
|
|||||||
@ -56,7 +56,7 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
label: 'Emails',
|
label: 'Emails',
|
||||||
Icon: IllustrationIconMail,
|
Icon: IllustrationIconMail,
|
||||||
subFields: ['primaryEmail', 'additionalEmails'],
|
subFields: ['primaryEmail', 'additionalEmails'],
|
||||||
filterableSubFields: ['primaryEmail'],
|
filterableSubFields: ['primaryEmail', 'additionalEmails'],
|
||||||
labelBySubField: {
|
labelBySubField: {
|
||||||
primaryEmail: 'Primary Email',
|
primaryEmail: 'Primary Email',
|
||||||
additionalEmails: 'Additional Emails',
|
additionalEmails: 'Additional Emails',
|
||||||
@ -81,7 +81,11 @@ export const SETTINGS_COMPOSITE_FIELD_TYPE_CONFIGS = {
|
|||||||
},
|
},
|
||||||
category: 'Basic',
|
category: 'Basic',
|
||||||
subFields: ['primaryLinkUrl', 'primaryLinkLabel', 'secondaryLinks'],
|
subFields: ['primaryLinkUrl', 'primaryLinkLabel', 'secondaryLinks'],
|
||||||
filterableSubFields: ['primaryLinkUrl', 'primaryLinkLabel'],
|
filterableSubFields: [
|
||||||
|
'primaryLinkUrl',
|
||||||
|
'primaryLinkLabel',
|
||||||
|
'secondaryLinks',
|
||||||
|
],
|
||||||
labelBySubField: {
|
labelBySubField: {
|
||||||
primaryLinkUrl: 'Link URL',
|
primaryLinkUrl: 'Link URL',
|
||||||
primaryLinkLabel: 'Link Label',
|
primaryLinkLabel: 'Link Label',
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
|||||||
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState';
|
||||||
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies';
|
||||||
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
import { AGGREGATE_OPERATIONS } from '@/object-record/record-table/constants/AggregateOperations';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
|
import { useGetViewGroupsFilters } from '@/views/hooks/useGetViewGroupsFilters';
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
|||||||
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy';
|
||||||
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
import { RecordFilterValueDependencies } from '@/object-record/record-filter/types/RecordFilterValueDependencies';
|
||||||
|
|
||||||
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeViewRecordGqlOperationFilter';
|
import { computeRecordGqlOperationFilter } from '@/object-record/record-filter/utils/computeRecordGqlOperationFilter';
|
||||||
import { View } from '@/views/types/View';
|
import { View } from '@/views/types/View';
|
||||||
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
|
import { mapViewFilterGroupsToRecordFilterGroups } from '@/views/utils/mapViewFilterGroupsToRecordFilterGroups';
|
||||||
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
import { mapViewFiltersToFilters } from '@/views/utils/mapViewFiltersToFilters';
|
||||||
|
|||||||
Reference in New Issue
Block a user