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
64 changes: 51 additions & 13 deletions packages/client/src/components/AddDataset.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogTitle from '@mui/material/DialogTitle';
import { useState } from 'react';
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 { Button } from '@mui/material';
import { ErrorObject } from 'ajv';

interface ShowProps {
show: boolean;
toggleModal: () => void;
toggleModal: (newDatasetCreated: boolean) => void;
}

const schema = {
Expand Down Expand Up @@ -44,22 +47,51 @@ const uischema = {

export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
const [error, setError] = useState(true);
const initialData = {
name: '',
description: ''
};
const [additionalErrors, setAdditionalErrors] = useState<ErrorObject[]>([]);
const [datasetExistsQuery, datasetExistsResults] = useDatasetExistsLazyQuery();

const initialData = {} as { name: string; description: string };

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

useEffect(() => {
if (datasetExistsResults.data?.datasetExists) {
setAdditionalErrors([
{
instancePath: '/name',
keyword: 'uniqueProjectName',
message: 'A dataset with this name already exists',
schemaPath: '#/properties/name/name',
params: { keyword: 'uniqueProjectName' }
}
]);
} else {
setAdditionalErrors([]);
}
}, [datasetExistsResults.data]);

const handleChange = (data: any) => {
useEffect(() => {
if (createDatasetResults?.createDataset) {
props.toggleModal(true);
}
//TODO handle creation server error with snackbar
}, [createDatasetResults]);

const handleChange = (data: any, errors: ErrorObject[] | undefined) => {
setData(data);
if (data.name.length > 1 && data.description.length > 1) {
if (!errors || errors.length === 0) {
datasetExistsQuery({ variables: { name: data.name } });
setError(false);
} else {
setError(true);
}
};

const onCreate = () => {
createDataset({ variables: { dataset: data } });
};

return (
<div>
<Dialog open={props.show} onClose={props.toggleModal}>
Expand All @@ -71,16 +103,22 @@ export const AddDataset: React.FC<ShowProps> = (props: ShowProps) => {
data={data}
renderers={materialRenderers}
cells={materialCells}
onChange={({ data }) => handleChange(data)}
onChange={({ data, errors }) => handleChange(data, errors)}
additionalErrors={additionalErrors}
/>
</DialogContent>
<DialogActions>
<button onClick={props.toggleModal} type="submit">
<Button onClick={() => props.toggleModal(false)} type="submit">
Cancel
</button>
<button disabled={error} onClick={props.toggleModal} type="submit">
</Button>
<Button
variant="contained"
disabled={loading || error || datasetExistsResults.data?.datasetExists}
onClick={onCreate}
type="submit"
>
Create
</button>
</Button>
</DialogActions>
</Dialog>
</div>
Expand Down
1 change: 0 additions & 1 deletion packages/client/src/components/SideBar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ export const SideBar: FC<SideBarProps> = ({ open, drawerWidth }) => {
{ name: 'New Study', action: () => navigate('/study/new'), visible: (p) => p!.projectAdmin },
{ name: 'Study Control', action: () => navigate('/study/controls'), visible: (p) => p!.projectAdmin },
{ name: 'User Permissions', action: () => navigate('/study/permissions'), visible: (p) => p!.studyAdmin },
{ name: 'Entry Controls', action: () => navigate('/study/controls'), visible: (p) => p!.studyAdmin },
{ name: 'Entry Controls', action: () => navigate('/study/entries'), visible: (p) => p!.studyAdmin },
{ name: 'Download Tags', action: () => navigate('/study/tags'), visible: (p) => p!.studyAdmin }
]
Expand Down
11 changes: 11 additions & 0 deletions packages/client/src/graphql/dataset/dataset.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,21 @@ query getDatasets {
}
}

query datasetExists($name: String!) {
datasetExists(name: $name)
}

query getDatasetsByProject($project: ID!) {
getDatasetsByProject(project: $project) {
_id
name
description
}
}

mutation createDataset($dataset: DatasetCreate!) {
createDataset(dataset: $dataset) {
name
description
}
}
83 changes: 82 additions & 1 deletion packages/client/src/graphql/dataset/dataset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,27 @@ export type GetDatasetsQueryVariables = Types.Exact<{ [key: string]: never; }>;

export type GetDatasetsQuery = { __typename?: 'Query', getDatasets: Array<{ __typename?: 'Dataset', _id: string, name: string, description: string }> };

export type DatasetExistsQueryVariables = Types.Exact<{
name: Types.Scalars['String']['input'];
}>;


export type DatasetExistsQuery = { __typename?: 'Query', datasetExists: boolean };

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


export type GetDatasetsByProjectQuery = { __typename?: 'Query', getDatasetsByProject: Array<{ __typename?: 'Dataset', _id: string, name: string, description: string }> };

export type CreateDatasetMutationVariables = Types.Exact<{
dataset: Types.DatasetCreate;
}>;


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


export const GetDatasetsDocument = gql`
query getDatasets {
Expand Down Expand Up @@ -54,6 +68,39 @@ export function useGetDatasetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
export type GetDatasetsQueryHookResult = ReturnType<typeof useGetDatasetsQuery>;
export type GetDatasetsLazyQueryHookResult = ReturnType<typeof useGetDatasetsLazyQuery>;
export type GetDatasetsQueryResult = Apollo.QueryResult<GetDatasetsQuery, GetDatasetsQueryVariables>;
export const DatasetExistsDocument = gql`
query datasetExists($name: String!) {
datasetExists(name: $name)
}
`;

/**
* __useDatasetExistsQuery__
*
* To run a query within a React component, call `useDatasetExistsQuery` and pass it any options that fit your needs.
* When your component renders, `useDatasetExistsQuery` 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 } = useDatasetExistsQuery({
* variables: {
* name: // value for 'name'
* },
* });
*/
export function useDatasetExistsQuery(baseOptions: Apollo.QueryHookOptions<DatasetExistsQuery, DatasetExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<DatasetExistsQuery, DatasetExistsQueryVariables>(DatasetExistsDocument, options);
}
export function useDatasetExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<DatasetExistsQuery, DatasetExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<DatasetExistsQuery, DatasetExistsQueryVariables>(DatasetExistsDocument, options);
}
export type DatasetExistsQueryHookResult = ReturnType<typeof useDatasetExistsQuery>;
export type DatasetExistsLazyQueryHookResult = ReturnType<typeof useDatasetExistsLazyQuery>;
export type DatasetExistsQueryResult = Apollo.QueryResult<DatasetExistsQuery, DatasetExistsQueryVariables>;
export const GetDatasetsByProjectDocument = gql`
query getDatasetsByProject($project: ID!) {
getDatasetsByProject(project: $project) {
Expand Down Expand Up @@ -90,4 +137,38 @@ export function useGetDatasetsByProjectLazyQuery(baseOptions?: Apollo.LazyQueryH
}
export type GetDatasetsByProjectQueryHookResult = ReturnType<typeof useGetDatasetsByProjectQuery>;
export type GetDatasetsByProjectLazyQueryHookResult = ReturnType<typeof useGetDatasetsByProjectLazyQuery>;
export type GetDatasetsByProjectQueryResult = Apollo.QueryResult<GetDatasetsByProjectQuery, GetDatasetsByProjectQueryVariables>;
export type GetDatasetsByProjectQueryResult = Apollo.QueryResult<GetDatasetsByProjectQuery, GetDatasetsByProjectQueryVariables>;
export const CreateDatasetDocument = gql`
mutation createDataset($dataset: DatasetCreate!) {
createDataset(dataset: $dataset) {
name
description
}
}
`;
export type CreateDatasetMutationFn = Apollo.MutationFunction<CreateDatasetMutation, CreateDatasetMutationVariables>;

/**
* __useCreateDatasetMutation__
*
* To run a mutation, you first call `useCreateDatasetMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateDatasetMutation` 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 [createDatasetMutation, { data, loading, error }] = useCreateDatasetMutation({
* variables: {
* dataset: // value for 'dataset'
* },
* });
*/
export function useCreateDatasetMutation(baseOptions?: Apollo.MutationHookOptions<CreateDatasetMutation, CreateDatasetMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateDatasetMutation, CreateDatasetMutationVariables>(CreateDatasetDocument, options);
}
export type CreateDatasetMutationHookResult = ReturnType<typeof useCreateDatasetMutation>;
export type CreateDatasetMutationResult = Apollo.MutationResult<CreateDatasetMutation>;
export type CreateDatasetMutationOptions = Apollo.BaseMutationOptions<CreateDatasetMutation, CreateDatasetMutationVariables>;
6 changes: 6 additions & 0 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ export type ProjectSettingsModel = {

export type Query = {
__typename?: 'Query';
datasetExists: Scalars['Boolean']['output'];
entryForDataset: Array<Entry>;
exists: Scalars['Boolean']['output'];
findStudies: Array<Study>;
Expand Down Expand Up @@ -585,6 +586,11 @@ export type Query = {
};


export type QueryDatasetExistsArgs = {
name: Scalars['String']['input'];
};


export type QueryEntryForDatasetArgs = {
dataset: Scalars['ID']['input'];
};
Expand Down
14 changes: 11 additions & 3 deletions packages/client/src/pages/datasets/DatasetControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { AddDataset } from '../../components/AddDataset.component';
import { useEffect, useState } from 'react';
import { UploadEntries } from '../../components/UploadEntries.component';
import { Dataset } from '../../graphql/graphql';
import { useGetDatasetsQuery } from '../../graphql/dataset/dataset';
import { useGetDatasetsLazyQuery } from '../../graphql/dataset/dataset';
import { DatasetsView } from '../../components/DatasetsView.component';
import { GridColDef, GridActionsCellItem, GridRowId } from '@mui/x-data-grid';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
Expand All @@ -15,10 +15,15 @@ export const DatasetControls: React.FC = () => {
const [add, setAdd] = useState(false);
const [upload, setUpload] = useState(false);
const [datasets, setDatasets] = useState<Dataset[]>([]);
const getDatasetsResults = useGetDatasetsQuery();
const [getDatasets, getDatasetsResults] = useGetDatasetsLazyQuery();
const [deleteEntryMutation] = useDeleteEntryMutation();

const confirmation = useConfirmation();

useEffect(() => {
getDatasets();
}, []);

useEffect(() => {
if (getDatasetsResults.data) {
setDatasets(getDatasetsResults.data.getDatasets);
Expand All @@ -33,8 +38,11 @@ export const DatasetControls: React.FC = () => {
}
};

const toggleAdd = () => {
const toggleAdd = (newDatasetCreated: boolean) => {
setAdd((add) => !add);
if (newDatasetCreated) {
getDatasets({ fetchPolicy: 'network-only' });
}
};

const toggleUpload = () => {
Expand Down
1 change: 1 addition & 0 deletions packages/server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ type Query {
getOrganizations: [Organization!]!
exists(name: String!): Boolean!
getDatasets: [Dataset!]!
datasetExists(name: String!): Boolean!
getDatasetsByProject(project: ID!): [Dataset!]!
getProjectPermissions(project: ID!): [ProjectPermissionModel!]!
getStudyPermissions(study: ID!): [StudyPermissionModel!]!
Expand Down
12 changes: 12 additions & 0 deletions packages/server/src/dataset/dataset.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ export class DatasetResolver {
return this.datasetService.findAll(organization._id);
}

@Query(() => Boolean)
async datasetExists(
@Args('name') name: string,
@OrganizationContext() organization: Organization,
@TokenContext() user: TokenPayload
): Promise<boolean> {
if (!(await this.enforcer.enforce(user.id, DatasetPermissions.READ, organization._id))) {
throw new UnauthorizedException('User does not have permission to read a dataset in this organization');
}
return this.datasetService.exists(name, organization._id);
}

@Mutation(() => Dataset)
async createDataset(
@Args('dataset') dataset: DatasetCreate,
Expand Down
5 changes: 5 additions & 0 deletions packages/server/src/dataset/dataset.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export class DatasetService {
return this.datasetModel.findOne({ organization, name });
}

async exists(name: string, organization: string): Promise<boolean> {
const dataset = await this.datasetModel.findOne({ name, organization });
return !!dataset;
}

async create(organization: string, datasetCreate: DatasetCreate): Promise<Dataset> {
// Create the dataset
const dataset = await this.datasetModel.create({ ...datasetCreate, organization });
Expand Down