mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 04:55:30 +03:00
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:
parent
764374f6b8
commit
f1b3d1537a
@ -6,6 +6,8 @@ module.exports = {
|
|||||||
'./src/modules/**/*.tsx',
|
'./src/modules/**/*.tsx',
|
||||||
'./src/modules/**/*.ts',
|
'./src/modules/**/*.ts',
|
||||||
'!./src/**/*.test.tsx',
|
'!./src/**/*.test.tsx',
|
||||||
|
'!./src/**/__mocks__/*.ts',
|
||||||
|
'!./src/modules/users/graphql/queries/getCurrentUserAndViews.ts'
|
||||||
],
|
],
|
||||||
overwrite: true,
|
overwrite: true,
|
||||||
generates: {
|
generates: {
|
||||||
|
@ -8,6 +8,7 @@ import { DefaultLayout } from '@/ui/layout/page/DefaultLayout';
|
|||||||
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
|
||||||
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
|
import { CommandMenuEffect } from '~/effect-components/CommandMenuEffect';
|
||||||
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
|
import { GotoHotkeysEffect } from '~/effect-components/GotoHotkeysEffect';
|
||||||
|
import { useDefaultHomePagePath } from '~/hooks/useDefaultHomePagePath';
|
||||||
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
import { CreateProfile } from '~/pages/auth/CreateProfile';
|
||||||
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
import { CreateWorkspace } from '~/pages/auth/CreateWorkspace';
|
||||||
import { PlanRequired } from '~/pages/auth/PlanRequired';
|
import { PlanRequired } from '~/pages/auth/PlanRequired';
|
||||||
@ -39,7 +40,10 @@ import { getPageTitleFromPath } from '~/utils/title-utils';
|
|||||||
|
|
||||||
export const App = () => {
|
export const App = () => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
const { defaultHomePagePath } = useDefaultHomePagePath();
|
||||||
|
|
||||||
const pageTitle = getPageTitleFromPath(pathname);
|
const pageTitle = getPageTitleFromPath(pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageTitle title={pageTitle} />
|
<PageTitle title={pageTitle} />
|
||||||
@ -54,7 +58,7 @@ export const App = () => {
|
|||||||
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
<Route path={AppPath.CreateWorkspace} element={<CreateWorkspace />} />
|
||||||
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
<Route path={AppPath.CreateProfile} element={<CreateProfile />} />
|
||||||
<Route path={AppPath.PlanRequired} element={<PlanRequired />} />
|
<Route path={AppPath.PlanRequired} element={<PlanRequired />} />
|
||||||
<Route path="/" element={<Navigate to="/objects/companies" />} />
|
<Route path="/" element={<Navigate to={defaultHomePagePath} />} />
|
||||||
<Route path={AppPath.TasksPage} element={<Tasks />} />
|
<Route path={AppPath.TasksPage} element={<Tasks />} />
|
||||||
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
|
<Route path={AppPath.Impersonate} element={<ImpersonateEffect />} />
|
||||||
|
|
||||||
|
@ -436,7 +436,7 @@ export enum RelationMetadataType {
|
|||||||
|
|
||||||
export type Sentry = {
|
export type Sentry = {
|
||||||
__typename?: 'Sentry';
|
__typename?: 'Sentry';
|
||||||
dsn: Scalars['String'];
|
dsn?: Maybe<Scalars['String']>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Sort Directions */
|
/** Sort Directions */
|
||||||
@ -747,7 +747,7 @@ export type CheckUserExistsQuery = { __typename?: 'Query', checkUserExists: { __
|
|||||||
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl: string }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn: string } } };
|
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, signUpDisabled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl: string }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null } } };
|
||||||
|
|
||||||
export type UploadFileMutationVariables = Exact<{
|
export type UploadFileMutationVariables = Exact<{
|
||||||
file: Scalars['Upload'];
|
file: Scalars['Upload'];
|
||||||
@ -779,11 +779,6 @@ export type UploadProfilePictureMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProfilePicture: string };
|
export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProfilePicture: string };
|
||||||
|
|
||||||
export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>;
|
|
||||||
|
|
||||||
|
|
||||||
export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean, subscriptionStatus: string, featureFlags?: Array<{ __typename?: 'FeatureFlag', id: string, key: string, value: boolean, workspaceId: string }> | null } } };
|
|
||||||
|
|
||||||
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
|
export type DeleteCurrentWorkspaceMutationVariables = Exact<{ [key: string]: never; }>;
|
||||||
|
|
||||||
|
|
||||||
@ -1452,40 +1447,6 @@ export function useUploadProfilePictureMutation(baseOptions?: Apollo.MutationHoo
|
|||||||
export type UploadProfilePictureMutationHookResult = ReturnType<typeof useUploadProfilePictureMutation>;
|
export type UploadProfilePictureMutationHookResult = ReturnType<typeof useUploadProfilePictureMutation>;
|
||||||
export type UploadProfilePictureMutationResult = Apollo.MutationResult<UploadProfilePictureMutation>;
|
export type UploadProfilePictureMutationResult = Apollo.MutationResult<UploadProfilePictureMutation>;
|
||||||
export type UploadProfilePictureMutationOptions = Apollo.BaseMutationOptions<UploadProfilePictureMutation, UploadProfilePictureMutationVariables>;
|
export type UploadProfilePictureMutationOptions = Apollo.BaseMutationOptions<UploadProfilePictureMutation, UploadProfilePictureMutationVariables>;
|
||||||
export const GetCurrentUserDocument = gql`
|
|
||||||
query GetCurrentUser {
|
|
||||||
currentUser {
|
|
||||||
...UserQueryFragment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
${UserQueryFragmentFragmentDoc}`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useGetCurrentUserQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useGetCurrentUserQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useGetCurrentUserQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
|
||||||
* you can use to render your UI.
|
|
||||||
*
|
|
||||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const { data, loading, error } = useGetCurrentUserQuery({
|
|
||||||
* variables: {
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useGetCurrentUserQuery(baseOptions?: Apollo.QueryHookOptions<GetCurrentUserQuery, GetCurrentUserQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<GetCurrentUserQuery, GetCurrentUserQueryVariables>(GetCurrentUserDocument, options);
|
|
||||||
}
|
|
||||||
export function useGetCurrentUserLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetCurrentUserQuery, GetCurrentUserQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<GetCurrentUserQuery, GetCurrentUserQueryVariables>(GetCurrentUserDocument, options);
|
|
||||||
}
|
|
||||||
export type GetCurrentUserQueryHookResult = ReturnType<typeof useGetCurrentUserQuery>;
|
|
||||||
export type GetCurrentUserLazyQueryHookResult = ReturnType<typeof useGetCurrentUserLazyQuery>;
|
|
||||||
export type GetCurrentUserQueryResult = Apollo.QueryResult<GetCurrentUserQuery, GetCurrentUserQueryVariables>;
|
|
||||||
export const DeleteCurrentWorkspaceDocument = gql`
|
export const DeleteCurrentWorkspaceDocument = gql`
|
||||||
mutation DeleteCurrentWorkspace {
|
mutation DeleteCurrentWorkspace {
|
||||||
deleteCurrentWorkspace {
|
deleteCurrentWorkspace {
|
||||||
|
27
packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
Normal file
27
packages/twenty-front/src/hooks/useDefaultHomePagePath.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
|
||||||
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
|
import { QueryMethodName } from '@/object-metadata/types/QueryMethodName';
|
||||||
|
|
||||||
|
export const useDefaultHomePagePath = () => {
|
||||||
|
const { objectMetadataItem: companyObjectMetadataItem } =
|
||||||
|
useObjectMetadataItem({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.Company,
|
||||||
|
});
|
||||||
|
const { objectMetadataItem: viewObjectMetadataItem } = useObjectMetadataItem({
|
||||||
|
objectNameSingular: CoreObjectNameSingular.View,
|
||||||
|
});
|
||||||
|
const { cachedRootQuery } = useCachedRootQuery({
|
||||||
|
objectMetadataItem: viewObjectMetadataItem,
|
||||||
|
queryMethodName: QueryMethodName.FindMany,
|
||||||
|
});
|
||||||
|
|
||||||
|
const companyViewId = cachedRootQuery?.views?.edges?.find(
|
||||||
|
(view: any) =>
|
||||||
|
view?.node?.objectMetadataId === companyObjectMetadataItem.id,
|
||||||
|
)?.node.id;
|
||||||
|
const defaultHomePagePath =
|
||||||
|
'/objects/companies' + (companyViewId ? `?view=${companyViewId}` : '');
|
||||||
|
|
||||||
|
return { defaultHomePagePath };
|
||||||
|
};
|
@ -20,11 +20,12 @@ import { SnackBarProviderScope } from '@/ui/feedback/snack-bar-manager/scopes/Sn
|
|||||||
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
import { AppThemeProvider } from '@/ui/theme/components/AppThemeProvider';
|
||||||
import { ThemeType } from '@/ui/theme/constants/theme';
|
import { ThemeType } from '@/ui/theme/constants/theme';
|
||||||
import { UserProvider } from '@/users/components/UserProvider';
|
import { UserProvider } from '@/users/components/UserProvider';
|
||||||
import { App } from '~/App';
|
|
||||||
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
|
import { PageChangeEffect } from '~/effect-components/PageChangeEffect';
|
||||||
|
|
||||||
import '@emotion/react';
|
import '@emotion/react';
|
||||||
|
|
||||||
|
import { App } from './App';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import 'react-loading-skeleton/dist/skeleton.css';
|
import 'react-loading-skeleton/dist/skeleton.css';
|
||||||
|
|
||||||
|
@ -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 { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { useCachedRootQuery } from '@/apollo/hooks/useCachedRootQuery';
|
||||||
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
|
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 { useIcons } from '@/ui/display/icon/hooks/useIcons';
|
||||||
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||||
|
|
||||||
export const ObjectMetadataNavItems = () => {
|
export const ObjectMetadataNavItems = () => {
|
||||||
const { activeObjectMetadataItems } = useObjectMetadataItemForSettings();
|
const { activeObjectMetadataItems, findObjectMetadataItemByNamePlural } =
|
||||||
|
useObjectMetadataItemForSettings();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const currentPath = useLocation().pathname;
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{[
|
{[
|
||||||
@ -39,18 +62,28 @@ export const ObjectMetadataNavItems = () => {
|
|||||||
? 1
|
? 1
|
||||||
: -1;
|
: -1;
|
||||||
}),
|
}),
|
||||||
].map((objectMetadataItem) => (
|
].map((objectMetadataItem) => {
|
||||||
|
const viewId = views?.find(
|
||||||
|
(view: any) => view?.objectMetadataId === objectMetadataItem.id,
|
||||||
|
)?.id;
|
||||||
|
|
||||||
|
const navigationPath = `/objects/${objectMetadataItem.namePlural}${
|
||||||
|
viewId ? `?view=${viewId}` : ''
|
||||||
|
}`;
|
||||||
|
|
||||||
|
return (
|
||||||
<NavigationDrawerItem
|
<NavigationDrawerItem
|
||||||
key={objectMetadataItem.id}
|
key={objectMetadataItem.id}
|
||||||
label={objectMetadataItem.labelPlural}
|
label={objectMetadataItem.labelPlural}
|
||||||
to={`/objects/${objectMetadataItem.namePlural}`}
|
to={navigationPath}
|
||||||
active={currentPath === `/objects/${objectMetadataItem.namePlural}`}
|
active={currentPath === navigationPath}
|
||||||
Icon={getIcon(objectMetadataItem.icon)}
|
Icon={getIcon(objectMetadataItem.icon)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/objects/${objectMetadataItem.namePlural}`);
|
navigate(navigationPath);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
export enum QueryMethodName {
|
||||||
|
FindOne = 'findOne',
|
||||||
|
FindMany = 'findMany',
|
||||||
|
}
|
@ -20,7 +20,7 @@ export const useGenerateFindManyRecordsQuery = () => {
|
|||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}FilterInput, $orderBy: ${capitalize(
|
)}FilterInput, $orderBy: ${capitalize(
|
||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
)}OrderByInput, $lastCursor: String, $limit: Float = 30) {
|
)}OrderByInput, $lastCursor: String, $limit: Float = 60) {
|
||||||
${
|
${
|
||||||
objectMetadataItem.namePlural
|
objectMetadataItem.namePlural
|
||||||
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
}(filter: $filter, orderBy: $orderBy, first: $limit, after: $lastCursor){
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useQuery } from '@apollo/client';
|
||||||
import { useSetRecoilState } from 'recoil';
|
import { useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
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 { ColorScheme } from '@/workspace-member/types/WorkspaceMember';
|
||||||
import { useGetCurrentUserQuery } from '~/generated/graphql';
|
|
||||||
|
|
||||||
export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -16,16 +17,18 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
|||||||
currentWorkspaceMemberState,
|
currentWorkspaceMemberState,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data: userData, loading: userLoading } = useGetCurrentUserQuery({});
|
const { loading: queryLoading, data: queryData } = useQuery(
|
||||||
|
GET_CURRENT_USER_AND_VIEWS,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userLoading) {
|
if (!queryLoading) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
if (userData?.currentUser?.workspaceMember) {
|
if (queryData?.currentUser?.workspaceMember) {
|
||||||
setCurrentUser(userData.currentUser);
|
setCurrentUser(queryData.currentUser);
|
||||||
setCurrentWorkspace(userData.currentUser.defaultWorkspace);
|
setCurrentWorkspace(queryData.currentUser.defaultWorkspace);
|
||||||
const workspaceMember = userData.currentUser.workspaceMember;
|
const workspaceMember = queryData.currentUser.workspaceMember;
|
||||||
setCurrentWorkspaceMember({
|
setCurrentWorkspaceMember({
|
||||||
...workspaceMember,
|
...workspaceMember,
|
||||||
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
|
colorScheme: (workspaceMember.colorScheme as ColorScheme) ?? 'Light',
|
||||||
@ -34,10 +37,10 @@ export const UserProvider = ({ children }: React.PropsWithChildren) => {
|
|||||||
}, [
|
}, [
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
isLoading,
|
isLoading,
|
||||||
userLoading,
|
queryLoading,
|
||||||
setCurrentWorkspace,
|
setCurrentWorkspace,
|
||||||
setCurrentWorkspaceMember,
|
setCurrentWorkspaceMember,
|
||||||
userData?.currentUser,
|
queryData?.currentUser,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return isLoading ? <></> : <>{children}</>;
|
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 { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
import { TopBar } from '@/ui/layout/top-bar/TopBar';
|
import { TopBar } from '@/ui/layout/top-bar/TopBar';
|
||||||
import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEffect';
|
import { FilterQueryParamsEffect } from '@/views/components/FilterQueryParamsEffect';
|
||||||
|
import { ViewBarEffect } from '@/views/components/ViewBarEffect';
|
||||||
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
import { ViewBarFilterEffect } from '@/views/components/ViewBarFilterEffect';
|
||||||
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
|
import { ViewBarSortEffect } from '@/views/components/ViewBarSortEffect';
|
||||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||||
@ -19,7 +20,6 @@ import { ViewsHotkeyScope } from '../types/ViewsHotkeyScope';
|
|||||||
|
|
||||||
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
import { UpdateViewButtonGroup } from './UpdateViewButtonGroup';
|
||||||
import { ViewBarDetails } from './ViewBarDetails';
|
import { ViewBarDetails } from './ViewBarDetails';
|
||||||
import { ViewBarEffect } from './ViewBarEffect';
|
|
||||||
import { ViewsDropdownButton } from './ViewsDropdownButton';
|
import { ViewsDropdownButton } from './ViewsDropdownButton';
|
||||||
|
|
||||||
export type ViewBarProps = {
|
export type ViewBarProps = {
|
||||||
|
@ -4,7 +4,7 @@ import { within } from '@storybook/test';
|
|||||||
import { graphql, HttpResponse } from 'msw';
|
import { graphql, HttpResponse } from 'msw';
|
||||||
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
PageDecoratorArgs,
|
PageDecoratorArgs,
|
||||||
@ -22,13 +22,16 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
graphql.query(
|
||||||
|
getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
|
||||||
|
() => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
currentUser: mockedOnboardingUsersData[0],
|
currentUser: mockedOnboardingUsersData[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
graphqlMocks.handlers,
|
graphqlMocks.handlers,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ import { within } from '@storybook/test';
|
|||||||
import { graphql, HttpResponse } from 'msw';
|
import { graphql, HttpResponse } from 'msw';
|
||||||
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
PageDecoratorArgs,
|
PageDecoratorArgs,
|
||||||
@ -22,7 +22,9 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
graphql.query(
|
||||||
|
getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
|
||||||
|
() => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
currentUser: {
|
currentUser: {
|
||||||
@ -34,7 +36,8 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
graphqlMocks.handlers,
|
graphqlMocks.handlers,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ import { fireEvent, within } from '@storybook/test';
|
|||||||
import { graphql, HttpResponse } from 'msw';
|
import { graphql, HttpResponse } from 'msw';
|
||||||
|
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
|
||||||
import {
|
import {
|
||||||
PageDecorator,
|
PageDecorator,
|
||||||
PageDecoratorArgs,
|
PageDecoratorArgs,
|
||||||
@ -22,13 +22,16 @@ const meta: Meta<PageDecoratorArgs> = {
|
|||||||
parameters: {
|
parameters: {
|
||||||
msw: {
|
msw: {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
graphql.query(
|
||||||
|
getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '',
|
||||||
|
() => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
currentUser: mockedOnboardingUsersData[0],
|
currentUser: mockedOnboardingUsersData[0],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
graphqlMocks.handlers,
|
graphqlMocks.handlers,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -4,7 +4,7 @@ import { graphql, HttpResponse } from 'msw';
|
|||||||
import { CREATE_EVENT } from '@/analytics/graphql/queries/createEvent';
|
import { CREATE_EVENT } from '@/analytics/graphql/queries/createEvent';
|
||||||
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
import { GET_CLIENT_CONFIG } from '@/client-config/graphql/queries/getClientConfig';
|
||||||
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
|
||||||
import { GET_CURRENT_USER } from '@/users/graphql/queries/getCurrentUser';
|
import { GET_CURRENT_USER_AND_VIEWS } from '@/users/graphql/queries/getCurrentUserAndViews';
|
||||||
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
import { REACT_APP_SERVER_BASE_URL } from '~/config';
|
||||||
import { mockedActivities } from '~/testing/mock-data/activities';
|
import { mockedActivities } from '~/testing/mock-data/activities';
|
||||||
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
import { mockedCompaniesData } from '~/testing/mock-data/companies';
|
||||||
@ -22,10 +22,22 @@ const metadataGraphql = graphql.link(`${REACT_APP_SERVER_BASE_URL}/metadata`);
|
|||||||
|
|
||||||
export const graphqlMocks = {
|
export const graphqlMocks = {
|
||||||
handlers: [
|
handlers: [
|
||||||
graphql.query(getOperationName(GET_CURRENT_USER) ?? '', () => {
|
graphql.query(getOperationName(GET_CURRENT_USER_AND_VIEWS) ?? '', () => {
|
||||||
return HttpResponse.json({
|
return HttpResponse.json({
|
||||||
data: {
|
data: {
|
||||||
currentUser: mockedUsersData[0],
|
currentUser: mockedUsersData[0],
|
||||||
|
views: {
|
||||||
|
edges: mockedViewsData.map((view) => ({
|
||||||
|
node: view,
|
||||||
|
cursor: null,
|
||||||
|
})),
|
||||||
|
pageInfo: {
|
||||||
|
hasNextPage: false,
|
||||||
|
hasPreviousPage: false,
|
||||||
|
startCursor: null,
|
||||||
|
endCursor: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
@ -172,6 +172,12 @@ export class TokenService {
|
|||||||
return { token };
|
return { token };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isTokenPresent(request: Request): boolean {
|
||||||
|
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
||||||
|
|
||||||
|
return !!token;
|
||||||
|
}
|
||||||
|
|
||||||
async validateToken(request: Request): Promise<Workspace> {
|
async validateToken(request: Request): Promise<Workspace> {
|
||||||
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
const token = ExtractJwt.fromAuthHeaderAsBearerToken()(request);
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
import { ContextIdFactory, ModuleRef } from '@nestjs/core';
|
||||||
import { GqlOptionsFactory } from '@nestjs/graphql';
|
import { GqlOptionsFactory } from '@nestjs/graphql';
|
||||||
|
|
||||||
@ -56,17 +56,22 @@ export class GraphQLConfigService
|
|||||||
},
|
},
|
||||||
conditionalSchema: async (context) => {
|
conditionalSchema: async (context) => {
|
||||||
try {
|
try {
|
||||||
let workspace: Workspace;
|
if (!this.tokenService.isTokenPresent(context.req)) {
|
||||||
|
|
||||||
// If token is not valid, it will return an empty schema
|
|
||||||
try {
|
|
||||||
workspace = await this.tokenService.validateToken(context.req);
|
|
||||||
} catch (err) {
|
|
||||||
return new GraphQLSchema({});
|
return new GraphQLSchema({});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const workspace = await this.tokenService.validateToken(context.req);
|
||||||
|
|
||||||
return await this.createSchema(context, workspace);
|
return await this.createSchema(context, workspace);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof UnauthorizedException) {
|
||||||
|
throw new GraphQLError('Unauthenticated', {
|
||||||
|
extensions: {
|
||||||
|
code: 'UNAUTHENTICATED',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (error instanceof JsonWebTokenError) {
|
if (error instanceof JsonWebTokenError) {
|
||||||
//mockedUserJWT
|
//mockedUserJWT
|
||||||
throw new GraphQLError('Unauthenticated', {
|
throw new GraphQLError('Unauthenticated', {
|
||||||
|
Loading…
Reference in New Issue
Block a user