Fix performances on view loading (#14859)

## Context

We are experiencing bad performance on Twenty. One of the root cause
hypothesis is that computing `currentUser.currentWorkspace.views` is CPU
consuming. Without views, the GetCurrentUser response is ~1000 lines.
With its ~10000 lines.

As graphql is going through all fields recursively this can be quite
heavy on CPU. We had a similar issues on ObjectMetadataItems 2 years ago
and came with storing the response in redis.

Note: I thought there was also a cache in RAM but this is not the case,
so to invalidate the cache we can just empty redis.

## How

- Extract getting all views from GetCurrentUser and update frontend to
perform both queries
- Add views to cached graphql operations
- invalidate the cache manually on view or related core entities update
/ create / delete / destroy

## Tests

I have tested a lot on v1
This commit is contained in:
Charles Bochet
2025-10-02 22:53:03 +02:00
committed by GitHub
parent 87256e82f5
commit 5dcd0607b3
26 changed files with 385 additions and 151 deletions
@@ -0,0 +1,198 @@
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { useIsLogged } from '@/auth/hooks/useIsLogged';
import { availableWorkspacesState } from '@/auth/states/availableWorkspacesState';
import { currentUserState } from '@/auth/states/currentUserState';
import { currentUserWorkspaceState } from '@/auth/states/currentUserWorkspaceState';
import { currentWorkspaceDeletedMembersState } from '@/auth/states/currentWorkspaceDeletedMembersState';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { currentWorkspaceMembersState } from '@/auth/states/currentWorkspaceMembersState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { isCurrentUserLoadedState } from '@/auth/states/isCurrentUserLoadedState';
import { useInitializeFormatPreferences } from '@/localization/hooks/useInitializeFormatPreferences';
import { getDateFnsLocale } from '@/ui/field/display/utils/getDateFnsLocale.util';
import { coreViewsState } from '@/views/states/coreViewState';
import { type CoreViewWithRelations } from '@/views/types/CoreViewWithRelations';
import { type ColorScheme } from '@/workspace-member/types/WorkspaceMember';
import { enUS } from 'date-fns/locale';
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { AppPath, type ObjectPermissions } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type WorkspaceMember,
useFindAllCoreViewsQuery,
useGetCurrentUserQuery,
} from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
import { isMatchingLocation } from '~/utils/isMatchingLocation';
export const UserAndViewsProviderEffect = () => {
const location = useLocation();
const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState(
isCurrentUserLoadedState,
);
const setCurrentUser = useSetRecoilState(currentUserState);
const setCurrentWorkspace = useSetRecoilState(currentWorkspaceState);
const setCurrentUserWorkspace = useSetRecoilState(currentUserWorkspaceState);
const setAvailableWorkspaces = useSetRecoilState(availableWorkspacesState);
const { initializeFormatPreferences } = useInitializeFormatPreferences();
const isLoggedIn = useIsLogged();
const updateLocaleCatalog = useRecoilCallback(
({ snapshot, set }) =>
async (newLocale: keyof typeof APP_LOCALES) => {
const localeValue = snapshot.getLoadable(dateLocaleState).getValue();
if (localeValue.locale !== newLocale) {
getDateFnsLocale(newLocale).then((localeCatalog) => {
set(dateLocaleState, {
locale: newLocale,
localeCatalog: localeCatalog || enUS,
});
});
}
},
[],
);
const setCoreViews = useRecoilCallback(
({ set, snapshot }) =>
(coreViews: CoreViewWithRelations[]) => {
const existingCoreViews = snapshot
.getLoadable(coreViewsState)
.getValue();
if (!isDeeplyEqual(existingCoreViews, coreViews)) {
set(coreViewsState, coreViews);
}
},
[],
);
const setCurrentWorkspaceMember = useSetRecoilState(
currentWorkspaceMemberState,
);
const setCurrentWorkspaceMembers = useSetRecoilState(
currentWorkspaceMembersState,
);
const setCurrentWorkspaceMembersWithDeleted = useSetRecoilState(
currentWorkspaceDeletedMembersState,
);
const shouldSkip =
!isLoggedIn ||
isCurrentUserLoaded ||
isMatchingLocation(location, AppPath.Verify) ||
isMatchingLocation(location, AppPath.VerifyEmail);
const { data: userQueryData, loading: userQueryLoading } =
useGetCurrentUserQuery({
skip: shouldSkip,
});
const { data: queryDataCoreViews } = useFindAllCoreViewsQuery({
skip: shouldSkip,
});
useEffect(() => {
if (!userQueryLoading) {
setIsCurrentUserLoaded(true);
}
if (!isDefined(userQueryData?.currentUser)) return;
setCurrentUser(userQueryData.currentUser);
if (isDefined(userQueryData.currentUser.currentWorkspace)) {
setCurrentWorkspace({
...userQueryData.currentUser.currentWorkspace,
defaultRole:
userQueryData.currentUser.currentWorkspace.defaultRole ?? null,
defaultAgent:
userQueryData.currentUser.currentWorkspace.defaultAgent ?? null,
});
}
if (isDefined(userQueryData.currentUser.currentUserWorkspace)) {
setCurrentUserWorkspace({
...userQueryData.currentUser.currentUserWorkspace,
objectsPermissions:
(userQueryData.currentUser.currentUserWorkspace
.objectsPermissions as Array<
ObjectPermissions & { objectMetadataId: string }
>) ?? [],
});
}
const {
workspaceMember,
workspaceMembers,
deletedWorkspaceMembers,
availableWorkspaces,
} = userQueryData.currentUser;
const affectDefaultValuesOnEmptyWorkspaceMemberFields = (
workspaceMember: WorkspaceMember,
) => {
return {
...workspaceMember,
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
locale:
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE,
};
};
if (isDefined(workspaceMember)) {
const updatedWorkspaceMember =
affectDefaultValuesOnEmptyWorkspaceMemberFields(workspaceMember);
setCurrentWorkspaceMember(updatedWorkspaceMember);
updateLocaleCatalog(updatedWorkspaceMember.locale);
// Initialize format preferences from workspace member
initializeFormatPreferences(updatedWorkspaceMember);
dynamicActivate(
(workspaceMember.locale as keyof typeof APP_LOCALES) ?? SOURCE_LOCALE,
);
}
if (isDefined(workspaceMembers)) {
setCurrentWorkspaceMembers(workspaceMembers);
}
if (isDefined(deletedWorkspaceMembers)) {
setCurrentWorkspaceMembersWithDeleted(deletedWorkspaceMembers);
}
if (isDefined(availableWorkspaces)) {
setAvailableWorkspaces(availableWorkspaces);
}
}, [
userQueryLoading,
userQueryData?.currentUser,
setCurrentUser,
setCurrentUserWorkspace,
setCurrentWorkspaceMembers,
setAvailableWorkspaces,
setCurrentWorkspace,
setCurrentWorkspaceMember,
setIsCurrentUserLoaded,
initializeFormatPreferences,
setCurrentWorkspaceMembersWithDeleted,
updateLocaleCatalog,
setCoreViews,
]);
useEffect(() => {
if (!isDefined(queryDataCoreViews?.getCoreViews)) return;
setCoreViews(queryDataCoreViews.getCoreViews);
}, [queryDataCoreViews?.getCoreViews, setCoreViews]);
return null;
};