Add useUpdateManyRecords hook and message folders sync status mutation (#16694)
- Added new `useUpdateManyRecords` hook for batch record updates with optimistic cache updates - Added `updateMessageFoldersSyncStatus` hook for managing message folder sync state - Redesigned Message Folders List with BreadCrumb and Animations
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { gql } from '@apollo/client';
|
||||
import { getMockPersonRecord } from '~/testing/mock-data/people';
|
||||
|
||||
export const query = gql`
|
||||
mutation UpdateManyPeople(
|
||||
$filter: PersonFilterInput!
|
||||
$data: PersonUpdateInput!
|
||||
) {
|
||||
updatePeople(filter: $filter, data: $data) {
|
||||
id
|
||||
__typename
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const personIds = [
|
||||
'a7286b9a-c039-4a89-9567-2dfa7953cda9',
|
||||
'37faabcd-cb39-4a0a-8618-7e3fda9afca0',
|
||||
];
|
||||
|
||||
export const personRecords = personIds.map<ObjectRecord>((personId, index) =>
|
||||
getMockPersonRecord({ id: personId }, index),
|
||||
);
|
||||
|
||||
export const updateInput = {
|
||||
city: 'Updated City',
|
||||
};
|
||||
|
||||
export const variables = {
|
||||
filter: {
|
||||
id: {
|
||||
in: personIds,
|
||||
},
|
||||
},
|
||||
data: updateInput,
|
||||
};
|
||||
|
||||
export const updatedPersonRecords = personIds.map<ObjectRecord>(
|
||||
(personId, index) =>
|
||||
getMockPersonRecord({ id: personId, city: 'Updated City' }, index),
|
||||
);
|
||||
|
||||
export const responseData = personIds.map((personId) => ({ id: personId }));
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { getRecordFromCache } from '@/object-record/cache/utils/getRecordFromCache';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
|
||||
import {
|
||||
personIds,
|
||||
personRecords,
|
||||
query,
|
||||
responseData,
|
||||
updateInput,
|
||||
variables,
|
||||
} from '@/object-record/hooks/__mocks__/useUpdateManyRecords';
|
||||
import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggregateQueries';
|
||||
import { useUpdateManyRecords } from '@/object-record/hooks/useUpdateManyRecords';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { InMemoryCache } from '@apollo/client';
|
||||
import { type MockedResponse } from '@apollo/client/testing';
|
||||
import { act } from 'react';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { getMockPersonObjectMetadataItem } from '~/testing/mock-data/people';
|
||||
import { generatedMockObjectMetadataItems } from '~/testing/utils/generatedMockObjectMetadataItems';
|
||||
|
||||
const getDefaultMocks = (
|
||||
overrides?: Partial<MockedResponse>,
|
||||
): MockedResponse[] => [
|
||||
{
|
||||
request: {
|
||||
query,
|
||||
variables,
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
updatePeople: responseData,
|
||||
},
|
||||
})),
|
||||
...overrides,
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('@/object-record/hooks/useRefetchAggregateQueries');
|
||||
const mockRefetchAggregateQueries = jest.fn();
|
||||
(useRefetchAggregateQueries as jest.Mock).mockReturnValue({
|
||||
refetchAggregateQueries: mockRefetchAggregateQueries,
|
||||
});
|
||||
|
||||
const objectMetadataItem = getMockPersonObjectMetadataItem();
|
||||
const objectMetadataItems = generatedMockObjectMetadataItems;
|
||||
|
||||
const expectedCachedRecordsWithUpdatedCity = personRecords.map(
|
||||
(personRecord) => ({
|
||||
...personRecord,
|
||||
city: 'Updated City',
|
||||
}),
|
||||
);
|
||||
|
||||
describe('useUpdateManyRecords', () => {
|
||||
let cache: InMemoryCache;
|
||||
|
||||
const assertCachedRecordsMatch = (expectedRecords: ObjectRecord[]) => {
|
||||
expectedRecords.forEach((expectedRecord) => {
|
||||
const cachedRecord = getRecordFromCache({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordId: expectedRecord.id,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
});
|
||||
expect(cachedRecord).not.toBeNull();
|
||||
if (cachedRecord === null) throw new Error('Should never occur');
|
||||
expect(expectedRecord).toMatchObject(cachedRecord);
|
||||
});
|
||||
};
|
||||
|
||||
const assertCachedRecordsIsNull = (recordIds: string[]) =>
|
||||
recordIds.forEach((recordId) =>
|
||||
expect(
|
||||
getRecordFromCache({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordId,
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
}),
|
||||
).toBeNull(),
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
cache = new InMemoryCache();
|
||||
});
|
||||
|
||||
describe('A. Starting from empty cache', () => {
|
||||
it('1. Should handle update many records when cache is empty', async () => {
|
||||
const apolloMocks = getDefaultMocks();
|
||||
const { result } = renderHook(
|
||||
() => useUpdateManyRecords({ objectNameSingular: 'person' }),
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks,
|
||||
cache,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.updateManyRecords({
|
||||
recordIdsToUpdate: personIds,
|
||||
updateOneRecordInput: updateInput,
|
||||
});
|
||||
expect(res).toEqual(responseData);
|
||||
assertCachedRecordsIsNull(personIds);
|
||||
});
|
||||
|
||||
expect(apolloMocks[0].result).toHaveBeenCalled();
|
||||
expect(mockRefetchAggregateQueries).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('B. Starting from filled cache', () => {
|
||||
beforeEach(() => {
|
||||
personRecords.forEach((record) =>
|
||||
updateRecordFromCache({
|
||||
cache,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
record,
|
||||
recordGqlFields: generateDepthRecordGqlFieldsFromRecord({
|
||||
objectMetadataItems: generatedMockObjectMetadataItems,
|
||||
objectMetadataItem,
|
||||
record,
|
||||
depth: 1,
|
||||
}),
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('1. Should handle optimistic behavior after many successful records update', async () => {
|
||||
const apolloMocks = getDefaultMocks();
|
||||
const { result } = renderHook(
|
||||
() => useUpdateManyRecords({ objectNameSingular: 'person' }),
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks,
|
||||
cache,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
const res = await result.current.updateManyRecords({
|
||||
recordIdsToUpdate: personIds,
|
||||
updateOneRecordInput: updateInput,
|
||||
});
|
||||
expect(res).toEqual(responseData);
|
||||
assertCachedRecordsMatch(expectedCachedRecordsWithUpdatedCity);
|
||||
});
|
||||
|
||||
expect(apolloMocks[0].result).toHaveBeenCalled();
|
||||
expect(mockRefetchAggregateQueries).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('2. Should handle optimistic behavior before send many record update', async () => {
|
||||
const apolloMocks = getDefaultMocks();
|
||||
const { result } = renderHook(
|
||||
() => useUpdateManyRecords({ objectNameSingular: 'person' }),
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: getDefaultMocks({
|
||||
delay: Number.POSITIVE_INFINITY,
|
||||
}),
|
||||
cache,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
result.current.updateManyRecords({
|
||||
recordIdsToUpdate: personIds,
|
||||
updateOneRecordInput: updateInput,
|
||||
});
|
||||
await waitFor(() =>
|
||||
assertCachedRecordsMatch(expectedCachedRecordsWithUpdatedCity),
|
||||
);
|
||||
});
|
||||
|
||||
expect(apolloMocks[0].result).not.toHaveBeenCalled();
|
||||
expect(mockRefetchAggregateQueries).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('3. Should rollback optimistic behavior after failing to update many records', async () => {
|
||||
const apolloMocks = getDefaultMocks();
|
||||
const { result } = renderHook(
|
||||
() => useUpdateManyRecords({ objectNameSingular: 'person' }),
|
||||
{
|
||||
wrapper: getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: getDefaultMocks({
|
||||
error: new Error('Internal server error'),
|
||||
}),
|
||||
cache,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
try {
|
||||
await result.current.updateManyRecords({
|
||||
recordIdsToUpdate: personIds,
|
||||
updateOneRecordInput: updateInput,
|
||||
});
|
||||
fail('Should have thrown an error');
|
||||
} catch (e) {
|
||||
expect(e).toMatchInlineSnapshot(
|
||||
`[ApolloError: Internal server error]`,
|
||||
);
|
||||
assertCachedRecordsMatch(personRecords);
|
||||
}
|
||||
});
|
||||
|
||||
expect(apolloMocks[0].result).not.toHaveBeenCalled();
|
||||
expect(mockRefetchAggregateQueries).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { print } from 'graphql';
|
||||
|
||||
import { useUpdateManyRecordsMutation } from '@/object-record/hooks/useUpdateManyRecordsMutation';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
const expectedQueryTemplate = `
|
||||
mutation UpdateManyPeople($filter: PersonFilterInput!, $data: PersonUpdateInput!) {
|
||||
updatePeople(filter: $filter, data: $data) {
|
||||
id
|
||||
}
|
||||
}
|
||||
`.replace(/\s/g, '');
|
||||
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: [],
|
||||
});
|
||||
|
||||
describe('useUpdateManyRecordsMutation', () => {
|
||||
it('should return a valid updateManyRecordsMutation', () => {
|
||||
const objectNameSingular = 'person';
|
||||
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useUpdateManyRecordsMutation({
|
||||
objectNameSingular,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
const { updateManyRecordsMutation } = result.current;
|
||||
|
||||
expect(updateManyRecordsMutation).toBeDefined();
|
||||
|
||||
const printedReceivedQuery = print(updateManyRecordsMutation).replace(
|
||||
/\s/g,
|
||||
'',
|
||||
);
|
||||
|
||||
expect(printedReceivedQuery).toEqual(expectedQueryTemplate);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,268 @@
|
||||
import { triggerUpdateRecordOptimisticEffectByBatch } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffectByBatch';
|
||||
import { apiConfigState } from '@/client-config/states/apiConfigState';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
|
||||
import { getRecordNodeFromRecord } from '@/object-record/cache/utils/getRecordNodeFromRecord';
|
||||
import { updateRecordFromCache } from '@/object-record/cache/utils/updateRecordFromCache';
|
||||
import { DEFAULT_MUTATION_BATCH_SIZE } from '@/object-record/constants/DefaultMutationBatchSize';
|
||||
import { useGenerateDepthRecordGqlFieldsFromObject } from '@/object-record/graphql/record-gql-fields/hooks/useGenerateDepthRecordGqlFieldsFromObject';
|
||||
import { generateDepthRecordGqlFieldsFromRecord } from '@/object-record/graphql/record-gql-fields/utils/generateDepthRecordGqlFieldsFromRecord';
|
||||
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 { getUpdateManyRecordsMutationResponseField } from '@/object-record/utils/getUpdateManyRecordsMutationResponseField';
|
||||
import { sanitizeRecordInput } from '@/object-record/utils/sanitizeRecordInput';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { sleep } from '~/utils/sleep';
|
||||
|
||||
type UseUpdateManyRecordsProps = {
|
||||
objectNameSingular: string;
|
||||
recordGqlFields?: Record<string, any>;
|
||||
};
|
||||
|
||||
export type UpdateManyRecordsProps<T extends ObjectRecord = ObjectRecord> = {
|
||||
recordIdsToUpdate: string[];
|
||||
updateOneRecordInput: Partial<Omit<T, 'id'>>;
|
||||
skipOptimisticEffect?: boolean;
|
||||
delayInMsBetweenRequests?: number;
|
||||
};
|
||||
|
||||
export const useUpdateManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
}: UseUpdateManyRecordsProps) => {
|
||||
const { registerObjectOperation } = useRegisterObjectOperation();
|
||||
const { upsertRecordsInStore } = useUpsertRecordsInStore();
|
||||
const apiConfig = useRecoilValue(apiConfigState);
|
||||
|
||||
const mutationPageSize =
|
||||
apiConfig?.mutationMaximumAffectedRecords ?? DEFAULT_MUTATION_BATCH_SIZE;
|
||||
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { recordGqlFields: depthOneRecordGqlFields } =
|
||||
useGenerateDepthRecordGqlFieldsFromObject({
|
||||
objectNameSingular,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
const computedRecordGqlFields = recordGqlFields ?? depthOneRecordGqlFields;
|
||||
|
||||
const getRecordFromCache = useGetRecordFromCache({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { updateManyRecordsMutation } = useUpdateManyRecordsMutation({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
const { refetchAggregateQueries } = useRefetchAggregateQueries({
|
||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||
});
|
||||
|
||||
const mutationResponseField = getUpdateManyRecordsMutationResponseField(
|
||||
objectMetadataItem.namePlural,
|
||||
);
|
||||
|
||||
const updateManyRecords = async ({
|
||||
recordIdsToUpdate,
|
||||
updateOneRecordInput,
|
||||
delayInMsBetweenRequests,
|
||||
skipOptimisticEffect = false,
|
||||
}: UpdateManyRecordsProps<T>) => {
|
||||
const numberOfBatches = Math.ceil(
|
||||
recordIdsToUpdate.length / mutationPageSize,
|
||||
);
|
||||
const updatedRecords: ObjectRecord[] = [];
|
||||
|
||||
for (let batchIndex = 0; batchIndex < numberOfBatches; batchIndex++) {
|
||||
const batchedIdsToUpdate = recordIdsToUpdate.slice(
|
||||
batchIndex * mutationPageSize,
|
||||
(batchIndex + 1) * mutationPageSize,
|
||||
);
|
||||
|
||||
const cachedRecords = batchedIdsToUpdate
|
||||
.map((idToUpdate) =>
|
||||
getRecordFromCache(idToUpdate, apolloCoreClient.cache),
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const cachedRecordsNode: RecordGqlNode[] = [];
|
||||
const computedOptimisticRecordsNode: RecordGqlNode[] = [];
|
||||
const computedOptimisticRecords: ObjectRecord[] = [];
|
||||
|
||||
if (!skipOptimisticEffect) {
|
||||
cachedRecords.forEach((cachedRecord) => {
|
||||
const cachedRecordNode = getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: cachedRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const computedOptimisticRecord = {
|
||||
...cachedRecord,
|
||||
...updateOneRecordInput,
|
||||
__typename: getObjectTypename(objectMetadataItem.nameSingular),
|
||||
};
|
||||
|
||||
computedOptimisticRecords.push(computedOptimisticRecord);
|
||||
|
||||
const optimisticRecordNode = getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: computedOptimisticRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
if (isDefined(optimisticRecordNode) && isDefined(cachedRecordNode)) {
|
||||
const recordGqlFieldsFromRecord =
|
||||
generateDepthRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
record: updateOneRecordInput,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: computedOptimisticRecord,
|
||||
recordGqlFields: recordGqlFieldsFromRecord,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
computedOptimisticRecordsNode.push(optimisticRecordNode);
|
||||
cachedRecordsNode.push(cachedRecordNode);
|
||||
}
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffectByBatch({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecords: cachedRecordsNode,
|
||||
updatedRecords: computedOptimisticRecordsNode,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
upsertRecordsInStore,
|
||||
});
|
||||
}
|
||||
|
||||
const sanitizedInput = sanitizeRecordInput({
|
||||
objectMetadataItem,
|
||||
recordInput: updateOneRecordInput,
|
||||
});
|
||||
|
||||
const updatedRecordsResponse = await apolloCoreClient
|
||||
.mutate<Record<string, ObjectRecord[]>>({
|
||||
mutation: updateManyRecordsMutation,
|
||||
variables: {
|
||||
filter: { id: { in: batchedIdsToUpdate } },
|
||||
data: sanitizedInput,
|
||||
},
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
if (skipOptimisticEffect) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const revertedCachedRecordsNode: RecordGqlNode[] = [];
|
||||
const revertedOptimisticRecordsNode: RecordGqlNode[] = [];
|
||||
|
||||
cachedRecords.forEach((cachedRecord, index) => {
|
||||
const recordGqlFieldsFromRecord =
|
||||
generateDepthRecordGqlFieldsFromRecord({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
record: cachedRecord,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
updateRecordFromCache({
|
||||
objectMetadataItems,
|
||||
objectMetadataItem,
|
||||
cache: apolloCoreClient.cache,
|
||||
record: cachedRecord,
|
||||
recordGqlFields: recordGqlFieldsFromRecord,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
|
||||
const cachedRecordWithConnection =
|
||||
getRecordNodeFromRecord<ObjectRecord>({
|
||||
record: cachedRecord,
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields: computedRecordGqlFields,
|
||||
computeReferences: false,
|
||||
});
|
||||
|
||||
const optimisticRecordWithConnection =
|
||||
computedOptimisticRecordsNode[index];
|
||||
|
||||
if (
|
||||
isDefined(optimisticRecordWithConnection) &&
|
||||
isDefined(cachedRecordWithConnection)
|
||||
) {
|
||||
revertedCachedRecordsNode.push(cachedRecordWithConnection);
|
||||
revertedOptimisticRecordsNode.push(
|
||||
optimisticRecordWithConnection,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
triggerUpdateRecordOptimisticEffectByBatch({
|
||||
cache: apolloCoreClient.cache,
|
||||
objectMetadataItem,
|
||||
currentRecords: revertedOptimisticRecordsNode,
|
||||
updatedRecords: revertedCachedRecordsNode,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
upsertRecordsInStore,
|
||||
});
|
||||
|
||||
throw error;
|
||||
});
|
||||
|
||||
const updatedRecordsForThisBatch =
|
||||
updatedRecordsResponse.data?.[mutationResponseField] ?? [];
|
||||
updatedRecords.push(...updatedRecordsForThisBatch);
|
||||
|
||||
if (isDefined(delayInMsBetweenRequests)) {
|
||||
await sleep(delayInMsBetweenRequests);
|
||||
}
|
||||
}
|
||||
|
||||
await refetchAggregateQueries();
|
||||
|
||||
registerObjectOperation(objectMetadataItem, {
|
||||
type: 'update-many',
|
||||
result: {
|
||||
updateInputs: recordIdsToUpdate.map((id) => ({
|
||||
id,
|
||||
...updateOneRecordInput,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
return updatedRecords;
|
||||
};
|
||||
|
||||
return { updateManyRecords };
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { generateUpdateManyRecordsMutation } from '@/object-metadata/utils/generateUpdateManyRecordsMutation';
|
||||
import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation';
|
||||
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
|
||||
|
||||
export const useUpdateManyRecordsMutation = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: string;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
if (isUndefinedOrNull(objectMetadataItem)) {
|
||||
return { updateManyRecordsMutation: EMPTY_MUTATION };
|
||||
}
|
||||
|
||||
const updateManyRecordsMutation = generateUpdateManyRecordsMutation({
|
||||
objectMetadataItem,
|
||||
});
|
||||
|
||||
return {
|
||||
updateManyRecordsMutation,
|
||||
};
|
||||
};
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import { capitalize } from 'twenty-shared/utils';
|
||||
|
||||
export const getUpdateManyRecordsMutationResponseField = (
|
||||
objectNamePlural: string,
|
||||
) => `update${capitalize(objectNamePlural)}`;
|
||||
Reference in New Issue
Block a user