diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..5b390797 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,14 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint', + ], + rules: { + "@typescript-eslint/ban-ts-comment": "off" + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + ], +}; \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 86c9caa4..d5467cf8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: "Bug report" about: Create a bug report to improve optuna-dashboard -title: "[Bug]" +title: "" labels: bug assignees: '' diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 66206360..a74a12e5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,7 +1,7 @@ --- name: "Feature request" about: Suggest an idea for new features in optuna-dashboard. -title: "[Feature]" +title: "" labels: enhancement assignees: '' diff --git a/.github/workflows/typescript-checks.yml b/.github/workflows/typescript-checks.yml index 5cbc63f1..c2317dae 100644 --- a/.github/workflows/typescript-checks.yml +++ b/.github/workflows/typescript-checks.yml @@ -3,6 +3,7 @@ on: pull_request: paths: - '.github/workflows/typescript-checks.yml' + - '.eslintrc.js' - '**.ts' - '**.tsx' jobs: diff --git a/README.md b/README.md index b4a62c0b..53af8720 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,3 @@ You can walk-through trials by filtering and sorting. ## Submitting patches If you want to contribute, please check [Developers Guide](./DEVELOPMENT.md). - - -## Alternatives - -* ['optuna dashboard' subcommand](https://optuna.readthedocs.io/en/stable/reference/cli.html#dashboard): Official Optuna dashboard based on Bokeh CLI. -* [ytsmiling/optdash](https://github.com/ytsmiling/optdash): a third-party dashboard for optuna. diff --git a/optuna_dashboard/static/action.ts b/optuna_dashboard/static/action.ts index 191419d6..b62e8b03 100644 --- a/optuna_dashboard/static/action.ts +++ b/optuna_dashboard/static/action.ts @@ -37,7 +37,7 @@ export const actionCreator = () => { const updateStudyDetail = (studyId: number) => { getStudyDetailAPI(studyId) .then((study) => { - let newVal = Object.assign({}, studyDetails) + const newVal = Object.assign({}, studyDetails) newVal[studyId] = study setStudyDetails(newVal) }) diff --git a/optuna_dashboard/static/apiClient.ts b/optuna_dashboard/static/apiClient.ts index 213e9984..cd68c40a 100644 --- a/optuna_dashboard/static/apiClient.ts +++ b/optuna_dashboard/static/apiClient.ts @@ -161,8 +161,8 @@ export const createNewStudyAPI = ( }) } -export const deleteStudyAPI = (studyId: number): Promise<{}> => { - return axiosInstance.delete<{}>(`/api/studies/${studyId}`).then((res) => { +export const deleteStudyAPI = (studyId: number) => { + return axiosInstance.delete(`/api/studies/${studyId}`).then((res) => { return {} }) } diff --git a/optuna_dashboard/static/components/App.tsx b/optuna_dashboard/static/components/App.tsx index 6fa5add2..e0c3ae5d 100644 --- a/optuna_dashboard/static/components/App.tsx +++ b/optuna_dashboard/static/components/App.tsx @@ -6,7 +6,7 @@ import { SnackbarProvider } from "notistack" import { StudyDetail } from "./StudyDetail" import { StudyList } from "./StudyList" -export const App: FC<{}> = () => { +export const App: FC = () => { return ( diff --git a/optuna_dashboard/static/components/DataGrid.tsx b/optuna_dashboard/static/components/DataGrid.tsx index 94828f6a..18b32890 100644 --- a/optuna_dashboard/static/components/DataGrid.tsx +++ b/optuna_dashboard/static/components/DataGrid.tsx @@ -48,6 +48,7 @@ interface DataGridColumn { field: keyof T label: string sortable?: boolean + less?: (i: number, j: number) => number filterable?: boolean toCellValue?: (rowIndex: number) => string | React.ReactNode padding?: "default" | "checkbox" | "none" @@ -71,7 +72,7 @@ function DataGrid(props: { const { columns, rows, keyField, dense, collapseBody } = props let { initialRowsPerPage, rowsPerPageOption } = props const [order, setOrder] = React.useState("asc") - const [orderBy, setOrderBy] = React.useState(keyField) + const [orderBy, setOrderBy] = React.useState(0) // index of columns const [page, setPage] = React.useState(0) const [filters, setFilters] = React.useState[]>([]) @@ -124,14 +125,14 @@ function DataGrid(props: { ) // Sorting - const createSortHandler = (property: keyof T) => ( + const createSortHandler = (columnId: number) => ( event: React.MouseEvent ) => { - const isAsc = orderBy === property && order === "asc" + const isAsc = orderBy === columnId && order === "asc" setOrder(isAsc ? "desc" : "asc") - setOrderBy(property) + setOrderBy(columnId) } - const sortedRows = stableSort(filteredRows, getComparator(order, orderBy)) + const sortedRows = stableSort(filteredRows, order, orderBy, columns) const currentPageRows = rowsPerPage > 0 ? sortedRows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) @@ -158,9 +159,9 @@ function DataGrid(props: { > {column.sortable ? ( {column.label} {orderBy === column.field ? ( @@ -300,28 +301,48 @@ function DataGridRow(props: { function getComparator( order: Order, - orderBy: keyof T + columns: DataGridColumn[], + orderBy: number ): (a: T, b: T) => number { return order === "desc" - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy) + ? (a, b) => descendingComparator(a, b, columns, orderBy) + : (a, b) => -descendingComparator(a, b, columns, orderBy) } -function descendingComparator(a: T, b: T, orderBy: keyof T) { - if (b[orderBy] < a[orderBy]) { +function descendingComparator( + a: T, + b: T, + columns: DataGridColumn[], + orderBy: number +): number { + const field = columns[orderBy].field + if (b[field] < a[field]) { return -1 } - if (b[orderBy] > a[orderBy]) { + if (b[field] > a[field]) { return 1 } return 0 } -function stableSort(array: T[], comparator: (a: T, b: T) => number) { +function stableSort( + array: T[], + order: Order, + orderBy: number, + columns: DataGridColumn[] +) { + // TODO(c-bata): Refactor here by implementing as the same comparator interface. + const less = columns[orderBy].less + const comparator = getComparator(order, columns, orderBy) const stabilizedThis = array.map((el, index) => [el, index] as [T, number]) stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]) - if (order !== 0) return order + if (less) { + const result = order == "asc" ? -less(a[1], b[1]) : less(a[1], b[1]) + if (result !== 0) return result + } else { + const result = comparator(a[0], b[0]) + if (result !== 0) return result + } return a[1] - b[1] }) return stabilizedThis.map((el) => el[0]) diff --git a/optuna_dashboard/static/components/GraphHistory.tsx b/optuna_dashboard/static/components/GraphHistory.tsx index 4ae8d7aa..a36a907b 100644 --- a/optuna_dashboard/static/components/GraphHistory.tsx +++ b/optuna_dashboard/static/components/GraphHistory.tsx @@ -1,4 +1,4 @@ -import * as plotly from "plotly.js-dist" +import * as plotly from "plotly.js-basic-dist" import React, { ChangeEvent, FC, useEffect, useState } from "react" import { Grid, @@ -199,7 +199,7 @@ const plotHistory = ( plotly.react(plotDomId, []) return } - let trialsForLinePlot: Trial[] = [] + const trialsForLinePlot: Trial[] = [] let currentBest: number | null = null filteredTrials.forEach((item) => { if (currentBest === null) { @@ -228,9 +228,9 @@ const plotHistory = ( : trial.datetime_complete! } - let xForLinePlot = trialsForLinePlot.map(getAxisX) + const xForLinePlot = trialsForLinePlot.map(getAxisX) xForLinePlot.push(getAxisX(filteredTrials[filteredTrials.length - 1])) - let yForLinePlot = trialsForLinePlot.map( + const yForLinePlot = trialsForLinePlot.map( (t: Trial): number => t.values![objectiveId] ) yForLinePlot.push(yForLinePlot[yForLinePlot.length - 1]) diff --git a/optuna_dashboard/static/components/GraphIntermediateValues.tsx b/optuna_dashboard/static/components/GraphIntermediateValues.tsx index 6a87bf00..498ea00c 100644 --- a/optuna_dashboard/static/components/GraphIntermediateValues.tsx +++ b/optuna_dashboard/static/components/GraphIntermediateValues.tsx @@ -1,4 +1,4 @@ -import * as plotly from "plotly.js-dist" +import * as plotly from "plotly.js-basic-dist" import React, { FC, useEffect } from "react" const plotDomId = "graph-intermediate-values" @@ -30,7 +30,7 @@ const plotIntermediateValue = (trials: Trial[]) => { return } - let filteredTrials = trials.filter( + const filteredTrials = trials.filter( (t) => t.state === "Complete" || t.state === "Pruned" ) const plotData: Partial[] = filteredTrials.map((trial) => { diff --git a/optuna_dashboard/static/components/GraphParallelCoordinate.tsx b/optuna_dashboard/static/components/GraphParallelCoordinate.tsx index cfa0b9d2..712ab465 100644 --- a/optuna_dashboard/static/components/GraphParallelCoordinate.tsx +++ b/optuna_dashboard/static/components/GraphParallelCoordinate.tsx @@ -1,4 +1,4 @@ -import * as plotly from "plotly.js-dist" +import * as plotly from "plotly.js-basic-dist" import React, { FC, useEffect } from "react" const plotDomId = "graph-parallel-coordinate" @@ -30,7 +30,7 @@ const plotCoordinate = (trials: Trial[], objectiveId: number) => { plotly.react(plotDomId, []) return } - let filteredTrials = trials.filter( + const filteredTrials = trials.filter( (t) => t.state === "Complete" || t.state === "Pruned" ) @@ -50,7 +50,7 @@ const plotCoordinate = (trials: Trial[], objectiveId: number) => { const objectiveValues: number[] = filteredTrials.map( (t) => t.values![objectiveId] ) - let dimensions = [ + const dimensions = [ { label: "Objective value", values: objectiveValues, diff --git a/optuna_dashboard/static/components/StudyDetail.tsx b/optuna_dashboard/static/components/StudyDetail.tsx index 1c8d87bb..55508830 100644 --- a/optuna_dashboard/static/components/StudyDetail.tsx +++ b/optuna_dashboard/static/components/StudyDetail.tsx @@ -92,7 +92,7 @@ export const useStudyDetailValue = (studyId: number): StudyDetail | null => { return studyDetails[studyId] || null } -export const StudyDetail: FC<{}> = () => { +export const StudyDetail: FC = () => { const classes = useStyles() const action = actionCreator() const { studyId } = useParams() @@ -129,7 +129,7 @@ export const StudyDetail: FC<{}> = () => {
{ + onClick={() => { setOpenReloadIntervalSelect(!openReloadIntervalSelect) }} > @@ -221,7 +221,7 @@ const TrialTable: FC<{ studyDetail: StudyDetail | null }> = ({ }) => { const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] - let columns: DataGridColumn[] = [ + const columns: DataGridColumn[] = [ { field: "number", label: "Number", sortable: true, padding: "none" }, { field: "state", @@ -237,7 +237,26 @@ const TrialTable: FC<{ studyDetail: StudyDetail | null }> = ({ field: "values", label: "Value", sortable: true, - toCellValue: (i) => trials[i].values?.[0] || null, + less: (i, j): number => { + const firstVal = trials[i].values?.[0] + const secondVal = trials[j].values?.[0] + + if (firstVal === secondVal) { + return 0 + } else if (firstVal && secondVal) { + return firstVal < secondVal ? 1 : -1 + } else if (firstVal) { + return -1 + } else { + return 1 + } + }, + toCellValue: (i) => { + if (trials[i].values === undefined) { + return null + } + return trials[i].values?.[0] + }, }) } else { const objectiveColumns: DataGridColumn< @@ -246,7 +265,26 @@ const TrialTable: FC<{ studyDetail: StudyDetail | null }> = ({ field: "values", label: `Objective ${objectiveId}`, sortable: true, - toCellValue: (i) => trials[i].values?.[objectiveId] || null, + less: (i, j): number => { + const firstVal = trials[i].values?.[objectiveId] + const secondVal = trials[j].values?.[objectiveId] + + if (firstVal === secondVal) { + return 0 + } else if (firstVal && secondVal) { + return firstVal < secondVal ? 1 : -1 + } else if (firstVal) { + return -1 + } else { + return 1 + } + }, + toCellValue: (i) => { + if (trials[i].values === undefined) { + return null + } + return trials[i].values?.[objectiveId] + }, })) columns.push(...objectiveColumns) } diff --git a/optuna_dashboard/static/components/StudyList.tsx b/optuna_dashboard/static/components/StudyList.tsx index d88dd1f4..ba1cc029 100644 --- a/optuna_dashboard/static/components/StudyList.tsx +++ b/optuna_dashboard/static/components/StudyList.tsx @@ -46,7 +46,7 @@ const useStyles = makeStyles((theme: Theme) => }) ) -export const StudyList: FC<{}> = () => { +export const StudyList: FC = () => { const classes = useStyles() const [ @@ -127,7 +127,7 @@ export const StudyList: FC<{}> = () => { aria-label="delete study" size="small" color="inherit" - onClick={(e) => { + onClick={() => { setDeleteStudyID(studies[i].study_id) setOpenDeleteStudyDialog(true) }} @@ -226,7 +226,7 @@ export const StudyList: FC<{}> = () => { { + onClick={() => { action.updateStudySummaries("Success to reload") }} color="inherit" @@ -256,12 +256,12 @@ export const StudyList: FC<{}> = () => { horizontal: "right", }} open={openNewStudySelection} - onClose={(e) => { + onClose={() => { setNewStudySelectionAnchorEl(null) }} > { + onClick={() => { setNewStudySelectionAnchorEl(null) setOpenNewSingleObjectiveStudyDialog(true) }} @@ -269,7 +269,7 @@ export const StudyList: FC<{}> = () => { Single-objective { + onClick={() => { setNewStudySelectionAnchorEl(null) setOpenNewMultiObjectiveStudyDialog(true) }} @@ -294,7 +294,7 @@ export const StudyList: FC<{}> = () => { { + onClose={() => { handleCloseNewSingleObjectiveStudyDialog() }} aria-labelledby="create-single-objective-study-form-dialog-title" @@ -323,7 +323,7 @@ export const StudyList: FC<{}> = () => { control={ { + onChange={() => { setMaximize(!maximize) }} color="primary" @@ -350,7 +350,7 @@ export const StudyList: FC<{}> = () => { { + onClose={() => { handleCloseNewMultiObjectiveStudyDialog() }} aria-labelledby="create-multi-objective-study-form-dialog-title" @@ -384,7 +384,7 @@ export const StudyList: FC<{}> = () => {