From ebda63bdae22ad8474441dace59608d135490481 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 11:43:10 -0500 Subject: [PATCH 1/7] Begin working on study creation --- .../components/NewStudyJsonForm.component.tsx | 30 +++++++---- .../client/src/graphql/study/study.graphql | 15 ++++++ packages/client/src/graphql/study/study.ts | 51 ++++++++++++++++++- .../client/src/pages/studies/NewStudy.tsx | 6 ++- packages/client/src/types/study.ts | 4 ++ 5 files changed, 92 insertions(+), 14 deletions(-) create mode 100644 packages/client/src/types/study.ts diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index d88a5099..a9642246 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -1,7 +1,9 @@ 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 } from 'react'; +import { PartialStudyCreate } from '../types/study'; +import { ErrorObject } from 'ajv'; const schema = { type: 'object', @@ -16,12 +18,12 @@ const schema = { instructions: { type: 'string' }, - times: { + tagsPerEntry: { type: 'number', default: 1 } }, - required: ['name', 'description', 'instructions'] + required: ['name', 'description', 'instructions', 'tagsPerEntry'] }; const uischema = { @@ -46,22 +48,28 @@ 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 = () => { +export interface NewStudyFormProps { + newStudy: PartialStudyCreate | null; + setNewStudy: Dispatch>; +} + +export const NewStudyJsonForm: React.FC = (props) => { const initialData = { - name: '', - description: '', - instructions: '' + tagsPerEntry: schema.properties.tagsPerEntry.default }; - const [data, setData] = useState(initialData); + const [data, setData] = useState(initialData); - const handleChange = (data: any) => { + const handleChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); + if (!errors) { + props.setNewStudy({ ...data }); + } }; return ( @@ -80,7 +88,7 @@ export const NewStudyJsonForm: React.FC = () => { data={data} renderers={materialRenderers} cells={materialCells} - onChange={({ data }) => handleChange(data)} + onChange={({ data, errors }) => handleChange(data, errors)} /> ); diff --git a/packages/client/src/graphql/study/study.graphql b/packages/client/src/graphql/study/study.graphql index c1f9c254..dff4cf84 100644 --- a/packages/client/src/graphql/study/study.graphql +++ b/packages/client/src/graphql/study/study.graphql @@ -16,3 +16,18 @@ 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 + } + } +} diff --git a/packages/client/src/graphql/study/study.ts b/packages/client/src/graphql/study/study.ts index c8fd2e53..49096702 100644 --- a/packages/client/src/graphql/study/study.ts +++ b/packages/client/src/graphql/study/study.ts @@ -19,6 +19,13 @@ 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 const FindStudiesDocument = gql` query findStudies($project: ID!) { @@ -94,4 +101,46 @@ export function useDeleteStudyMutation(baseOptions?: Apollo.MutationHookOptions< } export type DeleteStudyMutationHookResult = ReturnType; export type DeleteStudyMutationResult = Apollo.MutationResult; -export type DeleteStudyMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file +export type DeleteStudyMutationOptions = Apollo.BaseMutationOptions; +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; + +/** + * __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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateStudyDocument, options); + } +export type CreateStudyMutationHookResult = ReturnType; +export type CreateStudyMutationResult = Apollo.MutationResult; +export type CreateStudyMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index d846a771..2387a06f 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -3,10 +3,12 @@ import { TagsDisplay } from '../../components/TagsDisplay.component'; import { NewStudyJsonForm } from '../../components/NewStudyJsonForm.component'; import { TagTrainingComponent } from '../../components/TagTraining.component'; import { useState } from 'react'; +import { StudyCreate } from '../../graphql/graphql'; +import { PartialStudyCreate } from '../../types/study'; export const NewStudy: React.FC = () => { - //all constants const [activeStep, setActiveStep] = useState(0); + const [partialNewStudy, setPartialNewStudy] = useState(null); const handleNext = () => { setActiveStep((prevActiveStep: number) => prevActiveStep + 1); @@ -25,7 +27,7 @@ export const NewStudy: React.FC = () => { function getSectionComponent() { switch (activeStep) { case 0: - return ; + return ; case 1: return ; case 2: diff --git a/packages/client/src/types/study.ts b/packages/client/src/types/study.ts new file mode 100644 index 00000000..f4e37159 --- /dev/null +++ b/packages/client/src/types/study.ts @@ -0,0 +1,4 @@ +import { StudyCreate } from '../graphql/graphql'; + +/** Information to create a new study minus the schema */ +export interface PartialStudyCreate extends Omit {} From 51615294a08af63dccc1316a9f12ce63d9b5197b Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 13:23:10 -0500 Subject: [PATCH 2/7] Add ability to get JSON forms schema from tag building section --- .../components/NewStudyJsonForm.component.tsx | 4 ++- .../src/components/TagsDisplay.component.tsx | 22 ++++++++++-- .../client/src/pages/studies/NewStudy.tsx | 36 ++++++++++++++++--- 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index a9642246..21cab673 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -67,8 +67,10 @@ export const NewStudyJsonForm: React.FC = (props) => { const handleChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); - if (!errors) { + if (!errors || errors.length === 0) { props.setNewStudy({ ...data }); + } else { + props.setNewStudy(null); } }; diff --git a/packages/client/src/components/TagsDisplay.component.tsx b/packages/client/src/components/TagsDisplay.component.tsx index 18a030ef..7fffc937 100644 --- a/packages/client/src/components/TagsDisplay.component.tsx +++ b/packages/client/src/components/TagsDisplay.component.tsx @@ -12,8 +12,9 @@ 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; @@ -21,7 +22,12 @@ type TagPreviewInformation = { renderers: any; }; -export const TagsDisplay: React.FC = () => { +export interface TagsDisplayProps { + tagSchema: TagSchema | null; + setTagSchema: Dispatch>; +} + +export const TagsDisplay: React.FC = (props) => { const [tagFields, setTagFields] = useState([]); const [data, setData] = useState({ previewDataSchema: {}, @@ -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', diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index 2387a06f..75378493 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -2,19 +2,45 @@ import { Container, Typography, Button, Box, Stepper, Step, StepLabel } from '@m import { TagsDisplay } from '../../components/TagsDisplay.component'; import { NewStudyJsonForm } from '../../components/NewStudyJsonForm.component'; import { TagTrainingComponent } from '../../components/TagTraining.component'; -import { useState } from 'react'; -import { StudyCreate } from '../../graphql/graphql'; +import { useState, useEffect } from 'react'; +import { StudyCreate, TagSchema } from '../../graphql/graphql'; import { PartialStudyCreate } from '../../types/study'; export const NewStudy: React.FC = () => { const [activeStep, setActiveStep] = useState(0); + const [stepLimit, setStepLimit] = useState(0); const [partialNewStudy, setPartialNewStudy] = useState(null); + const [tagSchema, setTagSchema] = useState(null); + + // Handles mantaining which step the user is on and the step limit + useEffect(() => { + if (!partialNewStudy) { + setStepLimit(0); + return; + } + + if (!tagSchema) { + setStepLimit(1); + return; + } + + console.log(tagSchema); + + setStepLimit(2); + + }, [partialNewStudy, tagSchema]); const handleNext = () => { + if (activeStep === stepLimit) { + return; + } setActiveStep((prevActiveStep: number) => prevActiveStep + 1); }; const handleBack = () => { + if (activeStep === 0) { + return; + } setActiveStep((prevActiveStep: number) => prevActiveStep - 1); }; @@ -24,12 +50,12 @@ export const NewStudy: React.FC = () => { const steps = ['Study Identification', 'Construct Tagging Interface', 'Select Tag Training Items']; - function getSectionComponent() { + const getSectionComponent = () => { switch (activeStep) { case 0: return ; case 1: - return ; + return ; case 2: return ; default: @@ -69,7 +95,7 @@ export const NewStudy: React.FC = () => { - From 98c3684bb34709dec97824e47435fab111e1f133 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 13:36:10 -0500 Subject: [PATCH 3/7] Fix issue with permission checking --- packages/server/src/study/study.resolver.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index a26c2d3e..2cf5dcbe 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -53,8 +53,8 @@ export class StudyResolver { } @Mutation(() => Boolean) - async deleteStudy(@Args('study', { type: () => ID }, StudyPipe) study: Study): Promise { - if (!(await this.enforcer.enforce(study.name, StudyPermissions.DELETE, study._id))) { + async deleteStudy(@Args('study', { type: () => ID }, StudyPipe) study: Study, @TokenContext() user: TokenPayload): Promise { + if (!(await this.enforcer.enforce(user.id, StudyPermissions.DELETE, study._id))) { throw new UnauthorizedException('User cannot delete studies on this project'); } From f7f6d6b4424fd2187c1a3d9016e94df3305b3174 Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 13:36:27 -0500 Subject: [PATCH 4/7] Working study creation from UI, missing unique name check --- .../components/NewStudyJsonForm.component.tsx | 1 + .../client/src/pages/studies/NewStudy.tsx | 38 +++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index 21cab673..9f5a55d5 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -68,6 +68,7 @@ export const NewStudyJsonForm: React.FC = (props) => { const handleChange = (data: any, errors: ErrorObject[] | undefined) => { setData(data); if (!errors || errors.length === 0) { + // TODO: Check for unique study name props.setNewStudy({ ...data }); } else { props.setNewStudy(null); diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index 75378493..582a0ae0 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -5,12 +5,19 @@ import { TagTrainingComponent } from '../../components/TagTraining.component'; import { useState, useEffect } from 'react'; import { StudyCreate, TagSchema } from '../../graphql/graphql'; import { PartialStudyCreate } from '../../types/study'; +import { useCreateStudyMutation } from '../../graphql/study/study'; +import { useProject } from '../../context/Project.context'; +import { useStudy } from '../../context/Study.context'; export const NewStudy: React.FC = () => { const [activeStep, setActiveStep] = useState(0); const [stepLimit, setStepLimit] = useState(0); const [partialNewStudy, setPartialNewStudy] = useState(null); const [tagSchema, setTagSchema] = useState(null); + const { project } = useProject(); + const { updateStudies } = useStudy(); + + const [createStudyMutation, createStudyResults] = useCreateStudyMutation(); // Handles mantaining which step the user is on and the step limit useEffect(() => { @@ -24,9 +31,8 @@ export const NewStudy: React.FC = () => { return; } - console.log(tagSchema); - - setStepLimit(2); + // TODO: Future work will be done to add in the entry selection step + setStepLimit(3); }, [partialNewStudy, tagSchema]); @@ -34,9 +40,35 @@ export const NewStudy: React.FC = () => { if (activeStep === stepLimit) { return; } + if (activeStep === steps.length - 1) { + // Make sure the required fields are present + if (!partialNewStudy || !tagSchema) { + console.error('Reached submission with invalid data'); + return; + } + // Make sure a project is selected + if (!project) { + console.error('Reached submission with no project selected'); + return; + } + const study: StudyCreate = { + ...partialNewStudy, + project: project._id, + tagSchema: tagSchema + }; + createStudyMutation({ variables: { study } }); + + } setActiveStep((prevActiveStep: number) => prevActiveStep + 1); }; + useEffect(() => { + if (createStudyResults.data) { + // TODO: Add in a success message + updateStudies(); + } + }, [createStudyResults.data]); + const handleBack = () => { if (activeStep === 0) { return; From 21e02b37c1880aae702ef333045f9a892ac6757a Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 15:11:10 -0500 Subject: [PATCH 5/7] Checking for unique name on server --- .../components/NewStudyJsonForm.component.tsx | 112 +++++++++++------- .../client/src/graphql/study/study.graphql | 4 + packages/client/src/graphql/study/study.ts | 44 ++++++- packages/server/src/study/study.resolver.ts | 15 ++- 4 files changed, 122 insertions(+), 53 deletions(-) diff --git a/packages/client/src/components/NewStudyJsonForm.component.tsx b/packages/client/src/components/NewStudyJsonForm.component.tsx index 9f5a55d5..c40460b5 100644 --- a/packages/client/src/components/NewStudyJsonForm.component.tsx +++ b/packages/client/src/components/NewStudyJsonForm.component.tsx @@ -1,9 +1,73 @@ import { materialRenderers, materialCells } from '@jsonforms/material-renderers'; import { JsonForms } from '@jsonforms/react'; -import { Box } from '@mui/material'; -import { Dispatch, SetStateAction, 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>; +} + +export const NewStudyJsonForm: React.FC = (props) => { + const initialData = { + tagsPerEntry: schema.properties.tagsPerEntry.default + }; + + const [data, setData] = useState(initialData); + const [studyExistsQuery, studyExistsResults] = useStudyExistsLazyQuery(); + const { project } = useProject(); + const [additionalErrors, setAdditionalErrors] = useState([]); + + // Keep track of the new study internally to check to make sure the name is + // unique before submitting + const [potentialNewStudy, setPotentialNewStudy] = useState(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 ( + handleChange(data, errors)} + additionalErrors={additionalErrors} + /> + ); +}; const schema = { type: 'object', @@ -52,47 +116,3 @@ const uischema = { } ] }; - -export interface NewStudyFormProps { - newStudy: PartialStudyCreate | null; - setNewStudy: Dispatch>; -} - -export const NewStudyJsonForm: React.FC = (props) => { - const initialData = { - tagsPerEntry: schema.properties.tagsPerEntry.default - }; - - const [data, setData] = useState(initialData); - - const handleChange = (data: any, errors: ErrorObject[] | undefined) => { - setData(data); - if (!errors || errors.length === 0) { - // TODO: Check for unique study name - props.setNewStudy({ ...data }); - } else { - props.setNewStudy(null); - } - }; - - return ( - - handleChange(data, errors)} - /> - - ); -}; diff --git a/packages/client/src/graphql/study/study.graphql b/packages/client/src/graphql/study/study.graphql index dff4cf84..aff0536f 100644 --- a/packages/client/src/graphql/study/study.graphql +++ b/packages/client/src/graphql/study/study.graphql @@ -31,3 +31,7 @@ mutation createStudy($study: StudyCreate!) { } } } + +query studyExists($name: String!, $project: ID!) { + studyExists(name: $name, project: $project) +} diff --git a/packages/client/src/graphql/study/study.ts b/packages/client/src/graphql/study/study.ts index 49096702..7d85de42 100644 --- a/packages/client/src/graphql/study/study.ts +++ b/packages/client/src/graphql/study/study.ts @@ -26,6 +26,14 @@ export type CreateStudyMutationVariables = Types.Exact<{ 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!) { @@ -143,4 +151,38 @@ export function useCreateStudyMutation(baseOptions?: Apollo.MutationHookOptions< } export type CreateStudyMutationHookResult = ReturnType; export type CreateStudyMutationResult = Apollo.MutationResult; -export type CreateStudyMutationOptions = Apollo.BaseMutationOptions; \ No newline at end of file +export type CreateStudyMutationOptions = Apollo.BaseMutationOptions; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(StudyExistsDocument, options); + } +export function useStudyExistsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(StudyExistsDocument, options); + } +export type StudyExistsQueryHookResult = ReturnType; +export type StudyExistsLazyQueryHookResult = ReturnType; +export type StudyExistsQueryResult = Apollo.QueryResult; \ No newline at end of file diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index 2cf5dcbe..f0ceaf89 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -37,9 +37,10 @@ export class StudyResolver { @Query(() => Boolean) async studyExists( @Args('name') name: string, - @Args('project', { type: () => ID }, ProjectPipe) project: Project + @Args('project', { type: () => ID }, ProjectPipe) project: Project, + @TokenContext() user: TokenPayload ): Promise { - if (!(await this.enforcer.enforce(name, StudyPermissions.READ, project))) { + if (!(await this.enforcer.enforce(user.id, StudyPermissions.READ, project._id.toString()))) { throw new UnauthorizedException('User cannot read studies on this project'); } @@ -65,9 +66,10 @@ export class StudyResolver { @Mutation(() => Study) async changeStudyName( @Args('study', { type: () => ID }, StudyPipe) study: Study, - @Args('newName') newName: string + @Args('newName') newName: string, + @TokenContext() user: TokenPayload ): Promise { - if (!(await this.enforcer.enforce(study.name, StudyPermissions.UPDATE, study._id))) { + if (!(await this.enforcer.enforce(user.id, StudyPermissions.UPDATE, study._id))) { throw new UnauthorizedException('User cannot update studies on this project'); } @@ -77,9 +79,10 @@ export class StudyResolver { @Mutation(() => Study) async changeStudyDescription( @Args('study', { type: () => ID }, StudyPipe) study: Study, - @Args('newDescription') newDescription: string + @Args('newDescription') newDescription: string, + @TokenContext() user: TokenPayload ): Promise { - if (!(await this.enforcer.enforce(study.name, StudyPermissions.UPDATE, study._id))) { + if (!(await this.enforcer.enforce(user.id, StudyPermissions.UPDATE, study._id))) { throw new UnauthorizedException('User cannot update studies on this project'); } From 08a434b76810bfda661a6e9b7b8af82bf00ed87b Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 15:13:42 -0500 Subject: [PATCH 6/7] Remove complete TODO message --- packages/client/src/pages/studies/NewStudy.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index 582a0ae0..f69d0ab7 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -64,7 +64,6 @@ export const NewStudy: React.FC = () => { useEffect(() => { if (createStudyResults.data) { - // TODO: Add in a success message updateStudies(); } }, [createStudyResults.data]); From bd65bc7c1c035a9f4d0a6457f96892a46c06d6fc Mon Sep 17 00:00:00 2001 From: cbolles Date: Mon, 8 Jan 2024 15:14:22 -0500 Subject: [PATCH 7/7] Fix formatting --- packages/client/src/components/TagsDisplay.component.tsx | 2 +- packages/client/src/pages/studies/NewStudy.tsx | 4 +--- packages/server/src/study/study.resolver.ts | 5 ++++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/client/src/components/TagsDisplay.component.tsx b/packages/client/src/components/TagsDisplay.component.tsx index 7fffc937..57b2e33f 100644 --- a/packages/client/src/components/TagsDisplay.component.tsx +++ b/packages/client/src/components/TagsDisplay.component.tsx @@ -59,7 +59,7 @@ export const TagsDisplay: React.FC = (props) => { const schema = produceJSONForm(); props.setTagSchema({ dataSchema: schema.dataSchema, - uiSchema:schema.uiSchema + uiSchema: schema.uiSchema }); }, [valid, tagFields]); diff --git a/packages/client/src/pages/studies/NewStudy.tsx b/packages/client/src/pages/studies/NewStudy.tsx index f69d0ab7..ba801f4f 100644 --- a/packages/client/src/pages/studies/NewStudy.tsx +++ b/packages/client/src/pages/studies/NewStudy.tsx @@ -33,7 +33,6 @@ export const NewStudy: React.FC = () => { // TODO: Future work will be done to add in the entry selection step setStepLimit(3); - }, [partialNewStudy, tagSchema]); const handleNext = () => { @@ -57,7 +56,6 @@ export const NewStudy: React.FC = () => { tagSchema: tagSchema }; createStudyMutation({ variables: { study } }); - } setActiveStep((prevActiveStep: number) => prevActiveStep + 1); }; @@ -92,7 +90,7 @@ export const NewStudy: React.FC = () => { default: return null; } - } + }; return ( diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index f0ceaf89..f2110f9c 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -54,7 +54,10 @@ export class StudyResolver { } @Mutation(() => Boolean) - async deleteStudy(@Args('study', { type: () => ID }, StudyPipe) study: Study, @TokenContext() user: TokenPayload): Promise { + async deleteStudy( + @Args('study', { type: () => ID }, StudyPipe) study: Study, + @TokenContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, StudyPermissions.DELETE, study._id))) { throw new UnauthorizedException('User cannot delete studies on this project'); }