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:
Félix Malfait
2026-03-13 14:59:46 +01:00
committed by GitHub
parent 172bbd01bc
commit b470cb21a1
386 changed files with 3482 additions and 13593 deletions
@@ -37,7 +37,7 @@ describe('useLogicFunctionUpdateFormState', () => {
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
name: 'name',
description: '',
sourceHandlerCode: '',
isTool: false,
@@ -2,7 +2,7 @@ import { EXECUTE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { useMutation } from '@apollo/client';
import { useMutation } from '@apollo/client/react';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { LogicFunctionExecutionStatus } from '~/generated-metadata/graphql';
@@ -1,4 +1,4 @@
import { useQuery } from '@apollo/client';
import { useQuery } from '@apollo/client/react';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/logic-functions/graphql/queries/findManyAvailablePackages';
import {
type FindManyAvailablePackagesQuery,
@@ -1,5 +1,5 @@
import { GET_LOGIC_FUNCTION_SOURCE_CODE } from '@/logic-functions/graphql/queries/getLogicFunctionSourceCode';
import { useQuery } from '@apollo/client';
import { useQuery } from '@apollo/client/react';
import {
type GetLogicFunctionSourceCodeQuery,
type GetLogicFunctionSourceCodeQueryVariables,
@@ -1,17 +1,12 @@
import { FIND_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/queries/findOneLogicFunction';
import { useQuery } from '@apollo/client';
import { useQuery } from '@apollo/client/react';
import {
type FindOneLogicFunctionQuery,
type FindOneLogicFunctionQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunction = ({
id,
onCompleted,
}: LogicFunctionIdInput & {
onCompleted?: (data: FindOneLogicFunctionQuery) => void;
}) => {
export const useGetOneLogicFunction = ({ id }: LogicFunctionIdInput) => {
const { data, loading } = useQuery<
FindOneLogicFunctionQuery,
FindOneLogicFunctionQueryVariables
@@ -19,8 +14,8 @@ export const useGetOneLogicFunction = ({
variables: {
input: { id },
},
onCompleted,
});
return {
logicFunction: data?.findOneLogicFunction || null,
loading,
@@ -1,10 +1,7 @@
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
type FindOneLogicFunctionQuery,
type LogicFunction,
} from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { useGetLogicFunctionSourceCode } from '@/logic-functions/hooks/useGetLogicFunctionSourceCode';
import { DEFAULT_TOOL_INPUT_SCHEMA } from 'twenty-shared/logic-function';
@@ -48,22 +45,22 @@ export const useLogicFunctionUpdateFormState = ({
const { logicFunction, loading: logicFunctionLoading } =
useGetOneLogicFunction({
id: logicFunctionId,
onCompleted: (data: FindOneLogicFunctionQuery) => {
const fn = data?.findOneLogicFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
isTool: fn.isTool ?? false,
timeoutSeconds: fn.timeoutSeconds ?? 300,
toolInputSchema: fn.toolInputSchema || DEFAULT_TOOL_INPUT_SCHEMA,
}));
}
},
});
useEffect(() => {
if (isDefined(logicFunction)) {
setFormValues((prevState) => ({
...prevState,
name: logicFunction.name || '',
description: logicFunction.description || '',
isTool: logicFunction.isTool ?? false,
timeoutSeconds: logicFunction.timeoutSeconds ?? 300,
toolInputSchema:
logicFunction.toolInputSchema || DEFAULT_TOOL_INPUT_SCHEMA,
}));
}
}, [logicFunction]);
useEffect(() => {
if (isDefined(sourceHandlerCode)) {
setFormValues((prev) => ({
@@ -8,8 +8,9 @@ import { CREATE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/c
import { DELETE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/deleteOneLogicFunction';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/logic-functions/graphql/queries/findManyLogicFunctions';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { useMutation } from '@apollo/client/react';
import { CombinedGraphQLErrors } from '@apollo/client/errors';
import { getOperationName } from '~/utils/getOperationName';
import { t } from '@lingui/core/macro';
import { CrudOperationType } from 'twenty-shared/types';
import {
@@ -60,7 +61,7 @@ export const usePersistLogicFunction = () => {
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.CREATE,
@@ -96,7 +97,7 @@ export const usePersistLogicFunction = () => {
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.UPDATE,
@@ -140,7 +141,7 @@ export const usePersistLogicFunction = () => {
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
if (CombinedGraphQLErrors.is(error)) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.DELETE,