Skip to content

Commit

Permalink
Fix optimistic effect deletedAt (#7606)
Browse files Browse the repository at this point in the history
In this PR, I'm fixing part of the impact of soft deletion on optimistic
rendering.

## Backend Vision

1) Backend endpoints will not return soft deleted records (having
deletedAt set) by default. To get the softDeleted records, we will pass
a { withSoftDelete: true } additional param in the query.
2) Record relations will NEVER contain softDeleted relations

## Backend current state

Right now, we have the following behavior:
- if the query filters do not mention deletedAt, we don't return
softDeletedRecords
- if the query filters mention deletedAt, we take it into consideration.
Meaning that if we want to have the softDeleted records in any way we
need to do { or: [ deletedAt: NULL, deletedAt: NOT_NULL] }

## Optimistic rendering strategy

1) useDestroyOne/Many is triggering destroyOptimisticEffects (previously
deleteOptimisticEffects)
2) UseDeleteOne/Many and useRestoreOne/Many are actually triggering
updateOptimisticEffects (as they only update deletedAt field) AND we
need updateOptimisticEffects to take into account deletedAt (future
withSoftDelete: true) filter.
  • Loading branch information
charlesBochet authored Oct 11, 2024
1 parent d350143 commit 7b96be6
Show file tree
Hide file tree
Showing 21 changed files with 407 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import { isObjectRecordConnectionWithRefs } from '@/object-record/cache/utils/is
import { RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode';
import { isDefined } from '~/utils/isDefined';

export const triggerDeleteRecordsOptimisticEffect = ({
export const triggerDestroyRecordsOptimisticEffect = ({
cache,
objectMetadataItem,
recordsToDelete,
recordsToDestroy,
objectMetadataItems,
}: {
cache: ApolloCache<unknown>;
objectMetadataItem: ObjectMetadataItem;
recordsToDelete: RecordGqlNode[];
recordsToDestroy: RecordGqlNode[];
objectMetadataItems: ObjectMetadataItem[];
}) => {
cache.modify<StoreObject>({
Expand All @@ -36,7 +36,7 @@ export const triggerDeleteRecordsOptimisticEffect = ({

const rootQueryCachedObjectRecordConnection = rootQueryCachedResponse;

const recordIdsToDelete = recordsToDelete.map(({ id }) => id);
const recordIdsToDelete = recordsToDestroy.map(({ id }) => id);

const cachedEdges = readField<RecordGqlRefEdge[]>(
'edges',
Expand Down Expand Up @@ -69,20 +69,15 @@ export const triggerDeleteRecordsOptimisticEffect = ({
},
});

recordsToDelete.forEach((recordToDelete) => {
recordsToDestroy.forEach((recordToDestroy) => {
triggerUpdateRelationsOptimisticEffect({
cache,
sourceObjectMetadataItem: objectMetadataItem,
currentSourceRecord: recordToDelete,
currentSourceRecord: recordToDestroy,
updatedSourceRecord: null,
objectMetadataItems,
});

cache.modify({
id: cache.identify(recordToDelete),
fields: {
deletedAt: () => recordToDelete.deletedAt,
},
});
cache.evict({ id: cache.identify(recordToDestroy) });
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -65,50 +65,45 @@ export const triggerUpdateRecordOptimisticEffect = ({
const rootQueryFilter = rootQueryVariables?.filter;
const rootQueryOrderBy = rootQueryVariables?.orderBy;

const shouldTryToMatchFilter = isDefined(rootQueryFilter);

if (shouldTryToMatchFilter) {
const updatedRecordMatchesThisRootQueryFilter =
isRecordMatchingFilter({
record: updatedRecord,
filter: rootQueryFilter,
objectMetadataItem,
});
const updatedRecordMatchesThisRootQueryFilter = isRecordMatchingFilter({
record: updatedRecord,
filter: rootQueryFilter ?? {},
objectMetadataItem,
});

const updatedRecordIndexInRootQueryEdges =
rootQueryCurrentEdges.findIndex(
(cachedEdge) =>
readField('id', cachedEdge.node) === updatedRecord.id,
);

const updatedRecordIndexInRootQueryEdges =
rootQueryCurrentEdges.findIndex(
(cachedEdge) =>
readField('id', cachedEdge.node) === updatedRecord.id,
);

const updatedRecordFoundInRootQueryEdges =
updatedRecordIndexInRootQueryEdges > -1;

const updatedRecordShouldBeAddedToRootQueryEdges =
updatedRecordMatchesThisRootQueryFilter &&
!updatedRecordFoundInRootQueryEdges;

const updatedRecordShouldBeRemovedFromRootQueryEdges =
!updatedRecordMatchesThisRootQueryFilter &&
updatedRecordFoundInRootQueryEdges;

if (updatedRecordShouldBeAddedToRootQueryEdges) {
const updatedRecordNodeReference = toReference(updatedRecord);

if (isDefined(updatedRecordNodeReference)) {
rootQueryNextEdges.push({
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
node: updatedRecordNodeReference,
cursor: '',
});
}
}
const updatedRecordFoundInRootQueryEdges =
updatedRecordIndexInRootQueryEdges > -1;

const updatedRecordShouldBeAddedToRootQueryEdges =
updatedRecordMatchesThisRootQueryFilter &&
!updatedRecordFoundInRootQueryEdges;

const updatedRecordShouldBeRemovedFromRootQueryEdges =
!updatedRecordMatchesThisRootQueryFilter &&
updatedRecordFoundInRootQueryEdges;

if (updatedRecordShouldBeRemovedFromRootQueryEdges) {
rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1);
if (updatedRecordShouldBeAddedToRootQueryEdges) {
const updatedRecordNodeReference = toReference(updatedRecord);

if (isDefined(updatedRecordNodeReference)) {
rootQueryNextEdges.push({
__typename: getEdgeTypename(objectMetadataItem.nameSingular),
node: updatedRecordNodeReference,
cursor: '',
});
}
}

if (updatedRecordShouldBeRemovedFromRootQueryEdges) {
rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1);
}

const rootQueryNextEdgesShouldBeSorted = isDefined(rootQueryOrderBy);

if (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApolloCache } from '@apollo/client';

import { triggerAttachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerAttachRelationOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
import { triggerDetachRelationOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDetachRelationOptimisticEffect';
import { CORE_OBJECT_NAMES_TO_DELETE_ON_TRIGGER_RELATION_DETACH } from '@/apollo/types/coreObjectNamesToDeleteOnRelationDetach';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
Expand Down Expand Up @@ -122,10 +122,10 @@ export const triggerUpdateRelationsOptimisticEffect = ({
);

if (shouldCascadeDeleteTargetRecords) {
triggerDeleteRecordsOptimisticEffect({
triggerDestroyRecordsOptimisticEffect({
cache,
objectMetadataItem: fullTargetObjectMetadataItem,
recordsToDelete: targetRecordsToDetachFrom,
recordsToDestroy: targetRecordsToDetachFrom,
objectMetadataItems,
});
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export const useDeleteRecordFromCache = ({

const { objectMetadataItems } = useObjectMetadataItems();

return (recordToDelete: ObjectRecord) => {
return (recordToDestroy: ObjectRecord) => {
deleteRecordFromCache({
objectMetadataItem,
objectMetadataItems,
recordToDelete,
recordToDestroy,
cache: apolloClient.cache,
});
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { ApolloCache } from '@apollo/client';

import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';

export const deleteRecordFromCache = ({
objectMetadataItem,
objectMetadataItems,
recordToDelete,
recordToDestroy,
cache,
}: {
objectMetadataItem: ObjectMetadataItem;
objectMetadataItems: ObjectMetadataItem[];
recordToDelete: ObjectRecord;
recordToDestroy: ObjectRecord;
cache: ApolloCache<object>;
}) => {
triggerDeleteRecordsOptimisticEffect({
triggerDestroyRecordsOptimisticEffect({
cache,
objectMetadataItem,
objectMetadataItems,
recordsToDelete: [
recordsToDestroy: [
{
...recordToDelete,
...recordToDestroy,
__typename: getObjectTypename(objectMetadataItem.nameSingular),
},
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { act, renderHook } from '@testing-library/react';
import { renderHook } from '@testing-library/react';

import {
query,
responseData,
variables,
} from '@/object-record/hooks/__mocks__/useDeleteManyRecords';
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
import { act } from 'react';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';

const people = [
const personIds = [
'a7286b9a-c039-4a89-9567-2dfa7953cda9',
'37faabcd-cb39-4a0a-8618-7e3fda9afca0',
];
Expand Down Expand Up @@ -41,7 +42,7 @@ describe('useDeleteManyRecords', () => {
);

await act(async () => {
const res = await result.current.deleteManyRecords(people);
const res = await result.current.deleteManyRecords(personIds);
expect(res).toBeDefined();
expect(res[0]).toHaveProperty('id');
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client';
import { v4 } from 'uuid';

import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
Expand Down Expand Up @@ -122,19 +122,19 @@ export const useCreateManyRecords = <
},
})
.catch((error: Error) => {
recordsCreatedInCache.forEach((recordToDelete) => {
recordsCreatedInCache.forEach((recordToDestroy) => {
deleteRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
recordToDelete,
recordToDestroy,
});
});

triggerDeleteRecordsOptimisticEffect({
triggerDestroyRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToDelete: recordsCreatedInCache,
recordsToDestroy: recordsCreatedInCache,
objectMetadataItems,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useState } from 'react';
import { v4 } from 'uuid';

import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect';
import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect';
import { triggerDestroyRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useCreateOneRecordInCache } from '@/object-record/cache/hooks/useCreateOneRecordInCache';
Expand Down Expand Up @@ -118,13 +118,13 @@ export const useCreateOneRecord = <
objectMetadataItems,
objectMetadataItem,
cache: apolloClient.cache,
recordToDelete: recordCreatedInCache,
recordToDestroy: recordCreatedInCache,
});

triggerDeleteRecordsOptimisticEffect({
triggerDestroyRecordsOptimisticEffect({
cache: apolloClient.cache,
objectMetadataItem,
recordsToDelete: [recordCreatedInCache],
recordsToDestroy: [recordCreatedInCache],
objectMetadataItems,
});

Expand Down
Loading

0 comments on commit 7b96be6

Please sign in to comment.