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
111 changes: 71 additions & 40 deletions packages/client/src/components/NewStudyJsonForm.component.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,73 @@
import { materialRenderers, materialCells } from '@jsonforms/material-renderers';
import { JsonForms } from '@jsonforms/react';
import { Box } from '@mui/material';
import { useState } from 'react';
import { Dispatch, SetStateAction, useState, useEffect } from 'react';
import { PartialStudyCreate } from '../types/study';
import { ErrorObject } from 'ajv';
import { useStudyExistsLazyQuery } from '../graphql/study/study';
import { useProject } from '../context/Project.context';

export interface NewStudyFormProps {
newStudy: PartialStudyCreate | null;
setNewStudy: Dispatch<SetStateAction<PartialStudyCreate | null>>;
}

export const NewStudyJsonForm: React.FC<NewStudyFormProps> = (props) => {
const initialData = {
tagsPerEntry: schema.properties.tagsPerEntry.default
};

const [data, setData] = useState<any>(initialData);
const [studyExistsQuery, studyExistsResults] = useStudyExistsLazyQuery();
const { project } = useProject();
const [additionalErrors, setAdditionalErrors] = useState<ErrorObject[]>([]);

// Keep track of the new study internally to check to make sure the name is
// unique before submitting
const [potentialNewStudy, setPotentialNewStudy] = useState<PartialStudyCreate | null>(null);

const handleChange = (data: any, errors: ErrorObject[] | undefined) => {
setData(data);
if (!errors || errors.length === 0) {
// No errors in the format of the data, check if the study name is unique
setPotentialNewStudy({ ...data });
studyExistsQuery({ variables: { name: data.name, project: project!._id } });
} else {
setPotentialNewStudy(null);
}
};

useEffect(() => {
// If the study exists, notify the user of the error, otherwise the
// study is valid
if (studyExistsResults.data?.studyExists) {
setAdditionalErrors([
{
instancePath: '/name',
keyword: 'uniqueStudyName',
message: 'A study with this name already exists',
schemaPath: '#/properties/name/name',
params: { keyword: 'uniqueStudyName' }
}
]);
props.setNewStudy(null);
} else {
setAdditionalErrors([]);
props.setNewStudy(potentialNewStudy);
}
}, [studyExistsResults.data]);

return (
<JsonForms
schema={schema}
uischema={uischema}
data={data}
renderers={materialRenderers}
cells={materialCells}
onChange={({ data, errors }) => handleChange(data, errors)}
additionalErrors={additionalErrors}
/>
);
};

