Remove repetitive query of ClientConfig and CurrentWorkspace member (#4859)

## Removing repetitive queries (impacting performance on page load)

We have recently introduced the capability to detect schema version
mismatch. To do that, we add a new header in all our queries. On page
load, this header is added once we know the currentUser and especially
its currentWorkspace and related schema version.
However, applying this header to apollo client will re-trigger all
queries that have been already performed (GetClientConfig and
GetCurrentUser). To avoid re-triggering them, I'm introducing two new
"isLoaded" states and skip the query if the query has already been
performed

## Fixing Relation Detail not displaying data on show page

Small bug introduced in a previous PR
This commit is contained in:
Charles Bochet
2024-04-05 20:33:02 +02:00
committed by GitHub
parent a3184dcc2f
commit 9f2c9ee76e
8 changed files with 175 additions and 121 deletions

View File

@ -6,6 +6,7 @@ import { RecoilRoot } from 'recoil';
import { ApolloProvider } from '@/apollo/components/ApolloProvider';
import { ClientConfigProvider } from '@/client-config/components/ClientConfigProvider';
import { ClientConfigProviderEffect } from '@/client-config/components/ClientConfigProviderEffect';
import { ApolloDevLogEffect } from '@/debug/components/ApolloDevLogEffect';
import { RecoilDebugObserverEffect } from '@/debug/components/RecoilDebugObserver';
import { AppErrorBoundary } from '@/error-handler/components/AppErrorBoundary';
@ -22,6 +23,7 @@ import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/Sn
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
import { ThemeType } from '@/ui/theme/constants/ThemeLight';
import { UserProvider } from '@/users/components/UserProvider';
import { UserProviderEffect } from '@/users/components/UserProviderEffect';
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
import '@emotion/react';
@ -46,7 +48,9 @@ root.render(
<ExceptionHandlerProvider>
<ApolloProvider>
<HelmetProvider>
<ClientConfigProviderEffect />
<ClientConfigProvider>
<UserProviderEffect />
<UserProvider>
<ApolloMetadataClientProvider>
<ObjectMetadataItemsProvider>

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const isCurrentUserLoadedState = createState<boolean>({
key: 'isCurrentUserLoadedState',
defaultValue: false,
});

View File

@ -1,64 +1,11 @@
import { useEffect } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue } from 'recoil';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { telemetryState } from '@/client-config/states/telemetryState';
import { useGetClientConfigQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
export const ClientConfigProvider: React.FC<React.PropsWithChildren> = ({
children,
}) => {
const setAuthProviders = useSetRecoilState(authProvidersState);
const setIsDebugMode = useSetRecoilState(isDebugModeState);
const isClientConfigLoaded = useRecoilValue(isClientConfigLoadedState);
const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState);
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
const setBilling = useSetRecoilState(billingState);
const setTelemetry = useSetRecoilState(telemetryState);
const setSupportChat = useSetRecoilState(supportChatState);
const setSentryConfig = useSetRecoilState(sentryConfigState);
const { data, loading } = useGetClientConfigQuery();
useEffect(() => {
if (isDefined(data?.clientConfig)) {
setAuthProviders({
google: data?.clientConfig.authProviders.google,
password: data?.clientConfig.authProviders.password,
magicLink: false,
});
setIsDebugMode(data?.clientConfig.debugMode);
setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
setBilling(data?.clientConfig.billing);
setTelemetry(data?.clientConfig.telemetry);
setSupportChat(data?.clientConfig.support);
setSentryConfig({
dsn: data?.clientConfig?.sentry?.dsn,
});
}
}, [
data,
setAuthProviders,
setIsDebugMode,
setIsSignInPrefilled,
setIsSignUpDisabled,
setTelemetry,
setSupportChat,
setBilling,
setSentryConfig,
]);
return loading ? <></> : <>{children}</>;
return isClientConfigLoaded ? <>{children}</> : <></>;
};

View File

@ -0,0 +1,71 @@
import { useEffect } from 'react';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { authProvidersState } from '@/client-config/states/authProvidersState';
import { billingState } from '@/client-config/states/billingState';
import { isClientConfigLoadedState } from '@/client-config/states/isClientConfigLoadedState';
import { isDebugModeState } from '@/client-config/states/isDebugModeState';
import { isSignInPrefilledState } from '@/client-config/states/isSignInPrefilledState';
import { isSignUpDisabledState } from '@/client-config/states/isSignUpDisabledState';
import { sentryConfigState } from '@/client-config/states/sentryConfigState';
import { supportChatState } from '@/client-config/states/supportChatState';
import { telemetryState } from '@/client-config/states/telemetryState';
import { useGetClientConfigQuery } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
export const ClientConfigProviderEffect = () => {
const setAuthProviders = useSetRecoilState(authProvidersState);
const setIsDebugMode = useSetRecoilState(isDebugModeState);
const setIsSignInPrefilled = useSetRecoilState(isSignInPrefilledState);
const setIsSignUpDisabled = useSetRecoilState(isSignUpDisabledState);
const setBilling = useSetRecoilState(billingState);
const setTelemetry = useSetRecoilState(telemetryState);
const setSupportChat = useSetRecoilState(supportChatState);
const setSentryConfig = useSetRecoilState(sentryConfigState);
const [isClientConfigLoaded, setIsClientConfigLoaded] = useRecoilState(
isClientConfigLoadedState,
);
const { data, loading } = useGetClientConfigQuery({
skip: isClientConfigLoaded,
});
useEffect(() => {
if (!loading && isDefined(data?.clientConfig)) {
setIsClientConfigLoaded(true);
setAuthProviders({
google: data?.clientConfig.authProviders.google,
password: data?.clientConfig.authProviders.password,
magicLink: false,
});
setIsDebugMode(data?.clientConfig.debugMode);
setIsSignInPrefilled(data?.clientConfig.signInPrefilled);
setIsSignUpDisabled(data?.clientConfig.signUpDisabled);
setBilling(data?.clientConfig.billing);
setTelemetry(data?.clientConfig.telemetry);
setSupportChat(data?.clientConfig.support);
setSentryConfig({
dsn: data?.clientConfig?.sentry?.dsn,
});
}
}, [
data,
setAuthProviders,
setIsDebugMode,
setIsSignInPrefilled,
setIsSignUpDisabled,
setTelemetry,
setSupportChat,
setBilling,
setSentryConfig,
loading,
setIsClientConfigLoaded,
]);
return <></>;
};

View File

@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';
export const isClientConfigLoadedState = createState<boolean>({
key: 'isClientConfigLoadedState',
defaultValue: false,
});

View File

@ -32,8 +32,12 @@ export const useLazyFindOneRecord = <T extends ObjectRecord = ObjectRecord>({
findOneRecord: ({ objectRecordId, onCompleted }: FindOneRecordParams<T>) =>
findOneRecord({
variables: { objectRecordId },
onCompleted: (data) =>
onCompleted?.(getRecordFromRecordNode(data[objectNameSingular])),
onCompleted: (data) => {
const record = getRecordFromRecordNode<T>({
recordNode: data[objectNameSingular],
});
onCompleted?.(record);
},
}),
called,
error,

View File

@ -1,67 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useQuery } from '@apollo/client';
import { useSetRecoilState } from 'recoil';
import React from 'react';
import { useRecoilValue } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { workspacesState } from '@/auth/states/workspaces';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { User } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
export const UserProvider = ({ children }: React.PropsWithChildren) => {
const [isLoading, setIsLoading] = useState(true);
const isCurrentUserLoaded = useRecoilValue(isCurrentUserLoadedState);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const { loading: queryLoading, data: queryData } = useQuery<{
currentUser: User;
}>(GET_CURRENT_USER);
useEffect(() => {
if (!queryLoading) {
setIsLoading(false);
}
if (!isDefined(queryData?.currentUser)) return;
setCurrentUser(queryData.currentUser);
setCurrentWorkspace(queryData.currentUser.defaultWorkspace);
const { workspaceMember, workspaces: userWorkspaces } =
queryData.currentUser;
if (isDefined(workspaceMember)) {
setCurrentWorkspaceMember({
...workspaceMember,
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
});
}
if (isDefined(userWorkspaces)) {
const workspaces = userWorkspaces
.map(({ workspace }) => workspace)
.filter(isDefined);
setWorkspaces(workspaces);
}
}, [
setCurrentUser,
isLoading,
queryLoading,
setCurrentWorkspace,
setCurrentWorkspaceMember,
setWorkspaces,
queryData?.currentUser,
]);
return isLoading ? <></> : <>{children}</>;
return !isCurrentUserLoaded ? <></> : <>{children}</>;
};

View File

@ -0,0 +1,73 @@
import React, { useEffect, useState } from 'react';
import { useQuery } from '@apollo/client';
import { useRecoilState, useSetRecoilState } from 'recoil';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadingState';
import { workspacesState } from '@/auth/states/workspaces';
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { User } from '~/generated/graphql';
import { isDefined } from '~/utils/isDefined';
export const UserProviderEffect = () => {
const [isLoading, setIsLoading] = useState(true);
const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState(
isCurrentUserLoadedState,
);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setWorkspaces = useSetRecoilState(workspacesState);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const { loading: queryLoading, data: queryData } = useQuery<{
currentUser: User;
}>(GET_CURRENT_USER, { skip: isCurrentUserLoaded });
useEffect(() => {
if (!queryLoading) {
setIsLoading(false);
}
if (!isDefined(queryData?.currentUser)) return;
setIsCurrentUserLoaded(true);
setCurrentUser(queryData.currentUser);
setCurrentWorkspace(queryData.currentUser.defaultWorkspace);
const { workspaceMember, workspaces: userWorkspaces } =
queryData.currentUser;
if (isDefined(workspaceMember)) {
setCurrentWorkspaceMember({
...workspaceMember,
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
});
}
if (isDefined(userWorkspaces)) {
const workspaces = userWorkspaces
.map(({ workspace }) => workspace)
.filter(isDefined);
setWorkspaces(workspaces);
}
}, [
setCurrentUser,
isLoading,
queryLoading,
setCurrentWorkspace,
setCurrentWorkspaceMember,
setWorkspaces,
queryData?.currentUser,
setIsCurrentUserLoaded,
]);
return <></>;
};