Upgrade Apollo Client to v4 and refactor error handling (#18584)
## Summary This PR upgrades Apollo Client from v3.10.0 to v4 and refactors error handling patterns across the codebase to use a new centralized `useSnackBarOnQueryError` hook. ## Key Changes - **Dependency Update**: Upgraded `@apollo/client` from `^3.10.0` to `^3.11.0` in root package.json - **New Hook**: Added `useSnackBarOnQueryError` hook for centralized Apollo query error handling with snack bar notifications - **Error Handling Refactor**: Updated 100+ files to use the new error handling pattern: - Removed direct `ApolloError` imports where no longer needed - Replaced manual error handling logic with `useSnackBarOnQueryError` hook - Simplified error handling in hooks and components across multiple modules - **GraphQL Codegen**: Updated codegen configuration files to work with Apollo Client v3.11.0 - **Type Definitions**: Added TypeScript declaration file for `apollo-upload-client` module - **Test Updates**: Updated test files to reflect new error handling patterns ## Notable Implementation Details - The new `useSnackBarOnQueryError` hook provides a consistent way to handle Apollo query errors with automatic snack bar notifications - Changes span across multiple feature areas: auth, object records, settings, workflows, billing, and more - All changes maintain backward compatibility while improving code maintainability and reducing duplication - Jest configuration updated to work with the new Apollo Client version https://claude.ai/code/session_019WGZ6Rd7sEHuBg9sTrXRqJ --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
+8
-5
@@ -6,12 +6,15 @@ import {
|
||||
import { useAggregateRecords } from '@/object-record/hooks/useAggregateRecords';
|
||||
import { useAggregateRecordsQuery } from '@/object-record/hooks/useAggregateRecordsQuery';
|
||||
import { AggregateOperations } from '@/object-record/record-table/constants/AggregateOperations';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
|
||||
// Mocks
|
||||
jest.mock('@apollo/client');
|
||||
jest.mock('@apollo/client/react', () => ({
|
||||
...jest.requireActual('@apollo/client/react'),
|
||||
useQuery: jest.fn(),
|
||||
}));
|
||||
jest.mock('@/object-metadata/hooks/useObjectMetadataItem');
|
||||
jest.mock('@/object-record/hooks/useAggregateRecordsQuery');
|
||||
|
||||
@@ -41,7 +44,7 @@ describe('useAggregateRecords', () => {
|
||||
gqlFieldToFieldMap: mockGqlFieldToFieldMap,
|
||||
});
|
||||
|
||||
(useQuery as jest.Mock).mockReturnValue({
|
||||
(useQuery as unknown as jest.Mock).mockReturnValue({
|
||||
data: mockResponse,
|
||||
loading: false,
|
||||
error: undefined,
|
||||
@@ -77,7 +80,7 @@ describe('useAggregateRecords', () => {
|
||||
});
|
||||
|
||||
it('should handle loading state', () => {
|
||||
(useQuery as jest.Mock).mockReturnValue({
|
||||
(useQuery as unknown as jest.Mock).mockReturnValue({
|
||||
data: undefined,
|
||||
loading: true,
|
||||
error: undefined,
|
||||
@@ -102,7 +105,7 @@ describe('useAggregateRecords', () => {
|
||||
|
||||
it('should handle error state', () => {
|
||||
const mockError = new Error('Query failed');
|
||||
(useQuery as jest.Mock).mockReturnValue({
|
||||
(useQuery as unknown as jest.Mock).mockReturnValue({
|
||||
data: undefined,
|
||||
loading: false,
|
||||
error: mockError,
|
||||
|
||||
+1
-3
@@ -201,9 +201,7 @@ describe('useDeleteManyRecords', () => {
|
||||
});
|
||||
fail('Should have thrown an error');
|
||||
} catch (e) {
|
||||
expect(e).toMatchInlineSnapshot(
|
||||
`[ApolloError: Internal server error]`,
|
||||
);
|
||||
expect(e).toMatchInlineSnapshot(`[Error: Internal server error]`);
|
||||
assertCachedRecordsMatch(personRecords);
|
||||
}
|
||||
});
|
||||
|
||||
+1
-3
@@ -211,9 +211,7 @@ describe('useUpdateManyRecords', () => {
|
||||
});
|
||||
fail('Should have thrown an error');
|
||||
} catch (e) {
|
||||
expect(e).toMatchInlineSnapshot(
|
||||
`[ApolloError: Internal server error]`,
|
||||
);
|
||||
expect(e).toMatchInlineSnapshot(`[Error: Internal server error]`);
|
||||
assertCachedRecordsMatch(personRecords);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type RecordGqlFields } from '@/object-record/graphql/record-gql-fields/types/RecordGqlFields';
|
||||
import { type RecordGqlFieldsAggregate } from '@/object-record/graphql/types/RecordGqlFieldsAggregate';
|
||||
@@ -26,7 +25,6 @@ export const useAggregateRecordsQuery = ({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
const availableAggregations = useMemo(
|
||||
() =>
|
||||
getAvailableAggregationsFromObjectFields(
|
||||
@@ -35,33 +33,38 @@ export const useAggregateRecordsQuery = ({
|
||||
[objectMetadataItem.readableFields],
|
||||
);
|
||||
|
||||
const recordGqlFields: RecordGqlFields = {};
|
||||
const gqlFieldToFieldMap: GqlFieldToFieldMap = {};
|
||||
const { recordGqlFields, gqlFieldToFieldMap } = useMemo(() => {
|
||||
const fields: RecordGqlFields = {};
|
||||
const fieldMap: GqlFieldToFieldMap = {};
|
||||
|
||||
Object.entries(recordGqlFieldsAggregate).forEach(
|
||||
([fieldName, aggregateOperations]) => {
|
||||
aggregateOperations.forEach((aggregateOperation) => {
|
||||
const fieldToQuery =
|
||||
availableAggregations[fieldName]?.[aggregateOperation];
|
||||
Object.entries(recordGqlFieldsAggregate).forEach(
|
||||
([fieldName, aggregateOperations]) => {
|
||||
aggregateOperations.forEach((aggregateOperation) => {
|
||||
const fieldToQuery =
|
||||
availableAggregations[fieldName]?.[aggregateOperation];
|
||||
|
||||
if (!isDefined(fieldToQuery)) {
|
||||
return;
|
||||
}
|
||||
gqlFieldToFieldMap[fieldToQuery] = [fieldName, aggregateOperation];
|
||||
if (!isDefined(fieldToQuery)) {
|
||||
return;
|
||||
}
|
||||
fieldMap[fieldToQuery] = [fieldName, aggregateOperation];
|
||||
|
||||
recordGqlFields[fieldToQuery] = true;
|
||||
});
|
||||
},
|
||||
{
|
||||
client: apolloCoreClient,
|
||||
},
|
||||
fields[fieldToQuery] = true;
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
return { recordGqlFields: fields, gqlFieldToFieldMap: fieldMap };
|
||||
}, [availableAggregations, recordGqlFieldsAggregate]);
|
||||
|
||||
const aggregateQuery = useMemo(
|
||||
() =>
|
||||
generateAggregateQuery({
|
||||
objectMetadataItem,
|
||||
recordGqlFields,
|
||||
}),
|
||||
[objectMetadataItem, recordGqlFields],
|
||||
);
|
||||
|
||||
const aggregateQuery = generateAggregateQuery({
|
||||
objectMetadataItem,
|
||||
recordGqlFields,
|
||||
});
|
||||
|
||||
return {
|
||||
aggregateQuery,
|
||||
gqlFieldToFieldMap,
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggr
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
export const useBatchCreateManyRecords = <
|
||||
@@ -81,7 +81,10 @@ export const useBatchCreateManyRecords = <
|
||||
allCreatedRecords.push(...createdRecords);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ApolloError && error.message.includes('aborted')) {
|
||||
if (
|
||||
CombinedGraphQLErrors.is(error) &&
|
||||
error.message.includes('aborted')
|
||||
) {
|
||||
const formattedCreatedRecordsCount = formatNumber(createdRecordsCount);
|
||||
enqueueWarningSnackBar({
|
||||
message: t`Record creation stopped. ${formattedCreatedRecordsCount} records created.`,
|
||||
|
||||
@@ -195,7 +195,9 @@ export const useCreateManyRecords = <
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const records = data?.[mutationResponseField];
|
||||
const records = (data as Record<string, any>)?.[
|
||||
mutationResponseField
|
||||
];
|
||||
|
||||
if (
|
||||
!isDefined(records?.length) ||
|
||||
@@ -251,7 +253,10 @@ export const useCreateManyRecords = <
|
||||
operation: { type: 'create-many' },
|
||||
});
|
||||
|
||||
return createdObjects.data?.[mutationResponseField] ?? [];
|
||||
return (
|
||||
(createdObjects.data as Record<string, any>)?.[mutationResponseField] ??
|
||||
[]
|
||||
);
|
||||
};
|
||||
|
||||
return { createManyRecords };
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { triggerUpdateRecordOptimisticEffect } from '@/apollo/optimistic-effect/utils/triggerUpdateRecordOptimisticEffect';
|
||||
@@ -111,7 +110,9 @@ export const useDeleteOneRecord = ({
|
||||
idToDelete: idToDelete,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
const record = (data as Record<string, any>)?.[
|
||||
mutationResponseField
|
||||
];
|
||||
if (!isDefined(record) || !shouldHandleOptimisticCache) {
|
||||
return;
|
||||
}
|
||||
@@ -127,7 +128,7 @@ export const useDeleteOneRecord = ({
|
||||
});
|
||||
},
|
||||
})
|
||||
.catch((error: ApolloError) => {
|
||||
.catch((error) => {
|
||||
if (!shouldHandleOptimisticCache) {
|
||||
throw error;
|
||||
}
|
||||
@@ -172,7 +173,10 @@ export const useDeleteOneRecord = ({
|
||||
},
|
||||
});
|
||||
|
||||
return deletedRecord.data?.[mutationResponseField] ?? null;
|
||||
return (
|
||||
(deletedRecord.data as Record<string, any>)?.[mutationResponseField] ??
|
||||
null
|
||||
);
|
||||
},
|
||||
[
|
||||
getRecordFromCache,
|
||||
|
||||
@@ -58,7 +58,9 @@ export const useDestroyOneRecord = ({
|
||||
},
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
const record = (data as Record<string, any>)?.[
|
||||
mutationResponseField
|
||||
];
|
||||
if (!isDefined(record)) return;
|
||||
|
||||
const cachedRecord = getRecordFromCache(record.id, cache);
|
||||
@@ -95,7 +97,10 @@ export const useDestroyOneRecord = ({
|
||||
},
|
||||
});
|
||||
|
||||
return deletedRecord.data?.[mutationResponseField] ?? null;
|
||||
return (
|
||||
(deletedRecord.data as Record<string, any>)?.[mutationResponseField] ??
|
||||
null
|
||||
);
|
||||
},
|
||||
[
|
||||
getRecordFromCache,
|
||||
|
||||
+14
-19
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
type ApolloError,
|
||||
type ApolloQueryResult,
|
||||
type FetchMoreQueryOptions,
|
||||
type ApolloClient,
|
||||
type ErrorLike,
|
||||
type ObservableQuery,
|
||||
type OperationVariables,
|
||||
type WatchQueryFetchPolicy,
|
||||
} from '@apollo/client';
|
||||
import { type Unmasked } from '@apollo/client/masking';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
@@ -31,7 +29,7 @@ import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamily
|
||||
import { isFetchingMoreRecordsFamilyState } from '@/object-record/states/isFetchingMoreRecordsFamilyState';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { capitalize, isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
|
||||
@@ -50,21 +48,18 @@ type UseFindManyRecordsStateParams<
|
||||
'skip' | 'recordGqlFields' | 'fetchPolicy'
|
||||
> & {
|
||||
data: RecordGqlOperationFindManyResult | undefined;
|
||||
error: ApolloError | undefined;
|
||||
error: ErrorLike | undefined;
|
||||
fetchMore<
|
||||
TFetchData = TData,
|
||||
TFetchVars extends OperationVariables = OperationVariables,
|
||||
>(
|
||||
fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & {
|
||||
updateQuery?: (
|
||||
previousQueryResult: TData,
|
||||
options: {
|
||||
fetchMoreResult: Unmasked<TFetchData>;
|
||||
variables: TFetchVars;
|
||||
},
|
||||
) => TData;
|
||||
},
|
||||
): Promise<ApolloQueryResult<TFetchData>>;
|
||||
fetchMoreOptions: ObservableQuery.FetchMoreOptions<
|
||||
TData,
|
||||
OperationVariables,
|
||||
TFetchData,
|
||||
TFetchVars
|
||||
>,
|
||||
): Promise<ApolloClient.QueryResult<TFetchData>>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
@@ -190,8 +185,8 @@ export const useFetchMoreRecordsWithPagination = <
|
||||
data: fetchMoreDataResult?.[objectMetadataItem.namePlural],
|
||||
};
|
||||
} catch (error) {
|
||||
handleFindManyRecordsError(error as ApolloError);
|
||||
return { error: error as ApolloError };
|
||||
handleFindManyRecordsError(error as ErrorLike);
|
||||
return { error: error as ErrorLike };
|
||||
} finally {
|
||||
setIsFetchingMoreRecords(false);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
@@ -11,17 +12,13 @@ import { type RecordGqlOperationFindDuplicatesResult } from '@/object-record/gra
|
||||
import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDuplicatesRecordsQuery';
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { getFindDuplicateRecordsQueryResponseField } from '@/object-record/utils/getFindDuplicateRecordsQueryResponseField';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectRecordIds = [],
|
||||
objectNameSingular,
|
||||
onCompleted,
|
||||
skip,
|
||||
}: ObjectMetadataItemIdentifier & {
|
||||
objectRecordIds: string[] | undefined;
|
||||
onCompleted?: (data: RecordGqlConnectionEdgesRequired[]) => void;
|
||||
skip?: boolean;
|
||||
}) => {
|
||||
const findDuplicateQueryStateIdentifier = objectNameSingular;
|
||||
@@ -36,8 +33,6 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const queryResponseField = getFindDuplicateRecordsQueryResponseField(
|
||||
objectMetadataItem.nameSingular,
|
||||
);
|
||||
@@ -51,21 +46,11 @@ export const useFindDuplicateRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
ids: objectRecordIds,
|
||||
},
|
||||
client: apolloCoreClient,
|
||||
onCompleted: (data) => {
|
||||
onCompleted?.(data[queryResponseField]);
|
||||
},
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useFindDuplicateRecords for "${objectMetadataItem.nameSingular}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
useSnackBarOnQueryError(error);
|
||||
|
||||
const objectResults = data?.[queryResponseField];
|
||||
|
||||
const results = useMemo(
|
||||
|
||||
+10
-2
@@ -1,4 +1,5 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@@ -22,7 +23,8 @@ export const useFindDuplicateRecordsQuery = ({
|
||||
|
||||
const objectMetadataItems = useAtomStateValue(objectMetadataItemsState);
|
||||
|
||||
const findDuplicateRecordsQuery = gql`
|
||||
const findDuplicateRecordsQuery = useMemo(
|
||||
() => gql`
|
||||
query FindDuplicate${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}($ids: [UUID!]!) {
|
||||
@@ -44,7 +46,13 @@ export const useFindDuplicateRecordsQuery = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`,
|
||||
[
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
findDuplicateRecordsQuery,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { useQuery, type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { useEffect } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
@@ -98,11 +100,21 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
limit,
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
onCompleted: handleFindManyRecordsCompleted,
|
||||
onError: handleFindManyRecordsError,
|
||||
client: apolloCoreClient,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
handleFindManyRecordsCompleted(data);
|
||||
}
|
||||
}, [data, handleFindManyRecordsCompleted]);
|
||||
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
handleFindManyRecordsError(error);
|
||||
}
|
||||
}, [error, handleFindManyRecordsError]);
|
||||
|
||||
const { fetchMoreRecords, records, hasNextPage } =
|
||||
useFetchMoreRecordsWithPagination<T>({
|
||||
objectNameSingular,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -27,14 +29,25 @@ export const useFindManyRecordsQuery = ({
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const findManyRecordsQuery = generateFindManyRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
cursorDirection,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
const findManyRecordsQuery = useMemo(
|
||||
() =>
|
||||
generateFindManyRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
cursorDirection,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}),
|
||||
[
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
cursorDirection,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
findManyRecordsQuery,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
@@ -17,13 +17,11 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectNameSingular,
|
||||
objectRecordId = '',
|
||||
recordGqlFields,
|
||||
onCompleted,
|
||||
skip,
|
||||
withSoftDeleted = false,
|
||||
}: ObjectMetadataItemIdentifier & {
|
||||
objectRecordId: string | undefined;
|
||||
recordGqlFields?: RecordGqlOperationGqlRecordFields;
|
||||
onCompleted?: (data: T) => void;
|
||||
skip?: boolean;
|
||||
withSoftDeleted?: boolean;
|
||||
}) => {
|
||||
@@ -63,15 +61,6 @@ export const useFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
!hasReadPermission,
|
||||
variables: { objectRecordId },
|
||||
client: apolloCoreClient,
|
||||
onCompleted: (data) => {
|
||||
const recordWithoutConnection = getRecordFromRecordNode<T>({
|
||||
recordNode: { ...data[objectNameSingular] },
|
||||
});
|
||||
|
||||
if (isDefined(recordWithoutConnection)) {
|
||||
onCompleted?.(recordWithoutConnection);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Remove connection from record
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import gql from 'graphql-tag';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
@@ -25,7 +26,8 @@ export const useFindOneRecordQuery = ({
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const findOneRecordQuery = gql`
|
||||
const findOneRecordQuery = useMemo(
|
||||
() => gql`
|
||||
query FindOne${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}($objectRecordId: UUID!) {
|
||||
@@ -50,7 +52,15 @@ export const useFindOneRecordQuery = ({
|
||||
objectPermissionsByObjectMetadataId,
|
||||
})}
|
||||
},
|
||||
`;
|
||||
`,
|
||||
[
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
withSoftDeleted,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
findOneRecordQuery,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -22,13 +24,23 @@ export const useGroupByRecordsQuery = ({
|
||||
|
||||
const { objectPermissionsByObjectMetadataId } = useObjectPermissions();
|
||||
|
||||
const groupByRecordsQuery = generateGroupByRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
});
|
||||
const groupByRecordsQuery = useMemo(
|
||||
() =>
|
||||
generateGroupByRecordsQuery({
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
}),
|
||||
[
|
||||
objectMetadataItem,
|
||||
objectMetadataItems,
|
||||
recordGqlFields,
|
||||
computeReferences,
|
||||
objectPermissionsByObjectMetadataId,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
groupByRecordsQuery,
|
||||
|
||||
+11
-6
@@ -1,4 +1,5 @@
|
||||
import { type ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { type ErrorLike } from '@apollo/client';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
@@ -15,15 +16,19 @@ export const useHandleFindManyRecordsError = ({
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const handleFindManyRecordsError = useCallback(
|
||||
(error: ApolloError) => {
|
||||
(error: ErrorLike) => {
|
||||
logError(
|
||||
`useFindManyRecords for "${objectMetadataItem.namePlural}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
handleError?.(error);
|
||||
if (CombinedGraphQLErrors.is(error)) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({});
|
||||
}
|
||||
handleError?.(error as Error);
|
||||
},
|
||||
[enqueueErrorSnackBar, handleError, objectMetadataItem.namePlural],
|
||||
);
|
||||
|
||||
+16
-20
@@ -1,12 +1,10 @@
|
||||
import {
|
||||
type ApolloError,
|
||||
type ApolloQueryResult,
|
||||
type FetchMoreQueryOptions,
|
||||
type ApolloClient,
|
||||
type ErrorLike,
|
||||
type ObservableQuery,
|
||||
type OperationVariables,
|
||||
type WatchQueryFetchPolicy,
|
||||
} from '@apollo/client';
|
||||
import { type Unmasked } from '@apollo/client/masking';
|
||||
import { isNonEmptyArray } from '@apollo/client/utilities';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
@@ -29,7 +27,7 @@ import {
|
||||
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
|
||||
import { cursorFamilyState } from '@/object-record/states/cursorFamilyState';
|
||||
import { hasNextPageFamilyState } from '@/object-record/states/hasNextPageFamilyState';
|
||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||
import { capitalize, isDefined, isNonEmptyArray } from 'twenty-shared/utils';
|
||||
import { useStore } from 'jotai';
|
||||
|
||||
export type UseFindManyRecordsParams<T> = ObjectMetadataItemIdentifier &
|
||||
@@ -48,21 +46,18 @@ type UseFindManyRecordsStateParams<
|
||||
'skip' | 'recordGqlFields' | 'fetchPolicy' | 'onCompleted'
|
||||
> & {
|
||||
data: RecordGqlOperationFindManyResult | undefined;
|
||||
error: ApolloError | undefined;
|
||||
error: ErrorLike | undefined;
|
||||
fetchMore<
|
||||
TFetchData = TData,
|
||||
TFetchVars extends OperationVariables = OperationVariables,
|
||||
>(
|
||||
fetchMoreOptions: FetchMoreQueryOptions<TFetchVars, TFetchData> & {
|
||||
updateQuery?: (
|
||||
previousQueryResult: TData,
|
||||
options: {
|
||||
fetchMoreResult: Unmasked<TFetchData>;
|
||||
variables: TFetchVars;
|
||||
},
|
||||
) => TData;
|
||||
},
|
||||
): Promise<ApolloQueryResult<TFetchData>>;
|
||||
fetchMoreOptions: ObservableQuery.FetchMoreOptions<
|
||||
TData,
|
||||
OperationVariables,
|
||||
TFetchData,
|
||||
TFetchVars
|
||||
>,
|
||||
): Promise<ApolloClient.QueryResult<TFetchData>>;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
@@ -168,7 +163,8 @@ export const useLazyFetchMoreRecordsWithPagination = <
|
||||
records: getRecordsFromRecordConnection({
|
||||
recordConnection: {
|
||||
edges:
|
||||
fetchMoreDataResult?.[objectMetadataItem.namePlural]?.edges,
|
||||
fetchMoreDataResult?.[objectMetadataItem.namePlural]?.edges ??
|
||||
[],
|
||||
pageInfo:
|
||||
fetchMoreDataResult?.[objectMetadataItem.namePlural]
|
||||
?.pageInfo,
|
||||
@@ -176,8 +172,8 @@ export const useLazyFetchMoreRecordsWithPagination = <
|
||||
}) as T[],
|
||||
};
|
||||
} catch (error) {
|
||||
handleFindManyRecordsError(error as ApolloError);
|
||||
return { error: error as ApolloError };
|
||||
handleFindManyRecordsError(error as ErrorLike);
|
||||
return { error: error as ErrorLike };
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
import { useLazyQuery } from '@apollo/client/react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
@@ -60,13 +60,17 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
|
||||
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||
|
||||
const defaultVariables = useMemo(
|
||||
() => ({
|
||||
filter,
|
||||
limit,
|
||||
orderBy,
|
||||
}),
|
||||
[filter, limit, orderBy],
|
||||
);
|
||||
|
||||
const [findManyRecords, { data, error, fetchMore }] =
|
||||
useLazyQuery<RecordGqlOperationFindManyResult>(findManyRecordsQuery, {
|
||||
variables: {
|
||||
filter,
|
||||
limit,
|
||||
orderBy,
|
||||
},
|
||||
fetchPolicy,
|
||||
client: apolloCoreClient,
|
||||
});
|
||||
@@ -77,7 +81,7 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
orderBy,
|
||||
limit,
|
||||
fetchMore,
|
||||
data,
|
||||
data: data as RecordGqlOperationFindManyResult | undefined,
|
||||
error,
|
||||
objectMetadataItem,
|
||||
});
|
||||
@@ -96,7 +100,14 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
};
|
||||
}
|
||||
|
||||
const result = await findManyRecords();
|
||||
// In Apollo v4, useLazyQuery's execute aborts in-flight queries when
|
||||
// the query document changes (e.g. metadata/permissions loading).
|
||||
// Calling .retain() keeps the query running to completion even if
|
||||
// the ObservableQuery is updated, preventing AbortError rejections.
|
||||
const result = await findManyRecords({
|
||||
variables: defaultVariables,
|
||||
}).retain();
|
||||
|
||||
if (isDefined(result?.error)) {
|
||||
handleFindManyRecordsError(result.error);
|
||||
}
|
||||
@@ -136,6 +147,7 @@ export const useLazyFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
}, [
|
||||
hasReadPermission,
|
||||
findManyRecords,
|
||||
defaultVariables,
|
||||
objectMetadataItem.namePlural,
|
||||
queryIdentifier,
|
||||
handleFindManyRecordsError,
|
||||
|
||||
+21
-19
@@ -1,4 +1,3 @@
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
@@ -49,17 +48,6 @@ export const useLazyFindManyRecordsWithOffset = ({
|
||||
|
||||
const hasReadPermission = objectPermissions.canReadObjectRecords;
|
||||
|
||||
const [findManyRecords] = useLazyQuery<RecordGqlOperationFindManyResult>(
|
||||
findManyRecordsQuery,
|
||||
{
|
||||
variables: {
|
||||
...params,
|
||||
},
|
||||
onError: handleFindManyRecordsError,
|
||||
client: apolloCoreClient,
|
||||
},
|
||||
);
|
||||
|
||||
const findManyRecordsLazyWithOffset = useCallback(
|
||||
async (limit: number, offset: number) => {
|
||||
if (!hasReadPermission) {
|
||||
@@ -71,12 +59,19 @@ export const useLazyFindManyRecordsWithOffset = ({
|
||||
};
|
||||
}
|
||||
|
||||
const result = await findManyRecords({
|
||||
variables: {
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
const result =
|
||||
await apolloCoreClient.query<RecordGqlOperationFindManyResult>({
|
||||
query: findManyRecordsQuery,
|
||||
variables: {
|
||||
...params,
|
||||
limit,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
|
||||
if (result?.error) {
|
||||
handleFindManyRecordsError(result.error);
|
||||
}
|
||||
|
||||
const records = getRecordsFromRecordConnection({
|
||||
recordConnection: {
|
||||
@@ -96,7 +91,14 @@ export const useLazyFindManyRecordsWithOffset = ({
|
||||
error: result?.error,
|
||||
};
|
||||
},
|
||||
[hasReadPermission, findManyRecords, objectMetadataItem.namePlural],
|
||||
[
|
||||
hasReadPermission,
|
||||
apolloCoreClient,
|
||||
findManyRecordsQuery,
|
||||
params,
|
||||
objectMetadataItem.namePlural,
|
||||
handleFindManyRecordsError,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLazyQuery, type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useLazyQuery } from '@apollo/client/react';
|
||||
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { type ObjectMetadataItemIdentifier } from '@/object-metadata/types/ObjectMetadataItemIdentifier';
|
||||
@@ -43,6 +44,7 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
findOneRecordQuery,
|
||||
{
|
||||
client: apolloCoreClient,
|
||||
fetchPolicy,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -51,20 +53,19 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
|
||||
objectRecordId,
|
||||
onCompleted,
|
||||
}: FindOneRecordParams<T>) => {
|
||||
await findOneRecord({
|
||||
const result = await findOneRecord({
|
||||
variables: { objectRecordId },
|
||||
fetchPolicy,
|
||||
onCompleted: (data) => {
|
||||
const record = getRecordFromRecordNode<T>({
|
||||
recordNode: data[objectNameSingular],
|
||||
});
|
||||
onCompleted?.(record);
|
||||
},
|
||||
});
|
||||
}).retain();
|
||||
if (result.data) {
|
||||
const record = getRecordFromRecordNode<T>({
|
||||
recordNode: (result.data as Record<string, any>)[objectNameSingular],
|
||||
});
|
||||
onCompleted?.(record);
|
||||
}
|
||||
},
|
||||
called,
|
||||
error,
|
||||
loading,
|
||||
record: data?.[objectNameSingular] || undefined,
|
||||
record: (data as Record<string, any>)?.[objectNameSingular] || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useRefetchAggregateQueries } from '@/object-record/hooks/useRefetchAggr
|
||||
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { dispatchObjectRecordOperationBrowserEvent } from '@/browser-event/utils/dispatchObjectRecordOperationBrowserEvent';
|
||||
import { getMergeManyRecordsMutationResponseField } from '@/object-record/utils/getMergeManyRecordsMutationResponseField';
|
||||
import { getOperationName } from '@apollo/client/utilities';
|
||||
import { getOperationName } from '~/utils/getOperationName';
|
||||
import { type RecordGqlOperationGqlRecordFields } from 'twenty-shared/types';
|
||||
|
||||
export type MergeManySettings = {
|
||||
@@ -110,7 +110,10 @@ export const useMergeManyRecords = <
|
||||
});
|
||||
}
|
||||
|
||||
return mergedObject.data?.[mutationResponseField] ?? null;
|
||||
return (
|
||||
(mergedObject.data as Record<string, any>)?.[mutationResponseField] ??
|
||||
null
|
||||
);
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
throw error;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { type ObjectPermissions } from 'twenty-shared/types';
|
||||
@@ -10,26 +12,31 @@ type useObjectPermissionsReturnType = {
|
||||
>;
|
||||
};
|
||||
|
||||
const EMPTY_PERMISSIONS: Record<
|
||||
string,
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
> = {};
|
||||
|
||||
export const useObjectPermissions = (): useObjectPermissionsReturnType => {
|
||||
const currentUserWorkspace = useAtomStateValue(currentUserWorkspaceState);
|
||||
const objectsPermissions = currentUserWorkspace?.objectsPermissions;
|
||||
|
||||
if (!isDefined(objectsPermissions)) {
|
||||
return {
|
||||
objectPermissionsByObjectMetadataId: {},
|
||||
};
|
||||
}
|
||||
const objectPermissionsByObjectMetadataId = useMemo(() => {
|
||||
if (!isDefined(objectsPermissions)) {
|
||||
return EMPTY_PERMISSIONS;
|
||||
}
|
||||
|
||||
const objectPermissionsByObjectMetadataId = objectsPermissions?.reduce(
|
||||
(
|
||||
acc: Record<string, ObjectPermissions & { objectMetadataId: string }>,
|
||||
objectPermission,
|
||||
) => {
|
||||
acc[objectPermission.objectMetadataId] = objectPermission;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
return objectsPermissions.reduce(
|
||||
(
|
||||
acc: Record<string, ObjectPermissions & { objectMetadataId: string }>,
|
||||
objectPermission,
|
||||
) => {
|
||||
acc[objectPermission.objectMetadataId] = objectPermission;
|
||||
return acc;
|
||||
},
|
||||
{},
|
||||
);
|
||||
}, [objectsPermissions]);
|
||||
|
||||
return {
|
||||
objectPermissionsByObjectMetadataId,
|
||||
|
||||
+7
-19
@@ -2,24 +2,22 @@ import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMembe
|
||||
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
|
||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||
import { useDoObjectMetadataItemsExist } from '@/object-metadata/hooks/useDoObjectMetadataItemsExist';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError';
|
||||
import { type WatchQueryFetchPolicy } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { useMemo } from 'react';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
type ObjectRecordFilterInput,
|
||||
type SearchQuery,
|
||||
useSearchQuery,
|
||||
SearchDocument,
|
||||
} from '~/generated/graphql';
|
||||
import { logError } from '~/utils/logError';
|
||||
|
||||
// maybe we should look at ObjectMetadataItemIdentifier to update the API even though there are many location to update
|
||||
export type UseSearchRecordsParams = {
|
||||
objectNameSingulars: string[];
|
||||
limit?: number;
|
||||
onError?: (error?: Error) => void;
|
||||
onCompleted?: (data: SearchQuery) => void;
|
||||
skip?: boolean;
|
||||
fetchPolicy?: WatchQueryFetchPolicy;
|
||||
searchInput?: string;
|
||||
@@ -30,7 +28,6 @@ export const useObjectRecordSearchRecords = ({
|
||||
objectNameSingulars,
|
||||
searchInput,
|
||||
limit,
|
||||
onCompleted,
|
||||
skip,
|
||||
filter,
|
||||
fetchPolicy,
|
||||
@@ -38,10 +35,9 @@ export const useObjectRecordSearchRecords = ({
|
||||
const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState);
|
||||
const areDefined = useDoObjectMetadataItemsExist(objectNameSingulars);
|
||||
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const apolloCoreClient = useApolloCoreClient();
|
||||
|
||||
const { data, loading, error, previousData } = useSearchQuery({
|
||||
const { data, loading, error, previousData } = useQuery(SearchDocument, {
|
||||
skip:
|
||||
skip || !areDefined || !currentWorkspaceMember || !isDefined(searchInput),
|
||||
variables: {
|
||||
@@ -52,22 +48,14 @@ export const useObjectRecordSearchRecords = ({
|
||||
},
|
||||
fetchPolicy: fetchPolicy,
|
||||
client: apolloCoreClient,
|
||||
onCompleted: onCompleted,
|
||||
onError: (error) => {
|
||||
logError(
|
||||
`useSearchRecords for "${objectNameSingulars.join(', ')}" error : ` +
|
||||
error,
|
||||
);
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useSnackBarOnQueryError(error);
|
||||
|
||||
const effectiveData = loading ? previousData : data;
|
||||
|
||||
const searchRecords = useMemo(
|
||||
() => effectiveData?.search.edges.map((edge) => edge.node) || [],
|
||||
() => effectiveData?.search?.edges?.map((edge) => edge.node) || [],
|
||||
[effectiveData],
|
||||
);
|
||||
|
||||
|
||||
@@ -195,7 +195,9 @@ export const useRestoreManyRecords = ({
|
||||
});
|
||||
|
||||
const restoredRecordsForThisBatch =
|
||||
restoredRecordsResponse.data?.[mutationResponseField] ?? [];
|
||||
(restoredRecordsResponse.data as Record<string, any>)?.[
|
||||
mutationResponseField
|
||||
] ?? [];
|
||||
|
||||
restoredRecords.push(...restoredRecordsForThisBatch);
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ export const useUpdateOneRecord = () => {
|
||||
input: sanitizedInput,
|
||||
},
|
||||
update: (cache, { data }) => {
|
||||
const record = data?.[mutationResponseField];
|
||||
const record = (data as Record<string, any>)?.[mutationResponseField];
|
||||
if (!isDefined(record)) return;
|
||||
|
||||
const recordToUpsert = getRecordFromRecordNode({
|
||||
@@ -246,7 +246,9 @@ export const useUpdateOneRecord = () => {
|
||||
objectMetadataNamePlural: objectMetadataItem.namePlural,
|
||||
});
|
||||
|
||||
const resultRecord = updatedRecord?.data?.[mutationResponseField] ?? null;
|
||||
const resultRecord =
|
||||
(updatedRecord?.data as Record<string, any>)?.[mutationResponseField] ??
|
||||
null;
|
||||
|
||||
dispatchObjectRecordOperationBrowserEvent({
|
||||
objectMetadataItem,
|
||||
|
||||
Reference in New Issue
Block a user