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
5 changes: 5 additions & 0 deletions packages/client/src/context/Project.context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export const ProjectProvider: FC<ProjectProviderProps> = ({ children }) => {
useEffect(() => {
if (getProjectResults.data) {
setProjects(getProjectResults.data.getProjects);

// Check if the current project is still in the list
if (project && !getProjectResults.data.getProjects.find((p) => p._id === project._id)) {
setProject(null);
}
}

}, [getProjectResults.data, getProjectResults.error]);
Expand Down
5 changes: 5 additions & 0 deletions packages/client/src/graphql/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,11 @@ export type MutationCreateTagsArgs = {
};


export type MutationDeleteProjectArgs = {
project: Scalars['ID']['input'];
};


export type MutationDeleteStudyArgs = {
study: Scalars['ID']['input'];
};
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/graphql/project/project.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ query getProjects {
created
}
}

mutation deleteProject($project: ID!) {
deleteProject(project: $project)
}
40 changes: 39 additions & 1 deletion packages/client/src/graphql/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ export type GetProjectsQueryVariables = Types.Exact<{ [key: string]: never; }>;

export type GetProjectsQuery = { __typename?: 'Query', getProjects: Array<{ __typename?: 'Project', _id: string, name: string, description: string, created: any }> };

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


export type DeleteProjectMutation = { __typename?: 'Mutation', deleteProject: boolean };


