Remove useUpdateOneRecordV2 (#17355)

Previously we introduced useUpdateOneRecordV2 to handle morph relations.
In this PR we are removing it to only use useUpdateOneRecord which has
been adapted not to requires objectMetadataNameSingular in its props.
This commit is contained in:
Marie
2026-01-22 17:27:57 +01:00
committed by GitHub
parent 873749981b
commit 8868ef4ec3
57 changed files with 268 additions and 618 deletions
@@ -28,10 +28,8 @@ describe('useRefetchAggregateQueries', () => {
});
// Act
const { result } = renderHook(() =>
useRefetchAggregateQueries({ objectMetadataNamePlural }),
);
await result.current.refetchAggregateQueries();
const { result } = renderHook(() => useRefetchAggregateQueries());
await result.current.refetchAggregateQueries({ objectMetadataNamePlural });
// Assert
expect(mockRefetchQueries).toHaveBeenCalledTimes(1);
@@ -47,13 +45,11 @@ describe('useRefetchAggregateQueries', () => {
const objectMetadataNamePlural = 'opportunities';
// Act
const { result } = renderHook(() =>
useRefetchAggregateQueries({ objectMetadataNamePlural }),
);
const { result } = renderHook(() => useRefetchAggregateQueries());
// Assert
await expect(result.current.refetchAggregateQueries()).rejects.toThrow(
'Refetch failed',
);
await expect(
result.current.refetchAggregateQueries({ objectMetadataNamePlural }),
).rejects.toThrow('Refetch failed');
});
});
@@ -53,15 +53,13 @@ describe('useUpdateOneRecord', () => {
jest.clearAllMocks();
});
it('works as expected', async () => {
const { result } = renderHook(
() => useUpdateOneRecord({ objectNameSingular: 'person' }),
{
wrapper: Wrapper,
},
);
const { result } = renderHook(() => useUpdateOneRecord(), {
wrapper: Wrapper,
});
await act(async () => {
const res = await result.current.updateOneRecord({
objectNameSingular: 'person',
idToUpdate,
updateOneRecordInput: updateInput,
});
@@ -1,44 +0,0 @@
import { renderHook } from '@testing-library/react';
import { print } from 'graphql';
import { PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS } from '@/object-record/hooks/__mocks__/personFragments';
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery';
const expectedQueryTemplate = `
mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) {
updatePerson(id: $idToUpdate, data: $input) {
${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS}
}
}`;
const Wrapper = getJestMetadataAndApolloMocksWrapper({
apolloMocks: [],
});
describe('useUpdateOneRecordMutation', () => {
it('should return a valid createManyRecordsMutation', () => {
const objectNameSingular = 'person';
const { result } = renderHook(
() =>
useUpdateOneRecordMutation({
objectNameSingular,
}),
{
wrapper: Wrapper,
},
);
const { updateOneRecordMutation } = result.current;
expect(updateOneRecordMutation).toBeDefined();
const printedReceivedQuery = print(updateOneRecordMutation);
expect(normalizeGQLQuery(printedReceivedQuery)).toEqual(
normalizeGQLQuery(expectedQueryTemplate),
);
});
});
@@ -39,9 +39,7 @@ export const useBatchCreateManyRecords = <
objectNameSingular,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const { enqueueWarningSnackBar } = useSnackBar();
const { formatNumber } = useNumberFormat();
@@ -96,7 +94,9 @@ export const useBatchCreateManyRecords = <
}
}
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -82,9 +82,7 @@ export const useCreateManyRecords = <
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
type createManyRecordsProps = {
recordsToCreate: Partial<CreatedObjectRecord>[];
@@ -239,7 +237,9 @@ export const useCreateManyRecords = <
});
if (shouldRefetchAggregateQueries) {
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
}
dispatchObjectRecordOperationBrowserEvent({
@@ -79,9 +79,7 @@ export const useCreateOneRecord = <
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const createOneRecord = async (recordInput: Partial<CreatedObjectRecord>) => {
setLoading(true);
@@ -189,7 +187,9 @@ export const useCreateOneRecord = <
throw error;
});
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const positionToUse =
recordInput.position === 'first'
@@ -56,9 +56,7 @@ export const useDeleteManyRecords = ({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const mutationResponseField = getDeleteManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
@@ -221,7 +219,9 @@ export const useDeleteManyRecords = ({
await sleep(delayInMsBetweenRequests);
}
}
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -43,9 +43,7 @@ export const useDeleteOneRecord = ({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const mutationResponseField =
getDeleteOneRecordMutationResponseField(objectNameSingular);
@@ -162,7 +160,9 @@ export const useDeleteOneRecord = ({
throw error;
});
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -51,9 +51,7 @@ export const useDestroyManyRecords = ({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const mutationResponseField = getDestroyManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
@@ -142,7 +140,9 @@ export const useDestroyManyRecords = ({
}
}
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -60,9 +60,7 @@ export const useIncrementalDeleteManyRecords = <T>({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const { incrementalFetchAndMutate, progress, isProcessing, updateProgress } =
useIncrementalFetchAndMutateRecords<T>({
@@ -232,7 +230,9 @@ export const useIncrementalDeleteManyRecords = <T>({
},
);
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -55,9 +55,7 @@ export const useIncrementalDestroyManyRecords = <T>({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const mutationResponseField = getDestroyManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
@@ -162,7 +160,9 @@ export const useIncrementalDestroyManyRecords = <T>({
},
);
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -37,9 +37,7 @@ export const useIncrementalUpdateManyRecords = <
objectNameSingular,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const {
incrementalFetchAndMutate,
@@ -80,7 +78,9 @@ export const useIncrementalUpdateManyRecords = <
},
);
} finally {
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -48,9 +48,7 @@ export const useMergeManyRecords = <
recordGqlFields: computedRecordGqlFields,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const { findOneRecordQuery } = useFindOneRecordQuery({
objectNameSingular,
@@ -101,7 +99,9 @@ export const useMergeManyRecords = <
setLoading(false);
if (!preview) {
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
operation: {
@@ -5,7 +5,7 @@ import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCac
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpdateOneRecordV2 } from '@/object-record/hooks/useUpdateOneRecordV2';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { CustomError, isDefined } from 'twenty-shared/utils';
export const useRecordOneToManyFieldAttachTargetRecord = () => {
@@ -13,7 +13,7 @@ export const useRecordOneToManyFieldAttachTargetRecord = () => {
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { updateOneRecord } = useUpdateOneRecordV2();
const { updateOneRecord } = useUpdateOneRecord();
const recordOneToManyFieldAttachTargetRecord = async ({
sourceObjectNameSingular,
@@ -4,13 +4,13 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { getRefName } from '@/object-record/cache/utils/getRefName';
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
import { useUpdateOneRecordV2 } from '@/object-record/hooks/useUpdateOneRecordV2';
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
import { CustomError } from 'twenty-shared/utils';
export const useRecordOneToManyFieldDetachTargetRecord = () => {
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItems } = useObjectMetadataItems();
const { updateOneRecord } = useUpdateOneRecordV2();
const { updateOneRecord } = useUpdateOneRecord();
const recordOneToManyFieldDetachTargetRecord = async ({
sourceObjectNameSingular,
@@ -2,14 +2,14 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient
import { getGroupByAggregateQueryName } from '@/object-record/record-aggregate/utils/getGroupByAggregateQueryName';
import { getAggregateQueryName } from '@/object-record/utils/getAggregateQueryName';
export const useRefetchAggregateQueries = ({
objectMetadataNamePlural,
}: {
objectMetadataNamePlural: string;
}) => {
export const useRefetchAggregateQueries = () => {
const apolloCoreClient = useApolloCoreClient();
const refetchAggregateQueries = async () => {
const refetchAggregateQueries = async ({
objectMetadataNamePlural,
}: {
objectMetadataNamePlural: string;
}) => {
const queryName = getAggregateQueryName(objectMetadataNamePlural);
const groupByAggregateQueryName = getGroupByAggregateQueryName({
@@ -73,9 +73,7 @@ export const useUpdateManyRecords = <T extends ObjectRecord = ObjectRecord>({
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const mutationResponseField = getUpdateManyRecordsMutationResponseField(
objectMetadataItem.namePlural,
@@ -261,7 +259,9 @@ export const useUpdateManyRecords = <T extends ObjectRecord = ObjectRecord>({
}
if (!skipRefetchAggregateQueries) {
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
}
if (!skipRegisterObjectOperation) {
@@ -1,18 +1,18 @@
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
import { useUpdateOneRecordMutation } from '@/object-record/hooks/useUpdateOneRecordMutation';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
@@ -25,70 +25,71 @@ import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
type useUpdateOneRecordProps = {
objectNameSingular: string;
recordGqlFields?: Record<string, any>;
};
type UpdateOneRecordArgs<UpdatedObjectRecord> = {
objectNameSingular: string;
idToUpdate: string;
updateOneRecordInput: Partial<Omit<UpdatedObjectRecord, 'id'>>;
optimisticRecord?: Partial<ObjectRecord>;
recordGqlFields?: RecordGqlFields;
};
export const useUpdateOneRecord = <
UpdatedObjectRecord extends ObjectRecord = ObjectRecord,
>({
objectNameSingular,
recordGqlFields,
}: useUpdateOneRecordProps) => {
const { upsertRecordsInStore } = useUpsertRecordsInStore();
export const useUpdateOneRecord = () => {
const apolloCoreClient = useApolloCoreClient();
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const { recordGqlFields: depthOneRecordGqlFields } =
useGenerateDepthRecordGqlFieldsFromObject({
objectNameSingular,
depth: 1,
});
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
const getRecordFromCache = useGetRecordFromCache({
objectNameSingular,
});
const { updateOneRecordMutation } = useUpdateOneRecordMutation({
objectNameSingular,
recordGqlFields: computedRecordGqlFields,
});
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const { refetchAggregateQueries } = useRefetchAggregateQueries();
const { refetchAggregateQueries } = useRefetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const updateOneRecord = async ({
const updateOneRecord = async <
UpdatedObjectRecord extends ObjectRecord = ObjectRecord,
>({
objectNameSingular,
idToUpdate,
updateOneRecordInput,
optimisticRecord,
recordGqlFields,
}: UpdateOneRecordArgs<UpdatedObjectRecord>) => {
const objectMetadataItem = objectMetadataItems.find(
(item) => item.nameSingular === objectNameSingular,
);
if (!objectMetadataItem) {
throw new Error(
`Object metadata item not found for ${objectNameSingular}`,
);
}
const optimisticRecordInput =
optimisticRecord ??
computeOptimisticRecordFromInput({
objectMetadataItem,
currentWorkspaceMember: currentWorkspaceMember,
currentWorkspaceMember,
recordInput: updateOneRecordInput,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
const cachedRecord = getRecordFromCache<ObjectRecord>(idToUpdate);
const computedRecordGqlFields =
recordGqlFields ??
generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth: 1,
});
const cachedRecord = getRecordFromCache({
cache: apolloCoreClient.cache,
objectMetadataItem,
objectMetadataItems,
recordId: idToUpdate,
recordGqlFields: computedRecordGqlFields,
objectPermissionsByObjectMetadataId,
});
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
record: cachedRecord,
objectMetadataItem,
@@ -155,6 +156,15 @@ export const useUpdateOneRecord = <
recordInput: updateOneRecordInput,
}),
};
const updateOneRecordMutation = generateUpdateOneRecordMutation({
objectMetadataItem,
objectMetadataItems,
recordGqlFields: computedRecordGqlFields,
computeReferences: false,
objectPermissionsByObjectMetadataId,
});
const updatedRecord = await apolloCoreClient
.mutate({
mutation: updateOneRecordMutation,
@@ -232,9 +242,11 @@ export const useUpdateOneRecord = <
throw error;
});
await refetchAggregateQueries();
await refetchAggregateQueries({
objectMetadataNamePlural: objectMetadataItem.namePlural,
});
const udpatedRecord = updatedRecord?.data?.[mutationResponseField] ?? null;
const resultRecord = updatedRecord?.data?.[mutationResponseField] ?? null;
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
@@ -250,7 +262,7 @@ export const useUpdateOneRecord = <
},
});
return udpatedRecord;
return resultRecord;
};
return {
@@ -1,43 +0,0 @@
import { useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
export const useUpdateOneRecordMutation = ({
objectNameSingular,
recordGqlFields,
computeReferences = false,
}: {
objectNameSingular: string;
recordGqlFields?: RecordGqlOperationGqlRecordFields;
computeReferences?: boolean;
}) => {
const { objectMetadataItem } = useObjectMetadataItem({
objectNameSingular,
});
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
if (isUndefinedOrNull(objectMetadataItem)) {
return { updateOneRecordMutation: EMPTY_MUTATION };
}
const updateOneRecordMutation = generateUpdateOneRecordMutation({
objectMetadataItem,
objectMetadataItems,
recordGqlFields,
computeReferences,
objectPermissionsByObjectMetadataId,
});
return {
updateOneRecordMutation,
};
};
@@ -1,262 +0,0 @@
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
import { generateUpdateOneRecordMutation } from '@/object-metadata/utils/generateUpdateOneRecordMutation';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode';
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
import { generateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromObject';
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput';
import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent';
import { getUpdatedFieldsFromRecordInput } from '@/object-record/utils/getUpdatedFieldsFromRecordInput';
import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField';
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
import { isNull } from '@sniptt/guards';
import { useRecoilValue } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { buildRecordFromKeysWithSameValue } from '~/utils/array/buildRecordFromKeysWithSameValue';
type UpdateOneRecordArgs<UpdatedObjectRecord> = {
idToUpdate: string;
updateOneRecordInput: Partial<Omit<UpdatedObjectRecord, 'id'>>;
optimisticRecord?: Partial<ObjectRecord>;
objectNameSingular: string;
recordGqlFields?: RecordGqlFields;
};
export const useUpdateOneRecordV2 = () => {
const apolloCoreClient = useApolloCoreClient();
const { upsertRecordsInStore } = useUpsertRecordsInStore();
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const { objectMetadataItems } = useObjectMetadataItems();
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
const updateOneRecord = async <
UpdatedObjectRecord extends ObjectRecord = ObjectRecord,
>({
objectNameSingular,
recordGqlFields,
idToUpdate,
updateOneRecordInput,
optimisticRecord,
}: UpdateOneRecordArgs<UpdatedObjectRecord>) => {
const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) =>
objectMetadataItem.nameSingular === objectNameSingular,
);
if (!objectMetadataItem) {
throw new Error(
`Object metadata item not found for ${objectNameSingular}`,
);
}
const computedRecordGqlFields =
recordGqlFields ??
generateDepthRecordGqlFieldsFromObject({
objectMetadataItem,
objectMetadataItems,
depth: 1,
});
const optimisticRecordInput =
optimisticRecord ??
computeOptimisticRecordFromInput({
objectMetadataItem,
currentWorkspaceMember: currentWorkspaceMember,
recordInput: updateOneRecordInput,
cache: apolloCoreClient.cache,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
});
const cachedRecord = getRecordFromCache({
cache: apolloCoreClient.cache,
objectMetadataItem,
objectMetadataItems,
recordId: idToUpdate,
recordGqlFields: computedRecordGqlFields,
objectPermissionsByObjectMetadataId,
});
const cachedRecordWithConnection = getRecordNodeFromRecord<ObjectRecord>({
record: cachedRecord,
objectMetadataItem,
objectMetadataItems,
recordGqlFields: computedRecordGqlFields,
computeReferences: false,
});
const computedOptimisticRecord = {
...cachedRecord,
...optimisticRecordInput,
id: idToUpdate,
__typename: getObjectTypename(objectMetadataItem.nameSingular),
};
const optimisticRecordWithConnection =
getRecordNodeFromRecord<ObjectRecord>({
record: computedOptimisticRecord,
objectMetadataItem,
objectMetadataItems,
recordGqlFields: computedRecordGqlFields,
computeReferences: false,
});
const shouldHandleOptimisticCache =
!isNull(cachedRecord) &&
isDefined(optimisticRecordWithConnection) &&
isDefined(cachedRecordWithConnection);
if (shouldHandleOptimisticCache) {
const recordGqlFields = generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: optimisticRecordInput,
depth: 1,
});
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloCoreClient.cache,
record: computedOptimisticRecord,
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
cache: apolloCoreClient.cache,
objectMetadataItem,
currentRecord: cachedRecordWithConnection,
updatedRecord: optimisticRecordWithConnection,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
}
const mutationResponseField =
getUpdateOneRecordMutationResponseField(objectNameSingular);
const sanitizedInput = {
...sanitizeRecordInput({
objectMetadataItem,
recordInput: updateOneRecordInput,
}),
};
const updateOneRecordMutation = generateUpdateOneRecordMutation({
objectMetadataItem,
objectMetadataItems,
recordGqlFields,
computeReferences: false,
objectPermissionsByObjectMetadataId,
});
const updatedRecord = await apolloCoreClient
.mutate({
mutation: updateOneRecordMutation,
variables: {
idToUpdate,
input: sanitizedInput,
},
update: (cache, { data }) => {
const record = data?.[mutationResponseField];
if (!isDefined(record)) return;
const recordToUpsert = getRecordFromRecordNode({
recordNode: record,
});
upsertRecordsInStore({ partialRecords: [recordToUpsert] });
triggerUpdateRecordOptimisticEffect({
cache,
objectMetadataItem,
currentRecord: computedOptimisticRecord,
updatedRecord: record,
objectMetadataItems,
objectPermissionsByObjectMetadataId,
upsertRecordsInStore,
});
},
})
.catch((error: Error) => {
if (!shouldHandleOptimisticCache) {
throw error;
}
const cachedRecordKeys = new Set(Object.keys(cachedRecord));
const recordKeysAddedByOptimisticCache = Object.keys(
optimisticRecordInput,
).filter((diffKey) => !cachedRecordKeys.has(diffKey));
const recordGqlFields = {
...generateDepthRecordGqlFieldsFromRecord({
objectMetadataItem,
objectMetadataItems,
record: cachedRecord,
depth: 1,
}),
...buildRecordFromKeysWithSameValue(
recordKeysAddedByOptimisticCache,
true,
),
};
updateRecordFromCache({
objectMetadataItems,
objectMetadataItem,
cache: apolloCoreClient.cache,
record: {
...cachedRecord,
...buildRecordFromKeysWithSameValue(
recordKeysAddedByOptimisticCache,
null,
),
},
recordGqlFields,
objectPermissionsByObjectMetadataId,
});
triggerUpdateRecordOptimisticEffect({
cache: apolloCoreClient.cache,
objectMetadataItem,
currentRecord: optimisticRecordWithConnection,
updatedRecord: cachedRecordWithConnection,
objectMetadataItems,
upsertRecordsInStore,
objectPermissionsByObjectMetadataId,
});
throw error;
});
dispatchObjectRecordOperationBrowserEvent({
objectMetadataItem,
operation: {
type: 'update-one',
result: {
updateInput: {
recordId: idToUpdate,
updatedFields:
getUpdatedFieldsFromRecordInput(updateOneRecordInput),
},
},
},
});
return updatedRecord?.data?.[mutationResponseField] ?? null;
};
return {
updateOneRecord,
};
};