diff --git a/packages/twenty-front/src/modules/object-record/constants/ObjectRecordOperationBrowserEventName.ts b/packages/twenty-front/src/modules/object-record/constants/ObjectRecordOperationBrowserEventName.ts new file mode 100644 index 0000000000..c2d482e1ee --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/constants/ObjectRecordOperationBrowserEventName.ts @@ -0,0 +1,2 @@ +export const OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME = + 'object-record-operation-browser-event'; diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useIncrementalUpdateManyRecords.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useIncrementalUpdateManyRecords.test.tsx index 6498498458..89725cbebe 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useIncrementalUpdateManyRecords.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useIncrementalUpdateManyRecords.test.tsx @@ -1,12 +1,12 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useIncrementalFetchAndMutateRecords } from '@/object-record/hooks/useIncrementalFetchAndMutateRecords'; import { useIncrementalUpdateManyRecords } from '@/object-record/hooks/useIncrementalUpdateManyRecords'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpdateManyRecords } from '@/object-record/hooks/useUpdateManyRecords'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { renderHook } from '@testing-library/react'; jest.mock('@/object-metadata/hooks/useObjectMetadataItem'); -jest.mock('@/object-record/hooks/useRegisterObjectOperation'); +jest.mock('@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'); jest.mock('@/object-record/hooks/useUpdateManyRecords', () => ({ useUpdateManyRecords: jest.fn(), })); @@ -18,7 +18,9 @@ jest.mock('@/object-record/hooks/useRefetchAggregateQueries', () => ({ jest.mock('@/object-record/hooks/useIncrementalFetchAndMutateRecords'); const mockUseObjectMetadataItem = jest.mocked(useObjectMetadataItem); -const mockUseRegisterObjectOperation = jest.mocked(useRegisterObjectOperation); +const mockDispatchObjectRecordOperationBrowserEvent = jest.mocked( + dispatchObjectRecordOperationBrowserEvent, +); const mockUseUpdateManyRecords = jest.mocked(useUpdateManyRecords); const mockUseIncrementalFetchAndMutateRecords = jest.mocked( useIncrementalFetchAndMutateRecords, @@ -26,7 +28,6 @@ const mockUseIncrementalFetchAndMutateRecords = jest.mocked( describe('useIncrementalUpdateManyRecords', () => { const mockUpdateManyRecords = jest.fn(); - const mockRegisterObjectOperation = jest.fn(); const mockIncrementalFetchAndMutate = jest.fn(); const mockUpdateProgress = jest.fn(); @@ -39,9 +40,7 @@ describe('useIncrementalUpdateManyRecords', () => { } as any, }); - mockUseRegisterObjectOperation.mockReturnValue({ - registerObjectOperation: mockRegisterObjectOperation, - }); + mockDispatchObjectRecordOperationBrowserEvent.mockImplementation(jest.fn()); mockUseUpdateManyRecords.mockReturnValue({ updateManyRecords: mockUpdateManyRecords, @@ -85,18 +84,24 @@ describe('useIncrementalUpdateManyRecords', () => { abortSignal: expect.any(AbortSignal), }); expect(mockUpdateProgress).toHaveBeenCalledWith(2, 2); - expect(mockRegisterObjectOperation).toHaveBeenCalledWith( - expect.anything(), - { + expect(mockDispatchObjectRecordOperationBrowserEvent).toHaveBeenCalledWith({ + objectMetadataItem: expect.anything(), + operation: { type: 'update-many', result: { updateInputs: [ - { id: '1', name: 'New Name' }, - { id: '2', name: 'New Name' }, + { + recordId: '1', + updatedFields: [{ name: 'New Name' }], + }, + { + recordId: '2', + updatedFields: [{ name: 'New Name' }], + }, ], }, }, - ); + }); }); it('should pass abortSignal to updateManyRecords', async () => { @@ -140,14 +145,14 @@ describe('useIncrementalUpdateManyRecords', () => { result.current.incrementalUpdateManyRecords({ name: 'New Name' }), ).rejects.toThrow('Process failed'); - expect(mockRegisterObjectOperation).toHaveBeenCalledWith( - expect.anything(), - { + expect(mockDispatchObjectRecordOperationBrowserEvent).toHaveBeenCalledWith({ + objectMetadataItem: expect.anything(), + operation: { type: 'update-many', result: { updateInputs: [], }, }, - ); + }); }); }); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts index 41c039a235..f17621c287 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useBatchCreateManyRecords.ts @@ -6,8 +6,8 @@ import { type useCreateManyRecordsProps, } from '@/object-record/hooks/useCreateManyRecords'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ApolloError } from '@apollo/client'; import { t } from '@lingui/core/macro'; @@ -27,8 +27,6 @@ export const useBatchCreateManyRecords = < setBatchedRecordsCount?: (count: number) => void; abortController?: AbortController; }) => { - const { registerObjectOperation } = useRegisterObjectOperation(); - const { createManyRecords } = useCreateManyRecords({ objectNameSingular, recordGqlFields, @@ -100,7 +98,10 @@ export const useBatchCreateManyRecords = < await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { type: 'create-many' }); + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { type: 'create-many' }, + }); return allCreatedRecords; }; 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 fe6553533d..d31e069d8f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateManyRecords.ts @@ -12,18 +12,18 @@ import { deleteRecordFromCache } from '@/object-record/cache/utils/deleteRecordF import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; -import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { useCreateManyRecordsMutation } from '@/object-record/hooks/useCreateManyRecordsMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { type FieldActorForInputValue } from '@/object-record/record-field/ui/types/FieldMetadata'; 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 { getCreateManyRecordsMutationResponseField } from '@/object-record/utils/getCreateManyRecordsMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; import { useRecoilValue } from 'recoil'; +import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; type PartialObjectRecordWithId = Partial & { @@ -51,7 +51,6 @@ export const useCreateManyRecords = < shouldMatchRootQueryFilter, shouldRefetchAggregateQueries = true, }: useCreateManyRecordsProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -243,7 +242,10 @@ export const useCreateManyRecords = < await refetchAggregateQueries(); } - registerObjectOperation(objectMetadataItem, { type: 'create-many' }); + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { type: 'create-many' }, + }); return createdObjects.data?.[mutationResponseField] ?? []; }; 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 6052964cbc..fbbc356a11 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useCreateOneRecord.ts @@ -13,18 +13,18 @@ import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename import { getRecordFromRecordNode } from '@/object-record/cache/utils/getRecordFromRecordNode'; import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; -import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { useCreateOneRecordMutation } from '@/object-record/hooks/useCreateOneRecordMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { computeOptimisticCreateRecordBaseRecordInput } from '@/object-record/utils/computeOptimisticCreateRecordBaseRecordInput'; import { computeOptimisticRecordFromInput } from '@/object-record/utils/computeOptimisticRecordFromInput'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getCreateOneRecordMutationResponseField } from '@/object-record/utils/getCreateOneRecordMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; import { useRecoilValue } from 'recoil'; +import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { CustomError, isDefined } from 'twenty-shared/utils'; type useCreateOneRecordProps = { @@ -43,7 +43,6 @@ export const useCreateOneRecord = < shouldMatchRootQueryFilter, }: useCreateOneRecordProps) => { const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const { registerObjectOperation } = useRegisterObjectOperation(); const apolloCoreClient = useApolloCoreClient(); const [loading, setLoading] = useState(false); @@ -187,7 +186,10 @@ export const useCreateOneRecord = < await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { type: 'create-one' }); + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { type: 'create-one' }, + }); if (!isDefined(createdObject.data?.[mutationResponseField])) { throw new CustomError('Failed to create record'); 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 df4ee12870..a8d6f3f16c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteManyRecords.ts @@ -12,9 +12,9 @@ import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode' import { useDeleteManyRecordsMutation } from '@/object-record/hooks/useDeleteManyRecordsMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDeleteManyRecordsMutationResponseField } from '@/object-record/utils/getDeleteManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; @@ -34,7 +34,6 @@ export type DeleteManyRecordsProps = { export const useDeleteManyRecords = ({ objectNameSingular, }: useDeleteManyRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); @@ -224,8 +223,11 @@ export const useDeleteManyRecords = ({ } await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'delete-many', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'delete-many', + }, }); return deletedRecords; 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 b5955389a7..f5b791e7bd 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecord.ts @@ -12,9 +12,9 @@ import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordF import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRecordMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; import { isNull } from '@sniptt/guards'; import { isDefined } from 'twenty-shared/utils'; @@ -26,7 +26,6 @@ type useDeleteOneRecordProps = { export const useDeleteOneRecord = ({ objectNameSingular, }: useDeleteOneRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -165,8 +164,11 @@ export const useDeleteOneRecord = ({ await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'delete-one', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'delete-one', + }, }); return deletedRecord.data?.[mutationResponseField] ?? null; @@ -178,7 +180,6 @@ export const useDeleteOneRecord = ({ objectMetadataItems, deleteOneRecordMutation, refetchAggregateQueries, - registerObjectOperation, mutationResponseField, objectPermissionsByObjectMetadataId, upsertRecordsInStore, 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 5cc85c08f6..ffe92c29f6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyManyRecords.ts @@ -9,9 +9,9 @@ import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMu import { useDestroyManyRecordsMutation } from '@/object-record/hooks/useDestroyManyRecordsMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { capitalize, isDefined } from 'twenty-shared/utils'; @@ -31,7 +31,6 @@ export type DestroyManyRecordsProps = { export const useDestroyManyRecords = ({ objectNameSingular, }: useDestroyManyRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); @@ -145,8 +144,11 @@ export const useDestroyManyRecords = ({ await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'destroy-many', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'destroy-many', + }, }); return destroyedRecords; 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 42cd7f4ab7..df8361938f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecord.ts @@ -8,8 +8,8 @@ import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadat import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; import { useDestroyOneRecordMutation } from '@/object-record/hooks/useDestroyOneRecordMutation'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; import { capitalize, isDefined } from 'twenty-shared/utils'; @@ -21,7 +21,6 @@ type useDestroyOneRecordProps = { export const useDestroyOneRecord = ({ objectNameSingular, }: useDestroyOneRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -89,8 +88,11 @@ export const useDestroyOneRecord = ({ throw error; }); - registerObjectOperation(objectMetadataItem, { - type: 'destroy-one', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'destroy-one', + }, }); return deletedRecord.data?.[mutationResponseField] ?? null; @@ -101,7 +103,6 @@ export const useDestroyOneRecord = ({ destroyOneRecordMutation, mutationResponseField, objectNameSingular, - registerObjectOperation, objectMetadataItem, objectMetadataItems, upsertRecordsInStore, diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDeleteManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDeleteManyRecords.ts index 475831daf3..7fc99b26d4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDeleteManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDeleteManyRecords.ts @@ -13,9 +13,9 @@ import { type UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMor import { useIncrementalFetchAndMutateRecords } from '@/object-record/hooks/useIncrementalFetchAndMutateRecords'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { useCallback } from 'react'; import { isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -40,7 +40,6 @@ export const useIncrementalDeleteManyRecords = ({ delayInMsBetweenMutations = DEFAULT_DELAY_BETWEEN_MUTATIONS_MS, skipOptimisticEffect = false, }: UseIncrementalDeleteManyRecordsParams) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const mutationPageSize = pageSize; @@ -235,8 +234,11 @@ export const useIncrementalDeleteManyRecords = ({ await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'delete-many', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'delete-many', + }, }); return totalDeletedCount; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts index 427a277d5e..384ec5b859 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalDestroyManyRecords.ts @@ -10,9 +10,9 @@ import { type UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMor import { useIncrementalFetchAndMutateRecords } from '@/object-record/hooks/useIncrementalFetchAndMutateRecords'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getDestroyManyRecordsMutationResponseField } from '@/object-record/utils/getDestroyManyRecordsMutationResponseField'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { sleep } from '~/utils/sleep'; @@ -37,7 +37,6 @@ export const useIncrementalDestroyManyRecords = ({ delayInMsBetweenMutations = DEFAULT_DELAY_BETWEEN_MUTATIONS_MS, skipOptimisticEffect = false, }: UseIncrementalDestroyManyRecordsParams) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const mutationPageSize = pageSize; @@ -165,8 +164,11 @@ export const useIncrementalDestroyManyRecords = ({ await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'destroy-many', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'destroy-many', + }, }); return totalDestroyedCount; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalUpdateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalUpdateManyRecords.ts index 5e99ebe507..b1b3905980 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useIncrementalUpdateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useIncrementalUpdateManyRecords.ts @@ -3,9 +3,10 @@ import { DEFAULT_QUERY_PAGE_SIZE } from '@/object-record/constants/DefaultQueryP import { type UseFindManyRecordsParams } from '@/object-record/hooks/useFetchMoreRecordsWithPagination'; import { useIncrementalFetchAndMutateRecords } from '@/object-record/hooks/useIncrementalFetchAndMutateRecords'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpdateManyRecords } from '@/object-record/hooks/useUpdateManyRecords'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; +import { getUpdatedFieldsFromRecordInput } from '@/object-record/utils/getUpdatedFieldsFromRecordInput'; const DEFAULT_DELAY_BETWEEN_MUTATIONS_MS = 50; @@ -28,8 +29,6 @@ export const useIncrementalUpdateManyRecords = < pageSize = DEFAULT_QUERY_PAGE_SIZE, delayInMsBetweenMutations = DEFAULT_DELAY_BETWEEN_MUTATIONS_MS, }: UseIncrementalUpdateManyRecordsParams) => { - const { registerObjectOperation } = useRegisterObjectOperation(); - const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular, }); @@ -83,13 +82,16 @@ export const useIncrementalUpdateManyRecords = < } finally { await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'update-many', - result: { - updateInputs: allUpdatedRecordIds.map((id) => ({ - id, - ...fieldsToUpdate, - })), + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'update-many', + result: { + updateInputs: allUpdatedRecordIds.map((recordId) => ({ + recordId, + updatedFields: getUpdatedFieldsFromRecordInput(fieldsToUpdate), + })), + }, }, }); } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useListenToObjectRecordOperationBrowserEvent.ts b/packages/twenty-front/src/modules/object-record/hooks/useListenToObjectRecordOperationBrowserEvent.ts new file mode 100644 index 0000000000..11b65cc524 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/hooks/useListenToObjectRecordOperationBrowserEvent.ts @@ -0,0 +1,57 @@ +import { OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME } from '@/object-record/constants/ObjectRecordOperationBrowserEventName'; +import { type ObjectRecordOperation } from '@/object-record/types/ObjectRecordOperation'; +import { type ObjectRecordOperationBrowserEventDetail } from '@/object-record/types/ObjectRecordOperationBrowserEventDetail'; +import { useEffect } from 'react'; +import { isDefined, isNonEmptyArray } from 'twenty-shared/utils'; + +export const useListenToObjectRecordOperationBrowserEvent = ({ + onObjectRecordOperationBrowserEvent, + objectMetadataItemId, + operationTypes, +}: { + onObjectRecordOperationBrowserEvent: ( + detail: ObjectRecordOperationBrowserEventDetail, + ) => void; + objectMetadataItemId?: string; + operationTypes?: ObjectRecordOperation['type'][]; +}) => { + useEffect(() => { + const handleObjectRecordOperationEvent = (event: Event) => { + const detail = ( + event as CustomEvent + ).detail; + + if ( + isDefined(objectMetadataItemId) && + detail.objectMetadataItem.id !== objectMetadataItemId + ) { + return; + } + + if ( + isNonEmptyArray(operationTypes) && + !operationTypes.includes(detail.operation.type) + ) { + return; + } + + onObjectRecordOperationBrowserEvent(detail); + }; + + window.addEventListener( + OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME, + handleObjectRecordOperationEvent, + ); + + return () => { + window.removeEventListener( + OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME, + handleObjectRecordOperationEvent, + ); + }; + }, [ + objectMetadataItemId, + onObjectRecordOperationBrowserEvent, + operationTypes, + ]); +}; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts index 727125029e..66136b2bea 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useMergeManyRecords.ts @@ -3,15 +3,15 @@ import { useCallback, useState } from 'react'; import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject'; -import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery'; import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQuery'; import { useMergeManyRecordsMutation } from '@/object-record/hooks/useMergeManyRecordsMutation'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getMergeManyRecordsMutationResponseField } from '@/object-record/utils/getMergeManyRecordsMutationResponseField'; import { getOperationName } from '@apollo/client/utilities'; +import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types'; export type MergeManySettings = { conflictPriorityIndex: number; @@ -28,7 +28,6 @@ export const useMergeManyRecords = < objectNameSingular, recordGqlFields, }: UseMergeManyRecordsProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const apolloCoreClient = useApolloCoreClient(); const [loading, setLoading] = useState(false); @@ -103,8 +102,11 @@ export const useMergeManyRecords = < if (!preview) { await refetchAggregateQueries(); - registerObjectOperation(objectMetadataItem, { - type: 'merge-records', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'merge-records', + }, }); } @@ -121,7 +123,6 @@ export const useMergeManyRecords = < findOneRecordQuery, findDuplicateRecordsQuery, refetchAggregateQueries, - registerObjectOperation, ], ); diff --git a/packages/twenty-front/src/modules/object-record/hooks/useRegisterObjectOperation.ts b/packages/twenty-front/src/modules/object-record/hooks/useRegisterObjectOperation.ts deleted file mode 100644 index c59acf65d9..0000000000 --- a/packages/twenty-front/src/modules/object-record/hooks/useRegisterObjectOperation.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; -import { - objectOperationsState, - type ObjectOperationData, -} from '@/object-record/states/objectOperationsComponentState'; -import { useRecoilCallback } from 'recoil'; -import { v4 } from 'uuid'; - -export const useRegisterObjectOperation = () => { - const registerObjectOperation = useRecoilCallback( - ({ set }) => - (objectMetadataItem: ObjectMetadataItem, data: ObjectOperationData) => { - set(objectOperationsState, (currentValue) => { - const newValue = currentValue.concat(); - - newValue.push({ - id: v4(), - timestamp: +new Date(), - objectMetadataItemId: objectMetadataItem.id, - data, - }); - - return newValue; - }); - }, - [], - ); - - return { - registerObjectOperation, - }; -}; 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 5138d651e2..39aa17ad03 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useRestoreManyRecords.ts @@ -9,10 +9,10 @@ import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNo import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache'; import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useRestoreManyRecordsMutation } from '@/object-record/hooks/useRestoreManyRecordsMutation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { getRestoreManyRecordsMutationResponseField } from '@/object-record/utils/getRestoreManyRecordsMutationResponseField'; import { useRecoilValue } from 'recoil'; import { capitalize, isDefined } from 'twenty-shared/utils'; @@ -32,7 +32,6 @@ type RestoreManyRecordsProps = { export const useRestoreManyRecords = ({ objectNameSingular, }: useRestoreManyRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); @@ -200,8 +199,11 @@ export const useRestoreManyRecords = ({ restoredRecords.push(...restoredRecordsForThisBatch); - registerObjectOperation(objectMetadataItem, { - type: 'restore-many', + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'restore-many', + }, }); if (isDefined(delayInMsBetweenRequests)) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts index 3a61158c66..b2bd13dfb0 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateManyRecords.ts @@ -13,10 +13,11 @@ import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/ import { type RecordGqlNode } from '@/object-record/graphql/types/RecordGqlNode'; import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; import { useUpdateManyRecordsMutation } from '@/object-record/hooks/useUpdateManyRecordsMutation'; import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; +import { getUpdatedFieldsFromRecordInput } from '@/object-record/utils/getUpdatedFieldsFromRecordInput'; import { getUpdateManyRecordsMutationResponseField } from '@/object-record/utils/getUpdateManyRecordsMutationResponseField'; import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput'; import { useRecoilValue } from 'recoil'; @@ -42,7 +43,6 @@ export const useUpdateManyRecords = ({ objectNameSingular, recordGqlFields, }: UseUpdateManyRecordsProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apiConfig = useRecoilValue(apiConfigState); @@ -265,13 +265,17 @@ export const useUpdateManyRecords = ({ } if (!skipRegisterObjectOperation) { - registerObjectOperation(objectMetadataItem, { - type: 'update-many', - result: { - updateInputs: recordIdsToUpdate.map((id) => ({ - id, - ...updateOneRecordInput, - })), + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'update-many', + result: { + updateInputs: recordIdsToUpdate.map((recordId) => ({ + recordId, + updatedFields: + getUpdatedFieldsFromRecordInput(updateOneRecordInput), + })), + }, }, }); } 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 fcfd5718b5..a098865c84 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecord.ts @@ -12,11 +12,12 @@ import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graph 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 { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; 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'; +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'; @@ -39,7 +40,6 @@ export const useUpdateOneRecord = < objectNameSingular, recordGqlFields, }: useUpdateOneRecordProps) => { - const { registerObjectOperation } = useRegisterObjectOperation(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); const apolloCoreClient = useApolloCoreClient(); @@ -236,9 +236,18 @@ export const useUpdateOneRecord = < const udpatedRecord = updatedRecord?.data?.[mutationResponseField] ?? null; - registerObjectOperation(objectMetadataItem, { - type: 'update-one', - result: { updateInput: updateOneRecordInput }, + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'update-one', + result: { + updateInput: { + recordId: idToUpdate, + updatedFields: + getUpdatedFieldsFromRecordInput(updateOneRecordInput), + }, + }, + }, }); return udpatedRecord; diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts index db790e6b57..a1f3927ed4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordV2.ts @@ -12,10 +12,12 @@ import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/ 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 { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; 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'; @@ -33,7 +35,6 @@ type UpdateOneRecordArgs = { export const useUpdateOneRecordV2 = () => { const apolloCoreClient = useApolloCoreClient(); const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const { registerObjectOperation } = useRegisterObjectOperation(); const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState); @@ -238,9 +239,18 @@ export const useUpdateOneRecordV2 = () => { throw error; }); - registerObjectOperation(objectMetadataItem, { - type: 'update-one', - result: { updateInput: updateOneRecordInput }, + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'update-one', + result: { + updateInput: { + recordId: idToUpdate, + updatedFields: + getUpdatedFieldsFromRecordInput(updateOneRecordInput), + }, + }, + }, }); return updatedRecord?.data?.[mutationResponseField] ?? null; diff --git a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx index c345d175ce..7a67457936 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/record-table-body/components/RecordTableNoRecordGroupBody.tsx @@ -9,7 +9,7 @@ import { RecordTableCellPortals } from '@/object-record/record-table/record-tabl import { RecordTableAggregateFooter } from '@/object-record/record-table/record-table-footer/components/RecordTableAggregateFooter'; import { isRecordTableInitialLoadingComponentState } from '@/object-record/record-table/states/isRecordTableInitialLoadingComponentState'; import { RecordTableVirtualizedDataChangedEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect'; -import { RecordTableVirtualizedOnObjectRecordEventsEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedOnObjectRecordEventsEffect'; + import { RecordTableVirtualizedRowTreadmillEffect } from '@/object-record/record-table/virtualization/components/RecordTableVirtualizedRowTreadmillEffect'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; @@ -38,7 +38,6 @@ export const RecordTableNoRecordGroupBody = () => { )} - ); diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect.tsx index 1f659502b2..81f3251b32 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedDataChangedEffect.tsx @@ -1,154 +1,50 @@ -import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems'; -import { useGetObjectMetadataItemById } from '@/object-metadata/hooks/useGetObjectMetadataItemById'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useListenToObjectRecordOperationBrowserEvent } from '@/object-record/hooks/useListenToObjectRecordOperationBrowserEvent'; import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; +import { useGetShouldResetTableVirtualizationForUpdateInputs } from '@/object-record/record-table/virtualization/hooks/useGetShouldResetTableVirtualizationForUpdateInputs'; import { useResetVirtualizationBecauseDataChanged } from '@/object-record/record-table/virtualization/hooks/useResetVirtualizationBecauseDataChanged'; -import { lastObjectOperationThatResettedVirtualizationComponentState } from '@/object-record/record-table/virtualization/states/lastObjectOperationThatResettedVirtualizationComponentState'; -import { objectOperationsState } from '@/object-record/states/objectOperationsComponentState'; -import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -import { useEffect } from 'react'; -import { useRecoilValue } from 'recoil'; -import { isDefined, mapById } from 'twenty-shared/utils'; -import { FieldMetadataType } from '~/generated/graphql'; +import { type ObjectRecordOperationBrowserEventDetail } from '@/object-record/types/ObjectRecordOperationBrowserEventDetail'; export const RecordTableVirtualizedDataChangedEffect = () => { const { objectMetadataItem } = useRecordIndexContextOrThrow(); const { objectNameSingular } = useRecordTableContextOrThrow(); - const { getObjectMetadataItemById } = useGetObjectMetadataItemById(); - const { resetVirtualizationBecauseDataChanged } = useResetVirtualizationBecauseDataChanged(objectNameSingular); - const [ - lastObjectOperationThatResettedVirtualization, - setLastObjectOperationThatResettedVirtualization, - ] = useRecoilComponentState( - lastObjectOperationThatResettedVirtualizationComponentState, - ); + const { getShouldResetTableVirtualizationForUpdateInputs } = + useGetShouldResetTableVirtualizationForUpdateInputs(); - const { activeFieldMetadataItems } = useActiveFieldMetadataItems({ - objectMetadataItem, - }); + const handleObjectRecordOperation = ( + objectRecordOperationEventDetail: ObjectRecordOperationBrowserEventDetail, + ) => { + const objectRecordOperation = objectRecordOperationEventDetail.operation; - const currentRecordSorts = useRecoilComponentValue( - currentRecordSortsComponentState, - ); + const isUpdateOperation = + objectRecordOperation.type === 'update-one' || + objectRecordOperation.type === 'update-many'; - const currentRecordFilters = useRecoilComponentValue( - currentRecordFiltersComponentState, - ); + if (isUpdateOperation) { + const updateInputs = + objectRecordOperation.type === 'update-one' + ? [objectRecordOperation.result.updateInput] + : objectRecordOperation.result.updateInputs; - const objectOperations = useRecoilValue(objectOperationsState); + const shouldResetForUpdateOperation = + getShouldResetTableVirtualizationForUpdateInputs(updateInputs); - useEffect(() => { - const lastObjectOperation = objectOperations.at(-1); - - if ( - !isDefined(lastObjectOperationThatResettedVirtualization) && - !isDefined(lastObjectOperation) - ) { - return; - } - - if ( - lastObjectOperation?.id !== - lastObjectOperationThatResettedVirtualization?.id - ) { - setLastObjectOperationThatResettedVirtualization(lastObjectOperation); - - if (isDefined(lastObjectOperation)) { - if ( - lastObjectOperation.objectMetadataItemId !== objectMetadataItem.id - ) { - return; - } - - const updatedFieldNames = new Set(); - - let thereIsAnUpdateOnAFilteredField = false; - let thereIsAnUpdateOnASortedField = false; - - if ( - lastObjectOperation.data.type === 'update-one' || - lastObjectOperation.data.type === 'update-many' - ) { - const updateInputs = - lastObjectOperation.data.type === 'update-one' - ? [lastObjectOperation.data.result.updateInput] - : lastObjectOperation.data.result.updateInputs; - - for (const updateInput of updateInputs) { - const fieldNamesForUpdateInput = - Object.keys(updateInput ?? {}) ?? []; - - for (const fieldName of fieldNamesForUpdateInput) { - updatedFieldNames.add(fieldName); - } - - const updatedFieldMetadataItems = activeFieldMetadataItems.filter( - (fieldMetadataItemToFilter) => - fieldNamesForUpdateInput.includes( - fieldMetadataItemToFilter.name, - ) || - (fieldMetadataItemToFilter.type === - FieldMetadataType.RELATION && - fieldNamesForUpdateInput.includes( - `${fieldMetadataItemToFilter.name}Id`, - )), - ); - - const updatedFieldMetadataItemIds = - updatedFieldMetadataItems.map(mapById); - - const updateOnAFilteredField = currentRecordFilters.some( - (recordFilter) => - updatedFieldMetadataItemIds.includes( - recordFilter.fieldMetadataId, - ), - ); - - const updateOnASortedField = currentRecordSorts.some((recordSort) => - updatedFieldMetadataItemIds.includes(recordSort.fieldMetadataId), - ); - - if (updateOnAFilteredField) { - thereIsAnUpdateOnAFilteredField = true; - } - - if (updateOnASortedField) { - thereIsAnUpdateOnASortedField = true; - } - } - } else { - resetVirtualizationBecauseDataChanged(); - } - - if (updatedFieldNames.has('position')) { - resetVirtualizationBecauseDataChanged(); - } else if ( - thereIsAnUpdateOnAFilteredField || - thereIsAnUpdateOnASortedField - ) { - resetVirtualizationBecauseDataChanged(); - } + if (shouldResetForUpdateOperation) { + resetVirtualizationBecauseDataChanged(); } + } else { + resetVirtualizationBecauseDataChanged(); } - }, [ - lastObjectOperationThatResettedVirtualization, - objectOperations, - setLastObjectOperationThatResettedVirtualization, - resetVirtualizationBecauseDataChanged, - activeFieldMetadataItems, - currentRecordFilters, - currentRecordSorts, - getObjectMetadataItemById, - objectMetadataItem.id, - ]); + }; + + useListenToObjectRecordOperationBrowserEvent({ + onObjectRecordOperationBrowserEvent: handleObjectRecordOperation, + objectMetadataItemId: objectMetadataItem.id, + }); return <>; }; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedOnObjectRecordEventsEffect.tsx b/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedOnObjectRecordEventsEffect.tsx deleted file mode 100644 index 3fa32e36e2..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/components/RecordTableVirtualizedOnObjectRecordEventsEffect.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect'; -import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; -import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems'; -import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache'; -import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename'; -import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord'; -import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions'; -import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; -import { turnSortsIntoOrderBy } from '@/object-record/object-sort-dropdown/utils/turnSortsIntoOrderBy'; -import { useRecordsFieldVisibleGqlFields } from '@/object-record/record-field/hooks/useRecordsFieldVisibleGqlFields'; -import { currentRecordFilterGroupsComponentState } from '@/object-record/record-filter-group/states/currentRecordFilterGroupsComponentState'; -import { useFilterValueDependencies } from '@/object-record/record-filter/hooks/useFilterValueDependencies'; -import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; -import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; -import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; -import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore'; -import { useRecordTableContextOrThrow } from '@/object-record/record-table/contexts/RecordTableContext'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { useListenToObjectRecordEventsForQuery } from '@/sse-db-event/hooks/useListenToObjectRecordEventsForQuery'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; - -import { - computeRecordGqlOperationFilter, - isDefined, - isNonEmptyArray, -} from 'twenty-shared/utils'; -import { useDebouncedCallback } from 'use-debounce'; -import { DatabaseEventAction } from '~/generated/graphql'; - -export const RecordTableVirtualizedOnObjectRecordEventsEffect = () => { - const { objectMetadataItem } = useRecordIndexContextOrThrow(); - const { objectNameSingular } = useRecordTableContextOrThrow(); - const { registerObjectOperation } = useRegisterObjectOperation(); - const { refetchAggregateQueries } = useRefetchAggregateQueries({ - objectMetadataNamePlural: objectMetadataItem.namePlural, - }); - const apolloCoreClient = useApolloCoreClient(); - const getRecordFromCache = useGetRecordFromCache({ - objectNameSingular, - }); - const { upsertRecordsInStore } = useUpsertRecordsInStore(); - const { objectMetadataItems } = useObjectMetadataItems(); - - const recordGqlFields = useRecordsFieldVisibleGqlFields({ - objectMetadataItem, - }); - const { objectPermissionsByObjectMetadataId } = useObjectPermissions(); - - const debouncedRefetchAggregateQueries = useDebouncedCallback( - refetchAggregateQueries, - 200, - ); - - const currentRecordFilters = useRecoilComponentValue( - currentRecordFiltersComponentState, - ); - - const currentRecordSorts = useRecoilComponentValue( - currentRecordSortsComponentState, - ); - - const currentRecordFilterGroups = useRecoilComponentValue( - currentRecordFilterGroupsComponentState, - ); - - const { filterValueDependencies } = useFilterValueDependencies(); - - const queryId = `record-table-virtualized-${objectMetadataItem.nameSingular}`; - - useListenToObjectRecordEventsForQuery({ - queryId, - operationSignature: { - objectNameSingular: objectMetadataItem.nameSingular, - variables: { - filter: computeRecordGqlOperationFilter({ - fields: objectMetadataItem.fields, - recordFilters: currentRecordFilters, - recordFilterGroups: currentRecordFilterGroups, - filterValueDependencies, - }), - orderBy: turnSortsIntoOrderBy(objectMetadataItem, currentRecordSorts), - }, - }, - onObjectRecordEvents: (objectRecordEvents) => { - const cache = apolloCoreClient.cache; - - const updateEvents = objectRecordEvents.filter( - (objectRecordEvent) => - objectRecordEvent.action === DatabaseEventAction.UPDATED, - ); - - const updatedRecordsWithUpdatedFieldsOnly: ObjectRecord[] = []; - for (const updateEvent of updateEvents) { - const updatedRecord = updateEvent.properties.after; - - upsertRecordsInStore({ partialRecords: [updatedRecord] }); - - const cachedRecord = getRecordFromCache(updatedRecord.id); - const cachedRecordWithConnection = - getRecordNodeFromRecord({ - record: cachedRecord, - objectMetadataItem, - objectMetadataItems, - recordGqlFields: recordGqlFields, - computeReferences: false, - }); - - const computedOptimisticRecord = { - ...updatedRecord, - id: updatedRecord.id, - __typename: getObjectTypename(objectMetadataItem.nameSingular), - }; - - const computedOptimisticRecordWithConnection = - getRecordNodeFromRecord({ - record: computedOptimisticRecord, - objectMetadataItem, - objectMetadataItems, - recordGqlFields: recordGqlFields, - }); - - if ( - !isDefined(cachedRecordWithConnection) || - !isDefined(computedOptimisticRecordWithConnection) - ) { - continue; - } - - triggerUpdateRecordOptimisticEffect({ - cache, - objectMetadataItem, - currentRecord: cachedRecordWithConnection, - updatedRecord: computedOptimisticRecordWithConnection, - objectMetadataItems, - objectPermissionsByObjectMetadataId, - upsertRecordsInStore, - }); - - const updatedFields = updateEvent.properties?.updatedFields ?? []; - - updatedRecordsWithUpdatedFieldsOnly.push({ - ...Object.fromEntries( - updatedFields.map((fieldName) => { - return [fieldName, updatedRecord[fieldName]]; - }), - ), - id: updatedRecord.id, - __typename: getObjectTypename(objectMetadataItem.nameSingular), - }); - } - - registerObjectOperation(objectMetadataItem, { - type: 'update-many', - result: { updateInputs: updatedRecordsWithUpdatedFieldsOnly }, - }); - - if (isNonEmptyArray(updateEvents)) { - debouncedRefetchAggregateQueries(); - } - }, - }); - - return null; -}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useGetShouldResetTableVirtualizationForUpdateInputs.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useGetShouldResetTableVirtualizationForUpdateInputs.ts new file mode 100644 index 0000000000..c0df1bc795 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/record-table/virtualization/hooks/useGetShouldResetTableVirtualizationForUpdateInputs.ts @@ -0,0 +1,81 @@ +import { useActiveFieldMetadataItems } from '@/object-metadata/hooks/useActiveFieldMetadataItems'; +import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; +import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext'; +import { currentRecordSortsComponentState } from '@/object-record/record-sort/states/currentRecordSortsComponentState'; +import { type ObjectRecordOperationUpdateInput } from '@/object-record/types/ObjectRecordOperationUpdateInput'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { FieldMetadataType } from 'twenty-shared/types'; +import { mapById } from 'twenty-shared/utils'; + +export const useGetShouldResetTableVirtualizationForUpdateInputs = () => { + const { objectMetadataItem } = useRecordIndexContextOrThrow(); + + const { activeFieldMetadataItems } = useActiveFieldMetadataItems({ + objectMetadataItem, + }); + + const currentRecordSorts = useRecoilComponentValue( + currentRecordSortsComponentState, + ); + + const currentRecordFilters = useRecoilComponentValue( + currentRecordFiltersComponentState, + ); + + const getShouldResetTableVirtualizationForUpdateInputs = ( + updateInputs: ObjectRecordOperationUpdateInput[], + ) => { + const updatedFieldNames = new Set(); + + let thereIsAnUpdateOnAFilteredField = false; + let thereIsAnUpdateOnASortedField = false; + + for (const updateInput of updateInputs) { + const fieldNamesForUpdateInput = updateInput.updatedFields.flatMap( + (updatedField) => Object.keys(updatedField ?? {}), + ); + + for (const fieldName of fieldNamesForUpdateInput) { + updatedFieldNames.add(fieldName); + } + + const updatedFieldMetadataItems = activeFieldMetadataItems.filter( + (fieldMetadataItemToFilter) => + fieldNamesForUpdateInput.includes(fieldMetadataItemToFilter.name) || + (fieldMetadataItemToFilter.type === FieldMetadataType.RELATION && + fieldNamesForUpdateInput.includes( + `${fieldMetadataItemToFilter.name}Id`, + )), + ); + + const updatedFieldMetadataItemIds = + updatedFieldMetadataItems.map(mapById); + + const updateOnAFilteredField = currentRecordFilters.some((recordFilter) => + updatedFieldMetadataItemIds.includes(recordFilter.fieldMetadataId), + ); + + const updateOnASortedField = currentRecordSorts.some((recordSort) => + updatedFieldMetadataItemIds.includes(recordSort.fieldMetadataId), + ); + + if (updateOnAFilteredField) { + thereIsAnUpdateOnAFilteredField = true; + } + + if (updateOnASortedField) { + thereIsAnUpdateOnASortedField = true; + } + } + + if (updatedFieldNames.has('position')) { + return true; + } + + return thereIsAnUpdateOnAFilteredField || thereIsAnUpdateOnASortedField; + }; + + return { + getShouldResetTableVirtualizationForUpdateInputs, + }; +}; diff --git a/packages/twenty-front/src/modules/object-record/record-table/virtualization/states/lastObjectOperationThatResettedVirtualizationComponentState.ts b/packages/twenty-front/src/modules/object-record/record-table/virtualization/states/lastObjectOperationThatResettedVirtualizationComponentState.ts deleted file mode 100644 index e2ca873dbf..0000000000 --- a/packages/twenty-front/src/modules/object-record/record-table/virtualization/states/lastObjectOperationThatResettedVirtualizationComponentState.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { RecordTableComponentInstanceContext } from '@/object-record/record-table/states/context/RecordTableComponentInstanceContext'; -import { type ObjectOperation } from '@/object-record/states/objectOperationsComponentState'; -import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState'; -import { type Nullable } from 'twenty-shared/types'; - -export const lastObjectOperationThatResettedVirtualizationComponentState = - createComponentState>({ - key: 'lastObjectOperationThatResettedVirtualizationComponentState', - componentInstanceContext: RecordTableComponentInstanceContext, - defaultValue: null, - }); diff --git a/packages/twenty-front/src/modules/object-record/states/objectOperationsComponentState.ts b/packages/twenty-front/src/modules/object-record/states/objectOperationsComponentState.ts deleted file mode 100644 index 553ab357cf..0000000000 --- a/packages/twenty-front/src/modules/object-record/states/objectOperationsComponentState.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { createState } from 'twenty-ui/utilities'; - -export type ObjectOperationData = - | { - type: 'update-one'; - result: { - updateInput: any; - }; - } - | { - type: 'update-many'; - result: { - updateInputs: any[]; - }; - } - | { - type: - | 'create-one' - | 'create-many' - | 'destroy-one' - | 'destroy-many' - | 'delete-one' - | 'delete-many' - | 'restore-one' - | 'restore-many' - | 'merge-records'; - }; - -export type ObjectOperation = { - id: string; - objectMetadataItemId: string; - data: ObjectOperationData; - timestamp: number; -}; - -export const objectOperationsState = createState({ - key: 'objectOperationsState', - defaultValue: [], -}); diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperation.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperation.ts new file mode 100644 index 0000000000..232e157fcb --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperation.ts @@ -0,0 +1,27 @@ +import { type ObjectRecordOperationUpdateInput } from '@/object-record/types/ObjectRecordOperationUpdateInput'; + +export type ObjectRecordOperation = + | { + type: 'update-one'; + result: { + updateInput: ObjectRecordOperationUpdateInput; + }; + } + | { + type: 'update-many'; + result: { + updateInputs: ObjectRecordOperationUpdateInput[]; + }; + } + | { + type: + | 'create-one' + | 'create-many' + | 'destroy-one' + | 'destroy-many' + | 'delete-one' + | 'delete-many' + | 'restore-one' + | 'restore-many' + | 'merge-records'; + }; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationBrowserEventDetail.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationBrowserEventDetail.ts new file mode 100644 index 0000000000..846344bf5c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationBrowserEventDetail.ts @@ -0,0 +1,7 @@ +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { type ObjectRecordOperation } from '@/object-record/types/ObjectRecordOperation'; + +export type ObjectRecordOperationBrowserEventDetail = { + objectMetadataItem: ObjectMetadataItem; + operation: ObjectRecordOperation; +}; diff --git a/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationUpdateInput.ts b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationUpdateInput.ts new file mode 100644 index 0000000000..2ce44697ba --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/types/ObjectRecordOperationUpdateInput.ts @@ -0,0 +1,4 @@ +export type ObjectRecordOperationUpdateInput = { + recordId: string; + updatedFields: Record[]; +}; diff --git a/packages/twenty-front/src/modules/object-record/utils/dispatchObjectRecordOperationBrowserEvent.ts b/packages/twenty-front/src/modules/object-record/utils/dispatchObjectRecordOperationBrowserEvent.ts new file mode 100644 index 0000000000..9956d935f3 --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/dispatchObjectRecordOperationBrowserEvent.ts @@ -0,0 +1,12 @@ +import { OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME } from '@/object-record/constants/ObjectRecordOperationBrowserEventName'; +import { type ObjectRecordOperationBrowserEventDetail } from '@/object-record/types/ObjectRecordOperationBrowserEventDetail'; + +export const dispatchObjectRecordOperationBrowserEvent = ( + detail: ObjectRecordOperationBrowserEventDetail, +) => { + window.dispatchEvent( + new CustomEvent(OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME, { + detail, + }), + ); +}; diff --git a/packages/twenty-front/src/modules/object-record/utils/getUpdatedFieldsFromRecordInput.ts b/packages/twenty-front/src/modules/object-record/utils/getUpdatedFieldsFromRecordInput.ts new file mode 100644 index 0000000000..211e069d0c --- /dev/null +++ b/packages/twenty-front/src/modules/object-record/utils/getUpdatedFieldsFromRecordInput.ts @@ -0,0 +1,7 @@ +export const getUpdatedFieldsFromRecordInput = ( + recordInput: Record, +): Record[] => { + return Object.entries(recordInput) + .filter(([fieldName]) => fieldName !== 'id') + .map(([fieldName, fieldValue]) => ({ [fieldName]: fieldValue })); +}; diff --git a/packages/twenty-front/src/modules/workflow/hooks/useStopWorkflowRun.ts b/packages/twenty-front/src/modules/workflow/hooks/useStopWorkflowRun.ts index 375f03937d..5588a58dc6 100644 --- a/packages/twenty-front/src/modules/workflow/hooks/useStopWorkflowRun.ts +++ b/packages/twenty-front/src/modules/workflow/hooks/useStopWorkflowRun.ts @@ -1,7 +1,7 @@ import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; -import { useRegisterObjectOperation } from '@/object-record/hooks/useRegisterObjectOperation'; +import { dispatchObjectRecordOperationBrowserEvent } from '@/object-record/utils/dispatchObjectRecordOperationBrowserEvent'; import { useStopWorkflowRunMutation } from '~/generated-metadata/graphql'; export const useStopWorkflowRun = () => { @@ -9,7 +9,6 @@ export const useStopWorkflowRun = () => { const [mutate] = useStopWorkflowRunMutation({ client: apolloCoreClient, }); - const { registerObjectOperation } = useRegisterObjectOperation(); const { objectMetadataItem } = useObjectMetadataItem({ objectNameSingular: CoreObjectNameSingular.WorkflowRun, @@ -22,10 +21,16 @@ export const useStopWorkflowRun = () => { }, }); - registerObjectOperation(objectMetadataItem, { - type: 'update-one', - result: { - updateInput: { id: workflowRunId }, + dispatchObjectRecordOperationBrowserEvent({ + objectMetadataItem, + operation: { + type: 'update-one', + result: { + updateInput: { + recordId: workflowRunId, + updatedFields: [], + }, + }, }, }); };