diff --git a/optuna_dashboard/_app.py b/optuna_dashboard/_app.py index c2e5099a..bdbfb168 100644 --- a/optuna_dashboard/_app.py +++ b/optuna_dashboard/_app.py @@ -180,7 +180,10 @@ def create_app( @app.delete("/api/studies/") @json_api_view def delete_study(study_id: int) -> dict[str, Any]: - if artifact_store is not None: + data = request.json or {} + remove_associated_artifacts = data.get("remove_associated_artifacts", True) + + if artifact_store is not None and remove_associated_artifacts: delete_all_artifacts(artifact_store, storage, study_id) try: diff --git a/optuna_dashboard/ts/action.ts b/optuna_dashboard/ts/action.ts index 091e7339..f3109f0a 100644 --- a/optuna_dashboard/ts/action.ts +++ b/optuna_dashboard/ts/action.ts @@ -284,9 +284,9 @@ export const actionCreator = () => { }) } - const deleteStudy = (studyId: number) => { + const deleteStudy = (studyId: number, removeAssociatedArtifacts: boolean) => { apiClient - .deleteStudy(studyId) + .deleteStudy(studyId, removeAssociatedArtifacts) .then(() => { setStudySummaries(studySummaries.filter((s) => s.study_id !== studyId)) enqueueSnackbar(`Success to delete a study (id=${studyId})`, { diff --git a/optuna_dashboard/ts/apiClient.ts b/optuna_dashboard/ts/apiClient.ts index 038ea9f7..f1a7ad8c 100644 --- a/optuna_dashboard/ts/apiClient.ts +++ b/optuna_dashboard/ts/apiClient.ts @@ -185,7 +185,10 @@ export abstract class APIClient { studyName: string, directions: Optuna.StudyDirection[] ): Promise - abstract deleteStudy(studyId: number): Promise + abstract deleteStudy( + studyId: number, + removeAssociatedArtifacts: boolean + ): Promise abstract renameStudy( studyId: number, studyName: string diff --git a/optuna_dashboard/ts/axiosClient.ts b/optuna_dashboard/ts/axiosClient.ts index 1c52c42f..bb2b811a 100644 --- a/optuna_dashboard/ts/axiosClient.ts +++ b/optuna_dashboard/ts/axiosClient.ts @@ -116,10 +116,19 @@ export class AxiosClient extends APIClient { : undefined, } }) - deleteStudy = (studyId: number): Promise => - this.axiosInstance.delete(`/api/studies/${studyId}`).then(() => { - return - }) + deleteStudy = ( + studyId: number, + removeAssociatedArtifacts: boolean + ): Promise => + this.axiosInstance + .delete(`/api/studies/${studyId}`, { + data: { + remove_associated_artifacts: removeAssociatedArtifacts, + }, + }) + .then(() => { + return + }) renameStudy = (studyId: number, studyName: string): Promise => this.axiosInstance .post(`/api/studies/${studyId}/rename`, { diff --git a/optuna_dashboard/ts/components/Artifact/DeleteArtifactDialog.tsx b/optuna_dashboard/ts/components/Artifact/DeleteArtifactDialog.tsx index 7b5f90b2..d55dd32a 100644 --- a/optuna_dashboard/ts/components/Artifact/DeleteArtifactDialog.tsx +++ b/optuna_dashboard/ts/components/Artifact/DeleteArtifactDialog.tsx @@ -1,10 +1,12 @@ import { + Alert, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, + useTheme, } from "@mui/material" import React, { ReactNode, useState, FC } from "react" import { Artifact } from "ts/types/optuna" @@ -111,6 +113,7 @@ const DeleteDialog: FC<{ filename, handleDeleteArtifact, }) => { + const theme = useTheme() return ( - + Are you sure you want to delete an artifact (" {filename}")? + + If this artifact is linked to another study or trial, it will no + longer be accessible from that study or trial as well. +