Skip to content

Commit

Permalink
Remove repetitive query of ClientConfig and CurrentWorkspace member (t…
Browse files Browse the repository at this point in the history
…wentyhq#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
  • Loading branch information
charlesBochet authored Apr 5, 2024
1 parent a3184dc commit 9f2c9ee
Show file tree
Hide file tree
Showing 8 changed files with 175 additions and 121 deletions.
4 changes: 4 additions & 0 deletions packages/twenty-front/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand All @@ -46,7 +48,9 @@ root.render(
<ExceptionHandlerProvider>
<ApolloProvider>
<HelmetProvider>
<ClientConfigProviderEffect />
<ClientConfigProvider>
<UserProviderEffect />
<UserProvider>
<ApolloMetadataClientProvider>
<ObjectMetadataItemsProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';

export const isCurrentUserLoadedState = createState<boolean>({
key: 'isCurrentUserLoadedState',
defaultValue: false,
});
Original file line number Diff line number Diff line change
@@ -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}</> : <></>;
};
Original file line number Diff line number Diff line change
@@ -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 <></>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createState } from 'twenty-ui';

export const isClientConfigLoadedState = createState<boolean>({
key: 'isClientConfigLoadedState',
defaultValue: false,
});
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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}</>;
};
Original file line number Diff line number Diff line change
@@ -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 <></>;
};

0 comments on commit 9f2c9ee

Please sign in to comment.