const schema = {
type: 'object',
Expand All @@ -16,12 +82,12 @@ const schema = {
instructions: {
type: 'string'
},
times: {
tagsPerEntry: {
type: 'number',
default: 1
}
},
required: ['name', 'description', 'instructions']
required: ['name', 'description', 'instructions', 'tagsPerEntry']
};

const uischema = {
Expand All @@ -46,42 +112,7 @@ const uischema = {
{
type: 'Control',
label: 'Number of times each entry needs to be tagged (default 1)',
scope: '#/properties/times'
scope: '#/properties/tagsPerEntry'
}
]
};

export const NewStudyJsonForm: React.FC = () => {
const initialData = {
name: '',
description: '',
instructions: ''
};

const [data, setData] = useState(initialData);

const handleChange = (data: any) => {
setData(data);
};

return (
<Box
sx={{
'& .MuiTypography-h5': {
fontSize: '20px',
marginBottom: '-3%',
color: '#414048'
}
}}
>
<JsonForms
schema={schema}
uischema={uischema}
data={data}
renderers={materialRenderers}
cells={materialCells}
onChange={({ data }) => handleChange(data)}
/>
</Box>
);
};
22 changes: 20 additions & 2 deletions packages/client/src/components/TagsDisplay.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ import { materialRenderers } from '@jsonforms/material-renderers';
import { TagField, TagFieldType } from '../models/TagField';
import { TagFormPreviewDialog } from './TagFormPreview.component';
import { TagFieldGeneratorService } from '../services/tag-field-generator.service';
import { useState } from 'react';
import { useState, Dispatch, SetStateAction, useEffect } from 'react';
import { TagFieldView } from './TagField.component';
import { TagSchema } from '../graphql/graphql';

type TagPreviewInformation = {
previewDataSchema: any;
previewUiSchema: any;
renderers: any;
};

export const TagsDisplay: React.FC = () => {
export interface TagsDisplayProps {
tagSchema: TagSchema | null;
setTagSchema: Dispatch<SetStateAction<TagSchema | null>>;
}

export const TagsDisplay: React.FC<TagsDisplayProps> = (props) => {
const [tagFields, setTagFields] = useState<TagField[]>([]);
const [data, setData] = useState<TagPreviewInformation>({
previewDataSchema: {},
Expand All @@ -45,6 +51,18 @@ export const TagsDisplay: React.FC = () => {
setValid([...valid]);
};

// Handling keeping track of complete tag schema
useEffect(() => {
if (valid.length === 0 || valid.includes(false)) {
return;
}
const schema = produceJSONForm();
props.setTagSchema({
dataSchema: schema.dataSchema,
uiSchema: schema.uiSchema
});
}, [valid, tagFields]);

const produceJSONForm = () => {
const dataSchema: { type: string; properties: any; required: string[] } = {
type: 'object',
Expand Down
19 changes: 19 additions & 0 deletions packages/client/src/graphql/study/study.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,22 @@ query findStudies($project: ID!) {
mutation deleteStudy($study: ID!) {
deleteStudy(study: $study)
}

mutation createStudy($study: StudyCreate!) {
createStudy(study: $study) {
_id,
name,
description,
instructions,
project,
tagsPerEntry,
tagSchema {
dataSchema,
uiSchema
}
}
}

query studyExists($name: String!, $project: ID!) {
studyExists(name: $name, project: $project)
}
93 changes: 92 additions & 1 deletion packages/client/src/graphql/study/study.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ export type DeleteStudyMutationVariables = Types.Exact<{

export type DeleteStudyMutation = { __typename?: 'Mutation', deleteStudy: boolean };

export type CreateStudyMutationVariables = Types.Exact<{
study: Types.StudyCreate;
}>;


export type CreateStudyMutation = { __typename?: 'Mutation', createStudy: { __typename?: 'Study', _id: string, name: string, description: string, instructions: string, project: string, tagsPerEntry: number, tagSchema: { __typename?: 'TagSchema', dataSchema: any, uiSchema: any } } };

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


export type StudyExistsQuery = { __typename?: 'Query', studyExists: boolean };


export const FindStudiesDocument = gql`
query findStudies($project: ID!) {
Expand Down Expand Up @@ -94,4 +109,80 @@ export function useDeleteStudyMutation(baseOptions?: Apollo.MutationHookOptions<
}
export type DeleteStudyMutationHookResult = ReturnType<typeof useDeleteStudyMutation>;
export type DeleteStudyMutationResult = Apollo.MutationResult<DeleteStudyMutation>;
export type DeleteStudyMutationOptions = Apollo.BaseMutationOptions<DeleteStudyMutation, DeleteStudyMutationVariables>;
export type DeleteStudyMutationOptions = Apollo.BaseMutationOptions<DeleteStudyMutation, DeleteStudyMutationVariables>;
export const CreateStudyDocument = gql`
mutation createStudy($study: StudyCreate!) {
createStudy(study: $study) {
_id
name
description
instructions
project
tagsPerEntry
tagSchema {
dataSchema
uiSchema
}
}
}
`;
export type CreateStudyMutationFn = Apollo.MutationFunction<CreateStudyMutation, CreateStudyMutationVariables>;

/**
* __useCreateStudyMutation__
*
* To run a mutation, you first call `useCreateStudyMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateStudyMutation` 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 [createStudyMutation, { data, loading, error }] = useCreateStudyMutation({
* variables: {
* study: // value for 'study'
* },
* });
*/
export function useCreateStudyMutation(baseOptions?: Apollo.MutationHookOptions<CreateStudyMutation, CreateStudyMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateStudyMutation, CreateStudyMutationVariables>(CreateStudyDocument, options);
}
export type CreateStudyMutationHookResult = ReturnType<typeof useCreateStudyMutation>;
export type CreateStudyMutationResult = Apollo.MutationResult<CreateStudyMutation>;
export type CreateStudyMutationOptions = Apollo.BaseMutationOptions<CreateStudyMutation, CreateStudyMutationVariables>;
export const StudyExistsDocument = gql`
query studyExists($name: String!, $project: ID!) {
studyExists(name: $name, project: $project)
}
`;

/**
* __useStudyExistsQuery__
*
* To run a query within a React component, call `useStudyExistsQuery` and pass it any options that fit your needs.
* When your component renders, `useStudyExistsQuery` 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 } = useStudyExistsQuery({
* variables: {
* name: // value for 'name'
* project: // value for 'project'
* },
* });
*/
export function useStudyExistsQuery(baseOptions: Apollo.QueryHookOptions<StudyExistsQuery, StudyExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<StudyExistsQuery, StudyExistsQueryVariables>(StudyExistsDocument, options);
}
export function useStudyExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<StudyExistsQuery, StudyExistsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<StudyExistsQuery, StudyExistsQueryVariables>(StudyExistsDocument, options);
}
export type StudyExistsQueryHookResult = ReturnType<typeof useStudyExistsQuery>;
export type StudyExistsLazyQueryHookResult = ReturnType<typeof useStudyExistsLazyQuery>;
export type StudyExistsQueryResult = Apollo.QueryResult<StudyExistsQuery, StudyExistsQueryVariables>;
Loading