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
3 changes: 2 additions & 1 deletion packages/client/public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
"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"
"tagsQuery": "Failed to get tags",
"projectQuery": "Failed to find projects"
}
}
85 changes: 68 additions & 17 deletions packages/client/src/components/AddDataset.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,25 @@ import DialogTitle from '@mui/material/DialogTitle';
import { useEffect, useState } from 'react';
import { JsonForms } from '@jsonforms/react';
import { materialRenderers, materialCells } from '@jsonforms/material-renderers';
import { useCreateDatasetMutation, useDatasetExistsLazyQuery } from '../graphql/dataset/dataset';
import {
CreateDatasetDocument,
CreateDatasetMutation,
CreateDatasetMutationVariables,
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';
import { JsonFormsRendererRegistryEntry } from '@jsonforms/core';
import ProjectListSelect, { projectListTester } from './ProjectListSelect.component';
import { useApolloClient } from '@apollo/client';
import {
GrantProjectDatasetAccessDocument,
GrantProjectDatasetAccessMutation,
GrantProjectDatasetAccessMutationVariables
} from '../graphql/permission/permission';
import { useDataset } from '../context/Dataset.context';

interface ShowProps {
show: boolean;
Expand All @@ -26,6 +40,14 @@ const schema = {
description: {
type: 'string',
description: 'Please enter new dataset description'
},
projects: {
description: 'Select project that will have access to the dataset',
label: 'Project Access',
type: 'array',
items: {
type: 'string'
}
}
},
required: ['name', 'description']
Expand All @@ -43,6 +65,14 @@ const uischema = {
type: 'Control',
label: 'Description',
scope: '#/properties/description'
},
{
type: 'Control',
label: 'Projects with Access',
scope: '#/properties/projects',
options: {
customType: 'projectList'
}
}
]
};
Expand All @@ -51,16 +81,17 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
const [error, setError] = useState(true);
const [additionalErrors, setAdditionalErrors] = useState<ErrorObject[]>([]);
const [datasetExistsQuery, datasetExistsResults] = useDatasetExistsLazyQuery();
const { refetch: refetchDatasets } = useDataset();

const initialData = {} as { name: string; description: string };
const initialData = {} as { name: string; description: string; projects: string[] };

const [data, setData] = useState(initialData);
const [createDataset, { data: createDatasetResults, loading, error: createDatasetError }] =
useCreateDatasetMutation();

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

const client = useApolloClient();

