fix: fix Relation field optimistic effect on Record update (#3352)
* fix: fix Relation field optimistic effect on Record update Related to #3099 * Fix lint * Fix * fix --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -5,7 +5,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
files: ['.storybook/**/*', '**/*.stories.tsx', '**/*.test.ts'],
|
files: [
|
||||||
|
'.storybook/**/*',
|
||||||
|
'**/*.stories.tsx',
|
||||||
|
'**/*.test.ts',
|
||||||
|
'**/*.test.tsx',
|
||||||
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': 'off',
|
'no-console': 'off',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,35 +1,29 @@
|
|||||||
const globalCoverage = {
|
const globalCoverage = {
|
||||||
"statements": 60,
|
statements: 60,
|
||||||
"lines": 60,
|
lines: 60,
|
||||||
"functions": 60,
|
functions: 60,
|
||||||
"exclude": [
|
exclude: ['src/generated/**/*'],
|
||||||
"src/generated/**/*",
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const modulesCoverage = {
|
const modulesCoverage = {
|
||||||
"statements": 50,
|
statements: 50,
|
||||||
"lines": 50,
|
lines: 50,
|
||||||
"functions": 45,
|
functions: 45,
|
||||||
"include": [
|
include: ['src/modules/**/*'],
|
||||||
"src/modules/**/*",
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const pagesCoverage = {
|
const pagesCoverage = {
|
||||||
"statements": 50,
|
statements: 50,
|
||||||
"lines": 50,
|
lines: 50,
|
||||||
"functions": 45,
|
functions: 45,
|
||||||
"exclude": [
|
exclude: ['src/generated/**/*', 'src/modules/**/*'],
|
||||||
"src/generated/**/*",
|
|
||||||
"src/modules/**/*",
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const storybookStoriesFolders = process.env.STORYBOOK_SCOPE;
|
const storybookStoriesFolders = process.env.STORYBOOK_SCOPE;
|
||||||
|
|
||||||
module.exports = storybookStoriesFolders === 'pages'
|
module.exports =
|
||||||
? pagesCoverage
|
storybookStoriesFolders === 'pages'
|
||||||
: storybookStoriesFolders === 'modules'
|
? pagesCoverage
|
||||||
? modulesCoverage
|
: storybookStoriesFolders === 'modules'
|
||||||
: globalCoverage;
|
? modulesCoverage
|
||||||
|
: globalCoverage;
|
||||||
|
|||||||
@ -146,8 +146,8 @@ export const useOptimisticEffect = ({
|
|||||||
({ snapshot }) =>
|
({ snapshot }) =>
|
||||||
({
|
({
|
||||||
typename,
|
typename,
|
||||||
createdRecords,
|
createdRecords = [],
|
||||||
updatedRecords,
|
updatedRecords = [],
|
||||||
deletedRecordIds,
|
deletedRecordIds,
|
||||||
}: {
|
}: {
|
||||||
typename: string;
|
typename: string;
|
||||||
@ -162,17 +162,17 @@ export const useOptimisticEffect = ({
|
|||||||
for (const optimisticEffect of Object.values(optimisticEffects)) {
|
for (const optimisticEffect of Object.values(optimisticEffects)) {
|
||||||
// We need to update the typename when createObject type differs from listObject types
|
// We need to update the typename when createObject type differs from listObject types
|
||||||
// It is the case for apiKey, where the creation route returns an ApiKeyToken type
|
// It is the case for apiKey, where the creation route returns an ApiKeyToken type
|
||||||
const formattedCreatedRecords = isNonEmptyArray(createdRecords)
|
const formattedCreatedRecords = createdRecords.map((createdRecord) =>
|
||||||
? createdRecords.map((data: any) => {
|
typename.endsWith('Edge')
|
||||||
return { ...data, __typename: typename };
|
? createdRecord
|
||||||
})
|
: { ...createdRecord, __typename: typename },
|
||||||
: [];
|
);
|
||||||
|
|
||||||
const formattedUpdatedRecords = isNonEmptyArray(updatedRecords)
|
const formattedUpdatedRecords = updatedRecords.map((updatedRecord) =>
|
||||||
? updatedRecords.map((data: any) => {
|
typename.endsWith('Edge')
|
||||||
return { ...data, __typename: typename };
|
? updatedRecord
|
||||||
})
|
: { ...updatedRecord, __typename: typename },
|
||||||
: [];
|
);
|
||||||
|
|
||||||
if (optimisticEffect.typename === typename) {
|
if (optimisticEffect.typename === typename) {
|
||||||
optimisticEffect.writer({
|
optimisticEffect.writer({
|
||||||
|
|||||||
@ -29,8 +29,13 @@ export const getRecordOptimisticEffectDefinition = ({
|
|||||||
if (isNonEmptyArray(createdRecords)) {
|
if (isNonEmptyArray(createdRecords)) {
|
||||||
if (existingDataIsEmpty) {
|
if (existingDataIsEmpty) {
|
||||||
return {
|
return {
|
||||||
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
|
__typename: `${capitalize(
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
)}Connection`,
|
||||||
edges: createdRecords.map((createdRecord) => ({
|
edges: createdRecords.map((createdRecord) => ({
|
||||||
|
__typename: `${capitalize(
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
)}Edge`,
|
||||||
node: createdRecord,
|
node: createdRecord,
|
||||||
cursor: '',
|
cursor: '',
|
||||||
})),
|
})),
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { useApolloClient } from '@apollo/client';
|
import { Reference, useApolloClient } from '@apollo/client';
|
||||||
|
|
||||||
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
|
||||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||||
|
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
||||||
import { capitalize } from '~/utils/string/capitalize';
|
import { capitalize } from '~/utils/string/capitalize';
|
||||||
|
|
||||||
type useUpdateOneRecordProps = {
|
type useUpdateOneRecordProps = {
|
||||||
@ -59,6 +61,54 @@ export const useUpdateOneRecord = <T>({
|
|||||||
[`update${capitalize(objectMetadataItem.nameSingular)}`]:
|
[`update${capitalize(objectMetadataItem.nameSingular)}`]:
|
||||||
optimisticallyUpdatedRecord,
|
optimisticallyUpdatedRecord,
|
||||||
},
|
},
|
||||||
|
update: (cache, { data }) => {
|
||||||
|
const response =
|
||||||
|
data?.[`update${capitalize(objectMetadataItem.nameSingular)}`];
|
||||||
|
|
||||||
|
if (!response) return;
|
||||||
|
|
||||||
|
cache.modify<Record<string, Reference>>({
|
||||||
|
fields: {
|
||||||
|
[objectMetadataItem.namePlural]: (
|
||||||
|
existingConnectionRef,
|
||||||
|
{ readField, storeFieldName },
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
readField('__typename', existingConnectionRef) !==
|
||||||
|
`${capitalize(objectMetadataItem.nameSingular)}Connection`
|
||||||
|
)
|
||||||
|
return existingConnectionRef;
|
||||||
|
|
||||||
|
const { variables } = parseApolloStoreFieldName(storeFieldName);
|
||||||
|
|
||||||
|
const edges = readField<{ node: Reference }[]>(
|
||||||
|
'edges',
|
||||||
|
existingConnectionRef,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
variables?.filter &&
|
||||||
|
!isRecordMatchingFilter({
|
||||||
|
record: response,
|
||||||
|
filter: variables.filter,
|
||||||
|
objectMetadataItem,
|
||||||
|
}) &&
|
||||||
|
edges?.length
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
...existingConnectionRef,
|
||||||
|
edges: edges.filter(
|
||||||
|
(edge) =>
|
||||||
|
readField('id', readField('node', edge)) !== response.id,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingConnectionRef;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!updatedRecord?.data) {
|
if (!updatedRecord?.data) {
|
||||||
|
|||||||
@ -261,7 +261,6 @@ describe('useFilterDropdown', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle scopeId undefined on initial values', () => {
|
it('should handle scopeId undefined on initial values', () => {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error = jest.fn();
|
console.error = jest.fn();
|
||||||
|
|
||||||
const renderFunction = () => {
|
const renderFunction = () => {
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
DateFilter,
|
DateFilter,
|
||||||
FloatFilter,
|
FloatFilter,
|
||||||
FullNameFilter,
|
FullNameFilter,
|
||||||
LeafObjectRecordFilter,
|
|
||||||
NotObjectRecordFilter,
|
NotObjectRecordFilter,
|
||||||
ObjectRecordQueryFilter,
|
ObjectRecordQueryFilter,
|
||||||
OrObjectRecordFilter,
|
OrObjectRecordFilter,
|
||||||
@ -24,6 +23,18 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
import { isDefined } from '~/utils/isDefined';
|
import { isDefined } from '~/utils/isDefined';
|
||||||
import { isEmptyObject } from '~/utils/isEmptyObject';
|
import { isEmptyObject } from '~/utils/isEmptyObject';
|
||||||
|
|
||||||
|
const isAndFilter = (
|
||||||
|
filter: ObjectRecordQueryFilter,
|
||||||
|
): filter is AndObjectRecordFilter => 'and' in filter && !!filter.and;
|
||||||
|
|
||||||
|
const isOrFilter = (
|
||||||
|
filter: ObjectRecordQueryFilter,
|
||||||
|
): filter is OrObjectRecordFilter => 'or' in filter && !!filter.or;
|
||||||
|
|
||||||
|
const isNotFilter = (
|
||||||
|
filter: ObjectRecordQueryFilter,
|
||||||
|
): filter is NotObjectRecordFilter => 'not' in filter && !!filter.not;
|
||||||
|
|
||||||
export const isRecordMatchingFilter = ({
|
export const isRecordMatchingFilter = ({
|
||||||
record,
|
record,
|
||||||
filter,
|
filter,
|
||||||
@ -32,238 +43,173 @@ export const isRecordMatchingFilter = ({
|
|||||||
record: any;
|
record: any;
|
||||||
filter: ObjectRecordQueryFilter;
|
filter: ObjectRecordQueryFilter;
|
||||||
objectMetadataItem: ObjectMetadataItem;
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
}) => {
|
}): boolean => {
|
||||||
if (Object.keys(filter).length === 0) {
|
if (Object.keys(filter).length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentLevelFilterMatches: boolean[] = [];
|
if (isAndFilter(filter)) {
|
||||||
|
const filterValue = filter.and;
|
||||||
|
|
||||||
// We consider all the keys at the same level as an "and"
|
if (!Array.isArray(filterValue)) {
|
||||||
for (const filterKey in filter) {
|
throw new Error(
|
||||||
if (filterKey === 'and') {
|
'Unexpected value for "and" filter : ' + JSON.stringify(filterValue),
|
||||||
const filterValue = (filter as AndObjectRecordFilter).and;
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!Array.isArray(filterValue)) {
|
return (
|
||||||
throw new Error(
|
filterValue.length === 0 ||
|
||||||
'Unexpected value for "and" filter : ' + JSON.stringify(filterValue),
|
filterValue.every((andFilter) =>
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filterValue.length === 0) {
|
|
||||||
currentLevelFilterMatches.push(true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recordIsMatchingAndFilters = filterValue.every((andFilter) =>
|
|
||||||
isRecordMatchingFilter({
|
isRecordMatchingFilter({
|
||||||
record,
|
record,
|
||||||
filter: andFilter,
|
filter: andFilter,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
currentLevelFilterMatches.push(recordIsMatchingAndFilters);
|
if (isOrFilter(filter)) {
|
||||||
} else if (filterKey === 'or') {
|
const filterValue = filter.or;
|
||||||
const filterValue = (filter as OrObjectRecordFilter).or;
|
|
||||||
|
|
||||||
if (Array.isArray(filterValue)) {
|
if (Array.isArray(filterValue)) {
|
||||||
if (filterValue.length === 0) {
|
return (
|
||||||
currentLevelFilterMatches.push(true);
|
filterValue.length === 0 ||
|
||||||
continue;
|
filterValue.some((orFilter) =>
|
||||||
}
|
|
||||||
|
|
||||||
const recordIsMatchingOrFilters = filterValue.some((orFilter) =>
|
|
||||||
isRecordMatchingFilter({
|
isRecordMatchingFilter({
|
||||||
record,
|
record,
|
||||||
filter: orFilter,
|
filter: orFilter,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
}),
|
}),
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
currentLevelFilterMatches.push(recordIsMatchingOrFilters);
|
if (isObject(filterValue)) {
|
||||||
} else if (isObject(filterValue)) {
|
// The API considers "or" with an object as an "and"
|
||||||
// The API considers "or" with an object as an "and"
|
return isRecordMatchingFilter({
|
||||||
const recordIsMatchingOrFilters = isRecordMatchingFilter({
|
|
||||||
record,
|
|
||||||
filter: filterValue,
|
|
||||||
objectMetadataItem,
|
|
||||||
});
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(recordIsMatchingOrFilters);
|
|
||||||
} else {
|
|
||||||
throw new Error('Unexpected value for "or" filter : ' + filterValue);
|
|
||||||
}
|
|
||||||
} else if (filterKey === 'not') {
|
|
||||||
const filterValue = (filter as NotObjectRecordFilter).not;
|
|
||||||
|
|
||||||
if (!isDefined(filterValue)) {
|
|
||||||
throw new Error('Unexpected value for "not" filter : ' + filterValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmptyObject(filterValue)) {
|
|
||||||
currentLevelFilterMatches.push(true);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recordIsMatchingNotFilters = !isRecordMatchingFilter({
|
|
||||||
record,
|
record,
|
||||||
filter: filterValue,
|
filter: filterValue,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
});
|
});
|
||||||
|
|
||||||
currentLevelFilterMatches.push(recordIsMatchingNotFilters);
|
|
||||||
} else {
|
|
||||||
const filterValue = (filter as LeafObjectRecordFilter)[filterKey];
|
|
||||||
|
|
||||||
if (!isDefined(filterValue)) {
|
|
||||||
throw new Error(
|
|
||||||
'Unexpected value for filter key "' +
|
|
||||||
filterKey +
|
|
||||||
'" : ' +
|
|
||||||
filterValue,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEmptyObject(filterValue)) {
|
|
||||||
currentLevelFilterMatches.push(true);
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectMetadataField = objectMetadataItem.fields.find(
|
|
||||||
(field) => field.name === filterKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isDefined(objectMetadataField)) {
|
|
||||||
throw new Error(
|
|
||||||
'Field metadata item "' +
|
|
||||||
filterKey +
|
|
||||||
'" not found for object metadata item ' +
|
|
||||||
objectMetadataItem.nameSingular,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (objectMetadataField.type) {
|
|
||||||
case FieldMetadataType.Email:
|
|
||||||
case FieldMetadataType.Phone:
|
|
||||||
case FieldMetadataType.Text: {
|
|
||||||
const stringFilter = filterValue as StringFilter;
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingStringFilter({
|
|
||||||
stringFilter,
|
|
||||||
value: record[filterKey],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Link: {
|
|
||||||
const urlFilter = filterValue as URLFilter;
|
|
||||||
|
|
||||||
if (urlFilter.url !== undefined) {
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingStringFilter({
|
|
||||||
stringFilter: urlFilter.url,
|
|
||||||
value: record[filterKey].url,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (urlFilter.label !== undefined) {
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingStringFilter({
|
|
||||||
stringFilter: urlFilter.label,
|
|
||||||
value: record[filterKey].label,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.FullName: {
|
|
||||||
const fullNameFilter = filterValue as FullNameFilter;
|
|
||||||
|
|
||||||
if (fullNameFilter.firstName !== undefined) {
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingStringFilter({
|
|
||||||
stringFilter: fullNameFilter.firstName,
|
|
||||||
value: record[filterKey].firstName,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fullNameFilter.lastName !== undefined) {
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingStringFilter({
|
|
||||||
stringFilter: fullNameFilter.lastName,
|
|
||||||
value: record[filterKey].lastName,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.DateTime: {
|
|
||||||
const dateFilter = filterValue as DateFilter;
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingDateFilter({
|
|
||||||
dateFilter,
|
|
||||||
value: record[filterKey],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Number:
|
|
||||||
case FieldMetadataType.Numeric: {
|
|
||||||
const numberFilter = filterValue as FloatFilter;
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingFloatFilter({
|
|
||||||
floatFilter: numberFilter,
|
|
||||||
value: record[filterKey],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Uuid: {
|
|
||||||
const uuidFilter = filterValue as UUIDFilter;
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingUUIDFilter({
|
|
||||||
uuidFilter,
|
|
||||||
value: record[filterKey],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Boolean: {
|
|
||||||
const booleanFilter = filterValue as BooleanFilter;
|
|
||||||
|
|
||||||
currentLevelFilterMatches.push(
|
|
||||||
isMatchingBooleanFilter({
|
|
||||||
booleanFilter,
|
|
||||||
value: record[filterKey],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Relation: {
|
|
||||||
throw new Error(
|
|
||||||
`Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case FieldMetadataType.Currency:
|
|
||||||
case FieldMetadataType.MultiSelect:
|
|
||||||
case FieldMetadataType.Select:
|
|
||||||
case FieldMetadataType.Probability:
|
|
||||||
case FieldMetadataType.Rating: {
|
|
||||||
throw new Error('Not implemented yet');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new Error('Unexpected value for "or" filter : ' + filterValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentLevelFilterMatches.length > 0
|
if (isNotFilter(filter)) {
|
||||||
? currentLevelFilterMatches.every((match) => !!match)
|
const filterValue = filter.not;
|
||||||
: false;
|
|
||||||
|
if (!isDefined(filterValue)) {
|
||||||
|
throw new Error('Unexpected value for "not" filter : ' + filterValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
isEmptyObject(filterValue) ||
|
||||||
|
!isRecordMatchingFilter({
|
||||||
|
record,
|
||||||
|
filter: filterValue,
|
||||||
|
objectMetadataItem,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(filter).every(([filterKey, filterValue]) => {
|
||||||
|
if (!isDefined(filterValue)) {
|
||||||
|
throw new Error(
|
||||||
|
'Unexpected value for filter key "' + filterKey + '" : ' + filterValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEmptyObject(filterValue)) return true;
|
||||||
|
|
||||||
|
const objectMetadataField = objectMetadataItem.fields.find(
|
||||||
|
(field) => field.name === filterKey,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(objectMetadataField)) {
|
||||||
|
throw new Error(
|
||||||
|
'Field metadata item "' +
|
||||||
|
filterKey +
|
||||||
|
'" not found for object metadata item ' +
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (objectMetadataField.type) {
|
||||||
|
case FieldMetadataType.Email:
|
||||||
|
case FieldMetadataType.Phone:
|
||||||
|
case FieldMetadataType.Text: {
|
||||||
|
return isMatchingStringFilter({
|
||||||
|
stringFilter: filterValue as StringFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Link: {
|
||||||
|
const urlFilter = filterValue as URLFilter;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(urlFilter.url === undefined ||
|
||||||
|
isMatchingStringFilter({
|
||||||
|
stringFilter: urlFilter.url,
|
||||||
|
value: record[filterKey].url,
|
||||||
|
})) &&
|
||||||
|
(urlFilter.label === undefined ||
|
||||||
|
isMatchingStringFilter({
|
||||||
|
stringFilter: urlFilter.label,
|
||||||
|
value: record[filterKey].label,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case FieldMetadataType.FullName: {
|
||||||
|
const fullNameFilter = filterValue as FullNameFilter;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(fullNameFilter.firstName === undefined ||
|
||||||
|
isMatchingStringFilter({
|
||||||
|
stringFilter: fullNameFilter.firstName,
|
||||||
|
value: record[filterKey].firstName,
|
||||||
|
})) &&
|
||||||
|
(fullNameFilter.lastName === undefined ||
|
||||||
|
isMatchingStringFilter({
|
||||||
|
stringFilter: fullNameFilter.lastName,
|
||||||
|
value: record[filterKey].lastName,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case FieldMetadataType.DateTime: {
|
||||||
|
return isMatchingDateFilter({
|
||||||
|
dateFilter: filterValue as DateFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Number:
|
||||||
|
case FieldMetadataType.Numeric: {
|
||||||
|
return isMatchingFloatFilter({
|
||||||
|
floatFilter: filterValue as FloatFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Uuid: {
|
||||||
|
return isMatchingUUIDFilter({
|
||||||
|
uuidFilter: filterValue as UUIDFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Boolean: {
|
||||||
|
return isMatchingBooleanFilter({
|
||||||
|
booleanFilter: filterValue as BooleanFilter,
|
||||||
|
value: record[filterKey],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
case FieldMetadataType.Relation: {
|
||||||
|
throw new Error(
|
||||||
|
`Not implemented yet, use UUID filter instead on the corredponding "${filterKey}Id" field`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new Error('Not implemented yet');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
21
packages/twenty-front/src/utils/parseApolloStoreFieldName.ts
Normal file
21
packages/twenty-front/src/utils/parseApolloStoreFieldName.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// There is a feature request for receiving variables in `cache.modify`:
|
||||||
|
// @see https://github.com/apollographql/apollo-feature-requests/issues/259
|
||||||
|
// @see https://github.com/apollographql/apollo-client/issues/7129
|
||||||
|
// For now we need to parse `storeFieldName` to retrieve the variables.
|
||||||
|
export const parseApolloStoreFieldName = (storeFieldName: string) => {
|
||||||
|
const matches = storeFieldName.match(/([a-zA-Z][a-zA-Z0-9 ]*)\((.*)\)/);
|
||||||
|
|
||||||
|
if (!matches?.[1]) return {};
|
||||||
|
|
||||||
|
const [, fieldName, stringifiedVariables] = matches;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const variables = stringifiedVariables
|
||||||
|
? (JSON.parse(stringifiedVariables) as Record<string, unknown>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return { fieldName, variables };
|
||||||
|
} catch {
|
||||||
|
return { fieldName };
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user