From 200b0e57ca7aaa33267d24bcb48d7bcf59a37658 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 7 Mar 2024 15:28:13 -0500 Subject: [PATCH 1/6] Adding in snackbar context provider --- packages/client/src/App.tsx | 7 ++- .../src/components/AddDataset.component.tsx | 11 +++- .../client/src/context/Snackbar.context.tsx | 59 +++++++++++++++++++ 3 files changed, 72 insertions(+), 5 deletions(-) create mode 100644 packages/client/src/context/Snackbar.context.tsx diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 74234797..cf4417bb 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -29,6 +29,7 @@ import { DatasetProvider } from './context/Dataset.context'; import { EntryControls } from './pages/studies/EntryControls'; import { PermissionProvider } from './context/Permission.context'; import { TagTrainingView } from './pages/studies/TagTrainingView'; +import { SnackbarProvider } from './context/Snackbar.context'; const drawerWidth = 256; const Main = styled('main', { shouldForwardProp: (prop) => prop !== 'open' })<{ @@ -74,8 +75,10 @@ const App: FC = () => { - - + + + + diff --git a/packages/client/src/components/AddDataset.component.tsx b/packages/client/src/components/AddDataset.component.tsx index de2bc443..8dbea28e 100644 --- a/packages/client/src/components/AddDataset.component.tsx +++ b/packages/client/src/components/AddDataset.component.tsx @@ -8,6 +8,7 @@ import { materialRenderers, materialCells } from '@jsonforms/material-renderers' import { useCreateDatasetMutation, useDatasetExistsLazyQuery } from '../graphql/dataset/dataset'; import { Button } from '@mui/material'; import { ErrorObject } from 'ajv'; +import { useSnackbar } from '../context/Snackbar.context'; interface ShowProps { show: boolean; @@ -53,7 +54,9 @@ export const AddDataset: React.FC = (props: ShowProps) => { const initialData = {} as { name: string; description: string }; const [data, setData] = useState(initialData); - const [createDataset, { data: createDatasetResults, loading }] = useCreateDatasetMutation(); + const [createDataset, { data: createDatasetResults, loading, error: createDatasetError }] = useCreateDatasetMutation(); + + const { pushSnackbarMessage } = useSnackbar(); useEffect(() => { if (datasetExistsResults.data?.datasetExists) { @@ -74,9 +77,11 @@ export const AddDataset: React.FC = (props: ShowProps) => { useEffect(() => { if (createDatasetResults?.createDataset) { props.toggleModal(true); + } else if (createDatasetError) { + pushSnackbarMessage('Failed to create dataset, please try again or report this issue', 'error'); + console.error(createDatasetError); } - //TODO handle creation server error with snackbar - }, [createDatasetResults]); + }, [createDatasetResults, createDatasetError]); const handleChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); diff --git a/packages/client/src/context/Snackbar.context.tsx b/packages/client/src/context/Snackbar.context.tsx new file mode 100644 index 00000000..40e7a651 --- /dev/null +++ b/packages/client/src/context/Snackbar.context.tsx @@ -0,0 +1,59 @@ +import { Alert, Snackbar, Stack } from '@mui/material'; +import { createContext, FC, useState, useContext } from 'react'; + +type SnackbarType = 'success' | 'error' | 'warning' | 'info'; + +export interface SnackbarContextProps { + pushSnackbarMessage: (message: string, type?: SnackbarType) => void; +} + +const SnackbarContext = createContext({} as SnackbarContextProps); + +export interface SnackbarProviderProps { + children: React.ReactNode; +} + +export interface SnackbarMessage { + id: string; + message: string; + type: SnackbarType; +} + +export const SnackbarProvider: FC = ({ children, ...props }) => { + const [messages, setMessages] = useState([]); + + const pushSnackbarMessage = (message: string, type: SnackbarType = 'error') => { + setMessages([ + ...messages, + { + message, + type, + id: Math.random().toString(36) + } + ]); + }; + + const handleClose = (id: string, reason?: string) => { + if (reason === 'clickaway') { + return; + } + setMessages(messages.filter((message) => message.id !== id)); + }; + + return ( + + {children} + + {messages.map((message) => ( + handleClose(message.id)}> + handleClose(message.id)} sx={{ width: '100%' }}> + {message.message} + + + ))} + + + ); +}; + +export const useSnackbar = () => useContext(SnackbarContext); From 969aa7dd1e29da38b00f56c96a5ff795d424b010 Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 7 Mar 2024 15:38:53 -0500 Subject: [PATCH 2/6] Add error messages around CSV upload --- packages/client/public/locales/en/translation.json | 5 +++++ .../client/src/components/AddDataset.component.tsx | 4 +++- .../src/components/upload/CSVUpload.component.tsx | 13 ++++++++++--- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 404144b9..8c840f39 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -127,5 +127,10 @@ "tagView": { "originalEntry": "Original Entry" } + }, + "errors": { + "datasetCreate": "Failed to create dataset, please try again or report this issue", + "uploadSessionCreate": "Could not start the upload process, please try again", + "csvUpload": "Could not upload the CSV, please try again" } } diff --git a/packages/client/src/components/AddDataset.component.tsx b/packages/client/src/components/AddDataset.component.tsx index 8dbea28e..4ef75cdc 100644 --- a/packages/client/src/components/AddDataset.component.tsx +++ b/packages/client/src/components/AddDataset.component.tsx @@ -9,6 +9,7 @@ import { useCreateDatasetMutation, useDatasetExistsLazyQuery } from '../graphql/ import { Button } from '@mui/material'; import { ErrorObject } from 'ajv'; import { useSnackbar } from '../context/Snackbar.context'; +import { useTranslation } from 'react-i18next'; interface ShowProps { show: boolean; @@ -56,6 +57,7 @@ export const AddDataset: React.FC = (props: ShowProps) => { const [data, setData] = useState(initialData); const [createDataset, { data: createDatasetResults, loading, error: createDatasetError }] = useCreateDatasetMutation(); + const { t } = useTranslation(); const { pushSnackbarMessage } = useSnackbar(); useEffect(() => { @@ -78,7 +80,7 @@ export const AddDataset: React.FC = (props: ShowProps) => { if (createDatasetResults?.createDataset) { props.toggleModal(true); } else if (createDatasetError) { - pushSnackbarMessage('Failed to create dataset, please try again or report this issue', 'error'); + pushSnackbarMessage(t('errors.datasetCreate'), 'error'); console.error(createDatasetError); } }, [createDatasetResults, createDatasetError]); diff --git a/packages/client/src/components/upload/CSVUpload.component.tsx b/packages/client/src/components/upload/CSVUpload.component.tsx index a438540c..0a6c3e0c 100644 --- a/packages/client/src/components/upload/CSVUpload.component.tsx +++ b/packages/client/src/components/upload/CSVUpload.component.tsx @@ -10,6 +10,8 @@ import { useApolloClient } from '@apollo/client'; import axios from 'axios'; import { Box, Button } from '@mui/material'; import UploadIcon from '@mui/icons-material/Upload'; +import { useSnackbar } from '../../context/Snackbar.context'; +import { useTranslation } from 'react-i18next'; export interface CSVUploadProps { dataset: Dataset | null; @@ -26,6 +28,8 @@ export const CSVUpload: React.FC = ({ setCsvValid }) => { const apolloClient = useApolloClient(); + const { pushSnackbarMessage } = useSnackbar(); + const { t } = useTranslation(); // Implemented with using the apollo client directly instead of the useMutation hook // to reduce the need for multiple use effects to handle each step change @@ -42,7 +46,8 @@ export const CSVUpload: React.FC = ({ }); if (!sessionCreation.data?.createUploadSession) { - console.error('Failed to create upload session'); + pushSnackbarMessage(t('errors.uploadSessionCreate'), 'error'); + console.error(sessionCreation.errors); return; } @@ -56,7 +61,8 @@ export const CSVUpload: React.FC = ({ }); if (!uploadUrlQuery.data?.getCSVUploadURL) { - console.error('Failed to get upload url'); + pushSnackbarMessage(t('errors.csvUpload'), 'error'); + console.error(uploadUrlQuery.error); return; } @@ -70,7 +76,8 @@ export const CSVUpload: React.FC = ({ }); if (upload.status != 200) { - console.error('Failed to upload CSV'); + pushSnackbarMessage(t('errors.csvUpload'), 'error'); + console.error(uploadUrlQuery.error); return; } From 6aa225f041122cb74b2c065d28edb401aed3f0af Mon Sep 17 00:00:00 2001 From: cbolles Date: Thu, 7 Mar 2024 16:02:04 -0500 Subject: [PATCH 3/6] Integrate more error message handling --- packages/client/public/locales/en/translation.json | 7 ++++++- .../client/src/components/DatasetTable.component.tsx | 5 +++++ .../src/components/NewStudyJsonForm.component.tsx | 6 ++++++ .../client/src/components/TagTraining.component.tsx | 10 +++++++++- .../src/components/upload/EntryUpload.component.tsx | 11 +++++++++-- 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 8c840f39..21590713 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -131,6 +131,11 @@ "errors": { "datasetCreate": "Failed to create dataset, please try again or report this issue", "uploadSessionCreate": "Could not start the upload process, please try again", - "csvUpload": "Could not upload the CSV, please try again" + "csvUpload": "Could not upload the CSV, please try again", + "entryUpload": "Could not upload entry, please try again", + "entryUploadComplete": "Could not complete the upload process, please try again", + "entryQuery": "Failed to get entries for the dataset", + "studyExists": "Could not check if a study with that name exists", + "datasetsForProject": "Failed to get possible datasets" } } diff --git a/packages/client/src/components/DatasetTable.component.tsx b/packages/client/src/components/DatasetTable.component.tsx index 4fc93808..c21dbb2d 100644 --- a/packages/client/src/components/DatasetTable.component.tsx +++ b/packages/client/src/components/DatasetTable.component.tsx @@ -4,6 +4,7 @@ import { Dataset, Entry } from '../graphql/graphql'; import { useEntryForDatasetLazyQuery } from '../graphql/entry/entry'; import { EntryView } from './EntryView.component'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../context/Snackbar.context'; export interface DatasetTableProps { dataset: Dataset; @@ -12,6 +13,7 @@ export interface DatasetTableProps { export const DatasetTable: React.FC = (props) => { const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const defaultColumns: GridColDef[] = [ { @@ -41,6 +43,9 @@ export const DatasetTable: React.FC = (props) => { useEffect(() => { if (entryForDatasetResult.data) { setEntries(entryForDatasetResult.data.entryForDataset); + } else if (entryForDatasetResult.error) { + pushSnackbarMessage(t('errors.entryQuery'), 'error'); + console.error(entryForDatasetResult.error); } }, [entryForDatasetResult]); diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index 34634d87..1ae412a7 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -7,6 +7,7 @@ import { StudyExistsQuery, StudyExistsQueryVariables, StudyExistsDocument } from import { useProject } from '../context/Project.context'; import { useTranslation } from 'react-i18next'; import { useApolloClient } from '@apollo/client'; +import { useSnackbar } from '../context/Snackbar.context'; export interface NewStudyFormProps { newStudy: PartialStudyCreate | null; @@ -15,6 +16,7 @@ export interface NewStudyFormProps { export const NewStudyJsonForm: React.FC = (props) => { const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const schema = { type: 'object', @@ -110,6 +112,10 @@ export const NewStudyJsonForm: React.FC = (props) => { ]); props.setNewStudy(null); return; + } else if (exists.error) { + pushSnackbarMessage(t('errors.studyExists'), 'error'); + props.setNewStudy(null); + return; } // No errors diff --git a/packages/client/src/components/TagTraining.component.tsx b/packages/client/src/components/TagTraining.component.tsx index 21500ab1..7614347e 100644 --- a/packages/client/src/components/TagTraining.component.tsx +++ b/packages/client/src/components/TagTraining.component.tsx @@ -5,6 +5,9 @@ import { Dataset, Entry } from '../graphql/graphql'; import { GridColDef } from '@mui/x-data-grid'; import { Switch } from '@mui/material'; import { useProject } from '../context/Project.context'; +import { getData } from 'ajv/dist/compile/validate'; +import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../context/Snackbar.context'; export interface TagTrainingComponentProps { setTrainingSet: Dispatch>; @@ -17,6 +20,8 @@ export const TagTrainingComponent: React.FC = (props) const [getDatasetsQuery, getDatasetsResults] = useGetDatasetsByProjectLazyQuery(); const [trainingSet, setTrainingSet] = useState([]); const [taggingSet, setTaggingSet] = useState([]); + const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); useEffect(() => { if (project) { @@ -76,8 +81,11 @@ export const TagTrainingComponent: React.FC = (props) useEffect(() => { if (getDatasetsResults.data) { setDatasets(getDatasetsResults.data.getDatasetsByProject); + } else if (getDatasetsResults.error) { + pushSnackbarMessage(t('errors.datasetsForProject'), 'error'); + console.error(getDatasetsResults.error); } - }, [getDatasetsResults.data]); + }, [getDatasetsResults]); return ( <> diff --git a/packages/client/src/components/upload/EntryUpload.component.tsx b/packages/client/src/components/upload/EntryUpload.component.tsx index ffa2e67f..44f23186 100644 --- a/packages/client/src/components/upload/EntryUpload.component.tsx +++ b/packages/client/src/components/upload/EntryUpload.component.tsx @@ -6,6 +6,8 @@ import { UploadSession, UploadStatus } from '../../graphql/graphql'; import axios from 'axios'; import { Dispatch, SetStateAction, useState } from 'react'; import { StatusMessage } from '../../models/StatusMessage'; +import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export interface EntryUploadProps { uploadSession: UploadSession | null; @@ -24,6 +26,9 @@ export const EntryUpload: React.FC = ({ const [uploadProgress, setUploadProgress] = useState(0); const [uploadComplete, setUploadComplete] = useState(false); + const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); + const handleChange = async (event: React.ChangeEvent) => { if (!uploadSession) { console.error('No upload session'); @@ -47,7 +52,8 @@ export const EntryUpload: React.FC = ({ }); if (!uploadUrlQuery.data?.getEntryUploadURL) { - console.error('Failed to get upload url'); + pushSnackbarMessage(t('errors.entryUpload'), 'error'); + console.error(uploadUrlQuery.errors); return; } @@ -71,7 +77,8 @@ export const EntryUpload: React.FC = ({ const completionData = completionResult.data?.completeUploadSession; if (!completionData) { - console.error('Failed to complete upload session'); + pushSnackbarMessage(t('errors.entryUploadComplete'), 'error'); + console.error(completionData.errors); return; } From a26fafaa8f5c1b25573e7e051c369ce392793320 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 8 Mar 2024 10:49:27 -0500 Subject: [PATCH 4/6] Add in more error handling --- .../client/public/locales/en/translation.json | 7 ++++++- .../src/pages/contribute/TaggingInterface.tsx | 10 +++++++++- .../client/src/pages/datasets/ProjectAccess.tsx | 5 +++++ packages/client/src/pages/projects/NewProject.tsx | 5 ++++- .../client/src/pages/projects/ProjectControl.tsx | 7 ++++++- .../src/pages/projects/ProjectUserPermissions.tsx | 15 +++++++++++++-- .../client/src/pages/studies/EntryControls.tsx | 7 ++++++- 7 files changed, 49 insertions(+), 7 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 21590713..9d70fd64 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -136,6 +136,11 @@ "entryUploadComplete": "Could not complete the upload process, please try again", "entryQuery": "Failed to get entries for the dataset", "studyExists": "Could not check if a study with that name exists", - "datasetsForProject": "Failed to get possible datasets" + "datasetsForProject": "Failed to get possible datasets", + "tagComplete": "Failed to submit tag, please try again", + "datasetPermissionUpdate": "Could not give project permission", + "projectCreate": "Could not create the project, please try again", + "projectDelete": "An issue took place deleting the project", + "projectAdminUpdate": "Could not update the user's permssions" } } diff --git a/packages/client/src/pages/contribute/TaggingInterface.tsx b/packages/client/src/pages/contribute/TaggingInterface.tsx index edb6f3bd..5d27675f 100644 --- a/packages/client/src/pages/contribute/TaggingInterface.tsx +++ b/packages/client/src/pages/contribute/TaggingInterface.tsx @@ -7,6 +7,8 @@ import { useCompleteTagMutation } from '../../graphql/tag/tag'; import { Study } from '../../graphql/graphql'; import { TagProvider, useTag } from '../../context/Tag.context'; import { NoTagNotification } from '../../components/contribute/NoTagNotification.component'; +import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const TaggingInterface: React.FC = () => { const { study } = useStudy(); @@ -31,6 +33,9 @@ const MainView: React.FC = (props) => { const [completeTag, completeTagResult] = useCompleteTagMutation(); const [tagData, setTagData] = useState({}); + const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); + // Changes made to the tag data useEffect(() => { if (tagData && tag) { @@ -45,8 +50,11 @@ const MainView: React.FC = (props) => { if (completeTagResult.data) { // Assign a new tag requestTag(); + } else if (completeTagResult.error) { + pushSnackbarMessage(t('errors.tagComplete'), 'error'); + console.error(completeTagResult.error); } - }, [completeTagResult.data]); + }, [completeTagResult]); return ( <> diff --git a/packages/client/src/pages/datasets/ProjectAccess.tsx b/packages/client/src/pages/datasets/ProjectAccess.tsx index c0fc8a48..32f78fc2 100644 --- a/packages/client/src/pages/datasets/ProjectAccess.tsx +++ b/packages/client/src/pages/datasets/ProjectAccess.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from 'react'; import { DatasetProjectPermission, Project } from '../../graphql/graphql'; import { useGrantProjectDatasetAccessMutation } from '../../graphql/permission/permission'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const ProjectAccess: React.FC = () => { const { project } = useProject(); @@ -13,6 +14,7 @@ export const ProjectAccess: React.FC = () => { const [projectAccess, setProjectAccess] = useState([]); const [grantProjectDatasetAccess, grantProjectDatasetAccessResults] = useGrantProjectDatasetAccessMutation(); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); // For querying for the permissions useEffect(() => { @@ -25,6 +27,9 @@ export const ProjectAccess: React.FC = () => { useEffect(() => { if (datasetProjectPermissionResults.data) { setProjectAccess(datasetProjectPermissionResults.data.getDatasetProjectPermissions); + } else if (datasetProjectPermissionResults.error) { + pushSnackbarMessage(t('errors.datasetPermissionUpdate'), 'error'); + console.error(datasetProjectPermissionResults.error); } }, [datasetProjectPermissionResults]); diff --git a/packages/client/src/pages/projects/NewProject.tsx b/packages/client/src/pages/projects/NewProject.tsx index abe14f94..7158e448 100644 --- a/packages/client/src/pages/projects/NewProject.tsx +++ b/packages/client/src/pages/projects/NewProject.tsx @@ -6,6 +6,7 @@ import { JsonForms } from '@jsonforms/react'; import { useCreateProjectMutation, useProjectExistsLazyQuery } from '../../graphql/project/project'; import { ErrorObject } from 'ajv'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; const initialData = { name: '', @@ -21,6 +22,7 @@ export const NewProject: React.FC = () => { const [projectExistsQuery, projectExistsResults] = useProjectExistsLazyQuery(); const [additionalErrors, setAdditionalErrors] = useState([]); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const schema = { type: 'object', @@ -84,7 +86,8 @@ export const NewProject: React.FC = () => { useEffect(() => { if (error) { - //handle server side error here. For now a simple text is displayed + pushSnackbarMessage(t('errors.projectCreate'), 'error'); + console.error(error); } }, [error]); diff --git a/packages/client/src/pages/projects/ProjectControl.tsx b/packages/client/src/pages/projects/ProjectControl.tsx index 894a84b2..2d0c3aa9 100644 --- a/packages/client/src/pages/projects/ProjectControl.tsx +++ b/packages/client/src/pages/projects/ProjectControl.tsx @@ -8,6 +8,7 @@ import { useDeleteProjectMutation } from '../../graphql/project/project'; import { useConfirmation } from '../../context/Confirmation.context'; import { useEffect } from 'react'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; const ProjectControl: React.FC = () => { const { projects, updateProjectList } = useProject(); @@ -15,6 +16,7 @@ const ProjectControl: React.FC = () => { const [deleteProjectMutation, deleteProjectResults] = useDeleteProjectMutation(); const confirmation = useConfirmation(); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const handleDelete = async (id: GridRowId) => { // Execute delete mutation @@ -32,8 +34,11 @@ const ProjectControl: React.FC = () => { useEffect(() => { if (deleteProjectResults.called && deleteProjectResults.data) { updateProjectList(); + } else if (deleteProjectResults.error) { + pushSnackbarMessage(t('errors.projectDelete'), 'error'); + console.error(deleteProjectResults.error); } - }, [deleteProjectResults.data, deleteProjectResults.called]); + }, [deleteProjectResults.data, deleteProjectResults.called, deleteProjectResults.error]); const columns: GridColDef[] = [ { diff --git a/packages/client/src/pages/projects/ProjectUserPermissions.tsx b/packages/client/src/pages/projects/ProjectUserPermissions.tsx index 1b2307a1..f0202974 100644 --- a/packages/client/src/pages/projects/ProjectUserPermissions.tsx +++ b/packages/client/src/pages/projects/ProjectUserPermissions.tsx @@ -7,6 +7,7 @@ import { useGetProjectPermissionsQuery } from '../../graphql/permission/permissi import { DecodedToken, useAuth } from '../../context/Auth.context'; import { useGrantProjectPermissionsMutation } from '../../graphql/permission/permission'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const ProjectUserPermissions: React.FC = () => { const { project } = useProject(); @@ -29,6 +30,8 @@ interface EditAdminSwitchProps { const EditAdminSwitch: React.FC = (props) => { const [grantProjectPermissions, grantProjectPermissionsResults] = useGrantProjectPermissionsMutation(); + const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const handleChange = (event: ChangeEvent) => { grantProjectPermissions({ @@ -43,6 +46,9 @@ const EditAdminSwitch: React.FC = (props) => { useEffect(() => { if (grantProjectPermissionsResults.data) { props.refetch(); + } else if (grantProjectPermissionsResults.error) { + pushSnackbarMessage(t('errors.projectAdminUpdate'), 'error'); + console.error(grantProjectPermissionsResults.error); } }, [grantProjectPermissionsResults]); @@ -56,7 +62,8 @@ const EditAdminSwitch: React.FC = (props) => { }; const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { - const { data, refetch } = useGetProjectPermissionsQuery({ + + const { data, refetch, error } = useGetProjectPermissionsQuery({ variables: { project: project._id } @@ -65,12 +72,16 @@ const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { const [rows, setRows] = useState([]); const { decodedToken } = useAuth(); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); useEffect(() => { if (data?.getProjectPermissions) { setRows(data.getProjectPermissions); + } else if (error) { + pushSnackbarMessage(t('errors.projectAdminUpdate'), 'error'); + console.error(grantProjectPermissionsResults.error); } - }, [data]); + }, [data, error]); const columns: GridColDef[] = [ /* For now, only email is populated, this will change in the future diff --git a/packages/client/src/pages/studies/EntryControls.tsx b/packages/client/src/pages/studies/EntryControls.tsx index 428238c7..86e12338 100644 --- a/packages/client/src/pages/studies/EntryControls.tsx +++ b/packages/client/src/pages/studies/EntryControls.tsx @@ -7,6 +7,7 @@ import { useGetDatasetsByProjectQuery } from '../../graphql/dataset/dataset'; import { useProject } from '../../context/Project.context'; import ToggleEntryEnabled from '../../components/ToggleEntryEnabled.component'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const EntryControls: React.FC = () => { const { project } = useProject(); @@ -17,12 +18,16 @@ export const EntryControls: React.FC = () => { } }); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); useEffect(() => { if (getDatasetsByProjectResults.data) { setDatasets(getDatasetsByProjectResults.data.getDatasetsByProject); + } else if (getDatasetsByProjectResults.error) { + pushSnackbarMessage(t('errors.datasetsForProject'), 'error'); + console.error(getDatasetsByProjectResults.error); } - }, [getDatasetsByProjectResults.data]); + }, [getDatasetsByProjectResults]); const additionalColumns: GridColDef[] = [ { From d5728f87b441c0fd5b7afa6571e0164375a9c1e9 Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 8 Mar 2024 10:55:22 -0500 Subject: [PATCH 5/6] More error handling --- packages/client/public/locales/en/translation.json | 5 ++++- packages/client/src/pages/studies/NewStudy.tsx | 5 ++++- packages/client/src/pages/studies/StudyControl.tsx | 7 ++++++- packages/client/src/pages/studies/TagTrainingView.tsx | 5 +++++ packages/client/src/pages/studies/UserPermissions.tsx | 1 + 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/client/public/locales/en/translation.json b/packages/client/public/locales/en/translation.json index 9d70fd64..a1606a50 100644 --- a/packages/client/public/locales/en/translation.json +++ b/packages/client/public/locales/en/translation.json @@ -141,6 +141,9 @@ "datasetPermissionUpdate": "Could not give project permission", "projectCreate": "Could not create the project, please try again", "projectDelete": "An issue took place deleting the project", - "projectAdminUpdate": "Could not update the user's permssions" + "projectAdminUpdate": "Could not update the user's permssions", + "studyDelete": "Failed to delete the target study", + "studyCreate": "Failed to create the new study", + "tagsQuery": "Failed to get tags" } } diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index 24023038..febdc27f 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -19,6 +19,7 @@ import { } from '../../graphql/tag/tag'; import { useTranslation } from 'react-i18next'; import { TagFieldFragmentSchema, TagField } from '../../components/tagbuilder/TagProvider'; +import { useSnackbar } from '../../context/Snackbar.context'; export const NewStudy: React.FC = () => { const [activeStep, setActiveStep] = useState(0); @@ -37,6 +38,7 @@ export const NewStudy: React.FC = () => { const [tagSchemaFragments, setTagSchemaFragments] = useState<(TagFieldFragmentSchema | null)[]>([]); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); // Handles mantaining which step the user is on and the step limit useEffect(() => { @@ -83,7 +85,8 @@ export const NewStudy: React.FC = () => { }); if (result.errors || !result.data) { - console.error('Failed to create study'); + pushSnackbarMessage(t('errors.studyCreate'), 'error'); + console.error(result.errors); return; } diff --git a/packages/client/src/pages/studies/StudyControl.tsx b/packages/client/src/pages/studies/StudyControl.tsx index bda2db98..8daa93f3 100644 --- a/packages/client/src/pages/studies/StudyControl.tsx +++ b/packages/client/src/pages/studies/StudyControl.tsx @@ -8,6 +8,7 @@ import { useDeleteStudyMutation } from '../../graphql/study/study'; import { useEffect } from 'react'; import { useConfirmation } from '../../context/Confirmation.context'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const StudyControl: React.FC = () => { const { studies, updateStudies } = useStudy(); @@ -15,6 +16,7 @@ export const StudyControl: React.FC = () => { const [deleteStudyMutation, deleteStudyResults] = useDeleteStudyMutation(); const confirmation = useConfirmation(); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const handleDelete = async (id: GridRowId) => { // Execute delete mutation @@ -32,8 +34,11 @@ export const StudyControl: React.FC = () => { useEffect(() => { if (deleteStudyResults.called && deleteStudyResults.data) { updateStudies(); + } else if (deleteStudyResults.error) { + pushSnackbarMessage(t('errors.studyDelete'), 'error'); + console.error(deleteStudyResults.error); } - }, [deleteStudyResults.called, deleteStudyResults.data]); + }, [deleteStudyResults.called, deleteStudyResults.data, deleteStudyResults.error]); const columns: GridColDef[] = [ { diff --git a/packages/client/src/pages/studies/TagTrainingView.tsx b/packages/client/src/pages/studies/TagTrainingView.tsx index 3e0d920e..bf806617 100644 --- a/packages/client/src/pages/studies/TagTrainingView.tsx +++ b/packages/client/src/pages/studies/TagTrainingView.tsx @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react'; import { TagGridView } from '../../components/tag/view/TagGridView.component'; import { Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; +import { useSnackbar } from '../../context/Snackbar.context'; export const TagTrainingView: React.FC = () => { const state = useLocation().state; @@ -12,12 +13,16 @@ export const TagTrainingView: React.FC = () => { const study: Study = state.study; const [tags, setTags] = useState([]); const { t } = useTranslation(); + const { pushSnackbarMessage } = useSnackbar(); const trainingTags = useGetTrainingTagsQuery({ variables: { study: study._id, user: user.uid } }); useEffect(() => { if (trainingTags.data) { setTags(trainingTags.data.getTrainingTags); + } else if (trainingTags.error) { + pushSnackbarMessage(t('errors.tagsQuery'), 'error'); + console.error(trainingTags.error); } }, [trainingTags]); diff --git a/packages/client/src/pages/studies/UserPermissions.tsx b/packages/client/src/pages/studies/UserPermissions.tsx index df7fd8f7..ddb71e7b 100644 --- a/packages/client/src/pages/studies/UserPermissions.tsx +++ b/packages/client/src/pages/studies/UserPermissions.tsx @@ -12,6 +12,7 @@ import { import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; +import { useSnackbar } from '../../context/Snackbar.context'; export const StudyUserPermissions: React.FC = () => { const { study } = useStudy(); From 416765286dbc59b67bfb2dcb20a0d6bcff2a429a Mon Sep 17 00:00:00 2001 From: cbolles Date: Fri, 8 Mar 2024 10:58:00 -0500 Subject: [PATCH 6/6] Cleanup code --- .../src/components/AddDataset.component.tsx | 3 ++- .../src/components/TagTraining.component.tsx | 1 - packages/client/src/context/Snackbar.context.tsx | 15 +++++++++++++-- .../src/pages/projects/ProjectUserPermissions.tsx | 3 +-- .../client/src/pages/studies/UserPermissions.tsx | 1 - 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/client/src/components/AddDataset.component.tsx b/packages/client/src/components/AddDataset.component.tsx index 4ef75cdc..8d7f8113 100644 --- a/packages/client/src/components/AddDataset.component.tsx +++ b/packages/client/src/components/AddDataset.component.tsx @@ -55,7 +55,8 @@ export const AddDataset: React.FC = (props: ShowProps) => { const initialData = {} as { name: string; description: string }; const [data, setData] = useState(initialData); - const [createDataset, { data: createDatasetResults, loading, error: createDatasetError }] = useCreateDatasetMutation(); + const [createDataset, { data: createDatasetResults, loading, error: createDatasetError }] = + useCreateDatasetMutation(); const { t } = useTranslation(); const { pushSnackbarMessage } = useSnackbar(); diff --git a/packages/client/src/components/TagTraining.component.tsx b/packages/client/src/components/TagTraining.component.tsx index 7614347e..7afc0a0a 100644 --- a/packages/client/src/components/TagTraining.component.tsx +++ b/packages/client/src/components/TagTraining.component.tsx @@ -5,7 +5,6 @@ import { Dataset, Entry } from '../graphql/graphql'; import { GridColDef } from '@mui/x-data-grid'; import { Switch } from '@mui/material'; import { useProject } from '../context/Project.context'; -import { getData } from 'ajv/dist/compile/validate'; import { useTranslation } from 'react-i18next'; import { useSnackbar } from '../context/Snackbar.context'; diff --git a/packages/client/src/context/Snackbar.context.tsx b/packages/client/src/context/Snackbar.context.tsx index 40e7a651..9c7db744 100644 --- a/packages/client/src/context/Snackbar.context.tsx +++ b/packages/client/src/context/Snackbar.context.tsx @@ -45,8 +45,19 @@ export const SnackbarProvider: FC = ({ children, ...props {children} {messages.map((message) => ( - handleClose(message.id)}> - handleClose(message.id)} sx={{ width: '100%' }}> + handleClose(message.id)} + > + handleClose(message.id)} + sx={{ width: '100%' }} + > {message.message} diff --git a/packages/client/src/pages/projects/ProjectUserPermissions.tsx b/packages/client/src/pages/projects/ProjectUserPermissions.tsx index f0202974..dd50985b 100644 --- a/packages/client/src/pages/projects/ProjectUserPermissions.tsx +++ b/packages/client/src/pages/projects/ProjectUserPermissions.tsx @@ -62,7 +62,6 @@ const EditAdminSwitch: React.FC = (props) => { }; const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { - const { data, refetch, error } = useGetProjectPermissionsQuery({ variables: { project: project._id @@ -79,7 +78,7 @@ const UserPermissionTable: React.FC<{ project: Project }> = ({ project }) => { setRows(data.getProjectPermissions); } else if (error) { pushSnackbarMessage(t('errors.projectAdminUpdate'), 'error'); - console.error(grantProjectPermissionsResults.error); + console.error(error); } }, [data, error]); diff --git a/packages/client/src/pages/studies/UserPermissions.tsx b/packages/client/src/pages/studies/UserPermissions.tsx index ddb71e7b..df7fd8f7 100644 --- a/packages/client/src/pages/studies/UserPermissions.tsx +++ b/packages/client/src/pages/studies/UserPermissions.tsx @@ -12,7 +12,6 @@ import { import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useNavigate } from 'react-router-dom'; -import { useSnackbar } from '../../context/Snackbar.context'; export const StudyUserPermissions: React.FC = () => { const { study } = useStudy();