From 89e40417c21ce3575aaa5bc150505a68764d5e13 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 14 Sep 2023 16:07:44 -0400 Subject: [PATCH 1/7] Add placeholder for environment selection --- .../client/src/components/Environment.tsx | 73 ++++++------------- packages/client/src/components/SideBar.tsx | 6 +- 2 files changed, 25 insertions(+), 54 deletions(-) diff --git a/packages/client/src/components/Environment.tsx b/packages/client/src/components/Environment.tsx index c24a6f23..348c1560 100644 --- a/packages/client/src/components/Environment.tsx +++ b/packages/client/src/components/Environment.tsx @@ -1,57 +1,26 @@ -import { Box, Accordion, Button, Link } from '@mui/material'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import { useContext } from 'react'; -import { EnvironmentContext } from '../context/EnvironmentContext'; -import { useProject } from '../context/ProjectContext'; -import { ProjectModel } from '../graphql/graphql'; +import { Select, MenuItem, FormControl, InputLabel, Stack, Card, Paper, Typography } from '@mui/material'; export const Environment: React.FC = () => { - const { study } = useContext(EnvironmentContext); - const { project, updateProject } = useProject(); - - const handleClick = (newValue: string) => { - const newProject: ProjectModel = { name: newValue } as any; - updateProject(newProject); - }; - - const items = [ - { - name: `Project: ${project?.name}`, - subitems: [{ title: 'Project name 1' }, { title: 'Project name 2' }] - }, - { - name: `Study: ${study}`, - subitems: [{ title: 'Study name 1' }, { title: 'Study name 2' }] - } - ]; - return ( - - {items?.map((item: any) => ( - - }> - {item.name} - - - {item.subitems?.map((subitem: any) => ( -

- -

- ))} -
-
- ))} -
+ + Environment + + + Project Select + + + + + Study Select + + + + ); }; diff --git a/packages/client/src/components/SideBar.tsx b/packages/client/src/components/SideBar.tsx index 374f8b1d..977c19e1 100644 --- a/packages/client/src/components/SideBar.tsx +++ b/packages/client/src/components/SideBar.tsx @@ -2,7 +2,8 @@ import { FC, ReactNode, useState } from 'react'; import { Collapse, Divider, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { ExpandMore, ExpandLess, School, Dataset, Work, Logout, GroupWork } from '@mui/icons-material'; import { useAuth } from '../context/AuthContext'; -import {useNavigate} from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; +import { Environment } from './Environment'; interface SideBarProps { open: boolean; @@ -78,7 +79,8 @@ export const SideBar: FC = ({ open, drawerWidth }) => { anchor='left' open={open} > - + + {navItems.map((navItem) => )} From c98ddf427966b2d0d21877279a9ff96671471e9a Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 14 Sep 2023 16:50:58 -0400 Subject: [PATCH 2/7] Ability to switch between authenticated and unauthenticated view --- packages/client/src/App.tsx | 93 +++--- .../client/src/components/Environment.tsx | 2 +- packages/client/src/context/AuthContext.tsx | 4 +- .../client/src/context/ProjectContext.tsx | 46 +-- packages/client/src/context/Study.tsx | 22 ++ .../src/graphql/project/project.graphql | 93 +----- .../client/src/graphql/project/project.ts | 286 ++---------------- 7 files changed, 112 insertions(+), 434 deletions(-) create mode 100644 packages/client/src/context/Study.tsx diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 0eb8897a..a030dd1e 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -18,15 +18,15 @@ import { LoginPage } from './pages/LoginPage'; import { DatasetControls } from './pages/datasets/DatasetControls'; import { AuthCallback } from './pages/AuthCallback'; import { EnvironmentContextProvider } from './context/EnvironmentContext'; -import { AuthProvider } from './context/AuthContext'; +import { AuthProvider, useAuth } from './context/AuthContext'; import { AdminGuard } from './guards/AdminGuard'; import { LogoutPage } from './pages/LogoutPage'; import { CssBaseline, Box, styled } from '@mui/material'; -import { useState } from 'react'; +import { FC, ReactNode, useState } from 'react'; import { SideBar } from './components/SideBar'; +import { ProjectProvider } from './context/ProjectContext'; const drawerWidth = 256; - const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ open?: boolean; }>(({ theme, open }) => ({ @@ -46,45 +46,14 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ }) })); -function App() { - const [drawerOpen, setDrawerOpen] = useState(true); - +const App: FC = () => { return ( - - - -
- - - - - } /> - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - -
+
@@ -92,4 +61,56 @@ function App() { ); } +const AppInternal: FC = () => { + const [drawerOpen, setDrawerOpen] = useState(true); + const { authenticated } = useAuth(); + + const mainView: ReactNode = ( + <> + + + +
+ + + + + + +
+ + ); + + return (<>{ authenticated ? mainView : }); +}; + +const UnauthenticatedView: FC = () => { + return ; +}; + +const MyRoutes: FC = () => { + return ( + + } /> + } /> + } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +}; + export default App; diff --git a/packages/client/src/components/Environment.tsx b/packages/client/src/components/Environment.tsx index 348c1560..9d844d99 100644 --- a/packages/client/src/components/Environment.tsx +++ b/packages/client/src/components/Environment.tsx @@ -1,4 +1,4 @@ -import { Select, MenuItem, FormControl, InputLabel, Stack, Card, Paper, Typography } from '@mui/material'; +import { Select, MenuItem, FormControl, InputLabel, Stack, Paper, Typography } from '@mui/material'; export const Environment: React.FC = () => { return ( diff --git a/packages/client/src/context/AuthContext.tsx b/packages/client/src/context/AuthContext.tsx index 508dae5a..4fbb8385 100644 --- a/packages/client/src/context/AuthContext.tsx +++ b/packages/client/src/context/AuthContext.tsx @@ -63,7 +63,7 @@ export const AuthProvider: FC = (props) => { // If not token present, redirect to login if (!token) { setUnautheticated(); - navigate('/login'); + navigate('/loginpage'); return; } @@ -74,7 +74,7 @@ export const AuthProvider: FC = (props) => { // Handle expired token if (currentTime > decodedToken.exp) { setUnautheticated(); - navigate('/login'); + navigate('/loginpage'); return; } diff --git a/packages/client/src/context/ProjectContext.tsx b/packages/client/src/context/ProjectContext.tsx index e413b7df..696bc0f8 100644 --- a/packages/client/src/context/ProjectContext.tsx +++ b/packages/client/src/context/ProjectContext.tsx @@ -1,12 +1,13 @@ -import React, { createContext, FC, useContext, useEffect, useState } from 'react'; -import { ProjectModel } from '../graphql/graphql'; +import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from 'react'; +import { Project } from '../graphql/graphql'; import { useGetProjectLazyQuery } from '../graphql/project/project'; import { createTheme, ThemeProvider, useTheme } from '@mui/material'; import { useAuth } from '../context/AuthContext'; export interface ProjectContextProps { - project?: ProjectModel; - updateProject: (updatedProject: ProjectModel) => void; + project: Project | null; + setProject: Dispatch>; + projects: Project[]; } const ProjectContext = createContext({} as ProjectContextProps); @@ -15,42 +16,13 @@ export interface ProjectProviderProps { children: React.ReactNode; } -export const ProjectProvider: FC = (props) => { - const [project, setProject] = useState(); - const { decoded_token } = useAuth(); - const [getProject] = useGetProjectLazyQuery(); - const theme = useTheme(); - const [projectTheme, setProjectTheme] = useState(theme); +export const ProjectProvider: FC = ({ children }) => { + const [project, setProject] = useState(null); - useEffect(() => { - if (decoded_token?.projectId) { - getProject({ variables: { id: decoded_token.projectId } }).then((data: any) => { - if (data?.getProject) { - setProject(data.getProject as ProjectModel); - setProjectTheme( - createTheme({ - ...theme, - ...data.getProject.muiTheme - }) - ); - } - }); - } - }, [decoded_token]); - - const updateProject = (updatedProject: ProjectModel) => { - setProject(updatedProject); - }; return ( - - - {props.children} - + + {children} ); }; diff --git a/packages/client/src/context/Study.tsx b/packages/client/src/context/Study.tsx new file mode 100644 index 00000000..094a9fd9 --- /dev/null +++ b/packages/client/src/context/Study.tsx @@ -0,0 +1,22 @@ +import { Dispatch, FC, ReactNode, SetStateAction, createContext, useContext, useState } from 'react'; +import { Study } from '../graphql/graphql'; + +export interface StudyContextProps { + study: Study | null; + setStudy: Dispatch>; + studies: Study[]; +} + +const StudyContext = createContext({} as StudyContextProps); + +export interface StudyProviderProps { + children: ReactNode; +} + +export const StudyProvider: FC = (props) => { + const [study, setStudy] = useState(null); + + return {props.children}; +}; + +export const useStudy = () => useContext(StudyContext); diff --git a/packages/client/src/graphql/project/project.graphql b/packages/client/src/graphql/project/project.graphql index 5dd1fb64..7390927a 100644 --- a/packages/client/src/graphql/project/project.graphql +++ b/packages/client/src/graphql/project/project.graphql @@ -1,92 +1,7 @@ -query getProject($id: String!) { - getProject(id: $id) { - id - name +query getProjects { + getProjects { + _id, + name, description - logo - muiTheme - homePage - redirectUrl - createdAt - updatedAt - deletedAt - settings { - allowSignup - displayProjectName - } - authMethods { - emailAuth - googleAuth - } - } -} - -query listProjects { - listProjects { - id - name - description - logo - } -} - -mutation updateProjectSettings($id: String!, $displayProjectName: Boolean, $allowSignup: Boolean) { - updateProjectSettings(id: $id, projectSettings: { displayProjectName: $displayProjectName, allowSignup: $allowSignup }) { - id - name - description - logo - homePage - redirectUrl - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } - } -} - -mutation updateProjectAuthMethods($id: String!, $googleAuth: Boolean, $emailAuth: Boolean) { - updateProjectAuthMethods(id: $id, projectAuthMethods: { googleAuth: $googleAuth, emailAuth: $emailAuth }) { - id - name - description - logo - homePage - redirectUrl - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } - } -} - -mutation updateProject($id: String!, $name: String, $description: String, $logo: String, $muiTheme: JSON, $homePage: String, $redirectUrl: String) { - updateProject(id: $id, settings: { name: $name, description: $description, logo: $logo, muiTheme: $muiTheme, homePage: $homePage, redirectUrl: $redirectUrl }) { - id - name - description - logo - muiTheme - homePage - redirectUrl - createdAt - updatedAt - deletedAt - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } } } diff --git a/packages/client/src/graphql/project/project.ts b/packages/client/src/graphql/project/project.ts index 39b30e0b..82276ac1 100644 --- a/packages/client/src/graphql/project/project.ts +++ b/packages/client/src/graphql/project/project.ts @@ -5,297 +5,45 @@ import * as Types from '../graphql'; import { gql } from '@apollo/client'; import * as Apollo from '@apollo/client'; const defaultOptions = {} as const; -export type GetProjectQueryVariables = Types.Exact<{ - id: Types.Scalars['String']['input']; -}>; +export type GetProjectsQueryVariables = Types.Exact<{ [key: string]: never; }>; -export type GetProjectQuery = { __typename?: 'Query', getProject: { __typename?: 'ProjectModel', id: string, name: string, description?: string | null, logo?: string | null, muiTheme: any, homePage?: string | null, redirectUrl?: string | null, createdAt: any, updatedAt: any, deletedAt?: any | null, settings: { __typename?: 'ProjectSettingsModel', allowSignup: boolean, displayProjectName: boolean }, authMethods: { __typename?: 'ProjectAuthMethodsModel', emailAuth: boolean, googleAuth: boolean } } }; +export type GetProjectsQuery = { __typename?: 'Query', getProjects: Array<{ __typename?: 'Project', _id: string, name: string, description: string }> }; -export type ListProjectsQueryVariables = Types.Exact<{ [key: string]: never; }>; - -export type ListProjectsQuery = { __typename?: 'Query', listProjects: Array<{ __typename?: 'ProjectModel', id: string, name: string, description?: string | null, logo?: string | null }> }; - -export type UpdateProjectSettingsMutationVariables = Types.Exact<{ - id: Types.Scalars['String']['input']; - displayProjectName?: Types.InputMaybe; - allowSignup?: Types.InputMaybe; -}>; - - -export type UpdateProjectSettingsMutation = { __typename?: 'Mutation', updateProjectSettings: { __typename?: 'ProjectModel', id: string, name: string, description?: string | null, logo?: string | null, homePage?: string | null, redirectUrl?: string | null, settings: { __typename?: 'ProjectSettingsModel', displayProjectName: boolean, allowSignup: boolean }, authMethods: { __typename?: 'ProjectAuthMethodsModel', googleAuth: boolean, emailAuth: boolean } } }; - -export type UpdateProjectAuthMethodsMutationVariables = Types.Exact<{ - id: Types.Scalars['String']['input']; - googleAuth?: Types.InputMaybe; - emailAuth?: Types.InputMaybe; -}>; - - -export type UpdateProjectAuthMethodsMutation = { __typename?: 'Mutation', updateProjectAuthMethods: { __typename?: 'ProjectModel', id: string, name: string, description?: string | null, logo?: string | null, homePage?: string | null, redirectUrl?: string | null, settings: { __typename?: 'ProjectSettingsModel', displayProjectName: boolean, allowSignup: boolean }, authMethods: { __typename?: 'ProjectAuthMethodsModel', googleAuth: boolean, emailAuth: boolean } } }; - -export type UpdateProjectMutationVariables = Types.Exact<{ - id: Types.Scalars['String']['input']; - name?: Types.InputMaybe; - description?: Types.InputMaybe; - logo?: Types.InputMaybe; - muiTheme?: Types.InputMaybe; - homePage?: Types.InputMaybe; - redirectUrl?: Types.InputMaybe; -}>; - - -export type UpdateProjectMutation = { __typename?: 'Mutation', updateProject: { __typename?: 'ProjectModel', id: string, name: string, description?: string | null, logo?: string | null, muiTheme: any, homePage?: string | null, redirectUrl?: string | null, createdAt: any, updatedAt: any, deletedAt?: any | null, settings: { __typename?: 'ProjectSettingsModel', displayProjectName: boolean, allowSignup: boolean }, authMethods: { __typename?: 'ProjectAuthMethodsModel', googleAuth: boolean, emailAuth: boolean } } }; - - -export const GetProjectDocument = gql` - query getProject($id: String!) { - getProject(id: $id) { - id - name - description - logo - muiTheme - homePage - redirectUrl - createdAt - updatedAt - deletedAt - settings { - allowSignup - displayProjectName - } - authMethods { - emailAuth - googleAuth - } - } -} - `; - -/** - * __useGetProjectQuery__ - * - * To run a query within a React component, call `useGetProjectQuery` and pass it any options that fit your needs. - * When your component renders, `useGetProjectQuery` 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 } = useGetProjectQuery({ - * variables: { - * id: // value for 'id' - * }, - * }); - */ -export function useGetProjectQuery(baseOptions: Apollo.QueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetProjectDocument, options); - } -export function useGetProjectLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetProjectDocument, options); - } -export type GetProjectQueryHookResult = ReturnType; -export type GetProjectLazyQueryHookResult = ReturnType; -export type GetProjectQueryResult = Apollo.QueryResult; -export const ListProjectsDocument = gql` - query listProjects { - listProjects { - id +export const GetProjectsDocument = gql` + query getProjects { + getProjects { + _id name description - logo } } `; /** - * __useListProjectsQuery__ + * __useGetProjectsQuery__ * - * To run a query within a React component, call `useListProjectsQuery` and pass it any options that fit your needs. - * When your component renders, `useListProjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useGetProjectsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetProjectsQuery` 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 } = useListProjectsQuery({ + * const { data, loading, error } = useGetProjectsQuery({ * variables: { * }, * }); */ -export function useListProjectsQuery(baseOptions?: Apollo.QueryHookOptions) { +export function useGetProjectsQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(ListProjectsDocument, options); + return Apollo.useQuery(GetProjectsDocument, options); } -export function useListProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(ListProjectsDocument, options); + return Apollo.useLazyQuery(GetProjectsDocument, options); } -export type ListProjectsQueryHookResult = ReturnType; -export type ListProjectsLazyQueryHookResult = ReturnType; -export type ListProjectsQueryResult = Apollo.QueryResult; -export const UpdateProjectSettingsDocument = gql` - mutation updateProjectSettings($id: String!, $displayProjectName: Boolean, $allowSignup: Boolean) { - updateProjectSettings( - id: $id - projectSettings: {displayProjectName: $displayProjectName, allowSignup: $allowSignup} - ) { - id - name - description - logo - homePage - redirectUrl - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } - } -} - `; -export type UpdateProjectSettingsMutationFn = Apollo.MutationFunction; - -/** - * __useUpdateProjectSettingsMutation__ - * - * To run a mutation, you first call `useUpdateProjectSettingsMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpdateProjectSettingsMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [updateProjectSettingsMutation, { data, loading, error }] = useUpdateProjectSettingsMutation({ - * variables: { - * id: // value for 'id' - * displayProjectName: // value for 'displayProjectName' - * allowSignup: // value for 'allowSignup' - * }, - * }); - */ -export function useUpdateProjectSettingsMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpdateProjectSettingsDocument, options); - } -export type UpdateProjectSettingsMutationHookResult = ReturnType; -export type UpdateProjectSettingsMutationResult = Apollo.MutationResult; -export type UpdateProjectSettingsMutationOptions = Apollo.BaseMutationOptions; -export const UpdateProjectAuthMethodsDocument = gql` - mutation updateProjectAuthMethods($id: String!, $googleAuth: Boolean, $emailAuth: Boolean) { - updateProjectAuthMethods( - id: $id - projectAuthMethods: {googleAuth: $googleAuth, emailAuth: $emailAuth} - ) { - id - name - description - logo - homePage - redirectUrl - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } - } -} - `; -export type UpdateProjectAuthMethodsMutationFn = Apollo.MutationFunction; - -/** - * __useUpdateProjectAuthMethodsMutation__ - * - * To run a mutation, you first call `useUpdateProjectAuthMethodsMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpdateProjectAuthMethodsMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [updateProjectAuthMethodsMutation, { data, loading, error }] = useUpdateProjectAuthMethodsMutation({ - * variables: { - * id: // value for 'id' - * googleAuth: // value for 'googleAuth' - * emailAuth: // value for 'emailAuth' - * }, - * }); - */ -export function useUpdateProjectAuthMethodsMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpdateProjectAuthMethodsDocument, options); - } -export type UpdateProjectAuthMethodsMutationHookResult = ReturnType; -export type UpdateProjectAuthMethodsMutationResult = Apollo.MutationResult; -export type UpdateProjectAuthMethodsMutationOptions = Apollo.BaseMutationOptions; -export const UpdateProjectDocument = gql` - mutation updateProject($id: String!, $name: String, $description: String, $logo: String, $muiTheme: JSON, $homePage: String, $redirectUrl: String) { - updateProject( - id: $id - settings: {name: $name, description: $description, logo: $logo, muiTheme: $muiTheme, homePage: $homePage, redirectUrl: $redirectUrl} - ) { - id - name - description - logo - muiTheme - homePage - redirectUrl - createdAt - updatedAt - deletedAt - settings { - displayProjectName - allowSignup - } - authMethods { - googleAuth - emailAuth - } - } -} - `; -export type UpdateProjectMutationFn = Apollo.MutationFunction; - -/** - * __useUpdateProjectMutation__ - * - * To run a mutation, you first call `useUpdateProjectMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useUpdateProjectMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [updateProjectMutation, { data, loading, error }] = useUpdateProjectMutation({ - * variables: { - * id: // value for 'id' - * name: // value for 'name' - * description: // value for 'description' - * logo: // value for 'logo' - * muiTheme: // value for 'muiTheme' - * homePage: // value for 'homePage' - * redirectUrl: // value for 'redirectUrl' - * }, - * }); - */ -export function useUpdateProjectMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpdateProjectDocument, options); - } -export type UpdateProjectMutationHookResult = ReturnType; -export type UpdateProjectMutationResult = Apollo.MutationResult; -export type UpdateProjectMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file +export type GetProjectsQueryHookResult = ReturnType; +export type GetProjectsLazyQueryHookResult = ReturnType; +export type GetProjectsQueryResult = Apollo.QueryResult; \ No newline at end of file From 98efe6f1c18b3b58d95d62b17f97521f6ce37448 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 14 Sep 2023 17:34:47 -0400 Subject: [PATCH 3/7] Add project selection --- packages/client/src/App.tsx | 34 +++++++++++++++---- .../client/src/components/Environment.tsx | 17 ++++++++-- packages/client/src/context/AuthContext.tsx | 2 +- .../client/src/context/ProjectContext.tsx | 23 ++++++++++--- .../src/graphql/project/project.graphql | 3 +- .../client/src/graphql/project/project.ts | 3 +- 6 files changed, 64 insertions(+), 18 deletions(-) diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index a030dd1e..c6c6944b 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -18,13 +18,15 @@ import { LoginPage } from './pages/LoginPage'; import { DatasetControls } from './pages/datasets/DatasetControls'; import { AuthCallback } from './pages/AuthCallback'; import { EnvironmentContextProvider } from './context/EnvironmentContext'; -import { AuthProvider, useAuth } from './context/AuthContext'; +import { AuthProvider, useAuth, AUTH_TOKEN_STR } from './context/AuthContext'; import { AdminGuard } from './guards/AdminGuard'; import { LogoutPage } from './pages/LogoutPage'; import { CssBaseline, Box, styled } from '@mui/material'; import { FC, ReactNode, useState } from 'react'; import { SideBar } from './components/SideBar'; import { ProjectProvider } from './context/ProjectContext'; +import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink } from '@apollo/client'; +import { setContext } from '@apollo/client/link/context'; const drawerWidth = 256; const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ @@ -47,14 +49,32 @@ const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ })); const App: FC = () => { + const httpLink = createHttpLink({ uri: import.meta.env.VITE_GRAPHQL_ENDPOINT }); + const authLink = setContext((_, { headers }) => { + const token = localStorage.getItem(AUTH_TOKEN_STR); + return { + headers: { + ...headers, + authorization: token ? `Bearer ${token}` : '', + } + } + }); + + const apolloClient = new ApolloClient({ + cache: new InMemoryCache(), + link: httpLink.concat(authLink) + }); + return ( - - - - + + + + + + @@ -66,7 +86,7 @@ const AppInternal: FC = () => { const { authenticated } = useAuth(); const mainView: ReactNode = ( - <> + @@ -78,7 +98,7 @@ const AppInternal: FC = () => { - + ); return (<>{ authenticated ? mainView : }); diff --git a/packages/client/src/components/Environment.tsx b/packages/client/src/components/Environment.tsx index 9d844d99..a9ac70ed 100644 --- a/packages/client/src/components/Environment.tsx +++ b/packages/client/src/components/Environment.tsx @@ -1,15 +1,26 @@ import { Select, MenuItem, FormControl, InputLabel, Stack, Paper, Typography } from '@mui/material'; +import { useProject } from '../context/ProjectContext'; +import { Project } from '../graphql/graphql'; export const Environment: React.FC = () => { + const { project, projects, setProject } = useProject(); + + const handleChange = (newValue: string | Project) => { + if (typeof newValue == 'string') { + setProject(null); + return; + } + setProject(newValue); + }; + return ( Environment Project Select - handleChange(event.target.value)} renderValue={(value) => value.name}> + {projects.map((project) => {project.name})} diff --git a/packages/client/src/context/AuthContext.tsx b/packages/client/src/context/AuthContext.tsx index 4fbb8385..cf1579d6 100644 --- a/packages/client/src/context/AuthContext.tsx +++ b/packages/client/src/context/AuthContext.tsx @@ -2,7 +2,7 @@ import { createContext, FC, useContext, useEffect, useState, ReactNode } from 'r import jwt_decode from 'jwt-decode'; import { useNavigate } from 'react-router-dom'; -const AUTH_TOKEN_STR = 'token'; +export const AUTH_TOKEN_STR = 'token'; export interface DecodedToken { id: string; diff --git a/packages/client/src/context/ProjectContext.tsx b/packages/client/src/context/ProjectContext.tsx index 696bc0f8..fe426bd1 100644 --- a/packages/client/src/context/ProjectContext.tsx +++ b/packages/client/src/context/ProjectContext.tsx @@ -1,13 +1,12 @@ -import { createContext, Dispatch, FC, SetStateAction, useContext, useState } from 'react'; +import { createContext, Dispatch, FC, SetStateAction, useContext, useEffect, useState } from 'react'; import { Project } from '../graphql/graphql'; -import { useGetProjectLazyQuery } from '../graphql/project/project'; -import { createTheme, ThemeProvider, useTheme } from '@mui/material'; -import { useAuth } from '../context/AuthContext'; +import { useGetProjectsQuery } from '../graphql/project/project'; export interface ProjectContextProps { project: Project | null; setProject: Dispatch>; projects: Project[]; + updateProjectList: () => void; } const ProjectContext = createContext({} as ProjectContextProps); @@ -18,10 +17,24 @@ export interface ProjectProviderProps { export const ProjectProvider: FC = ({ children }) => { const [project, setProject] = useState(null); + const [projects, setProjects] = useState([]); + // Query for projects + const getProjectResults = useGetProjectsQuery(); + useEffect(() => { + if (getProjectResults.data) { + setProjects(getProjectResults.data.getProjects); + } + + }, [getProjectResults.data, getProjectResults.error]); return ( - + getProjectResults.refetch() + }}> {children} ); diff --git a/packages/client/src/graphql/project/project.graphql b/packages/client/src/graphql/project/project.graphql index 7390927a..d5920dd4 100644 --- a/packages/client/src/graphql/project/project.graphql +++ b/packages/client/src/graphql/project/project.graphql @@ -2,6 +2,7 @@ query getProjects { getProjects { _id, name, - description + description, + created } } diff --git a/packages/client/src/graphql/project/project.ts b/packages/client/src/graphql/project/project.ts index 82276ac1..a2462004 100644 --- a/packages/client/src/graphql/project/project.ts +++ b/packages/client/src/graphql/project/project.ts @@ -8,7 +8,7 @@ const defaultOptions = {} as const; export type GetProjectsQueryVariables = Types.Exact<{ [key: string]: never; }>; -export type GetProjectsQuery = { __typename?: 'Query', getProjects: Array<{ __typename?: 'Project', _id: string, name: string, description: string }> }; +export type GetProjectsQuery = { __typename?: 'Query', getProjects: Array<{ __typename?: 'Project', _id: string, name: string, description: string, created: any }> }; export const GetProjectsDocument = gql` @@ -17,6 +17,7 @@ export const GetProjectsDocument = gql` _id name description + created } } `; From 025bb6132f55ba66648806879ef94e45828bb9d9 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 14 Sep 2023 17:47:09 -0400 Subject: [PATCH 4/7] Fix apollo warning --- packages/client/src/App.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index c6c6944b..95c4312a 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -25,7 +25,7 @@ import { CssBaseline, Box, styled } from '@mui/material'; import { FC, ReactNode, useState } from 'react'; import { SideBar } from './components/SideBar'; import { ProjectProvider } from './context/ProjectContext'; -import { ApolloClient, ApolloProvider, InMemoryCache, createHttpLink } from '@apollo/client'; +import { ApolloClient, ApolloProvider, InMemoryCache, concat, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; const drawerWidth = 256; @@ -62,7 +62,7 @@ const App: FC = () => { const apolloClient = new ApolloClient({ cache: new InMemoryCache(), - link: httpLink.concat(authLink) + link: concat(authLink, httpLink) }); return ( From bca10bb1d514f4a3668a7c95d93d3b513adc63ee Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 14 Sep 2023 18:08:38 -0400 Subject: [PATCH 5/7] Rearrange --- packages/client/src/components/SideBar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/src/components/SideBar.tsx b/packages/client/src/components/SideBar.tsx index 977c19e1..0af456b5 100644 --- a/packages/client/src/components/SideBar.tsx +++ b/packages/client/src/components/SideBar.tsx @@ -72,17 +72,17 @@ export const SideBar: FC = ({ open, drawerWidth }) => { boxSizing: 'border-box', backgroundColor: '#103F68', color: 'white', - paddingTop: 18, + paddingTop: 2, mt: '64px' } }} anchor='left' open={open} > - {navItems.map((navItem) => )} + ); }; From e1394982cde5dbfba6bb56ca57e1928ba8ba1a89 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 15 Sep 2023 10:59:30 -0400 Subject: [PATCH 6/7] Add in study context --- packages/client/src/App.tsx | 23 +++--- .../client/src/components/Environment.tsx | 74 +++++++++++++------ packages/client/src/context/Study.tsx | 26 ++++++- packages/client/src/graphql/graphql.ts | 5 ++ .../client/src/graphql/study/study.graphql | 14 ++++ packages/client/src/graphql/study/study.ts | 59 +++++++++++++++ packages/server/schema.gql | 2 +- packages/server/src/study/study.resolver.ts | 4 +- packages/server/src/study/study.service.ts | 5 +- 9 files changed, 171 insertions(+), 41 deletions(-) create mode 100644 packages/client/src/graphql/study/study.graphql create mode 100644 packages/client/src/graphql/study/study.ts diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 95c4312a..6eb692f0 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -27,6 +27,7 @@ import { SideBar } from './components/SideBar'; import { ProjectProvider } from './context/ProjectContext'; import { ApolloClient, ApolloProvider, InMemoryCache, concat, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; +import {StudyProvider} from './context/Study'; const drawerWidth = 256; const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ @@ -87,17 +88,19 @@ const AppInternal: FC = () => { const mainView: ReactNode = ( - - - -
- - - - - + + + -
+
+ + + + + + +
+
); diff --git a/packages/client/src/components/Environment.tsx b/packages/client/src/components/Environment.tsx index a9ac70ed..652c61a2 100644 --- a/packages/client/src/components/Environment.tsx +++ b/packages/client/src/components/Environment.tsx @@ -1,37 +1,63 @@ import { Select, MenuItem, FormControl, InputLabel, Stack, Paper, Typography } from '@mui/material'; import { useProject } from '../context/ProjectContext'; -import { Project } from '../graphql/graphql'; +import { useStudy } from '../context/Study'; +import { Dispatch, SetStateAction, FC } from 'react'; -export const Environment: React.FC = () => { +export const Environment: FC = () => { const { project, projects, setProject } = useProject(); - - const handleChange = (newValue: string | Project) => { - if (typeof newValue == 'string') { - setProject(null); - return; - } - setProject(newValue); - }; + const { study, studies, setStudy } = useStudy(); return ( Environment - - Project Select - - - - - Study Select - - + {/* Project Selection */} + option._id} + display={(option) => option.name} + /> + {/* Study Selection */} + option._id} + display={(option) => option.name} + /> ); }; + +interface FieldSelectorProps { + value: T | null, + setValue: Dispatch>; + label: string; + options: T[]; + getKey: (option: T) => string; + display: (option: T) => string; +} + +function FieldSelector(props: FieldSelectorProps) { + const handleChange = (newValue: string | T) => { + if (typeof newValue == 'string') { + props.setValue(null); + return; + } + props.setValue(newValue); + }; + + return ( + + {props.label} + + + ); +}; diff --git a/packages/client/src/context/Study.tsx b/packages/client/src/context/Study.tsx index 094a9fd9..0b3fff67 100644 --- a/packages/client/src/context/Study.tsx +++ b/packages/client/src/context/Study.tsx @@ -1,5 +1,7 @@ -import { Dispatch, FC, ReactNode, SetStateAction, createContext, useContext, useState } from 'react'; +import { Dispatch, FC, ReactNode, SetStateAction, createContext, useContext, useState, useEffect } from 'react'; import { Study } from '../graphql/graphql'; +import { useProject } from './ProjectContext'; +import {useFindStudiesLazyQuery} from '../graphql/study/study'; export interface StudyContextProps { study: Study | null; @@ -15,8 +17,28 @@ export interface StudyProviderProps { export const StudyProvider: FC = (props) => { const [study, setStudy] = useState(null); + const [studies, setStudies] = useState([]); - return {props.children}; + const [findStudies, findStudiesResults] = useFindStudiesLazyQuery(); + + const { project } = useProject(); + + // Effect to re-query for studies + useEffect(() => { + if (!project) { + return; + } + findStudies({ variables: { project: project._id } }); + }, [project]); + + // Effect to update list of studies + useEffect(() => { + if (findStudiesResults.data) { + setStudies(findStudiesResults.data.findStudies); + } + }, [findStudiesResults]); + + return {props.children}; }; export const useStudy = () => useContext(StudyContext); diff --git a/packages/client/src/graphql/graphql.ts b/packages/client/src/graphql/graphql.ts index a2c17d5d..38b86997 100644 --- a/packages/client/src/graphql/graphql.ts +++ b/packages/client/src/graphql/graphql.ts @@ -489,6 +489,11 @@ export type QueryExistsArgs = { }; +export type QueryFindStudiesArgs = { + project: Scalars['ID']['input']; +}; + + export type QueryGetProjectArgs = { id: Scalars['String']['input']; }; diff --git a/packages/client/src/graphql/study/study.graphql b/packages/client/src/graphql/study/study.graphql new file mode 100644 index 00000000..1574ae94 --- /dev/null +++ b/packages/client/src/graphql/study/study.graphql @@ -0,0 +1,14 @@ +query findStudies($project: ID!) { + findStudies(project: $project) { + _id, + name, + description, + instructions, + project, + tagsPerEntry, + tagSchema { + dataSchema, + uiSchema + } + } +} diff --git a/packages/client/src/graphql/study/study.ts b/packages/client/src/graphql/study/study.ts new file mode 100644 index 00000000..1ed2cd74 --- /dev/null +++ b/packages/client/src/graphql/study/study.ts @@ -0,0 +1,59 @@ +/* Generated File DO NOT EDIT. */ +/* tslint:disable */ +import * as Types from '../graphql'; + +import { gql } from '@apollo/client'; +import * as Apollo from '@apollo/client'; +const defaultOptions = {} as const; +export type FindStudiesQueryVariables = Types.Exact<{ + project: Types.Scalars['ID']['input']; +}>; + + +export type FindStudiesQuery = { __typename?: 'Query', findStudies: Array<{ __typename?: 'Study', _id: string, name: string, description: string, instructions: string, project: string, tagsPerEntry: number, tagSchema: { __typename?: 'TagSchema', dataSchema: any, uiSchema: any } }> }; + + +export const FindStudiesDocument = gql` + query findStudies($project: ID!) { + findStudies(project: $project) { + _id + name + description + instructions + project + tagsPerEntry + tagSchema { + dataSchema + uiSchema + } + } +} + `; + +/** + * __useFindStudiesQuery__ + * + * To run a query within a React component, call `useFindStudiesQuery` and pass it any options that fit your needs. + * When your component renders, `useFindStudiesQuery` 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 } = useFindStudiesQuery({ + * variables: { + * project: // value for 'project' + * }, + * }); + */ +export function useFindStudiesQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindStudiesDocument, options); + } +export function useFindStudiesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindStudiesDocument, options); + } +export type FindStudiesQueryHookResult = ReturnType; +export type FindStudiesLazyQueryHookResult = ReturnType; +export type FindStudiesQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/packages/server/schema.gql b/packages/server/schema.gql index 3e92a494..92f4564d 100644 --- a/packages/server/schema.gql +++ b/packages/server/schema.gql @@ -87,7 +87,7 @@ type Query { projectExists(name: String!): Boolean! getProjects: [Project!]! studyExists(name: String!, project: ID!): Boolean! - findStudies: [Study!]! + findStudies(project: ID!): [Study!]! entryForDataset(dataset: ID!): [Entry!]! } diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index 148193d1..2d95a3a1 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -24,8 +24,8 @@ export class StudyResolver { // TODO: Replace with user specific study query @Query(() => [Study]) - async findStudies(): Promise { - return this.studyService.findAll(); + async findStudies(@Args('project', { type: () => ID }, ProjectPipe) project: Project): Promise { + return this.studyService.findAll(project); } @Mutation(() => Boolean) diff --git a/packages/server/src/study/study.service.ts b/packages/server/src/study/study.service.ts index 020daee2..97192ef2 100644 --- a/packages/server/src/study/study.service.ts +++ b/packages/server/src/study/study.service.ts @@ -4,6 +4,7 @@ import { InjectModel } from '@nestjs/mongoose'; import { Study } from './study.model'; import { StudyCreate } from './dtos/create.dto'; import { Validator } from 'jsonschema'; +import { Project } from 'src/project/project.model'; @Injectable() export class StudyService { @@ -13,8 +14,8 @@ export class StudyService { return this.studyModel.create(study); } - async findAll(): Promise { - return this.studyModel.find({}); + async findAll(project: Project): Promise { + return this.studyModel.find({ project: project._id }); } async exists(studyName: string, project: string): Promise { From d540257febc17e4284acadf0409116b240175a90 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 15 Sep 2023 11:43:22 -0400 Subject: [PATCH 7/7] Working study query --- packages/client/src/context/Study.tsx | 1 + packages/client/src/pages/LoginPage.tsx | 2 -- packages/server/src/study/study.model.ts | 6 +++--- packages/server/src/study/study.service.ts | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/client/src/context/Study.tsx b/packages/client/src/context/Study.tsx index 0b3fff67..41bee296 100644 --- a/packages/client/src/context/Study.tsx +++ b/packages/client/src/context/Study.tsx @@ -26,6 +26,7 @@ export const StudyProvider: FC = (props) => { // Effect to re-query for studies useEffect(() => { if (!project) { + setStudies([]); return; } findStudies({ variables: { project: project._id } }); diff --git a/packages/client/src/pages/LoginPage.tsx b/packages/client/src/pages/LoginPage.tsx index 9333197a..5068676b 100644 --- a/packages/client/src/pages/LoginPage.tsx +++ b/packages/client/src/pages/LoginPage.tsx @@ -10,13 +10,11 @@ export const LoginPage: FC = () => { const projectId = import.meta.env.VITE_AUTH_PROJECT_ID; const redirectUrl = encodeURIComponent(window.location.origin + '/callback'); const authUrl = `${authUrlBase}/?projectId=${projectId}&redirectUrl=${redirectUrl}`; - console.log(authUrl); const { authenticated } = useAuth(); const navigate = useNavigate(); useEffect(() => { - console.log(authenticated); if (authenticated) { navigate('/'); } else { diff --git a/packages/server/src/study/study.model.ts b/packages/server/src/study/study.model.ts index 9a9cbe70..576589d1 100644 --- a/packages/server/src/study/study.model.ts +++ b/packages/server/src/study/study.model.ts @@ -8,11 +8,11 @@ import { Schema as JSONSchema } from 'jsonschema'; @Schema() @ObjectType() export class TagSchema { - @Prop({ type: mongoose.Schema.Types.Mixed }) + @Prop({ type: mongoose.Schema.Types.Mixed, required: true }) @Field(() => JSON) dataSchema: JSONSchema; - @Prop({ type: mongoose.Schema.Types.Mixed }) + @Prop({ type: mongoose.Schema.Types.Mixed, required: true }) @Field(() => JSON) uiSchema: any; } @@ -40,7 +40,7 @@ export class Study { @Field() instructions: string; - @Prop({ type: TagSchemaSchema }) + @Prop({ type: TagSchemaSchema, required: true }) @Field(() => TagSchema) tagSchema: TagSchema; diff --git a/packages/server/src/study/study.service.ts b/packages/server/src/study/study.service.ts index 97192ef2..274cd54b 100644 --- a/packages/server/src/study/study.service.ts +++ b/packages/server/src/study/study.service.ts @@ -15,7 +15,7 @@ export class StudyService { } async findAll(project: Project): Promise { - return this.studyModel.find({ project: project._id }); + return this.studyModel.find({ project: project._id.toString() }); } async exists(studyName: string, project: string): Promise {