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:
+35
-24
@@ -22,6 +22,7 @@ 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';
|
||||
@@ -29,7 +30,7 @@ import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
import { isMatchingLocation } from '~/utils/isMatchingLocation';
|
||||
|
||||
export const UserProviderEffect = () => {
|
||||
export const UserAndViewsProviderEffect = () => {
|
||||
const location = useLocation();
|
||||
|
||||
const [isCurrentUserLoaded, setIsCurrentUserLoaded] = useRecoilState(
|
||||
@@ -82,53 +83,57 @@ export const UserProviderEffect = () => {
|
||||
currentWorkspaceDeletedMembersState,
|
||||
);
|
||||
|
||||
const { data: queryData, loading: queryLoading } = useGetCurrentUserQuery({
|
||||
skip:
|
||||
!isLoggedIn ||
|
||||
isCurrentUserLoaded ||
|
||||
isMatchingLocation(location, AppPath.Verify) ||
|
||||
isMatchingLocation(location, AppPath.VerifyEmail),
|
||||
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 (!queryLoading) {
|
||||
if (!userQueryLoading) {
|
||||
setIsCurrentUserLoaded(true);
|
||||
}
|
||||
|
||||
if (!isDefined(queryData?.currentUser)) return;
|
||||
if (!isDefined(userQueryData?.currentUser)) return;
|
||||
|
||||
setCurrentUser(queryData.currentUser);
|
||||
setCurrentUser(userQueryData.currentUser);
|
||||
|
||||
if (isDefined(queryData.currentUser.currentWorkspace)) {
|
||||
if (isDefined(userQueryData.currentUser.currentWorkspace)) {
|
||||
setCurrentWorkspace({
|
||||
...queryData.currentUser.currentWorkspace,
|
||||
defaultRole: queryData.currentUser.currentWorkspace.defaultRole ?? null,
|
||||
...userQueryData.currentUser.currentWorkspace,
|
||||
defaultRole:
|
||||
userQueryData.currentUser.currentWorkspace.defaultRole ?? null,
|
||||
defaultAgent:
|
||||
queryData.currentUser.currentWorkspace.defaultAgent ?? null,
|
||||
userQueryData.currentUser.currentWorkspace.defaultAgent ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(queryData.currentUser.currentUserWorkspace)) {
|
||||
if (isDefined(userQueryData.currentUser.currentUserWorkspace)) {
|
||||
setCurrentUserWorkspace({
|
||||
...queryData.currentUser.currentUserWorkspace,
|
||||
...userQueryData.currentUser.currentUserWorkspace,
|
||||
objectsPermissions:
|
||||
(queryData.currentUser.currentUserWorkspace
|
||||
(userQueryData.currentUser.currentUserWorkspace
|
||||
.objectsPermissions as Array<
|
||||
ObjectPermissions & { objectMetadataId: string }
|
||||
>) ?? [],
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(queryData.currentUser?.currentWorkspace?.views)) {
|
||||
setCoreViews(queryData.currentUser.currentWorkspace.views);
|
||||
}
|
||||
|
||||
const {
|
||||
workspaceMember,
|
||||
workspaceMembers,
|
||||
deletedWorkspaceMembers,
|
||||
availableWorkspaces,
|
||||
} = queryData.currentUser;
|
||||
} = userQueryData.currentUser;
|
||||
|
||||
const affectDefaultValuesOnEmptyWorkspaceMemberFields = (
|
||||
workspaceMember: WorkspaceMember,
|
||||
@@ -168,8 +173,8 @@ export const UserProviderEffect = () => {
|
||||
setAvailableWorkspaces(availableWorkspaces);
|
||||
}
|
||||
}, [
|
||||
queryLoading,
|
||||
queryData?.currentUser,
|
||||
userQueryLoading,
|
||||
userQueryData?.currentUser,
|
||||
setCurrentUser,
|
||||
setCurrentUserWorkspace,
|
||||
setCurrentWorkspaceMembers,
|
||||
@@ -183,5 +188,11 @@ export const UserProviderEffect = () => {
|
||||
setCoreViews,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDefined(queryDataCoreViews?.getCoreViews)) return;
|
||||
|
||||
setCoreViews(queryDataCoreViews.getCoreViews);
|
||||
}, [queryDataCoreViews?.getCoreViews, setCoreViews]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -4,14 +4,13 @@ import {
|
||||
} from '@/auth/graphql/fragments/authFragments';
|
||||
import { OBJECT_PERMISSION_FRAGMENT } from '@/settings/roles/graphql/fragments/objectPermissionFragment';
|
||||
import { ROLE_FRAGMENT } from '@/settings/roles/graphql/fragments/roleFragment';
|
||||
import { BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/billingSubscriptionsFragment';
|
||||
import { CURRENT_BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/currentBillingSubscriptionFragement';
|
||||
import { WORKSPACE_URLS_FRAGMENT } from '@/users/graphql/fragments/workspaceUrlsFragment';
|
||||
import { VIEW_FRAGMENT } from '@/views/graphql/fragments/viewFragment';
|
||||
import { DELETED_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/deletedWorkspaceMemberQueryFragment';
|
||||
import { PARTIAL_WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/partialWorkspaceMemberQueryFragment';
|
||||
import { WORKSPACE_MEMBER_QUERY_FRAGMENT } from '@/workspace-member/graphql/fragments/workspaceMemberQueryFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
import { CURRENT_BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/currentBillingSubscriptionFragement';
|
||||
import { BILLING_SUBSCRIPTION_FRAGMENT } from '@/users/graphql/fragments/billingSubscriptionsFragment';
|
||||
|
||||
export const USER_QUERY_FRAGMENT = gql`
|
||||
fragment UserQueryFragment on User {
|
||||
@@ -80,9 +79,6 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
id
|
||||
}
|
||||
isTwoFactorAuthenticationEnforced
|
||||
views {
|
||||
...ViewFragment
|
||||
}
|
||||
}
|
||||
availableWorkspaces {
|
||||
...AvailableWorkspacesFragment
|
||||
@@ -96,7 +92,6 @@ export const USER_QUERY_FRAGMENT = gql`
|
||||
${OBJECT_PERMISSION_FRAGMENT}
|
||||
${WORKSPACE_URLS_FRAGMENT}
|
||||
${ROLE_FRAGMENT}
|
||||
${VIEW_FRAGMENT}
|
||||
${AVAILABLE_WORKSPACES_FOR_AUTH_FRAGMENT}
|
||||
${AVAILABLE_WORKSPACE_FOR_AUTH_FRAGMENT}
|
||||
${CURRENT_BILLING_SUBSCRIPTION_FRAGMENT}
|
||||
|
||||
@@ -14,7 +14,10 @@ import { SOURCE_LOCALE, type APP_LOCALES } from 'twenty-shared/translations';
|
||||
import { type ObjectPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { type ColorScheme } from 'twenty-ui/input';
|
||||
import { useGetCurrentUserLazyQuery } from '~/generated-metadata/graphql';
|
||||
import {
|
||||
useFindAllCoreViewsLazyQuery,
|
||||
useGetCurrentUserLazyQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { getWorkspaceUrl } from '~/utils/getWorkspaceUrl';
|
||||
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
|
||||
|
||||
@@ -37,12 +40,17 @@ export const useLoadCurrentUser = () => {
|
||||
const { isOnAWorkspace } = useIsCurrentLocationOnAWorkspace();
|
||||
|
||||
const [getCurrentUser] = useGetCurrentUserLazyQuery();
|
||||
const [findAllCoreViews] = useFindAllCoreViewsLazyQuery();
|
||||
|
||||
const loadCurrentUser = useCallback(async () => {
|
||||
const currentUserResult = await getCurrentUser({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
const coreViewsResult = await findAllCoreViews({
|
||||
fetchPolicy: 'network-only',
|
||||
});
|
||||
|
||||
if (isDefined(currentUserResult.error)) {
|
||||
throw new Error(currentUserResult.error.message);
|
||||
}
|
||||
@@ -102,8 +110,8 @@ export const useLoadCurrentUser = () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(workspace) && isDefined(workspace.views)) {
|
||||
setCoreViews(workspace.views);
|
||||
if (isDefined(coreViewsResult.data?.getCoreViews)) {
|
||||
setCoreViews(coreViewsResult.data.getCoreViews);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -113,16 +121,17 @@ export const useLoadCurrentUser = () => {
|
||||
};
|
||||
}, [
|
||||
getCurrentUser,
|
||||
isOnAWorkspace,
|
||||
setAvailableWorkspaces,
|
||||
setCoreViews,
|
||||
findAllCoreViews,
|
||||
setCurrentUser,
|
||||
setCurrentUserWorkspace,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
isOnAWorkspace,
|
||||
setCurrentWorkspaceMembers,
|
||||
setAvailableWorkspaces,
|
||||
setCurrentUserWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
initializeFormatPreferences,
|
||||
setLastAuthenticateWorkspaceDomain,
|
||||
setCoreViews,
|
||||
]);
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user