diff --git a/optuna_dashboard/app.py b/optuna_dashboard/app.py index 616905ca..ce4fe26c 100644 --- a/optuna_dashboard/app.py +++ b/optuna_dashboard/app.py @@ -115,6 +115,15 @@ def create_app(storage): {"study_summary": serializer.serialize_study_summary(summary)} ) + @app.delete("/api/studies/") + @handle_json_api_exception + def create_study(study_id: int): + response.content_type = "application/json" + + storage.delete_study(study_id) + response.status = 204 # No content + return "" + @app.get("/api/studies/") @handle_json_api_exception def get_study_detail(study_id: int): diff --git a/optuna_dashboard/static/action.ts b/optuna_dashboard/static/action.ts index c137b831..85741ab4 100644 --- a/optuna_dashboard/static/action.ts +++ b/optuna_dashboard/static/action.ts @@ -4,6 +4,7 @@ import { getStudyDetailAPI, getStudySummariesAPI, createNewStudyAPI, + deleteStudyAPI, } from "./apiClient" import { studyDetailsState, studySummariesState } from "./state" @@ -65,10 +66,27 @@ export const actionCreator = () => { }) } + const deleteStudy = (studyId: number) => { + deleteStudyAPI(studyId) + .then((study) => { + setStudySummaries(studySummaries.filter((s) => s.study_id !== studyId)) + enqueueSnackbar(`Success to delete a study (study_name=${studyId})`, { + variant: "success", + }) + }) + .catch((err) => { + enqueueSnackbar(`Failed to delete study (id=${studyId})`, { + variant: "error", + }) + console.log(err) + }) + } + return { updateStudyDetail, updateStudySummaries, createNewStudy, + deleteStudy, } } diff --git a/optuna_dashboard/static/apiClient.ts b/optuna_dashboard/static/apiClient.ts index fec2d692..3e5b5f74 100644 --- a/optuna_dashboard/static/apiClient.ts +++ b/optuna_dashboard/static/apiClient.ts @@ -160,3 +160,9 @@ export const createNewStudyAPI = ( } }) } + +export const deleteStudyAPI = (studyId: number): Promise<{}> => { + return axiosInstance.delete<{}>(`/api/studies/${studyId}`).then((res) => { + return {} + }) +} diff --git a/optuna_dashboard/static/components/DataGrid.tsx b/optuna_dashboard/static/components/DataGrid.tsx index 1f98b4c3..12ac8209 100644 --- a/optuna_dashboard/static/components/DataGrid.tsx +++ b/optuna_dashboard/static/components/DataGrid.tsx @@ -50,6 +50,7 @@ interface DataGridColumn { sortable?: boolean filterable?: boolean toCellValue?: (rowIndex: number) => string | React.ReactNode + padding?: "default" | "checkbox" | "none" } interface RowFilter { @@ -152,6 +153,7 @@ function DataGrid(props: { {columns.map((column, index) => ( {column.sortable ? ( @@ -241,7 +243,7 @@ function DataGridRow(props: { return ( - + {collapseBody ? ( (props: { ) : null} - {columns.map((column) => { + {columns.map((column, columnIndex) => { const cellItem = column.toCellValue ? column.toCellValue(rowIndex) : row[column.field] return column.filterable ? ( { handleClickFilterCell(column.field, row[column.field]) }} @@ -268,7 +271,10 @@ function DataGridRow(props: {
{cellItem}
) : ( - + {cellItem} ) diff --git a/optuna_dashboard/static/components/StudyDetail.tsx b/optuna_dashboard/static/components/StudyDetail.tsx index 8471aaeb..2ec18205 100644 --- a/optuna_dashboard/static/components/StudyDetail.tsx +++ b/optuna_dashboard/static/components/StudyDetail.tsx @@ -125,12 +125,13 @@ export const StudyDetail: FC<{}> = () => { const TrialTable: FC<{ trials: Trial[] }> = ({ trials = [] }) => { const columns: DataGridColumn[] = [ - { field: "number", label: "Number", sortable: true }, + { field: "number", label: "Number", sortable: true, padding: "none" }, { field: "state", label: "State", sortable: true, filterable: true, + padding: "none", toCellValue: (i) => trials[i].state.toString(), }, { field: "value", label: "Value", sortable: true }, @@ -141,9 +142,7 @@ const TrialTable: FC<{ trials: Trial[] }> = ({ trials = [] }) => { trials[i].params.map((p) => p.name + ": " + p.value).join(", "), }, ] - const collapseParamColumns: DataGridColumn< - TrialParam - >[] = [ + const collapseParamColumns: DataGridColumn[] = [ { field: "name", label: "Name", sortable: true }, { field: "value", label: "Value", sortable: true }, ] diff --git a/optuna_dashboard/static/components/StudyList.tsx b/optuna_dashboard/static/components/StudyList.tsx index dace6750..954d1fd4 100644 --- a/optuna_dashboard/static/components/StudyList.tsx +++ b/optuna_dashboard/static/components/StudyList.tsx @@ -21,7 +21,7 @@ import { FormControlLabel, Checkbox, } from "@material-ui/core" -import { AddBox, Refresh } from "@material-ui/icons" +import { AddBox, Delete, Refresh } from "@material-ui/icons" import { actionCreator } from "../action" import { DataGrid, DataGridColumn } from "./DataGrid" @@ -40,7 +40,11 @@ const useStyles = makeStyles((theme: Theme) => export const StudyList: FC<{}> = () => { const classes = useStyles() - const [openDialog, setOpenDialog] = React.useState(false) + const [openNewStudyDialog, setOpenNewStudyDialog] = React.useState(false) + const [openDeleteStudyDialog, setOpenDeleteStudyDialog] = React.useState( + false + ) + const [deleteStudyID, setDeleteStudyID] = React.useState(-1) const [newStudyName, setNewStudyName] = React.useState("") const [maximize, setMaximize] = React.useState(false) @@ -83,6 +87,25 @@ export const StudyList: FC<{}> = () => { sortable: false, toCellValue: (i) => studies[i].best_trial?.value || null, }, + { + field: "study_name", + label: "", + sortable: false, + padding: "none", + toCellValue: (i) => ( + { + setDeleteStudyID(studies[i].study_id) + setOpenDeleteStudyDialog(true) + }} + > + + + ), + }, ] const collapseAttrColumns: DataGridColumn[] = [ @@ -92,16 +115,27 @@ export const StudyList: FC<{}> = () => { const handleCloseNewStudyDialog = () => { setNewStudyName("") - setOpenDialog(false) + setOpenNewStudyDialog(false) } const handleCreateNewStudy = () => { const direction = maximize ? "maximize" : "minimize" action.createNewStudy(newStudyName, direction) - setOpenDialog(false) + setOpenNewStudyDialog(false) setNewStudyName("") } + const handleCloseDeleteStudyDialog = () => { + setOpenDeleteStudyDialog(false) + setDeleteStudyID(-1) + } + + const handleDeleteStudy = () => { + action.deleteStudy(deleteStudyID) + setOpenDeleteStudyDialog(false) + setDeleteStudyID(-1) + } + const collapseBody = (index: number) => { return ( @@ -160,7 +194,7 @@ export const StudyList: FC<{}> = () => { aria-controls="menu-appbar" aria-haspopup="true" onClick={(e) => { - setOpenDialog(true) + setOpenNewStudyDialog(true) }} color="inherit" > @@ -182,11 +216,13 @@ export const StudyList: FC<{}> = () => { handleCloseNewStudyDialog} - aria-labelledby="form-dialog-title" + open={openNewStudyDialog} + onClose={(e) => { + handleCloseNewStudyDialog() + }} + aria-labelledby="create-study-form-dialog-title" > - New study + New study To create a new study, please enter the study name here. @@ -230,6 +266,28 @@ export const StudyList: FC<{}> = () => { + { + handleCloseDeleteStudyDialog() + }} + aria-labelledby="delete-study-dialog-title" + > + Delete study + + + Are you sure to delete a study (study_id={deleteStudyID})? + + + + + + + ) }