feat: delete favorite in cache on related record deletion (#3751)
* feat: delete favorite in cache on related record deletion * fix: fix useCreateOneRecord tests * fix: fix usePipelineSteps tests * fix: fix useCreateManyRecords tests * fix: add null relation field values in useGenerateObjectRecordOptimisticResponse
This commit is contained in:
@ -1,8 +1,10 @@
|
||||
import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { capitalize } from '~/utils/string/capitalize';
|
||||
|
||||
@ -15,15 +17,27 @@ export const triggerCreateRecordsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
records: CachedObjectRecord[];
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
const objectEdgeTypeName = `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Edge`;
|
||||
|
||||
records.forEach((record) =>
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord: null,
|
||||
nextRecord: record,
|
||||
getRelationMetadata,
|
||||
}),
|
||||
);
|
||||
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
[objectMetadataItem.namePlural]: (
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
import { parseApolloStoreFieldName } from '~/utils/parseApolloStoreFieldName';
|
||||
@ -12,10 +14,12 @@ export const triggerDeleteRecordsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
records,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
records: Pick<CachedObjectRecord, 'id' | '__typename'>[];
|
||||
records: CachedObjectRecord[];
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
@ -64,5 +68,15 @@ export const triggerDeleteRecordsOptimisticEffect = ({
|
||||
},
|
||||
});
|
||||
|
||||
cache.gc();
|
||||
records.forEach((record) => {
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord: record,
|
||||
nextRecord: null,
|
||||
getRelationMetadata,
|
||||
});
|
||||
|
||||
cache.evict({ id: cache.identify(record) });
|
||||
});
|
||||
};
|
||||
|
||||
@ -2,9 +2,11 @@ import { ApolloCache, StoreObject } from '@apollo/client';
|
||||
|
||||
import { isCachedObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isCachedObjectRecordConnection';
|
||||
import { sortCachedObjectEdges } from '@/apollo/optimistic-effect/utils/sortCachedObjectEdges';
|
||||
import { triggerUpdateRelationsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { CachedObjectRecordEdge } from '@/apollo/types/CachedObjectRecordEdge';
|
||||
import { CachedObjectRecordQueryVariables } from '@/apollo/types/CachedObjectRecordQueryVariables';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isRecordMatchingFilter } from '@/object-record/record-filter/utils/isRecordMatchingFilter';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
@ -14,16 +16,29 @@ import { capitalize } from '~/utils/string/capitalize';
|
||||
export const triggerUpdateRecordOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
record,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
record: CachedObjectRecord;
|
||||
previousRecord: CachedObjectRecord;
|
||||
nextRecord: CachedObjectRecord;
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) => {
|
||||
const objectEdgeTypeName = `${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}Edge`;
|
||||
|
||||
triggerUpdateRelationsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
});
|
||||
|
||||
// Optimistically update record lists
|
||||
cache.modify<StoreObject>({
|
||||
fields: {
|
||||
[objectMetadataItem.namePlural]: (
|
||||
@ -49,18 +64,20 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
);
|
||||
let nextCachedEdges = cachedEdges ? [...cachedEdges] : [];
|
||||
|
||||
// Test if the record matches this list's filters
|
||||
if (variables?.filter) {
|
||||
const matchesFilter = isRecordMatchingFilter({
|
||||
record,
|
||||
record: nextRecord,
|
||||
filter: variables.filter,
|
||||
objectMetadataItem,
|
||||
});
|
||||
const recordIndex = nextCachedEdges.findIndex(
|
||||
(cachedEdge) => readField('id', cachedEdge.node) === record.id,
|
||||
(cachedEdge) => readField('id', cachedEdge.node) === nextRecord.id,
|
||||
);
|
||||
|
||||
// If after update, the record matches this list's filters, then add it to the list
|
||||
if (matchesFilter && recordIndex === -1) {
|
||||
const nodeReference = toReference(record);
|
||||
const nodeReference = toReference(nextRecord);
|
||||
nodeReference &&
|
||||
nextCachedEdges.push({
|
||||
__typename: objectEdgeTypeName,
|
||||
@ -69,11 +86,13 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
});
|
||||
}
|
||||
|
||||
// If after update, the record does not match this list's filters anymore, then remove it from the list
|
||||
if (!matchesFilter && recordIndex > -1) {
|
||||
nextCachedEdges.splice(recordIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort updated list
|
||||
if (variables?.orderBy) {
|
||||
nextCachedEdges = sortCachedObjectEdges({
|
||||
edges: nextCachedEdges,
|
||||
@ -82,6 +101,7 @@ export const triggerUpdateRecordOptimisticEffect = ({
|
||||
});
|
||||
}
|
||||
|
||||
// Limit the updated list to the required size
|
||||
if (isDefined(variables?.first)) {
|
||||
// If previous edges length was exactly at the required limit,
|
||||
// but after update next edges length is under the limit,
|
||||
|
||||
@ -0,0 +1,112 @@
|
||||
import { ApolloCache } from '@apollo/client';
|
||||
|
||||
import { isObjectRecordConnection } from '@/apollo/optimistic-effect/utils/isObjectRecordConnection';
|
||||
import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
|
||||
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
|
||||
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
|
||||
import { CachedObjectRecord } from '@/apollo/types/CachedObjectRecord';
|
||||
import { coreObjectNamesToDeleteOnRelationDetach } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
|
||||
import { useGetRelationMetadata } from '@/object-metadata/hooks/useGetRelationMetadata';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecordConnection } from '@/object-record/types/ObjectRecordConnection';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
export const triggerUpdateRelationsOptimisticEffect = ({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
previousRecord,
|
||||
nextRecord,
|
||||
getRelationMetadata,
|
||||
}: {
|
||||
cache: ApolloCache<unknown>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
previousRecord: CachedObjectRecord | null;
|
||||
nextRecord: CachedObjectRecord | null;
|
||||
getRelationMetadata: ReturnType<typeof useGetRelationMetadata>;
|
||||
}) =>
|
||||
// Optimistically update relation records
|
||||
objectMetadataItem.fields.forEach((fieldMetadataItem) => {
|
||||
if (nextRecord && !(fieldMetadataItem.name in nextRecord)) return;
|
||||
|
||||
const relationMetadata = getRelationMetadata({
|
||||
fieldMetadataItem,
|
||||
});
|
||||
|
||||
if (!relationMetadata) return;
|
||||
|
||||
const {
|
||||
// Object metadata for the related record
|
||||
relationObjectMetadataItem,
|
||||
// Field on the related record
|
||||
relationFieldMetadataItem,
|
||||
} = relationMetadata;
|
||||
|
||||
const previousFieldValue:
|
||||
| ObjectRecordConnection
|
||||
| CachedObjectRecord
|
||||
| null = previousRecord?.[fieldMetadataItem.name];
|
||||
const nextFieldValue: ObjectRecordConnection | CachedObjectRecord | null =
|
||||
nextRecord?.[fieldMetadataItem.name];
|
||||
|
||||
if (isDeeplyEqual(previousFieldValue, nextFieldValue)) return;
|
||||
|
||||
const isPreviousFieldValueRecordConnection = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
previousFieldValue,
|
||||
);
|
||||
const relationRecordsToDetach = isPreviousFieldValueRecordConnection
|
||||
? previousFieldValue.edges.map(({ node }) => node as CachedObjectRecord)
|
||||
: [previousFieldValue].filter(isDefined);
|
||||
|
||||
const isNextFieldValueRecordConnection = isObjectRecordConnection(
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
nextFieldValue,
|
||||
);
|
||||
const relationRecordsToAttach = isNextFieldValueRecordConnection
|
||||
? nextFieldValue.edges.map(({ node }) => node as CachedObjectRecord)
|
||||
: [nextFieldValue].filter(isDefined);
|
||||
|
||||
if (previousRecord && relationRecordsToDetach.length) {
|
||||
const shouldDeleteRelationRecord =
|
||||
coreObjectNamesToDeleteOnRelationDetach.includes(
|
||||
relationObjectMetadataItem.nameSingular as CoreObjectNameSingular,
|
||||
);
|
||||
|
||||
if (shouldDeleteRelationRecord) {
|
||||
triggerDeleteRecordsOptimisticEffect({
|
||||
cache,
|
||||
objectMetadataItem: relationObjectMetadataItem,
|
||||
records: relationRecordsToDetach,
|
||||
getRelationMetadata,
|
||||
});
|
||||
} else {
|
||||
relationRecordsToDetach.forEach((relationRecordToDetach) => {
|
||||
triggerDetachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordId: previousRecord.id,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationRecordId: relationRecordToDetach.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (nextRecord && relationRecordsToAttach.length) {
|
||||
relationRecordsToAttach.forEach((relationRecordToAttach) =>
|
||||
triggerAttachRelationOptimisticEffect({
|
||||
cache,
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
recordId: nextRecord.id,
|
||||
relationFieldName: relationFieldMetadataItem.name,
|
||||
relationObjectMetadataNameSingular:
|
||||
relationObjectMetadataItem.nameSingular,
|
||||
relationRecordId: relationRecordToAttach.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user