diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts similarity index 85% rename from packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts rename to packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts index 80c880094a..5f62ff3bdb 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerDestroyRecordsOptimisticEffect.ts @@ -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; objectMetadataItem: ObjectMetadataItem; - recordsToDelete: RecordGqlNode[]; + recordsToDestroy: RecordGqlNode[]; objectMetadataItems: ObjectMetadataItem[]; }) => { cache.modify({ @@ -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( 'edges', @@ -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) }); }); }; diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts index 3c09c213f6..15878796f5 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect.ts @@ -65,48 +65,43 @@ export const triggerUpdateRecordOptimisticEffect = ({ const rootQueryFilter = rootQueryVariables?.filter; const rootQueryOrderBy = rootQueryVariables?.orderBy; - const shouldTryToMatchFilter = isDefined(rootQueryFilter); + const updatedRecordMatchesThisRootQueryFilter = isRecordMatchingFilter({ + record: updatedRecord, + filter: rootQueryFilter ?? {}, + objectMetadataItem, + }); - if (shouldTryToMatchFilter) { - const updatedRecordMatchesThisRootQueryFilter = - isRecordMatchingFilter({ - record: updatedRecord, - filter: rootQueryFilter, - objectMetadataItem, + 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 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: '', - }); - } } + } - if (updatedRecordShouldBeRemovedFromRootQueryEdges) { - rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1); - } + if (updatedRecordShouldBeRemovedFromRootQueryEdges) { + rootQueryNextEdges.splice(updatedRecordIndexInRootQueryEdges, 1); } const rootQueryNextEdgesShouldBeSorted = isDefined(rootQueryOrderBy); diff --git a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts index 535f33db4f..4e44e098ad 100644 --- a/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts +++ b/packages/twenty-front/src/modules/apollo/optimistic-effect/utils/triggerUpdateRelationsOptimisticEffect.ts @@ -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'; @@ -122,10 +122,10 @@ export const triggerUpdateRelationsOptimisticEffect = ({ ); if (shouldCascadeDeleteTargetRecords) { - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem: fullTargetObjectMetadataItem, - recordsToDelete: targetRecordsToDetachFrom, + recordsToDestroy: targetRecordsToDetachFrom, objectMetadataItems, }); } else { diff --git a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts index 693236975e..90105c7005 100644 --- a/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/hooks/useDeleteRecordFromCache.ts @@ -18,11 +18,11 @@ export const useDeleteRecordFromCache = ({ const { objectMetadataItems } = useObjectMetadataItems(); - return (recordToDelete: ObjectRecord) => { + return (recordToDestroy: ObjectRecord) => { deleteRecordFromCache({ objectMetadataItem, objectMetadataItems, - recordToDelete, + recordToDestroy, cache: apolloClient.cache, }); }; diff --git a/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts index ec9ec8b3a4..10bc657ca3 100644 --- a/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts +++ b/packages/twenty-front/src/modules/object-record/cache/utils/deleteRecordFromCache.ts @@ -1,6 +1,6 @@ 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'; @@ -8,21 +8,21 @@ 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; }) => { - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, objectMetadataItems, - recordsToDelete: [ + recordsToDestroy: [ { - ...recordToDelete, + ...recordToDestroy, __typename: getObjectTypename(objectMetadataItem.nameSingular), }, ], diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx index ada53864a7..89d5d12059 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteManyRecords.test.tsx @@ -1,4 +1,4 @@ -import { act, renderHook } from '@testing-library/react'; +import { renderHook } from '@testing-library/react'; import { query, @@ -6,9 +6,10 @@ import { 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', ]; @@ -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'); }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts index 017dad72de..c236baede8 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -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'; @@ -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, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts index 2e9d792390..73c9cd9897 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -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'; @@ -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, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts index 38bd825d55..61cabcb72c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -1,14 +1,15 @@ import { useApolloClient } from '@apollo/client'; -import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { isDefined } from '~/utils/isDefined'; @@ -62,49 +63,74 @@ export const useDeleteManyRecords = ({ const deletedRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToDelete.slice( + const batchedIdsToDelete = idsToDelete.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); + const currentTimestamp = new Date().toISOString(); + + const cachedRecords = batchedIdsToDelete + .map((idToDelete) => getRecordFromCache(idToDelete, apolloClient.cache)) + .filter(isDefined); + + if (!options?.skipOptimisticEffect) { + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord || !cachedRecord.id) { + return; + } + + const cachedRecordWithConnection = + getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); + }); + } + const deletedRecordsResponse = await apolloClient .mutate({ mutation: deleteManyRecordsMutation, variables: { - filter: { id: { in: batchIds } }, + filter: { id: { in: batchedIdsToDelete } }, }, - optimisticResponse: options?.skipOptimisticEffect - ? undefined - : { - [mutationResponseField]: batchIds.map((idToDelete) => ({ - __typename: capitalize(objectNameSingular), - id: idToDelete, - })), - }, - update: options?.skipOptimisticEffect - ? undefined - : (cache, { data }) => { - const records = data?.[mutationResponseField]; - - if (!records?.length) return; - - const cachedRecords = records - .map((record) => getRecordFromCache(record.id, cache)) - .filter(isDefined); - - triggerDeleteRecordsOptimisticEffect({ - cache, - objectMetadataItem, - recordsToDelete: cachedRecords, - objectMetadataItems, - }); - }, }) .catch((error: Error) => { - const cachedRecords = batchIds.map((idToDelete) => - getRecordFromCache(idToDelete, apolloClient.cache), - ); - cachedRecords.forEach((cachedRecord) => { if (!cachedRecord) { return; @@ -114,23 +140,45 @@ export const useDeleteManyRecords = ({ objectMetadataItems, objectMetadataItem, cache: apolloClient.cache, - record: { - ...cachedRecord, - deletedAt: null, - }, + record: cachedRecord, }); - }); - triggerCreateRecordsOptimisticEffect({ - cache: apolloClient.cache, - objectMetadataItem, - objectMetadataItems, - recordsToCreate: cachedRecords - .filter(isDefined) - .map((cachedRecord) => ({ - ...cachedRecord, - deletedAt: null, - })), + const cachedRecordWithConnection = + getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if ( + !optimisticRecordWithConnection || + !cachedRecordWithConnection + ) { + return null; + } + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, + objectMetadataItems, + }); }); throw error; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts index b39871ba27..969b830114 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -1,13 +1,14 @@ import { useApolloClient } from '@apollo/client'; import { useCallback } from 'react'; -import { triggerCreateRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerCreateRecordsOptimisticEffect'; -import { triggerDeleteRecordsOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerDeleteRecordsOptimisticEffect'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { capitalize } from '~/utils/string/capitalize'; @@ -41,66 +42,85 @@ export const useDeleteOneRecord = ({ async (idToDelete: string) => { const currentTimestamp = new Date().toISOString(); + const cachedRecord = getRecordFromCache(idToDelete, apolloClient.cache); + + const cachedRecordWithConnection = getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: idToDelete, deletedAt: currentTimestamp }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); + const deletedRecord = await apolloClient .mutate({ mutation: deleteOneRecordMutation, variables: { idToDelete: idToDelete, }, - optimisticResponse: { - [mutationResponseField]: { - __typename: capitalize(objectNameSingular), - id: idToDelete, - deletedAt: currentTimestamp, - }, - }, update: (cache, { data }) => { const record = data?.[mutationResponseField]; - if (!record) return; + if (!record || !cachedRecord) return; - const cachedRecord = getRecordFromCache(record.id, cache); - - if (!cachedRecord) return; - - triggerDeleteRecordsOptimisticEffect({ + triggerUpdateRecordOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + currentRecord: cachedRecord, + updatedRecord: record, objectMetadataItems, }); }, }) .catch((error: Error) => { - const cachedRecord = getRecordFromCache( - idToDelete, - apolloClient.cache, - ); - if (!cachedRecord) { throw error; } - updateRecordFromCache({ objectMetadataItems, objectMetadataItem, cache: apolloClient.cache, - record: { - ...cachedRecord, - deletedAt: null, - }, + record: cachedRecord, }); - triggerCreateRecordsOptimisticEffect({ + triggerUpdateRecordOptimisticEffect({ cache: apolloClient.cache, objectMetadataItem, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, objectMetadataItems, - recordsToCreate: [ - { - ...cachedRecord, - deletedAt: null, - }, - ], }); throw error; @@ -114,7 +134,6 @@ export const useDeleteOneRecord = ({ getRecordFromCache, mutationResponseField, objectMetadataItem, - objectNameSingular, objectMetadataItems, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts index 3ba7283b96..7889508448 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -1,7 +1,7 @@ import { useApolloClient } from '@apollo/client'; 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 { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; @@ -61,12 +61,12 @@ export const useDestroyManyRecords = ({ const destroyedRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToDestroy.slice( + const batchedIdToDestroy = idsToDestroy.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); - const originalRecords = idsToDestroy + const originalRecords = batchedIdToDestroy .map((recordId) => getRecordFromCache(recordId, apolloClient.cache)) .filter(isDefined); @@ -74,15 +74,17 @@ export const useDestroyManyRecords = ({ .mutate({ mutation: destroyManyRecordsMutation, variables: { - filter: { id: { in: batchIds } }, + filter: { id: { in: batchedIdToDestroy } }, }, optimisticResponse: options?.skipOptimisticEffect ? undefined : { - [mutationResponseField]: batchIds.map((idToDestroy) => ({ - __typename: capitalize(objectNameSingular), - id: idToDestroy, - })), + [mutationResponseField]: batchedIdToDestroy.map( + (idToDestroy) => ({ + __typename: capitalize(objectNameSingular), + id: idToDestroy, + }), + ), }, update: options?.skipOptimisticEffect ? undefined @@ -95,10 +97,10 @@ export const useDestroyManyRecords = ({ .map((record) => getRecordFromCache(record.id, cache)) .filter(isDefined); - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: cachedRecords, + recordsToDestroy: cachedRecords, objectMetadataItems, }); }, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts index 91446a8726..1ea6aacea1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts @@ -2,7 +2,7 @@ import { useApolloClient } from '@apollo/client'; import { useCallback } from 'react'; 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 { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; @@ -65,10 +65,10 @@ export const useDestroyOneRecord = ({ if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + recordsToDestroy: [cachedRecord], objectMetadataItems, }); }, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts index 66af0949fe..a223f260f7 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts @@ -1,9 +1,15 @@ import { useApolloClient } from '@apollo/client'; +import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { apiConfigState } from '@/client-config/states/apiConfigState'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; +import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; +import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; +import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation'; +import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { isDefined } from '~/utils/isDefined'; @@ -34,10 +40,16 @@ export const useRestoreManyRecords = ({ objectNameSingular, }); + const getRecordFromCache = useGetRecordFromCache({ + objectNameSingular, + }); + const { restoreManyRecordsMutation } = useRestoreManyRecordsMutation({ objectNameSingular, }); + const { objectMetadataItems } = useObjectMetadataItems(); + const mutationResponseField = getRestoreManyRecordsMutationResponseField( objectMetadataItem.namePlural, ); @@ -51,36 +63,124 @@ export const useRestoreManyRecords = ({ const restoredRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchIds = idsToRestore.slice( + const batchedIdsToRestore = idsToRestore.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); - // TODO: fix optimistic effect - const findOneQueryName = `FindOne${capitalize(objectNameSingular)}`; - const findManyQueryName = `FindMany${capitalize( - objectMetadataItem.namePlural, - )}`; + const cachedRecords = batchedIdsToRestore + .map((idToRestore) => + getRecordFromCache(idToRestore, apolloClient.cache), + ) + .filter(isDefined); + + if (!options?.skipOptimisticEffect) { + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord || !cachedRecord.id) { + return; + } + + const cachedRecordWithConnection = + getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: null }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + return null; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: computedOptimisticRecord, + }); + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: cachedRecordWithConnection, + updatedRecord: optimisticRecordWithConnection, + objectMetadataItems, + }); + }); + } const restoredRecordsResponse = await apolloClient .mutate({ mutation: restoreManyRecordsMutation, - refetchQueries: [findOneQueryName, findManyQueryName], variables: { - filter: { id: { in: batchIds } }, + filter: { id: { in: batchedIdsToRestore } }, }, - optimisticResponse: options?.skipOptimisticEffect - ? undefined - : { - [mutationResponseField]: batchIds.map((idToRestore) => ({ - __typename: capitalize(objectNameSingular), - id: idToRestore, - deletedAt: null, - })), - }, }) .catch((error: Error) => { - // TODO: revert optimistic effect (once optimistic effect is fixed) + cachedRecords.forEach((cachedRecord) => { + if (!cachedRecord) { + return; + } + + updateRecordFromCache({ + objectMetadataItems, + objectMetadataItem, + cache: apolloClient.cache, + record: cachedRecord, + }); + + const cachedRecordWithConnection = + getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + const computedOptimisticRecord = { + ...cachedRecord, + ...{ id: cachedRecord.id, deletedAt: null }, + ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, + }; + + const optimisticRecordWithConnection = + getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: true, + }); + + if ( + !optimisticRecordWithConnection || + !cachedRecordWithConnection + ) { + return null; + } + + triggerUpdateRecordOptimisticEffect({ + cache: apolloClient.cache, + objectMetadataItem, + currentRecord: optimisticRecordWithConnection, + updatedRecord: cachedRecordWithConnection, + objectMetadataItems, + }); + }); + throw error; }); diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts index a8c26aa1eb..929956d771 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts @@ -11,6 +11,7 @@ import { EmailsFilter, FloatFilter, FullNameFilter, + LeafObjectRecordFilter, LinksFilter, NotObjectRecordFilter, OrObjectRecordFilter, @@ -29,6 +30,12 @@ import { FieldMetadataType } from '~/generated-metadata/graphql'; import { isDefined } from '~/utils/isDefined'; import { isEmptyObject } from '~/utils/isEmptyObject'; +const isLeafFilter = ( + filter: RecordGqlOperationFilter, +): filter is LeafObjectRecordFilter => { + return !isAndFilter(filter) && !isOrFilter(filter) && !isNotFilter(filter); +}; + const isAndFilter = ( filter: RecordGqlOperationFilter, ): filter is AndObjectRecordFilter => 'and' in filter && !!filter.and; @@ -50,7 +57,7 @@ export const isRecordMatchingFilter = ({ filter: RecordGqlOperationFilter; objectMetadataItem: ObjectMetadataItem; }): boolean => { - if (Object.keys(filter).length === 0) { + if (Object.keys(filter).length === 0 && record.deletedAt === null) { return true; } @@ -120,6 +127,12 @@ export const isRecordMatchingFilter = ({ ); } + if (isLeafFilter(filter)) { + if (isDefined(record.deletedAt) && filter.deletedAt === undefined) { + return false; + } + } + return Object.entries(filter).every(([filterKey, filterValue]) => { if (!isDefined(filterValue)) { throw new Error( diff --git a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts index 8ce580dad6..345e114538 100644 --- a/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts +++ b/packages/twenty-front/src/modules/object-record/record-index/options/hooks/useDeleteTableData.ts @@ -1,11 +1,7 @@ import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; -import { useFetchAllRecordIds } from '@/object-record/hooks/useFetchAllRecordIds'; import { UseTableDataOptions } from '@/object-record/record-index/options/hooks/useTableData'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; -import { tableRowIdsComponentState } from '@/object-record/record-table/states/tableRowIdsComponentState'; -import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId'; -import { useRecoilValue } from 'recoil'; type UseDeleteTableDataOptions = Pick< UseTableDataOptions, @@ -16,41 +12,16 @@ export const useDeleteTableData = ({ objectNameSingular, recordIndexId, }: UseDeleteTableDataOptions) => { - const { fetchAllRecordIds } = useFetchAllRecordIds({ - objectNameSingular, + const { resetTableRowSelection } = useRecordTable({ + recordTableId: recordIndexId, }); - const { resetTableRowSelection, hasUserSelectedAllRowsState } = - useRecordTable({ - recordTableId: recordIndexId, - }); - - const tableRowIds = useRecoilValue( - tableRowIdsComponentState({ - scopeId: getScopeIdFromComponentId(recordIndexId), - }), - ); - const { deleteManyRecords } = useDeleteManyRecords({ objectNameSingular, }); const { favorites, deleteFavorite } = useFavorites(); - const hasUserSelectedAllRows = useRecoilValue(hasUserSelectedAllRowsState); - const deleteRecords = async (recordIdsToDelete: string[]) => { - if (hasUserSelectedAllRows) { - const allRecordIds = await fetchAllRecordIds(); - - const unselectedRecordIds = tableRowIds.filter( - (recordId) => !recordIdsToDelete.includes(recordId), - ); - - recordIdsToDelete = allRecordIds.filter( - (recordId) => !unselectedRecordIds.includes(recordId), - ); - } - resetTableRowSelection(); for (const recordIdToDelete of recordIdsToDelete) { diff --git a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx index 30efbdf71f..8b44e15639 100644 --- a/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx +++ b/packages/twenty-front/src/modules/ui/layout/show-page/components/ShowPageMoreButton.tsx @@ -11,7 +11,7 @@ import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown'; import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem'; import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState'; -import { useDestroyManyRecords } from '@/object-record/hooks/useDestroyManyRecords'; +import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { Dropdown } from '../../dropdown/components/Dropdown'; @@ -35,7 +35,7 @@ export const ShowPageMoreButton = ({ const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular, }); - const { destroyManyRecords } = useDestroyManyRecords({ + const { destroyOneRecord } = useDestroyOneRecord({ objectNameSingular, }); const { restoreManyRecords } = useRestoreManyRecords({ @@ -48,7 +48,7 @@ export const ShowPageMoreButton = ({ }; const handleDestroy = () => { - destroyManyRecords([recordId]); + destroyOneRecord(recordId); closeDropdown(); navigate(navigationMemorizedUrl, { replace: true }); }; diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts index 4161777a57..488b33c31b 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewFilterRecords.ts @@ -1,15 +1,15 @@ -import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { useCallback } from 'react'; 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 { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; -import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { GraphQLView } from '@/views/types/GraphQLView'; @@ -24,7 +24,7 @@ export const usePersistViewFilterRecords = () => { objectNameSingular: CoreObjectNameSingular.ViewFilter, }); - const { deleteOneRecordMutation } = useDeleteOneRecordMutation({ + const { destroyOneRecordMutation } = useDestroyOneRecordMutation({ objectNameSingular: CoreObjectNameSingular.ViewFilter, }); @@ -129,12 +129,12 @@ export const usePersistViewFilterRecords = () => { return Promise.all( viewFilterIdsToDelete.map((viewFilterId) => apolloClient.mutate({ - mutation: deleteOneRecordMutation, + mutation: destroyOneRecordMutation, variables: { - idToDelete: viewFilterId, + idToDestroy: viewFilterId, }, update: (cache, { data }) => { - const record = data?.['deleteViewFilter']; + const record = data?.['destroyViewFilter']; if (!record) return; @@ -142,10 +142,10 @@ export const usePersistViewFilterRecords = () => { if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + recordsToDestroy: [cachedRecord], objectMetadataItems, }); }, @@ -155,7 +155,7 @@ export const usePersistViewFilterRecords = () => { }, [ apolloClient, - deleteOneRecordMutation, + destroyOneRecordMutation, getRecordFromCache, objectMetadataItem, objectMetadataItems, diff --git a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts index 139fff85ff..1dae1bf39d 100644 --- a/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts +++ b/packages/twenty-front/src/modules/views/hooks/internal/usePersistViewSortRecords.ts @@ -1,15 +1,15 @@ -import { useCallback } from 'react'; import { useApolloClient } from '@apollo/client'; +import { useCallback } from 'react'; 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 { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; -import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; +import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation'; import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { GraphQLView } from '@/views/types/GraphQLView'; @@ -24,7 +24,7 @@ export const usePersistViewSortRecords = () => { objectNameSingular: CoreObjectNameSingular.ViewSort, }); - const { deleteOneRecordMutation } = useDeleteOneRecordMutation({ + const { destroyOneRecordMutation } = useDestroyOneRecordMutation({ objectNameSingular: CoreObjectNameSingular.ViewSort, }); @@ -124,12 +124,12 @@ export const usePersistViewSortRecords = () => { return Promise.all( viewSortIdsToDelete.map((viewSortId) => apolloClient.mutate({ - mutation: deleteOneRecordMutation, + mutation: destroyOneRecordMutation, variables: { - idToDelete: viewSortId, + idToDestroy: viewSortId, }, update: (cache, { data }) => { - const record = data?.['deleteViewSort']; + const record = data?.['destroyViewSort']; if (!record) return; @@ -137,10 +137,10 @@ export const usePersistViewSortRecords = () => { if (!cachedRecord) return; - triggerDeleteRecordsOptimisticEffect({ + triggerDestroyRecordsOptimisticEffect({ cache, objectMetadataItem, - recordsToDelete: [cachedRecord], + recordsToDestroy: [cachedRecord], objectMetadataItems, }); }, @@ -150,7 +150,7 @@ export const usePersistViewSortRecords = () => { }, [ apolloClient, - deleteOneRecordMutation, + destroyOneRecordMutation, getRecordFromCache, objectMetadataItem, objectMetadataItems, diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts index 920fa01c56..63f64b8ef3 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-field.parser.ts @@ -82,62 +82,62 @@ export class GraphqlQueryFilterFieldParser { switch (operator) { case 'eq': return { - sql: `${objectNameSingular}.${key} = :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" = :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'neq': return { - sql: `${objectNameSingular}.${key} != :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" != :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'gt': return { - sql: `${objectNameSingular}.${key} > :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" > :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'gte': return { - sql: `${objectNameSingular}.${key} >= :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" >= :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'lt': return { - sql: `${objectNameSingular}.${key} < :${key}${uuid}`, + sql: `"${objectNameSingular}".${key} < :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'lte': return { - sql: `${objectNameSingular}.${key} <= :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" <= :${key}${uuid}`, params: { [`${key}${uuid}`]: value }, }; case 'in': return { - sql: `${objectNameSingular}.${key} IN (:...${key}${uuid})`, + sql: `"${objectNameSingular}"."${key}" IN (:...${key}${uuid})`, params: { [`${key}${uuid}`]: value }, }; case 'is': return { - sql: `${objectNameSingular}.${key} IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`, + sql: `"${objectNameSingular}"."${key}" IS ${value === 'NULL' ? 'NULL' : 'NOT NULL'}`, params: {}, }; case 'like': return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, params: { [`${key}${uuid}`]: `${value}` }, }; case 'ilike': return { - sql: `${objectNameSingular}.${key} ILIKE :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" ILIKE :${key}${uuid}`, params: { [`${key}${uuid}`]: `${value}` }, }; case 'startsWith': return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, params: { [`${key}${uuid}`]: `${value}` }, }; case 'endsWith': return { - sql: `${objectNameSingular}.${key} LIKE :${key}${uuid}`, + sql: `"${objectNameSingular}"."${key}" LIKE :${key}${uuid}`, params: { [`${key}${uuid}`]: `${value}` }, }; default: diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts index 483222a490..5467d6c0ff 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-destroy-one-resolver.service.ts @@ -58,7 +58,7 @@ export class GraphqlQueryDestroyOneResolverService ); const nonFormattedDeletedObjectRecords = await queryBuilder - .where({ + .where(`"${objectMetadataMapItem.nameSingular}".id = :id`, { id: args.id, }) .take(1) diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts index 270e6fd819..020bf08fa7 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service.ts @@ -16,6 +16,7 @@ import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/obj import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; +import { computeTableName } from 'src/engine/utils/compute-table-name.util'; @Injectable() export class GraphqlQueryUpdateManyResolverService @@ -57,9 +58,14 @@ export class GraphqlQueryUpdateManyResolverService objectMetadataMapItem.nameSingular, ); + const tableName = computeTableName( + objectMetadataMapItem.nameSingular, + objectMetadataMapItem.isCustom, + ); + const withFilterQueryBuilder = graphqlQueryParser.applyFilterToBuilder( queryBuilder, - objectMetadataMapItem.nameSingular, + tableName, args.filter, );