Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,5 +127,23 @@
"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",
"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",
"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",
"studyDelete": "Failed to delete the target study",
"studyCreate": "Failed to create the new study",
"tagsQuery": "Failed to get tags"
}
}
7 changes: 5 additions & 2 deletions packages/client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' })<{
Expand Down Expand Up @@ -74,8 +75,10 @@ const App: FC = () => {
<ApolloProvider client={apolloClient}>
<AuthProvider>
<ConfirmationProvider>
<CssBaseline />
<AppInternal />
<SnackbarProvider>
<CssBaseline />
<AppInternal />
</SnackbarProvider>
</ConfirmationProvider>
</AuthProvider>
</ApolloProvider>
Expand Down
14 changes: 11 additions & 3 deletions packages/client/src/components/AddDataset.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ 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';
import { useTranslation } from 'react-i18next';

interface ShowProps {
show: boolean;
Expand Down Expand Up @@ -53,7 +55,11 @@ export const AddDataset: React.FC<ShowProps> = (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 { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

useEffect(() => {
if (datasetExistsResults.data?.datasetExists) {
Expand All @@ -74,9 +80,11 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
useEffect(() => {
if (createDatasetResults?.createDataset) {
props.toggleModal(true);
} else if (createDatasetError) {
pushSnackbarMessage(t('errors.datasetCreate'), 'error');
console.error(createDatasetError);
}
//TODO handle creation server error with snackbar
}, [createDatasetResults]);
}, [createDatasetResults, createDatasetError]);

const handleChange = (data: any, errors: ErrorObject[] | undefined) => {
setData(data);
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/components/DatasetTable.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -12,6 +13,7 @@ export interface DatasetTableProps {

export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

const defaultColumns: GridColDef[] = [
{
Expand Down Expand Up @@ -41,6 +43,9 @@ export const DatasetTable: React.FC<DatasetTableProps> = (props) => {
useEffect(() => {
if (entryForDatasetResult.data) {
setEntries(entryForDatasetResult.data.entryForDataset);
} else if (entryForDatasetResult.error) {
pushSnackbarMessage(t('errors.entryQuery'), 'error');
console.error(entryForDatasetResult.error);
}
}, [entryForDatasetResult]);

Expand Down
6 changes: 6 additions & 0 deletions packages/client/src/components/NewStudyJsonForm.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,6 +16,7 @@ export interface NewStudyFormProps {

export const NewStudyJsonForm: React.FC<NewStudyFormProps> = (props) => {
const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

const schema = {
type: 'object',
Expand Down Expand Up @@ -110,6 +112,10 @@ export const NewStudyJsonForm: React.FC<NewStudyFormProps> = (props) => {
]);
props.setNewStudy(null);
return;
} else if (exists.error) {
pushSnackbarMessage(t('errors.studyExists'), 'error');
props.setNewStudy(null);
return;
}

// No errors
Expand Down
9 changes: 8 additions & 1 deletion packages/client/src/components/TagTraining.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ 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 { useTranslation } from 'react-i18next';
import { useSnackbar } from '../context/Snackbar.context';

export interface TagTrainingComponentProps {
setTrainingSet: Dispatch<SetStateAction<string[]>>;
Expand All @@ -17,6 +19,8 @@ export const TagTrainingComponent: React.FC<TagTrainingComponentProps> = (props)
const [getDatasetsQuery, getDatasetsResults] = useGetDatasetsByProjectLazyQuery();
const [trainingSet, setTrainingSet] = useState<string[]>([]);
const [taggingSet, setTaggingSet] = useState<string[]>([]);
const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

useEffect(() => {
if (project) {
Expand Down Expand Up @@ -76,8 +80,11 @@ export const TagTrainingComponent: React.FC<TagTrainingComponentProps> = (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 (
<>
Expand Down
13 changes: 10 additions & 3 deletions packages/client/src/components/upload/CSVUpload.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -26,6 +28,8 @@ export const CSVUpload: React.FC<CSVUploadProps> = ({
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
Expand All @@ -42,7 +46,8 @@ export const CSVUpload: React.FC<CSVUploadProps> = ({
});

if (!sessionCreation.data?.createUploadSession) {
console.error('Failed to create upload session');
pushSnackbarMessage(t('errors.uploadSessionCreate'), 'error');
console.error(sessionCreation.errors);
return;
}

Expand All @@ -56,7 +61,8 @@ export const CSVUpload: React.FC<CSVUploadProps> = ({
});

if (!uploadUrlQuery.data?.getCSVUploadURL) {
console.error('Failed to get upload url');
pushSnackbarMessage(t('errors.csvUpload'), 'error');
console.error(uploadUrlQuery.error);
return;
}

Expand All @@ -70,7 +76,8 @@ export const CSVUpload: React.FC<CSVUploadProps> = ({
});

if (upload.status != 200) {
console.error('Failed to upload CSV');
pushSnackbarMessage(t('errors.csvUpload'), 'error');
console.error(uploadUrlQuery.error);
return;
}

Expand Down
11 changes: 9 additions & 2 deletions packages/client/src/components/upload/EntryUpload.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,6 +26,9 @@ export const EntryUpload: React.FC<EntryUploadProps> = ({
const [uploadProgress, setUploadProgress] = useState<number>(0);
const [uploadComplete, setUploadComplete] = useState<boolean>(false);

const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

const handleChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
if (!uploadSession) {
console.error('No upload session');
Expand All @@ -47,7 +52,8 @@ export const EntryUpload: React.FC<EntryUploadProps> = ({
});

if (!uploadUrlQuery.data?.getEntryUploadURL) {
console.error('Failed to get upload url');
pushSnackbarMessage(t('errors.entryUpload'), 'error');
console.error(uploadUrlQuery.errors);
return;
}

Expand All @@ -71,7 +77,8 @@ export const EntryUpload: React.FC<EntryUploadProps> = ({

const completionData = completionResult.data?.completeUploadSession;
if (!completionData) {
console.error('Failed to complete upload session');
pushSnackbarMessage(t('errors.entryUploadComplete'), 'error');
console.error(completionData.errors);
return;
}

Expand Down
70 changes: 70 additions & 0 deletions packages/client/src/context/Snackbar.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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<SnackbarContextProps>({} as SnackbarContextProps);

export interface SnackbarProviderProps {
children: React.ReactNode;
}

export interface SnackbarMessage {
id: string;
message: string;
type: SnackbarType;
}

export const SnackbarProvider: FC<SnackbarProviderProps> = ({ children, ...props }) => {
const [messages, setMessages] = useState<SnackbarMessage[]>([]);

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 (
<SnackbarContext.Provider value={{ pushSnackbarMessage }} {...props}>
{children}
<Stack spacing={2} sx={{ maxWidth: 600 }}>
{messages.map((message) => (
<Snackbar
key={message.id}
open={true}
autoHideDuration={5000}
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
onClose={() => handleClose(message.id)}
>
<Alert
variant="filled"
severity={message.type}
onClose={() => handleClose(message.id)}
sx={{ width: '100%' }}
>
{message.message}
</Alert>
</Snackbar>
))}
</Stack>
</SnackbarContext.Provider>
);
};

export const useSnackbar = () => useContext(SnackbarContext);
10 changes: 9 additions & 1 deletion packages/client/src/pages/contribute/TaggingInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -31,6 +33,9 @@ const MainView: React.FC<MainViewProps> = (props) => {
const [completeTag, completeTagResult] = useCompleteTagMutation();
const [tagData, setTagData] = useState<any>({});

const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

// Changes made to the tag data
useEffect(() => {
if (tagData && tag) {
Expand All @@ -45,8 +50,11 @@ const MainView: React.FC<MainViewProps> = (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 (
<>
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/pages/datasets/ProjectAccess.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ 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();
const [getDatasetProjectPermissions, datasetProjectPermissionResults] = useGetDatasetProjectPermissionsLazyQuery();
const [projectAccess, setProjectAccess] = useState<DatasetProjectPermission[]>([]);
const [grantProjectDatasetAccess, grantProjectDatasetAccessResults] = useGrantProjectDatasetAccessMutation();
const { t } = useTranslation();
const { pushSnackbarMessage } = useSnackbar();

// For querying for the permissions
useEffect(() => {
Expand All @@ -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]);

Expand Down
Loading