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:
+9
-6
@@ -12,10 +12,11 @@ import { H2Title, IconArchive, IconPlug, IconRobot } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { useMutation, useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
useCreateDatabaseConfigVariableMutation,
|
||||
useGetAdminAiModelsQuery,
|
||||
useSetAdminAiModelEnabledMutation,
|
||||
CreateDatabaseConfigVariableDocument,
|
||||
GetAdminAiModelsDocument,
|
||||
SetAdminAiModelEnabledDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { getModelIcon } from '~/pages/settings/ai/utils/getModelIcon';
|
||||
import { getModelProviderLabel } from '~/pages/settings/ai/utils/getModelProviderLabel';
|
||||
@@ -27,9 +28,11 @@ export const SettingsAdminAI = () => {
|
||||
const [showDeprecated, setShowDeprecated] = useState(false);
|
||||
const { refetch: refetchClientConfig } = useClientConfig();
|
||||
|
||||
const { data } = useGetAdminAiModelsQuery();
|
||||
const [createConfigVariable] = useCreateDatabaseConfigVariableMutation();
|
||||
const [setModelEnabled] = useSetAdminAiModelEnabledMutation();
|
||||
const { data } = useQuery(GetAdminAiModelsDocument);
|
||||
const [createConfigVariable] = useMutation(
|
||||
CreateDatabaseConfigVariableDocument,
|
||||
);
|
||||
const [setModelEnabled] = useMutation(SetAdminAiModelEnabledDocument);
|
||||
|
||||
const autoEnableNewModels =
|
||||
data?.getAdminAiModels?.autoEnableNewModels ?? true;
|
||||
|
||||
+6
-4
@@ -1,10 +1,9 @@
|
||||
import { FIND_ALL_APPLICATION_REGISTRATIONS } from '@/settings/admin-panel/apps/graphql/queries/findAllApplicationRegistrations';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableBody } from '@/ui/layout/table/components/TableBody';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
@@ -15,7 +14,10 @@ import { SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type ApplicationRegistrationFragmentFragment } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
type ApplicationRegistrationFragmentFragment,
|
||||
FindAllApplicationRegistrationsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[3]};
|
||||
@@ -30,7 +32,7 @@ const TABLE_GRID = '1fr 1fr 100px 80px';
|
||||
export const SettingsAdminApps = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
const { data } = useQuery(FIND_ALL_APPLICATION_REGISTRATIONS);
|
||||
const { data } = useQuery(FindAllApplicationRegistrationsDocument);
|
||||
|
||||
const registrations: ApplicationRegistrationFragmentFragment[] =
|
||||
data?.findAllApplicationRegistrations ?? [];
|
||||
|
||||
+3
-2
@@ -11,7 +11,8 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useState } from 'react';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useUserLookupAdminPanelMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { UserLookupAdminPanelDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -52,7 +53,7 @@ export const SettingsAdminGeneral = () => {
|
||||
);
|
||||
const [isUserLookupLoading, setIsUserLookupLoading] = useState(false);
|
||||
|
||||
const [userLookup] = useUserLookupAdminPanelMutation();
|
||||
const [userLookup] = useMutation(UserLookupAdminPanelDocument);
|
||||
|
||||
const currentUser = useAtomStateValue(currentUserState);
|
||||
|
||||
|
||||
+3
-2
@@ -2,10 +2,11 @@ import { SettingsAdminTableCard } from '@/settings/admin-panel/components/Settin
|
||||
import { SettingsAdminVersionDisplay } from '@/settings/admin-panel/components/SettingsAdminVersionDisplay';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconCircleDot, IconStatusChange } from 'twenty-ui/display';
|
||||
import { useGetVersionInfoQuery } from '~/generated-metadata/graphql';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { GetVersionInfoDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SettingsAdminVersionContainer = () => {
|
||||
const { data, loading } = useGetVersionInfoQuery();
|
||||
const { data, loading } = useQuery(GetVersionInfoDocument);
|
||||
const { currentVersion, latestVersion } = data?.versionInfo ?? {};
|
||||
|
||||
const versionItems = [
|
||||
|
||||
+5
-4
@@ -35,10 +35,11 @@ import { Button, Toggle } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import {
|
||||
type FeatureFlagKey,
|
||||
useImpersonateMutation,
|
||||
useUpdateWorkspaceFeatureFlagMutation,
|
||||
ImpersonateDocument,
|
||||
UpdateWorkspaceFeatureFlagDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type SettingsAdminWorkspaceContentProps = {
|
||||
@@ -64,11 +65,11 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
const [currentUser] = useAtomState(currentUserState);
|
||||
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
|
||||
|
||||
const [updateFeatureFlag] = useUpdateWorkspaceFeatureFlagMutation();
|
||||
const [updateFeatureFlag] = useMutation(UpdateWorkspaceFeatureFlagDocument);
|
||||
const [isImpersonateLoading, setIsImpersonationLoading] = useState(false);
|
||||
const { executeImpersonationAuth } = useImpersonationAuth();
|
||||
const { executeImpersonationRedirect } = useImpersonationRedirect();
|
||||
const [impersonate] = useImpersonateMutation();
|
||||
const [impersonate] = useMutation(ImpersonateDocument);
|
||||
|
||||
const { updateFeatureFlagState } = useFeatureFlagState();
|
||||
const userLookupResult = useAtomStateValue(userLookupResultState);
|
||||
|
||||
+7
-4
@@ -11,9 +11,10 @@ import { t } from '@lingui/core/macro';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
ConfigSource,
|
||||
useGetConfigVariablesGroupedQuery,
|
||||
GetConfigVariablesGroupedDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { ConfigVariableSearchInput } from './ConfigVariableSearchInput';
|
||||
@@ -30,10 +31,12 @@ const StyledTableContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SettingsAdminConfigVariables = () => {
|
||||
const { data: configVariables, loading: configVariablesLoading } =
|
||||
useGetConfigVariablesGroupedQuery({
|
||||
const { data: configVariables, loading: configVariablesLoading } = useQuery(
|
||||
GetConfigVariablesGroupedDocument,
|
||||
{
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [showHiddenGroupVariables, setShowHiddenGroupVariables] = useAtomState(
|
||||
|
||||
+13
-9
@@ -5,10 +5,11 @@ import { GET_DATABASE_CONFIG_VARIABLE } from '@/settings/admin-panel/config-vari
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { type ConfigVariableValue } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import {
|
||||
useCreateDatabaseConfigVariableMutation,
|
||||
useDeleteDatabaseConfigVariableMutation,
|
||||
useUpdateDatabaseConfigVariableMutation,
|
||||
CreateDatabaseConfigVariableDocument,
|
||||
DeleteDatabaseConfigVariableDocument,
|
||||
UpdateDatabaseConfigVariableDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useConfigVariableActions = (variableName: string) => {
|
||||
@@ -16,12 +17,15 @@ export const useConfigVariableActions = (variableName: string) => {
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const { refetch: refetchClientConfig } = useClientConfig();
|
||||
|
||||
const [updateDatabaseConfigVariable] =
|
||||
useUpdateDatabaseConfigVariableMutation();
|
||||
const [createDatabaseConfigVariable] =
|
||||
useCreateDatabaseConfigVariableMutation();
|
||||
const [deleteDatabaseConfigVariable] =
|
||||
useDeleteDatabaseConfigVariableMutation();
|
||||
const [updateDatabaseConfigVariable] = useMutation(
|
||||
UpdateDatabaseConfigVariableDocument,
|
||||
);
|
||||
const [createDatabaseConfigVariable] = useMutation(
|
||||
CreateDatabaseConfigVariableDocument,
|
||||
);
|
||||
const [deleteDatabaseConfigVariable] = useMutation(
|
||||
DeleteDatabaseConfigVariableDocument,
|
||||
);
|
||||
|
||||
const handleUpdateVariable = async (
|
||||
value: ConfigVariableValue,
|
||||
|
||||
+8
-4
@@ -3,12 +3,16 @@ import { SettingsAdminHealthStatusListCard } from '@/settings/admin-panel/health
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useGetSystemHealthStatusQuery } from '~/generated-metadata/graphql';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { GetSystemHealthStatusDocument } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SettingsAdminHealthStatus = () => {
|
||||
const { data, loading: loadingHealthStatus } = useGetSystemHealthStatusQuery({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
const { data, loading: loadingHealthStatus } = useQuery(
|
||||
GetSystemHealthStatusDocument,
|
||||
{
|
||||
fetchPolicy: 'network-only',
|
||||
},
|
||||
);
|
||||
|
||||
const services = data?.getSystemHealthStatus.services ?? [];
|
||||
|
||||
|
||||
+3
-2
@@ -18,10 +18,11 @@ import { useState } from 'react';
|
||||
import { IconRefresh, IconTrash } from 'twenty-ui/display';
|
||||
import { Button, Checkbox } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
JobState,
|
||||
type QueueJob,
|
||||
useGetQueueJobsQuery,
|
||||
GetQueueJobsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
|
||||
@@ -96,7 +97,7 @@ export const SettingsAdminQueueJobsTable = ({
|
||||
|
||||
const offset = page * LIMIT;
|
||||
|
||||
const { data, loading, refetch } = useGetQueueJobsQuery({
|
||||
const { data, loading, refetch } = useQuery(GetQueueJobsDocument, {
|
||||
variables: {
|
||||
queueName,
|
||||
state: stateFilter,
|
||||
|
||||
+6
-10
@@ -1,15 +1,16 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||
import { SettingsAdminWorkerMetricsTooltip } from '@/settings/admin-panel/health-status/components/SettingsAdminWorkerMetricsTooltip';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useSnackBarOnQueryError } from '@/apollo/hooks/useSnackBarOnQueryError';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
QueueMetricsTimeRange,
|
||||
useGetQueueMetricsQuery,
|
||||
GetQueueMetricsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledGraphContainer = styled.div`
|
||||
@@ -48,22 +49,17 @@ export const SettingsAdminWorkerMetricsGraph = ({
|
||||
timeRange,
|
||||
}: SettingsAdminWorkerMetricsGraphProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const { loading, data } = useGetQueueMetricsQuery({
|
||||
const { loading, data, error } = useQuery(GetQueueMetricsDocument, {
|
||||
variables: {
|
||||
queueName,
|
||||
timeRange,
|
||||
},
|
||||
fetchPolicy: 'no-cache',
|
||||
onError: (error) => {
|
||||
const errorMessage = error.message;
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Error fetching worker metrics: ${errorMessage}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
useSnackBarOnQueryError(error);
|
||||
|
||||
const metricsData = data?.getQueueMetrics?.data || [];
|
||||
const hasData =
|
||||
metricsData.length > 0 &&
|
||||
|
||||
+7
-7
@@ -1,15 +1,16 @@
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { plural, t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDeleteJobsMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { DeleteJobsDocument } from '~/generated-metadata/graphql';
|
||||
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
|
||||
|
||||
export const useDeleteJobs = (queueName: string, onSuccess?: () => void) => {
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [deleteJobsMutation] = useDeleteJobsMutation();
|
||||
const [deleteJobsMutation] = useMutation(DeleteJobsDocument);
|
||||
|
||||
const deleteJobs = async (jobIds: string[]) => {
|
||||
setIsDeleting(true);
|
||||
@@ -66,10 +67,9 @@ export const useDeleteJobs = (queueName: string, onSuccess?: () => void) => {
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
message:
|
||||
error instanceof ApolloError
|
||||
? getErrorMessageFromApolloError(error)
|
||||
: t`Failed to delete jobs. Please try again later.`,
|
||||
message: CombinedGraphQLErrors.is(error)
|
||||
? getErrorMessageFromApolloError(error)
|
||||
: t`Failed to delete jobs. Please try again later.`,
|
||||
});
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
|
||||
+7
-7
@@ -1,15 +1,16 @@
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { CombinedGraphQLErrors } from '@apollo/client/errors';
|
||||
import { plural, t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useRetryJobsMutation } from '~/generated-metadata/graphql';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import { RetryJobsDocument } from '~/generated-metadata/graphql';
|
||||
import { getErrorMessageFromApolloError } from '~/utils/get-error-message-from-apollo-error.util';
|
||||
|
||||
export const useRetryJobs = (queueName: string, onSuccess?: () => void) => {
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const [isRetrying, setIsRetrying] = useState(false);
|
||||
const [retryJobsMutation] = useRetryJobsMutation();
|
||||
const [retryJobsMutation] = useMutation(RetryJobsDocument);
|
||||
|
||||
const retryJobs = async (jobIds: string[]) => {
|
||||
setIsRetrying(true);
|
||||
@@ -70,10 +71,9 @@ export const useRetryJobs = (queueName: string, onSuccess?: () => void) => {
|
||||
}
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
message:
|
||||
error instanceof ApolloError
|
||||
? getErrorMessageFromApolloError(error)
|
||||
: t`Failed to retry jobs. Please try again later.`,
|
||||
message: CombinedGraphQLErrors.is(error)
|
||||
? getErrorMessageFromApolloError(error)
|
||||
: t`Failed to retry jobs. Please try again later.`,
|
||||
});
|
||||
} finally {
|
||||
setIsRetrying(false);
|
||||
|
||||
Reference in New Issue
Block a user