Feat/record optimistic effect (#3076)

* WIP

* WIP

* POC working on hard coded completedAt field

* Finished isRecordMatchingFilter, mock of pg_graphql filtering mechanism

* Fixed and cleaned

* Unregister unused optimistic effects

* Fix lint

* Fixes from review

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Lucas Bordeau
2023-12-20 20:31:48 +01:00
committed by GitHub
parent a5f28b4395
commit 687c9131f4
37 changed files with 2309 additions and 233 deletions

View File

@ -1,68 +1,109 @@
import { isNonEmptyArray } from '@sniptt/guards';
import { produce } from 'immer';
import { OptimisticEffectDefinition } from '@/apollo/optimistic-effect/types/OptimisticEffectDefinition';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
import { PaginatedRecordTypeResults } from '@/object-record/types/PaginatedRecordTypeResults';
import { isDefined } from '~/utils/isDefined';
import { capitalize } from '~/utils/string/capitalize';
export const getRecordOptimisticEffectDefinition = ({
objectMetadataItem,
}: {
objectMetadataItem: ObjectMetadataItem;
}) =>
({
key: `record-create-optimistic-effect-definition-${objectMetadataItem.nameSingular}`,
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
resolver: ({
currentData,
newData,
deletedRecordIds,
}: {
currentData: unknown;
newData: { id: string } & Record<string, any>;
deletedRecordIds?: string[];
}) => {
const newRecordPaginatedCacheField = produce<
PaginatedRecordTypeResults<any>
>(currentData as PaginatedRecordTypeResults<any>, (draft) => {
if (newData) {
if (!draft) {
return {
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
edges: [{ node: newData, cursor: '' }],
pageInfo: {
endCursor: '',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
},
};
}
}): OptimisticEffectDefinition => ({
typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
resolver: ({
currentCacheData: currentData,
createdRecords,
updatedRecords,
deletedRecordIds,
variables,
}) => {
const newRecordPaginatedCacheField = produce<
PaginatedRecordTypeResults<any>
>(currentData as PaginatedRecordTypeResults<any>, (draft) => {
const existingDataIsEmpty = !draft || !draft.edges || !draft.edges[0];
const existingRecord = draft.edges.find(
(edge) => edge.node.id === newData.id,
);
if (existingRecord) {
existingRecord.node = newData;
return;
}
draft.edges.unshift({
node: newData,
cursor: '',
if (isNonEmptyArray(createdRecords)) {
if (existingDataIsEmpty) {
return {
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
});
}
edges: createdRecords.map((createdRecord) => ({
node: createdRecord,
cursor: '',
})),
pageInfo: {
endCursor: '',
hasNextPage: false,
hasPreviousPage: false,
startCursor: '',
},
};
} else {
for (const createdRecord of createdRecords) {
const existingRecord = draft.edges.find(
(edge) => edge.node.id === createdRecord.id,
);
if (deletedRecordIds) {
draft.edges = draft.edges.filter(
(edge) => !deletedRecordIds.includes(edge.node.id),
);
}
});
if (existingRecord) {
existingRecord.node = createdRecord;
continue;
}
return newRecordPaginatedCacheField;
},
isUsingFlexibleBackend: true,
objectMetadataItem,
}) satisfies OptimisticEffectDefinition;
draft.edges.unshift({
node: createdRecord,
cursor: '',
__typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`,
});
}
}
}
if (deletedRecordIds) {
draft.edges = draft.edges.filter(
(edge) => !deletedRecordIds.includes(edge.node.id),
);
}
if (isNonEmptyArray(updatedRecords)) {
for (const updatedRecord of updatedRecords) {
const updatedRecordIsOutOfQueryFilter =
isDefined(variables.filter) &&
!isRecordMatchingFilter({
record: updatedRecord,
filter: variables.filter,
objectMetadataItem,
});
if (updatedRecordIsOutOfQueryFilter) {
draft.edges = draft.edges.filter(
(edge) => edge.node.id !== updatedRecord.id,
);
} else {
const foundUpdatedRecordInCacheQuery = draft.edges.find(
(edge) => edge.node.id === updatedRecord.id,
);
if (foundUpdatedRecordInCacheQuery) {
foundUpdatedRecordInCacheQuery.node = updatedRecord;
} else {
// TODO: add order by
draft.edges.push({
node: updatedRecord,
cursor: '',
__typename: `${capitalize(
objectMetadataItem.nameSingular,
)}Edge`,
});
}
}
}
}
});
return newRecordPaginatedCacheField;
},
objectMetadataItem,
});