diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx index 9eba7ca23..0f0b2efd6 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/__tests__/useDeleteMultipleRecordsAction.test.tsx @@ -1,3 +1,4 @@ +import { DeleteManyRecordsProps } from '@/object-record/hooks/useDeleteManyRecords'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { renderHook, waitFor } from '@testing-library/react'; import { act } from 'react'; @@ -75,13 +76,12 @@ describe('useDeleteMultipleRecordsAction', () => { result.current.ConfirmationModal?.props?.onConfirmClick(); }); + const expectedParams: DeleteManyRecordsProps = { + recordIdsToDelete: [peopleMock[0].id, peopleMock[1].id], + }; await waitFor(() => { expect(resetTableRowSelectionMock).toHaveBeenCalled(); - - expect(deleteManyRecordsMock).toHaveBeenCalledWith([ - peopleMock[0].id, - peopleMock[1].id, - ]); + expect(deleteManyRecordsMock).toHaveBeenCalledWith(expectedParams); }); }); }); diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx index 1bd31b625..6b052d60c 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/multiple-records/hooks/useDeleteMultipleRecordsAction.tsx @@ -73,7 +73,9 @@ export const useDeleteMultipleRecordsAction: ActionHookWithObjectMetadataItem = resetTableRowSelection(); - await deleteManyRecords(recordIdsToDelete); + await deleteManyRecords({ + recordIdsToDelete, + }); }, [deleteManyRecords, fetchAllRecordIds, resetTableRowSelection]); const isRemoteObject = objectMetadataItem.isRemote; diff --git a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx index 107d68aab..cea62d143 100644 --- a/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx +++ b/packages/twenty-front/src/modules/action-menu/actions/record-actions/single-record/hooks/useDestroySingleRecordAction.tsx @@ -4,11 +4,13 @@ import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext'; import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord'; import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; +import { AppPath } from '@/types/AppPath'; import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer'; import { useCallback, useContext, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-ui'; +import { useNavigateApp } from '~/hooks/useNavigateApp'; export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ objectMetadataItem, @@ -18,6 +20,8 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ const [isDestroyRecordsModalOpen, setIsDestroyRecordsModalOpen] = useState(false); + const navigateApp = useNavigateApp(); + const { resetTableRowSelection } = useRecordTable({ recordTableId: objectMetadataItem.namePlural, }); @@ -34,7 +38,16 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({ resetTableRowSelection(); await destroyOneRecord(recordId); - }, [resetTableRowSelection, destroyOneRecord, recordId]); + navigateApp(AppPath.RecordIndexPage, { + objectNamePlural: objectMetadataItem.namePlural, + }); + }, [ + resetTableRowSelection, + destroyOneRecord, + recordId, + navigateApp, + objectMetadataItem.namePlural, + ]); const isRemoteObject = objectMetadataItem.isRemote; diff --git a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts index 6513ac7d2..98dc4a0a4 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useOpenCreateActivityDrawer.ts @@ -92,28 +92,26 @@ export const useOpenCreateActivityDrawer = ({ const targetableObjectRelationIdName = `${targetableObjects[0].targetObjectNameSingular}Id`; await createOneActivityTarget({ - taskId: - activityObjectNameSingular === CoreObjectNameSingular.Task - ? activity.id - : undefined, - noteId: - activityObjectNameSingular === CoreObjectNameSingular.Note - ? activity.id - : undefined, + ...(activityObjectNameSingular === CoreObjectNameSingular.Task + ? { + taskId: activity.id, + } + : { + noteId: activity.id, + }), [targetableObjectRelationIdName]: targetableObjects[0].id, }); setActivityTargetableEntityArray(targetableObjects); } else { await createOneActivityTarget({ - taskId: - activityObjectNameSingular === CoreObjectNameSingular.Task - ? activity.id - : undefined, - noteId: - activityObjectNameSingular === CoreObjectNameSingular.Note - ? activity.id - : undefined, + ...(activityObjectNameSingular === CoreObjectNameSingular.Task + ? { + taskId: activity.id, + } + : { + noteId: activity.id, + }), }); setActivityTargetableEntityArray([]); diff --git a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx index 5db27a0e8..b3c4c6534 100644 --- a/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx +++ b/packages/twenty-front/src/modules/activities/inline-cell/components/ActivityTargetInlineCellEditMode.tsx @@ -15,8 +15,8 @@ import { getJoinObjectNameSingular } from '@/activities/utils/getJoinObjectNameS import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateManyRecordsInCache } from '@/object-record/cache/hooks/useCreateManyRecordsInCache'; -import { useCreateManyRecords } from '@/object-record/hooks/useCreateManyRecords'; -import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords'; +import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord'; import { activityTargetObjectRecordFamilyState } from '@/object-record/record-field/states/activityTargetObjectRecordFamilyState'; import { objectRecordMultiSelectCheckedRecordsIdsComponentState } from '@/object-record/record-field/states/objectRecordMultiSelectCheckedRecordsIdsComponentState'; import { @@ -54,17 +54,15 @@ export const ActivityTargetInlineCellEditMode = ({ }), ); - const { createManyRecords: createManyActivityTargets } = useCreateManyRecords< + const { createOneRecord: createOneActivityTarget } = useCreateOneRecord< NoteTarget | TaskTarget >({ objectNameSingular: getJoinObjectNameSingular(activityObjectNameSingular), }); - const { deleteManyRecords: deleteManyActivityTargets } = useDeleteManyRecords( - { - objectNameSingular: getJoinObjectNameSingular(activityObjectNameSingular), - }, - ); + const { deleteOneRecord: deleteOneActivityTarget } = useDeleteOneRecord({ + objectNameSingular: getJoinObjectNameSingular(activityObjectNameSingular), + }); const { closeInlineCell: closeEditableField } = useInlineCell(); @@ -168,36 +166,21 @@ export const ActivityTargetInlineCellEditMode = ({ ); const newActivityTargetId = v4(); - const fieldName = record.objectMetadataItem.nameSingular; const fieldNameWithIdSuffix = getActivityTargetObjectFieldIdName({ nameSingular: record.objectMetadataItem.nameSingular, }); + const newActivityTargetInput = { + id: newActivityTargetId, + ...(activityObjectNameSingular === CoreObjectNameSingular.Task + ? { taskId: activity.id } + : { noteId: activity.id }), + [fieldNameWithIdSuffix]: recordId, + }; + const newActivityTarget = prefillRecord({ objectMetadataItem: objectMetadataItemActivityTarget, - input: { - id: newActivityTargetId, - taskId: - activityObjectNameSingular === CoreObjectNameSingular.Task - ? activity.id - : null, - task: - activityObjectNameSingular === CoreObjectNameSingular.Task - ? activity - : null, - noteId: - activityObjectNameSingular === CoreObjectNameSingular.Note - ? activity.id - : null, - note: - activityObjectNameSingular === CoreObjectNameSingular.Note - ? activity - : null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - [fieldName]: record.record, - [fieldNameWithIdSuffix]: recordId, - }, + input: newActivityTargetInput, }); activityTargetsAfterUpdate.push(newActivityTarget); @@ -215,7 +198,7 @@ export const ActivityTargetInlineCellEditMode = ({ }, }); } else { - await createManyActivityTargets([newActivityTarget]); + await createOneActivityTarget(newActivityTargetInput); } set(activityTargetObjectRecordFamilyState(recordId), { @@ -252,7 +235,7 @@ export const ActivityTargetInlineCellEditMode = ({ }, }); } else { - await deleteManyActivityTargets([activityTargetToDeleteId]); + await deleteOneActivityTarget(activityTargetToDeleteId); } set(activityTargetObjectRecordFamilyState(recordId), { @@ -263,9 +246,9 @@ export const ActivityTargetInlineCellEditMode = ({ [ activity, activityTargetWithTargetRecords, - createManyActivityTargets, + createOneActivityTarget, createManyActivityTargetsInCache, - deleteManyActivityTargets, + deleteOneActivityTarget, isActivityInCreateMode, objectMetadataItemActivityTarget, recordPickerInstanceId, 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 f62650f68..d74849eec 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 @@ -27,6 +27,10 @@ export const triggerUpdateRelationsOptimisticEffect = ({ updatedSourceRecord, objectMetadataItems, }: triggerUpdateRelationsOptimisticEffectArgs) => { + const isDeletion = + isDefined(updatedSourceRecord) && + isDefined(updatedSourceRecord['deletedAt']); + return sourceObjectMetadataItem.fields.forEach( (fieldMetadataItemOnSourceRecord) => { const notARelationField = @@ -72,15 +76,15 @@ export const triggerUpdateRelationsOptimisticEffect = ({ | RecordGqlNode | null = updatedSourceRecord?.[fieldMetadataItemOnSourceRecord.name]; - if ( - isDeeplyEqual( - currentFieldValueOnSourceRecord, - updatedFieldValueOnSourceRecord, - { strict: true }, - ) - ) { + const noDiff = isDeeplyEqual( + currentFieldValueOnSourceRecord, + updatedFieldValueOnSourceRecord, + { strict: true }, + ); + if (noDiff && !isDeletion) { return; } + const extractTargetRecordsFromRelation = ( value: RecordGqlConnection | RecordGqlNode | null, ): RecordGqlNode[] => { @@ -96,11 +100,12 @@ export const triggerUpdateRelationsOptimisticEffect = ({ return [value]; }; + + const recordToExtractDetachFrom = isDeletion + ? updatedFieldValueOnSourceRecord + : currentFieldValueOnSourceRecord; const targetRecordsToDetachFrom = extractTargetRecordsFromRelation( - currentFieldValueOnSourceRecord, - ); - const targetRecordsToAttachTo = extractTargetRecordsFromRelation( - updatedFieldValueOnSourceRecord, + recordToExtractDetachFrom, ); // TODO: see if we can de-hardcode this, put cascade delete in relation metadata item @@ -129,7 +134,11 @@ export const triggerUpdateRelationsOptimisticEffect = ({ }); } - if (isDefined(updatedSourceRecord)) { + if (!isDeletion && isDefined(updatedSourceRecord)) { + const targetRecordsToAttachTo = extractTargetRecordsFromRelation( + updatedFieldValueOnSourceRecord, + ); + targetRecordsToAttachTo.forEach((targetRecordToAttachTo) => triggerAttachRelationOptimisticEffect({ cache, diff --git a/packages/twenty-front/src/modules/favorites/hooks/useCreateFavoriteFolder.ts b/packages/twenty-front/src/modules/favorites/hooks/useCreateFavoriteFolder.ts index c8b121e69..bee2fbea9 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useCreateFavoriteFolder.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useCreateFavoriteFolder.ts @@ -22,7 +22,6 @@ export const useCreateFavoriteFolder = () => { ); await createFavoriteFolder({ - workspaceMemberId: currentWorkspaceMemberId, name, position: maxPosition + 1, }); 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 2f6f68b15..6d0bf4250 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 @@ -52,7 +52,9 @@ describe('useDeleteManyRecords', () => { ); await act(async () => { - const res = await result.current.deleteManyRecords(personIds); + const res = await result.current.deleteManyRecords({ + recordIdsToDelete: 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 da2b76662..0988811a6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -135,7 +135,7 @@ export const useCreateManyRecords = < update: (cache, { data }) => { const records = data?.[mutationResponseField]; - if (!records?.length || skipPostOptmisticEffect) return; + if (!isDefined(records?.length) || skipPostOptmisticEffect) return; triggerCreateRecordsOptimisticEffect({ cache, 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 c60986e73..cbeaf1bce 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -24,7 +24,8 @@ type useDeleteManyRecordProps = { refetchFindManyQuery?: boolean; }; -type DeleteManyRecordsOptions = { +export type DeleteManyRecordsProps = { + recordIdsToDelete: string[]; skipOptimisticEffect?: boolean; delayInMsBetweenRequests?: number; }; @@ -61,16 +62,18 @@ export const useDeleteManyRecords = ({ objectMetadataItem.namePlural, ); - const deleteManyRecords = async ( - idsToDelete: string[], - options?: DeleteManyRecordsOptions, - ) => { - const numberOfBatches = Math.ceil(idsToDelete.length / mutationPageSize); - + const deleteManyRecords = async ({ + recordIdsToDelete, + delayInMsBetweenRequests, + skipOptimisticEffect = false, + }: DeleteManyRecordsProps) => { + const numberOfBatches = Math.ceil( + recordIdsToDelete.length / mutationPageSize, + ); const deletedRecords = []; for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) { - const batchedIdsToDelete = idsToDelete.slice( + const batchedIdsToDelete = recordIdsToDelete.slice( batchIndex * mutationPageSize, (batchIndex + 1) * mutationPageSize, ); @@ -81,22 +84,21 @@ export const useDeleteManyRecords = ({ .map((idToDelete) => getRecordFromCache(idToDelete, apolloClient.cache)) .filter(isDefined); - const cachedRecordsWithConnection: RecordGqlNode[] = []; - const optimisticRecordsWithConnection: RecordGqlNode[] = []; + if (!skipOptimisticEffect) { + const cachedRecordsNode: RecordGqlNode[] = []; + const computedOptimisticRecordsNode: RecordGqlNode[] = []; - if (!options?.skipOptimisticEffect) { cachedRecords.forEach((cachedRecord) => { - if (!cachedRecord || !cachedRecord.id) { + if (!isDefined(cachedRecord) || !isDefined(cachedRecord.id)) { return; } - const cachedRecordWithConnection = - getRecordNodeFromRecord({ - record: cachedRecord, - objectMetadataItem, - objectMetadataItems, - computeReferences: true, - }); + const cachedRecordNode = getRecordNodeFromRecord({ + record: cachedRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: false, + }); const computedOptimisticRecord = { ...cachedRecord, @@ -104,34 +106,36 @@ export const useDeleteManyRecords = ({ ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, }; - const optimisticRecordWithConnection = - getRecordNodeFromRecord({ - record: computedOptimisticRecord, - objectMetadataItem, - objectMetadataItems, - computeReferences: true, - }); + const optimisticRecordNode = getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: false, + }); - if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { - return null; + if ( + !isDefined(optimisticRecordNode) || + !isDefined(cachedRecordNode) + ) { + return; } - cachedRecordsWithConnection.push(cachedRecordWithConnection); - optimisticRecordsWithConnection.push(optimisticRecordWithConnection); - updateRecordFromCache({ objectMetadataItems, objectMetadataItem, cache: apolloClient.cache, record: computedOptimisticRecord, }); + + computedOptimisticRecordsNode.push(optimisticRecordNode); + cachedRecordsNode.push(cachedRecordNode); }); triggerUpdateRecordOptimisticEffectByBatch({ cache: apolloClient.cache, objectMetadataItem, - currentRecords: cachedRecordsWithConnection, - updatedRecords: optimisticRecordsWithConnection, + currentRecords: cachedRecordsNode, + updatedRecords: computedOptimisticRecordsNode, objectMetadataItems, }); } @@ -144,8 +148,8 @@ export const useDeleteManyRecords = ({ }, }) .catch((error: Error) => { - const cachedRecordsWithConnection: RecordGqlNode[] = []; - const optimisticRecordsWithConnection: RecordGqlNode[] = []; + const cachedRecordsNode: RecordGqlNode[] = []; + const computedOptimisticRecordsNode: RecordGqlNode[] = []; cachedRecords.forEach((cachedRecord) => { if (isUndefinedOrNull(cachedRecord?.id)) { @@ -164,7 +168,7 @@ export const useDeleteManyRecords = ({ record: cachedRecord, objectMetadataItem, objectMetadataItems, - computeReferences: true, + computeReferences: false, }); const computedOptimisticRecord = { @@ -178,27 +182,25 @@ export const useDeleteManyRecords = ({ record: computedOptimisticRecord, objectMetadataItem, objectMetadataItems, - computeReferences: true, + computeReferences: false, }); if ( - !optimisticRecordWithConnection || - !cachedRecordWithConnection + !isDefined(optimisticRecordWithConnection) || + !isDefined(cachedRecordWithConnection) ) { return; } - cachedRecordsWithConnection.push(cachedRecordWithConnection); - optimisticRecordsWithConnection.push( - optimisticRecordWithConnection, - ); + cachedRecordsNode.push(cachedRecordWithConnection); + computedOptimisticRecordsNode.push(optimisticRecordWithConnection); }); triggerUpdateRecordOptimisticEffectByBatch({ cache: apolloClient.cache, objectMetadataItem, - currentRecords: optimisticRecordsWithConnection, - updatedRecords: cachedRecordsWithConnection, + currentRecords: computedOptimisticRecordsNode, + updatedRecords: cachedRecordsNode, objectMetadataItems, }); @@ -210,8 +212,8 @@ export const useDeleteManyRecords = ({ deletedRecords.push(...deletedRecordsForThisBatch); - if (isDefined(options?.delayInMsBetweenRequests)) { - await sleep(options.delayInMsBetweenRequests); + if (isDefined(delayInMsBetweenRequests)) { + await sleep(delayInMsBetweenRequests); } } await refetchAggregateQueries(); 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 816c975d4..58dc741e1 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -12,6 +12,7 @@ import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggr import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { capitalize } from 'twenty-shared'; +import { isDefined } from 'twenty-ui'; type useDeleteOneRecordProps = { objectNameSingular: string; @@ -49,11 +50,11 @@ export const useDeleteOneRecord = ({ const cachedRecord = getRecordFromCache(idToDelete, apolloClient.cache); - const cachedRecordWithConnection = getRecordNodeFromRecord({ + const cachedRecordNode = getRecordNodeFromRecord({ record: cachedRecord, objectMetadataItem, objectMetadataItems, - computeReferences: true, + computeReferences: false, }); const computedOptimisticRecord = { @@ -62,15 +63,14 @@ export const useDeleteOneRecord = ({ ...{ __typename: capitalize(objectMetadataItem.nameSingular) }, }; - const optimisticRecordWithConnection = - getRecordNodeFromRecord({ - record: computedOptimisticRecord, - objectMetadataItem, - objectMetadataItems, - computeReferences: true, - }); + const optimisticRecordNode = getRecordNodeFromRecord({ + record: computedOptimisticRecord, + objectMetadataItem, + objectMetadataItems, + computeReferences: false, + }); - if (!optimisticRecordWithConnection || !cachedRecordWithConnection) { + if (!isDefined(optimisticRecordNode) || !isDefined(cachedRecordNode)) { return null; } @@ -84,8 +84,8 @@ export const useDeleteOneRecord = ({ triggerUpdateRecordOptimisticEffect({ cache: apolloClient.cache, objectMetadataItem, - currentRecord: cachedRecordWithConnection, - updatedRecord: optimisticRecordWithConnection, + currentRecord: cachedRecordNode, + updatedRecord: optimisticRecordNode, objectMetadataItems, }); @@ -98,12 +98,13 @@ export const useDeleteOneRecord = ({ update: (cache, { data }) => { const record = data?.[mutationResponseField]; - if (!record || !cachedRecord) return; + if (!isDefined(record) || !isDefined(computedOptimisticRecord)) + return; triggerUpdateRecordOptimisticEffect({ cache, objectMetadataItem, - currentRecord: cachedRecord, + currentRecord: computedOptimisticRecord, updatedRecord: record, objectMetadataItems, }); @@ -123,8 +124,8 @@ export const useDeleteOneRecord = ({ triggerUpdateRecordOptimisticEffect({ cache: apolloClient.cache, objectMetadataItem, - currentRecord: optimisticRecordWithConnection, - updatedRecord: cachedRecordWithConnection, + currentRecord: optimisticRecordNode, + updatedRecord: cachedRecordNode, objectMetadataItems, }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts index f5ffd7dc0..3cc80ffc0 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -14,6 +14,7 @@ import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeO import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; import { capitalize } from 'twenty-shared'; +import { isDefined } from 'twenty-ui'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; type useUpdateOneRecordProps = { @@ -130,7 +131,8 @@ export const useUpdateOneRecord = < update: (cache, { data }) => { const record = data?.[mutationResponseField]; - if (!record || !computedOptimisticRecord) return; + if (!isDefined(record) || !isDefined(computedOptimisticRecord)) + return; triggerUpdateRecordOptimisticEffect({ cache, diff --git a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts index 825e71d34..0f9152797 100644 --- a/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts +++ b/packages/twenty-front/src/modules/object-record/utils/__tests__/computeOptimisticRecordFromInput.test.ts @@ -179,4 +179,28 @@ describe('computeOptimisticRecordFromInput', () => { `"Should never provide relation mutation through anything else than the fieldId e.g companyId"`, ); }); + + it('should throw an error if recordInput contains both the relationFieldId and relationField even if null', () => { + const cache = new InMemoryCache(); + const personObjectMetadataItem = generatedMockObjectMetadataItems.find( + (item) => item.nameSingular === 'person', + ); + if (!personObjectMetadataItem) { + throw new Error('Person object metadata item not found'); + } + + expect(() => + computeOptimisticRecordFromInput({ + objectMetadataItems: generatedMockObjectMetadataItems, + objectMetadataItem: personObjectMetadataItem, + recordInput: { + companyId: '123', + company: null, + }, + cache, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Should never provide relation mutation through anything else than the fieldId e.g companyId"`, + ); + }); }); diff --git a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts index 932f89166..a553c3f2e 100644 --- a/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/computeOptimisticRecordFromInput.ts @@ -91,7 +91,7 @@ export const computeOptimisticRecordFromInput = ({ continue; } - if (isDefined(recordInputFieldValue)) { + if (!isUndefined(recordInputFieldValue)) { throw new Error( 'Should never provide relation mutation through anything else than the fieldId e.g companyId', ); diff --git a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts index c1043c0b4..effe3c870 100644 --- a/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts +++ b/packages/twenty-front/src/modules/object-record/utils/sanitizeRecordInput.ts @@ -34,8 +34,10 @@ export const sanitizeRecordInput = ({ (field) => field.name === relationIdFieldName, ); + const relationIdFieldValue = recordInput[relationIdFieldName]; + return relationIdFieldMetadataItem - ? [relationIdFieldName, fieldValue?.id ?? null] + ? [relationIdFieldName, relationIdFieldValue ?? null] : undefined; }