Load views on user load and read in cache (#3552)
* WIP * Poc * Use cached root query + remove proloaded views state * Fix storybook test + fix codegen * Return default schema if token is absent, unauthenticated if token is invalid * Use enum instead of bool --------- Co-authored-by: Thomas Trompette <thomast@twenty.com> Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -0,0 +1,50 @@
|
||||
import { useApolloClient } from '@apollo/client/react/hooks/useApolloClient';
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
import { useMapFieldMetadataToGraphQLQuery } from '@/object-metadata/hooks/useMapFieldMetadataToGraphQLQuery';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
|
||||
|
||||
export const useCachedRootQuery = ({
|
||||
objectMetadataItem,
|
||||
queryMethodName,
|
||||
}: {
|
||||
objectMetadataItem: ObjectMetadataItem | undefined;
|
||||
queryMethodName: QueryMethodName;
|
||||
}) => {
|
||||
const mapFieldMetadataToGraphQLQuery = useMapFieldMetadataToGraphQLQuery();
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
if (!objectMetadataItem) {
|
||||
return { cachedRootQuery: null };
|
||||
}
|
||||
|
||||
const buildRecordFieldsFragment = () => {
|
||||
return objectMetadataItem.fields
|
||||
.filter((field) => field.type !== 'RELATION')
|
||||
.map((field) => mapFieldMetadataToGraphQLQuery(field))
|
||||
.join(' \n');
|
||||
};
|
||||
|
||||
const cacheReadFragment = gql`
|
||||
fragment RootQuery on Query {
|
||||
${
|
||||
QueryMethodName.FindMany === queryMethodName
|
||||
? objectMetadataItem.namePlural
|
||||
: objectMetadataItem.nameSingular
|
||||
} {
|
||||
${QueryMethodName.FindMany === queryMethodName ? 'edges { node { ' : ''}
|
||||
${buildRecordFieldsFragment()}
|
||||
${QueryMethodName.FindMany === queryMethodName ? '}}' : ''}
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const cachedRootQuery = apolloClient.readFragment({
|
||||
id: 'ROOT_QUERY',
|
||||
fragment: cacheReadFragment,
|
||||
});
|
||||
|
||||
return { cachedRootQuery };
|
||||
};
|
||||
@ -1,15 +1,38 @@
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
|
||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
|
||||
export const ObjectMetadataNavItems = () => {
|
||||
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
|
||||
const { activeObjectMetadataItems, findObjectMetadataItemByNamePlural } =
|
||||
useObjectMetadataItemForSettings();
|
||||
const navigate = useNavigate();
|
||||
const { getIcon } = useIcons();
|
||||
const currentPath = useLocation().pathname;
|
||||
|
||||
const viewObjectMetadataItem = findObjectMetadataItemByNamePlural('views');
|
||||
|
||||
const { cachedRootQuery } = useCachedRootQuery({
|
||||
objectMetadataItem: viewObjectMetadataItem,
|
||||
queryMethodName: QueryMethodName.FindMany,
|
||||
});
|
||||
|
||||
const { records } = useFindManyRecords({
|
||||
skip: cachedRootQuery?.views,
|
||||
objectNameSingular: CoreObjectNameSingular.View,
|
||||
useRecordsWithoutConnection: true,
|
||||
});
|
||||
|
||||
const views =
|
||||
records.length > 0
|
||||
? records
|
||||
: cachedRootQuery?.views?.edges?.map((edge: any) => edge?.node);
|
||||
|
||||
return (
|
||||
<>
|
||||
{[
|
||||
@ -39,18 +62,28 @@ export const ObjectMetadataNavItems = () => {
|
||||
? 1
|
||||
: -1;
|
||||
}),
|
||||
].map((objectMetadataItem) => (
|
||||
<NavigationDrawerItem
|
||||
key={objectMetadataItem.id}
|
||||
label={objectMetadataItem.labelPlural}
|
||||
to={`/objects/${objectMetadataItem.namePlural}`}
|
||||
active={currentPath === `/objects/${objectMetadataItem.namePlural}`}
|
||||
Icon={getIcon(objectMetadataItem.icon)}
|
||||
onClick={() => {
|
||||
navigate(`/objects/${objectMetadataItem.namePlural}`);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
].map((objectMetadataItem) => {
|
||||
const viewId = views?.find(
|
||||
(view: any) => view?.objectMetadataId === objectMetadataItem.id,
|
||||
)?.id;
|
||||
|
||||
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
|
||||
viewId ? `?view=${viewId}` : ''
|
||||
}`;
|
||||
|
||||
return (
|
||||
<NavigationDrawerItem
|
||||
key={objectMetadataItem.id}
|
||||
label={objectMetadataItem.labelPlural}
|
||||
to={navigationPath}
|
||||
active={currentPath === navigationPath}
|
||||
Icon={getIcon(objectMetadataItem.icon)}
|
||||
onClick={() => {
|
||||
navigate(navigationPath);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
export enum QueryMethodName {
|
||||
FindOne = 'findOne',
|
||||
FindMany = 'findMany',
|
||||
}
|
||||
@ -20,7 +20,7 @@ export const useGenerateFindManyRecordsQuery = () => {
|
||||
objectMetadataItem.nameSingular,
|
||||
)}FilterInput, $orderBy: ${capitalize(
|
||||
objectMetadataItem.nameSingular,
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float = 30) {
|
||||
)}OrderByInput, $lastCursor: String, $limit: Float = 60) {
|
||||
${
|
||||
objectMetadataItem.namePlural
|
||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
|
||||
import { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||
import { useGetCurrentUserQuery } from '~/generated/graphql';
|
||||
|
||||
export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@ -16,16 +17,18 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
||||
currentWorkspaceMemberState,
|
||||
);
|
||||
|
||||
const { data: userData, loading: userLoading } = useGetCurrentUserQuery({});
|
||||
const { loading: queryLoading, data: queryData } = useQuery(
|
||||
GET_CURRENT_USER_AND_VIEWS,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!userLoading) {
|
||||
if (!queryLoading) {
|
||||
setIsLoading(false);
|
||||
}
|
||||
if (userData?.currentUser?.workspaceMember) {
|
||||
setCurrentUser(userData.currentUser);
|
||||
setCurrentWorkspace(userData.currentUser.defaultWorkspace);
|
||||
const workspaceMember = userData.currentUser.workspaceMember;
|
||||
if (queryData?.currentUser?.workspaceMember) {
|
||||
setCurrentUser(queryData.currentUser);
|
||||
setCurrentWorkspace(queryData.currentUser.defaultWorkspace);
|
||||
const workspaceMember = queryData.currentUser.workspaceMember;
|
||||
setCurrentWorkspaceMember({
|
||||
...workspaceMember,
|
||||
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
|
||||
@ -34,10 +37,10 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
||||
}, [
|
||||
setCurrentUser,
|
||||
isLoading,
|
||||
userLoading,
|
||||
queryLoading,
|
||||
setCurrentWorkspace,
|
||||
setCurrentWorkspaceMember,
|
||||
userData?.currentUser,
|
||||
queryData?.currentUser,
|
||||
]);
|
||||
|
||||
return isLoading ? <></> : <>{children}</>;
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_CURRENT_USER = gql`
|
||||
query GetCurrentUser {
|
||||
currentUser {
|
||||
...UserQueryFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -0,0 +1,103 @@
|
||||
// This query cannot be put in the graphQL folder because it cannot be generated by the graphQL codegen.
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const GET_CURRENT_USER_AND_VIEWS = gql`
|
||||
query GetCurrentUserAndViews {
|
||||
currentUser {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
email
|
||||
canImpersonate
|
||||
supportUserHash
|
||||
workspaceMember {
|
||||
id
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
colorScheme
|
||||
avatarUrl
|
||||
locale
|
||||
}
|
||||
defaultWorkspace {
|
||||
id
|
||||
displayName
|
||||
logo
|
||||
domainName
|
||||
inviteHash
|
||||
allowImpersonation
|
||||
subscriptionStatus
|
||||
featureFlags {
|
||||
id
|
||||
key
|
||||
value
|
||||
workspaceId
|
||||
}
|
||||
}
|
||||
}
|
||||
views {
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
name
|
||||
objectMetadataId
|
||||
type
|
||||
deletedAt
|
||||
viewFilters {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
fieldMetadataId
|
||||
operand
|
||||
value
|
||||
displayValue
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
viewSorts {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
fieldMetadataId
|
||||
direction
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
viewFields {
|
||||
edges {
|
||||
cursor
|
||||
node {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
fieldMetadataId
|
||||
isVisible
|
||||
size
|
||||
position
|
||||
deletedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -7,6 +7,7 @@ import { ObjectSortDropdownButton } from '@/object-record/object-sort-dropdown/c
|
||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||
import { TopBar } from '@/ui/layout/top-bar/TopBar';
|
||||
import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEffect';
|
||||
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
|
||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
|
||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||
@ -19,7 +20,6 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
|
||||
|
||||
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
||||
import { ViewBarDetails } from './ViewBarDetails';
|
||||
import { ViewBarEffect } from './ViewBarEffect';
|
||||
import { ViewsDropdownButton } from './ViewsDropdownButton';
|
||||
|
||||
export type ViewBarProps = {
|
||||
|
||||
Reference in New Issue
Block a user