export const GetProjectsDocument = gql`
query getProjects {
Expand Down Expand Up @@ -47,4 +54,35 @@ export function useGetProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOption
}
export type GetProjectsQueryHookResult = ReturnType<typeof useGetProjectsQuery>;
export type GetProjectsLazyQueryHookResult = ReturnType<typeof useGetProjectsLazyQuery>;
export type GetProjectsQueryResult = Apollo.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>;
export type GetProjectsQueryResult = Apollo.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>;
export const DeleteProjectDocument = gql`
mutation deleteProject($project: ID!) {
deleteProject(project: $project)
}
`;
export type DeleteProjectMutationFn = Apollo.MutationFunction<DeleteProjectMutation, DeleteProjectMutationVariables>;

/**
* __useDeleteProjectMutation__
*
* To run a mutation, you first call `useDeleteProjectMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteProjectMutation` 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 [deleteProjectMutation, { data, loading, error }] = useDeleteProjectMutation({
* variables: {
* project: // value for 'project'
* },
* });
*/
export function useDeleteProjectMutation(baseOptions?: Apollo.MutationHookOptions<DeleteProjectMutation, DeleteProjectMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteProjectMutation, DeleteProjectMutationVariables>(DeleteProjectDocument, options);
}
export type DeleteProjectMutationHookResult = ReturnType<typeof useDeleteProjectMutation>;
export type DeleteProjectMutationResult = Apollo.MutationResult<DeleteProjectMutation>;
export type DeleteProjectMutationOptions = Apollo.BaseMutationOptions<DeleteProjectMutation, DeleteProjectMutationVariables>;
38 changes: 29 additions & 9 deletions packages/client/src/pages/projects/ProjectControl.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
import { Box, Typography } from '@mui/material';
import { DataGrid, GridColDef } from '@mui/x-data-grid';
import { DataGrid, GridColDef, GridRowId } from '@mui/x-data-grid';
import { useProject } from '../../context/Project.context';
import { GridActionsCellItem } from '@mui/x-data-grid-pro';
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
import { Project } from '../../graphql/graphql';
import { useDeleteProjectMutation } from '../../graphql/project/project';
import { useConfirmation } from '../../context/Confirmation.context';
import {useEffect} from 'react';


const ProjectControl: React.FC = () => {
const { projects } = useProject();
const { projects, updateProjectList } = useProject();

const [deleteProjectMutation, deleteProjectResults] = useDeleteProjectMutation();
const confirmation = useConfirmation();

const handleDelete = async (id: GridRowId) => {
// Execute delete mutation
confirmation.pushConfirmationRequest({
title: 'Delete Study',
message: 'Are you sure you want to delete this project? Doing so will delete all contained studies and tags',
onConfirm: () => {
deleteProjectMutation({ variables: { project: id.toString() } });
},
onCancel: () => {}
});
};

// TODO: Add error message
useEffect(() => {
if (deleteProjectResults.called && deleteProjectResults.data) {
updateProjectList();
}
}, [deleteProjectResults.data, deleteProjectResults.called]);

const columns: GridColDef[] = [
{
Expand All @@ -29,17 +54,12 @@ const ProjectControl: React.FC = () => {
width: 120,
maxWidth: 120,
cellClassName: 'delete',
getActions: () => {
return [<GridActionsCellItem icon={<DeleteIcon />} label="Delete" />];
getActions: (params) => {
return [<GridActionsCellItem icon={<DeleteIcon />} label="Delete" onClick={() => handleDelete(params.id)}/>];
}
}
];

// Make sure the lines between rows are visible
const rowStyle = {
borderBottom: '1px solid rgba(224, 224, 224, 1)'
};

return (
<>
<Typography variant='h3'>Project Control</Typography>
Expand Down
3 changes: 2 additions & 1 deletion packages/client/src/pages/studies/StudyControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@ export const StudyControl: React.FC = () => {
// Execute delete mutation
confirmation.pushConfirmationRequest({
title: 'Delete Study',
message: 'Are you sure you want to delete this study?',
message: 'Are you sure you want to delete this study? Doing so will delete all contained tags',
onConfirm: () => {
deleteStudyMutation({ variables: { study: id.toString() } });
},
onCancel: () => {}
});
};

// TODO: Add error message
useEffect(() => {
if (deleteStudyResults.called && deleteStudyResults.data) {
updateStudies();
Expand Down
2 changes: 1 addition & 1 deletion packages/server/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ type Mutation {
changeDatasetName(dataset: ID!, newName: String!): Boolean!
changeDatasetDescription(dataset: ID!, newDescription: String!): Boolean!
signLabCreateProject(project: ProjectCreate!): Project!
deleteProject: Boolean!
deleteProject(project: ID!): Boolean!
createStudy(study: StudyCreate!): Study!
deleteStudy(study: ID!): Boolean!
changeStudyName(study: ID!, newName: String!): Study!
Expand Down
20 changes: 19 additions & 1 deletion packages/server/src/project/project.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,28 @@ import { ProjectService } from './project.service';
import { MongooseModule } from '@nestjs/mongoose';
import { Project, ProjectSchema } from './project.model';
import { ProjectPipe } from './pipes/project.pipe';
import { MongooseMiddlewareService } from 'src/shared/service/mongoose-callback.service';
import { SharedModule } from 'src/shared/shared.module';

@Module({
imports: [
MongooseModule.forFeature([{ name: Project.name, schema: ProjectSchema }])
MongooseModule.forFeatureAsync([
{
name: Project.name,
useFactory: (middlewareService: MongooseMiddlewareService) => {
const schema = ProjectSchema;

schema.pre('deleteOne', async function () {
const project = await this.model.findOne(this.getQuery());
await middlewareService.apply(Project.name, 'deleteOne', project);
});

return schema;
},
imports: [SharedModule],
inject: [MongooseMiddlewareService],
}
])
],
providers: [ProjectResolver, ProjectService, ProjectPipe],
exports: [ProjectPipe, ProjectService]
Expand Down
6 changes: 4 additions & 2 deletions packages/server/src/project/project.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {BadRequestException} from '@nestjs/common';
import { Resolver, Mutation, Query, Args } from '@nestjs/graphql';
import { Resolver, Mutation, Query, Args, ID } from '@nestjs/graphql';
import { OrganizationContext } from 'src/organization/organization.context';
import { Organization } from 'src/organization/organization.model';
import { ProjectCreate } from './dtos/create.dto';
import { Project } from './project.model';
import { ProjectService } from './project.service';
import {ProjectPipe} from './pipes/project.pipe';

@Resolver(() => Project)
export class ProjectResolver {
Expand All @@ -26,7 +27,8 @@ export class ProjectResolver {

// TODO: Handle Project deletion
@Mutation(() => Boolean)
async deleteProject(): Promise<boolean> {
async deleteProject(@Args('project', { type: () => ID }, ProjectPipe) project: Project): Promise<boolean> {
await this.projectService.delete(project);
return true;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/server/src/project/project.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ export class ProjectService {
async findAll(organization: string): Promise<Project[]> {
return this.projectModel.find({ organization }).exec();
}

async delete(project: Project): Promise<void> {
await this.projectModel.deleteOne({ _id: project._id });
}
}
15 changes: 14 additions & 1 deletion packages/server/src/study/study.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import { Study } from './study.model';
import { StudyCreate } from './dtos/create.dto';
import { Validator } from 'jsonschema';
import { Project } from 'src/project/project.model';
import { MongooseMiddlewareService } from 'src/shared/service/mongoose-callback.service';

@Injectable()
export class StudyService {
constructor(@InjectModel(Study.name) private readonly studyModel: Model<Study>) {}
constructor(@InjectModel(Study.name) private readonly studyModel: Model<Study>, middlewareService: MongooseMiddlewareService) {
// Remove cooresponding studies when a project is deleted
middlewareService.register(Project.name, 'deleteOne', async (project: Project) => {
await this.removeForProject(project);
});
}

async create(study: StudyCreate): Promise<Study> {
return this.studyModel.create(study);
Expand Down Expand Up @@ -58,4 +64,11 @@ export class StudyService {
async delete(study: Study): Promise<void> {
await this.studyModel.deleteOne({ _id: study._id });
}

private async removeForProject(project: Project): Promise<void> {
const studies = await this.findAll(project);
for (const study of studies) {
await this.delete(study);
}
}
}