diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts index 7c89d820a..fa35ff9d8 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/useObjectMetadataItem.ts @@ -10,6 +10,7 @@ import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadat import { getObjectMetadataItemsMock } from '@/object-metadata/utils/getObjectMetadataItemsMock'; import { useGenerateCreateManyRecordMutation } from '@/object-record/hooks/useGenerateCreateManyRecordMutation'; import { useGenerateCreateOneRecordMutation } from '@/object-record/hooks/useGenerateCreateOneRecordMutation'; +import { useGenerateDeleteManyRecordMutation } from '@/object-record/hooks/useGenerateDeleteManyRecordMutation'; import { useGenerateExecuteQuickActionOnOneRecordMutation } from '@/object-record/hooks/useGenerateExecuteQuickActionOnOneRecordMutation'; import { useGenerateFindManyRecordsQuery } from '@/object-record/hooks/useGenerateFindManyRecordsQuery'; import { useGenerateFindOneRecordQuery } from '@/object-record/hooks/useGenerateFindOneRecordQuery'; @@ -107,6 +108,10 @@ export const useObjectMetadataItem = ( objectMetadataItem, }); + const deleteManyRecordsMutation = useGenerateDeleteManyRecordMutation({ + objectMetadataItem, + }); + const executeQuickActionOnOneRecordMutation = useGenerateExecuteQuickActionOnOneRecordMutation({ objectMetadataItem, @@ -131,6 +136,7 @@ export const useObjectMetadataItem = ( deleteOneRecordMutation, executeQuickActionOnOneRecordMutation, createManyRecordsMutation, + deleteManyRecordsMutation, mapToObjectRecordIdentifier, getObjectOrderByField, }; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts new file mode 100644 index 000000000..16bbcc72b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -0,0 +1,70 @@ +import { useApolloClient } from '@apollo/client'; +import { getOperationName } from '@apollo/client/utilities'; + +import { useOptimisticEffect } from '@/apollo/optimistic-effect/hooks/useOptimisticEffect'; +import { useOptimisticEvict } from '@/apollo/optimistic-effect/hooks/useOptimisticEvict'; +import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { ObjectRecordQueryFilter } from '@/object-record/record-filter/types/ObjectRecordQueryFilter'; +import { capitalize } from '~/utils/string/capitalize'; + +type useDeleteOneRecordProps = { + objectNameSingular: string; + refetchFindManyQuery?: boolean; +}; + +export const useDeleteManyRecords = ({ + objectNameSingular, + refetchFindManyQuery = false, +}: useDeleteOneRecordProps) => { + const { performOptimisticEvict } = useOptimisticEvict(); + const { triggerOptimisticEffects } = useOptimisticEffect({ + objectNameSingular, + }); + + const { + objectMetadataItem, + deleteManyRecordsMutation, + findManyRecordsQuery, + } = useObjectMetadataItem({ + objectNameSingular, + }); + + const apolloClient = useApolloClient(); + + const deleteManyRecords = async (idsToDelete: string[]) => { + triggerOptimisticEffects({ + typename: `${capitalize(objectMetadataItem.nameSingular)}Edge`, + deletedRecordIds: idsToDelete, + }); + + idsToDelete.forEach((idToDelete) => { + performOptimisticEvict( + capitalize(objectMetadataItem.nameSingular), + 'id', + idToDelete, + ); + }); + + const deleteRecordFilter: ObjectRecordQueryFilter = { + id: { + in: idsToDelete, + }, + }; + const deletedRecords = await apolloClient.mutate({ + mutation: deleteManyRecordsMutation, + variables: { + filter: deleteRecordFilter, + // atMost: idsToDelete.length, + }, + refetchQueries: refetchFindManyQuery + ? [getOperationName(findManyRecordsQuery) ?? ''] + : [], + }); + + return deletedRecords.data[ + `delete${capitalize(objectMetadataItem.namePlural)}` + ] as T; + }; + + return { deleteManyRecords }; +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts new file mode 100644 index 000000000..9efc5464b --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useGenerateDeleteManyRecordMutation.ts @@ -0,0 +1,27 @@ +import { gql } from '@apollo/client'; + +import { EMPTY_MUTATION } from '@/object-metadata/hooks/useObjectMetadataItem'; +import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { capitalize } from '~/utils/string/capitalize'; + +export const useGenerateDeleteManyRecordMutation = ({ + objectMetadataItem, +}: { + objectMetadataItem: ObjectMetadataItem; +}) => { + if (!objectMetadataItem) { + return EMPTY_MUTATION; + } + + const capitalizedObjectName = capitalize(objectMetadataItem.namePlural); + + return gql` + mutation DeleteMany${capitalizedObjectName}($filter: ${capitalize( + objectMetadataItem.nameSingular, + )}FilterInput!) { + delete${capitalizedObjectName}(filter: $filter) { + id + } + } + `; +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx b/packages/twenty-front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx index 7abe0a655..ea7b4a473 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/useRecordTableContextMenuEntries.tsx @@ -4,7 +4,7 @@ import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil'; import { useFavorites } from '@/favorites/hooks/useFavorites'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; -import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; import { useExecuteQuickActionOnOneRecord } from '@/object-record/hooks/useExecuteQuickActionOnOneRecord'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { RecordTableScopeInternalContext } from '@/object-record/record-table/scopes/scope-internal-context/RecordTableScopeInternalContext'; @@ -71,7 +71,7 @@ export const useRecordTableContextMenuEntries = ( } }); - const { deleteOneRecord } = useDeleteOneRecord({ + const { deleteManyRecords } = useDeleteManyRecords({ objectNameSingular, }); @@ -87,13 +87,9 @@ export const useRecordTableContextMenuEntries = ( .getValue(); resetTableRowSelection(); - await Promise.all( - rowIdsToDelete.map(async (rowId) => { - await deleteOneRecord(rowId); - }), - ); + await deleteManyRecords(rowIdsToDelete); }, - [deleteOneRecord, resetTableRowSelection], + [deleteManyRecords, resetTableRowSelection], ); const handleExecuteQuickActionOnClick = useRecoilCallback( diff --git a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal.ts b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal.ts index 05a07114e..ab88e85bc 100644 --- a/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal.ts +++ b/packages/twenty-front/src/modules/object-record/record-board/hooks/internal/useDeleteSelectedRecordBoardCardsInternal.ts @@ -1,7 +1,7 @@ import { useApolloClient } from '@apollo/client'; import { useRecoilCallback } from 'recoil'; -import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; +import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; import { useRecordBoardScopedStates } from '@/object-record/record-board/hooks/internal/useRecordBoardScopedStates'; import { Opportunity } from '@/pipeline/types/Opportunity'; @@ -11,8 +11,8 @@ export const useDeleteSelectedRecordBoardCardsInternal = () => { const removeCardIds = useRemoveRecordBoardCardIdsInternal(); const apolloClient = useApolloClient(); - const { deleteOneRecord: deleteOneOpportunity } = - useDeleteOneRecord({ + const { deleteManyRecords: deleteManyOpportunities } = + useDeleteManyRecords({ objectNameSingular: 'opportunity', }); @@ -25,11 +25,7 @@ export const useDeleteSelectedRecordBoardCardsInternal = () => { .getLoadable(selectedCardIdsSelector) .getValue(); - await Promise.all( - selectedCardIds.map(async (id) => { - await deleteOneOpportunity?.(id); - }), - ); + await deleteManyOpportunities?.(selectedCardIds); removeCardIds(selectedCardIds); selectedCardIds.forEach((id) => { apolloClient.cache.evict({ id: `Opportunity:${id}` }); @@ -38,7 +34,7 @@ export const useDeleteSelectedRecordBoardCardsInternal = () => { [ selectedCardIdsSelector, removeCardIds, - deleteOneOpportunity, + deleteManyOpportunities, apolloClient.cache, ], ); diff --git a/packages/twenty-server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts b/packages/twenty-server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts index d51a078bb..1c0086d41 100644 --- a/packages/twenty-server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts +++ b/packages/twenty-server/src/workspace/workspace-query-builder/factories/delete-many-query.factory.ts @@ -24,7 +24,7 @@ export class DeleteManyQueryFactory { mutation { deleteFrom${ options.targetTableName - }Collection(filter: ${stringifyWithoutKeyQuote(args.filter)}) { + }Collection(filter: ${stringifyWithoutKeyQuote(args.filter)}, atMost: 30) { affectedCount records { ${fieldsString}