fix(client-config): set isLoaded to false on API status update (#12371)

Attempt at #12289 (edit Félix: removed fix keyword since I don't think
it fixes it)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Antoine Moreaux
2025-05-30 14:44:31 +02:00
committed by GitHub
parent 35a4b07bc2
commit b7473371b3
25 changed files with 224 additions and 170 deletions

View File

@ -1,7 +1,7 @@
import { InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { useMemo, useRef } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
@ -9,7 +9,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { previousUrlState } from '@/auth/states/previousUrlState';
import { tokenPairState } from '@/auth/states/tokenPairState';
import { workspacesState } from '@/auth/states/workspaces';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { useUpdateEffect } from '~/hooks/useUpdateEffect';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
@ -22,18 +21,16 @@ import { ApolloFactory, Options } from '../services/apollo.factory';
export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
// eslint-disable-next-line @nx/workspace-no-state-useref
const apolloRef = useRef<ApolloFactory<NormalizedCacheObject> | null>(null);
const [isDebugMode] = useRecoilState(isDebugModeState);
const navigate = useNavigate();
const setTokenPair = useSetRecoilState(tokenPairState);
const [currentWorkspace, setCurrentWorkspace] = useRecoilState(
currentWorkspaceState,
);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspaceMember = useSetRecoilState(
const [currentWorkspaceMember, setCurrentWorkspaceMember] = useRecoilState(
currentWorkspaceMemberState,
);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
@ -50,18 +47,15 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
},
},
}),
headers: {
...(currentWorkspace?.metadataVersion && {
'X-Schema-Version': `${currentWorkspace.metadataVersion}`,
}),
},
defaultOptions: {
watchQuery: {
fetchPolicy: 'cache-and-network',
},
},
connectToDevTools: isDebugMode,
connectToDevTools: process.env.IS_DEBUG_MODE === 'true',
currentWorkspaceMember: currentWorkspaceMember,
currentWorkspace: currentWorkspace,
onTokenPairChange: (tokenPair) => {
setTokenPair(tokenPair);
},
@ -83,7 +77,7 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
}
},
extraLinks: [],
isDebugMode,
isDebugMode: process.env.IS_DEBUG_MODE === 'true',
// Override options
...options,
});
@ -96,8 +90,6 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
setCurrentWorkspaceMember,
setCurrentWorkspace,
setWorkspaces,
isDebugMode,
currentWorkspace?.metadataVersion,
setPreviousUrl,
]);
@ -107,5 +99,11 @@ export const useApolloFactory = (options: Partial<Options<any>> = {}) => {
}
}, [currentWorkspaceMember]);
useUpdateEffect(() => {
if (isDefined(apolloRef.current)) {
apolloRef.current.updateCurrentWorkspace(currentWorkspace);
}
}, [currentWorkspace]);
return apolloClient;
};

View File

@ -1,6 +1,7 @@
import { ApolloError, gql, InMemoryCache } from '@apollo/client';
import fetchMock, { enableFetchMocks } from 'jest-fetch-mock';
import { WorkspaceActivationStatus } from '~/generated/graphql';
import { ApolloFactory, Options } from '../apollo.factory';
enableFetchMocks();
@ -31,9 +32,32 @@ const mockWorkspaceMember = {
colorScheme: 'Light' as const,
};
const mockWorkspace = {
id: 'workspace-id',
metadataVersion: 1,
allowImpersonation: false,
activationStatus: WorkspaceActivationStatus.ACTIVE,
billingSubscriptions: [],
currentBillingSubscription: null,
workspaceMembersCount: 0,
isPublicInviteLinkEnabled: false,
isGoogleAuthEnabled: false,
isMicrosoftAuthEnabled: false,
isPasswordAuthEnabled: false,
isCustomDomainEnabled: false,
hasValidEnterpriseKey: false,
subdomain: 'test',
customDomain: 'test.com',
workspaceUrls: {
subdomainUrl: 'test.com',
customUrl: 'test.com',
},
};
const createMockOptions = (): Options<any> => ({
uri: 'http://localhost:3000',
currentWorkspaceMember: mockWorkspaceMember,
currentWorkspace: mockWorkspace,
cache: new InMemoryCache(),
isDebugMode: true,
onError: mockOnError,

View File

@ -13,6 +13,7 @@ import { createUploadLink } from 'apollo-upload-client';
import { renewToken } from '@/auth/services/AuthService';
import { CurrentWorkspaceMember } from '@/auth/states/currentWorkspaceMemberState';
import { CurrentWorkspace } from '@/auth/states/currentWorkspaceState';
import { AuthTokenPair } from '~/generated/graphql';
import { logDebug } from '~/utils/logDebug';
@ -34,6 +35,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
onTokenPairChange?: (tokenPair: AuthTokenPair) => void;
onUnauthenticatedError?: () => void;
currentWorkspaceMember: CurrentWorkspaceMember | null;
currentWorkspace: CurrentWorkspace | null;
extraLinks?: ApolloLink[];
isDebugMode?: boolean;
}
@ -41,6 +43,7 @@ export interface Options<TCacheShape> extends ApolloClientOptions<TCacheShape> {
export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
private client: ApolloClient<TCacheShape>;
private currentWorkspaceMember: CurrentWorkspaceMember | null = null;
private currentWorkspace: CurrentWorkspace | null = null;
constructor(opts: Options<TCacheShape>) {
const {
@ -50,12 +53,14 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
onTokenPairChange,
onUnauthenticatedError,
currentWorkspaceMember,
currentWorkspace,
extraLinks,
isDebugMode,
...options
} = opts;
this.currentWorkspaceMember = currentWorkspaceMember;
this.currentWorkspace = currentWorkspace;
const buildApolloLink = (): ApolloLink => {
const httpLink = createUploadLink({
@ -84,6 +89,9 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
...(this.currentWorkspaceMember?.locale
? { 'x-locale': this.currentWorkspaceMember.locale }
: { 'x-locale': i18n.locale }),
...(this.currentWorkspace?.metadataVersion && {
'X-Schema-Version': `${this.currentWorkspace.metadataVersion}`,
}),
},
};
});
@ -188,6 +196,10 @@ export class ApolloFactory<TCacheShape> implements ApolloManager<TCacheShape> {
this.currentWorkspaceMember = workspaceMember;
}
updateCurrentWorkspace(workspace: CurrentWorkspace | null) {
this.currentWorkspace = workspace;
}
getClient() {
return this.client;
}

View File

@ -0,0 +1,7 @@
import { getTokenPair } from '@/apollo/utils/getTokenPair';
import { isDefined } from 'twenty-shared/utils';
export const hasTokenPair = () => {
const tokenPair = getTokenPair();
return isDefined(tokenPair) && isDefined(tokenPair.accessToken?.token);
};