useEffect(() => {
if (datasetExistsResults.data?.datasetExists) {
setAdditionalErrors([
Expand All @@ -77,15 +108,6 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
}
}, [datasetExistsResults.data]);

useEffect(() => {
if (createDatasetResults?.createDataset) {
props.toggleModal(true);
} else if (createDatasetError) {
pushSnackbarMessage(t('errors.datasetCreate'), 'error');
console.error(createDatasetError);
}
}, [createDatasetResults, createDatasetError]);

const handleChange = (data: any, errors: ErrorObject[] | undefined) => {
setData(data);
if (!errors || errors.length === 0) {
Expand All @@ -96,10 +118,39 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
}
};

const onCreate = () => {
createDataset({ variables: { dataset: data } });
const onCreate = async () => {
const datasetCreateResult = await client.mutate<CreateDatasetMutation, CreateDatasetMutationVariables>({
mutation: CreateDatasetDocument,
variables: { dataset: { name: data.name, description: data.description } }
});

// If the dataset failed to be created, stop
if (datasetCreateResult.errors || !datasetCreateResult.data) {
console.error(datasetCreateResult.errors);
pushSnackbarMessage(t('errors.datasetCreate'), 'error');
return;
}

refetchDatasets();

// Now grant each project selected access to the dataset
const datasetID = datasetCreateResult.data.createDataset._id;
for (const projectID of data.projects) {
await client.mutate<GrantProjectDatasetAccessMutation, GrantProjectDatasetAccessMutationVariables>({
mutation: GrantProjectDatasetAccessDocument,
variables: { project: projectID, dataset: datasetID, hasAccess: true }
});
}

// Finally toggle the model
props.toggleModal(true);
};

const renderers: JsonFormsRendererRegistryEntry[] = [
...materialRenderers,
{ tester: projectListTester, renderer: ProjectListSelect }
];

return (
<div>
<Dialog open={props.show} onClose={props.toggleModal}>
Expand All @@ -109,7 +160,7 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
schema={schema}
uischema={uischema}
data={data}
renderers={materialRenderers}
renderers={renderers}
cells={materialCells}
onChange={({ data, errors }) => handleChange(data, errors)}
additionalErrors={additionalErrors}
Expand All @@ -121,7 +172,7 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
</Button>
<Button
variant="contained"
disabled={loading || error || datasetExistsResults.data?.datasetExists}
disabled={error || datasetExistsResults.data?.datasetExists}
onClick={onCreate}
type="submit"
>
Expand Down
77 changes: 77 additions & 0 deletions packages/client/src/components/ProjectListSelect.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ControlProps, rankWith, RankedTester } from '@jsonforms/core';
import { withJsonFormsControlProps } from '@jsonforms/react';
import {
OutlinedInput,
Select,
Box,
Chip,
MenuItem,
SelectChangeEvent,
Checkbox,
ListItemText,
InputLabel
} from '@mui/material';
import { useState, useEffect } from 'react';
import { Project } from '../graphql/graphql';
import { useGetProjectsQuery } from '../graphql/project/project';
import { useTranslation } from 'react-i18next';
import { useSnackbar } from '../context/Snackbar.context';

const ProjectListSelect: React.FC<ControlProps> = (props) => {
const [projects, setProjects] = useState<Project[]>([]);
const [selectedProjects, setSelectedProjects] = useState<Project[]>([]);
const getProjects = useGetProjectsQuery();

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

const handleChange = (event: SelectChangeEvent<typeof selectedProjects>) => {
const newProjects = event.target.value as Project[];
setSelectedProjects(newProjects);
props.handleChange(
props.path,
newProjects.map((project) => project._id)
);
};

useEffect(() => {
if (getProjects.data) {
setProjects(getProjects.data.getProjects);
} else if (getProjects.error) {
pushSnackbarMessage(t('errors.projectQuery'), 'error');
}
}, [getProjects.data, getProjects.error]);

return (
<>
<InputLabel>{props.label}</InputLabel>
<Select
multiple
input={<OutlinedInput label={props.label} />}
value={selectedProjects}
onChange={handleChange}
fullWidth
renderValue={(selected) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{selected.map((value) => (
<Chip key={value._id} label={value.name} />
))}
</Box>
)}
>
{projects.map((project) => (
<MenuItem key={project._id} value={project as any}>
<Checkbox checked={selectedProjects.indexOf(project) > -1} />
<ListItemText primary={project.name} />
</MenuItem>
))}
</Select>
</>
);
};

export const projectListTester: RankedTester = rankWith(10, (uischema, _schema, _rootSchema) => {
return uischema.options != undefined && uischema.options && uischema.options.customType === 'projectList';
});

export default withJsonFormsControlProps(ProjectListSelect);
8 changes: 6 additions & 2 deletions packages/client/src/context/Dataset.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useGetDatasetsQuery } from '../graphql/dataset/dataset';

export interface DatasetContextProps {
datasets: Dataset[];
refetch: () => void;
}

const DatasetContext = createContext({} as DatasetContextProps);
Expand All @@ -14,16 +15,19 @@ export interface DatasetProviderProps {

export const DatasetProvider: FC<DatasetProviderProps> = ({ children }) => {
const [datasets, setDatasets] = useState<Dataset[]>([]);

const getDatasetsResults = useGetDatasetsQuery();

const refetch = () => {
getDatasetsResults.refetch();
};

useEffect(() => {
if (getDatasetsResults.data) {
setDatasets(getDatasetsResults.data.getDatasets);
}
}, [getDatasetsResults]);

return <DatasetContext.Provider value={{ datasets }}>{children}</DatasetContext.Provider>;
return <DatasetContext.Provider value={{ datasets, refetch }}>{children}</DatasetContext.Provider>;
};

export const useDataset = () => useContext(DatasetContext);
1 change: 1 addition & 0 deletions packages/client/src/graphql/dataset/dataset.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ query getDatasetsByProject($project: ID!) {

mutation createDataset($dataset: DatasetCreate!) {
createDataset(dataset: $dataset) {
_id
name
description
}
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/graphql/dataset/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type CreateDatasetMutationVariables = Types.Exact<{
}>;


export type CreateDatasetMutation = { __typename?: 'Mutation', createDataset: { __typename?: 'Dataset', name: string, description: string } };
export type CreateDatasetMutation = { __typename?: 'Mutation', createDataset: { __typename?: 'Dataset', _id: string, name: string, description: string } };


export const GetDatasetsDocument = gql`
Expand Down Expand Up @@ -141,6 +141,7 @@ export type GetDatasetsByProjectQueryResult = Apollo.QueryResult<GetDatasetsByPr
export const CreateDatasetDocument = gql`
mutation createDataset($dataset: DatasetCreate!) {
createDataset(dataset: $dataset) {
_id
name
description
}
Expand Down
2 changes: 2 additions & 0 deletions packages/client/src/graphql/entry/entry.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ query entryForDataset($dataset: ID!) {
meta
signedUrl
signedUrlExpiration
isTraining
}
}

Expand All @@ -25,6 +26,7 @@ query entryFromID($entry: ID!) {
meta
signedUrl
signedUrlExpiration
isTraining
}
}

Expand Down
6 changes: 4 additions & 2 deletions packages/client/src/graphql/entry/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ export type EntryForDatasetQueryVariables = Types.Exact<{
}>;


export type EntryForDatasetQuery = { __typename?: 'Query', entryForDataset: Array<{ __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number }> };
export type EntryForDatasetQuery = { __typename?: 'Query', entryForDataset: Array<{ __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number, isTraining: boolean }> };

export type EntryFromIdQueryVariables = Types.Exact<{
entry: Types.Scalars['ID']['input'];
}>;


export type EntryFromIdQuery = { __typename?: 'Query', entryFromID: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number } };
export type EntryFromIdQuery = { __typename?: 'Query', entryFromID: { __typename?: 'Entry', _id: string, organization: string, entryID: string, contentType: string, dataset: string, creator: string, dateCreated: any, meta?: any | null, signedUrl: string, signedUrlExpiration: number, isTraining: boolean } };

export type DeleteEntryMutationVariables = Types.Exact<{
entry: Types.Scalars['ID']['input'];
Expand All @@ -40,6 +40,7 @@ export const EntryForDatasetDocument = gql`
meta
signedUrl
signedUrlExpiration
isTraining
}
}
`;
Expand Down Expand Up @@ -84,6 +85,7 @@ export const EntryFromIdDocument = gql`
meta
signedUrl
signedUrlExpiration
isTraining
}
}
`;
Expand Down
1 change: 1 addition & 0 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export type Entry = {
dataset: Scalars['ID']['output'];
dateCreated: Scalars['DateTime']['output'];
entryID: Scalars['String']['output'];
isTraining: Scalars['Boolean']['output'];
meta?: Maybe<Scalars['JSON']['output']>;
organization: Scalars['ID']['output'];
signedUrl: Scalars['String']['output'];
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/graphql/tag/tag.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mutation assignTag($study: ID!) {
meta
signedUrl
signedUrlExpiration
isTraining
}
}
}
Expand Down Expand Up @@ -58,6 +59,7 @@ query getTags($study: ID!) {
meta
signedUrl
signedUrlExpiration
isTraining
}
data
complete
Expand All @@ -77,6 +79,7 @@ query getTrainingTags($study: ID!, $user: String!) {
meta
signedUrl
signedUrlExpiration
isTraining
}
data
complete
Expand Down
Loading