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:
@@ -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;
|
||||
};
|
||||
Reference in New Issue
Block a user