Refactored object operation dispatch for SSE (#17174)
This PR refactors object record operation dispatch through a browser event instead of a state that was registering all operations. This is a cleaner pattern as it is a synchronous event code path instead of relying on a useEffect to watch state change, which is not ideal. We introduce this change first to then rely on this new pattern to dispatch SSE events in a following PR.
This commit is contained in:
+2
@@ -0,0 +1,2 @@
|
||||
export const OBJECT_RECORD_OPERATION_BROWSER_EVENT_NAME =
|
||||
'object-record-operation-browser-event';
|
||||
+22
-17
@@ -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: [],
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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<ObjectRecord> & {
|
||||
@@ -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] ?? [];
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
+6
-4
@@ -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 = <T>({
|
||||
delayInMsBetweenMutations = DEFAULT_DELAY_BETWEEN_MUTATIONS_MS,
|
||||
skipOptimisticEffect = false,
|
||||
}: UseIncrementalDeleteManyRecordsParams<T>) => {
|
||||
const { registerObjectOperation } = useRegisterObjectOperation();
|
||||
const { upsertRecordsInStore } = useUpsertRecordsInStore();
|
||||
|
||||
const mutationPageSize = pageSize;
|
||||
@@ -235,8 +234,11 @@ export const useIncrementalDeleteManyRecords = <T>({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'delete-many',
|
||||
dispatchObjectRecordOperationBrowserEvent({
|
||||
objectMetadataItem,
|
||||
operation: {
|
||||
type: 'delete-many',
|
||||
},
|
||||
});
|
||||
|
||||
return totalDeletedCount;
|
||||
|
||||
+6
-4
@@ -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 = <T>({
|
||||
delayInMsBetweenMutations = DEFAULT_DELAY_BETWEEN_MUTATIONS_MS,
|
||||
skipOptimisticEffect = false,
|
||||
}: UseIncrementalDestroyManyRecordsParams<T>) => {
|
||||
const { registerObjectOperation } = useRegisterObjectOperation();
|
||||
const { upsertRecordsInStore } = useUpsertRecordsInStore();
|
||||
|
||||
const mutationPageSize = pageSize;
|
||||
@@ -165,8 +164,11 @@ export const useIncrementalDestroyManyRecords = <T>({
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'destroy-many',
|
||||
dispatchObjectRecordOperationBrowserEvent({
|
||||
objectMetadataItem,
|
||||
operation: {
|
||||
type: 'destroy-many',
|
||||
},
|
||||
});
|
||||
|
||||
return totalDestroyedCount;
|
||||
|
||||
+12
-10
@@ -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<T>) => {
|
||||
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),
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
+57
@@ -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<ObjectRecordOperationBrowserEventDetail>
|
||||
).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,
|
||||
]);
|
||||
};
|
||||
@@ -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,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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 = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
}: UseUpdateManyRecordsProps) => {
|
||||
const { registerObjectOperation } = useRegisterObjectOperation();
|
||||
const { upsertRecordsInStore } = useUpsertRecordsInStore();
|
||||
const apiConfig = useRecoilValue(apiConfigState);
|
||||
|
||||
@@ -265,13 +265,17 @@ export const useUpdateManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
}
|
||||
|
||||
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),
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<UpdatedObjectRecord> = {
|
||||
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;
|
||||
|
||||
+1
-2
@@ -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 = () => {
|
||||
)}
|
||||
<RecordTableVirtualizedRowTreadmillEffect />
|
||||
<RecordTableVirtualizedDataChangedEffect />
|
||||
<RecordTableVirtualizedOnObjectRecordEventsEffect />
|
||||
</RecordTableBodyNoRecordGroupDragDropContextProvider>
|
||||
</RecordTableNoRecordGroupBodyContextProvider>
|
||||
);
|
||||
|
||||
+29
-133
@@ -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<string>();
|
||||
|
||||
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 <></>;
|
||||
};
|
||||
|
||||
-165
@@ -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<ObjectRecord>(updatedRecord.id);
|
||||
const cachedRecordWithConnection =
|
||||
getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: cachedRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: recordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const computedOptimisticRecord = {
|
||||
...updatedRecord,
|
||||
id: updatedRecord.id,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
};
|
||||
|
||||
const computedOptimisticRecordWithConnection =
|
||||
getRecordNodeFromRecord<ObjectRecord>({
|
||||
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;
|
||||
};
|
||||
+81
@@ -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<string>();
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
-11
@@ -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<Nullable<ObjectOperation>>({
|
||||
key: 'lastObjectOperationThatResettedVirtualizationComponentState',
|
||||
componentInstanceContext: RecordTableComponentInstanceContext,
|
||||
defaultValue: null,
|
||||
});
|
||||
-39
@@ -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<ObjectOperation[]>({
|
||||
key: 'objectOperationsState',
|
||||
defaultValue: [],
|
||||
});
|
||||
@@ -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';
|
||||
};
|
||||
+7
@@ -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;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export type ObjectRecordOperationUpdateInput = {
|
||||
recordId: string;
|
||||
updatedFields: Record<string, unknown>[];
|
||||
};
|
||||
+12
@@ -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,
|
||||
}),
|
||||
);
|
||||
};
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export const getUpdatedFieldsFromRecordInput = (
|
||||
recordInput: Record<string, unknown>,
|
||||
): Record<string, unknown>[] => {
|
||||
return Object.entries(recordInput)
|
||||
.filter(([fieldName]) => fieldName !== 'id')
|
||||
.map(([fieldName, fieldValue]) => ({ [fieldName]: fieldValue }));
|
||||
};
|
||||
@@ -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: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user