diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 828108ef..5a3d6573 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -15,8 +15,6 @@ import { import { CompareStudies } from "./CompareStudies" import { StudyDetail } from "./StudyDetail" import { StudyList } from "./StudyList" -import { StudyDetailBeta } from "./StudyDetailBeta" -import { StudyListBeta } from "./StudyListBeta" export const App: FC = () => { const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)") @@ -53,19 +51,10 @@ export const App: FC = () => { - - } - /> @@ -74,7 +63,7 @@ export const App: FC = () => { @@ -83,7 +72,7 @@ export const App: FC = () => { @@ -92,7 +81,7 @@ export const App: FC = () => { @@ -101,7 +90,7 @@ export const App: FC = () => { @@ -109,7 +98,12 @@ export const App: FC = () => { /> } + children={ + + } /> { } /> - } - /> } diff --git a/optuna_dashboard/ts/components/AppDrawer.tsx b/optuna_dashboard/ts/components/AppDrawer.tsx index 15d63c42..540aa7cc 100644 --- a/optuna_dashboard/ts/components/AppDrawer.tsx +++ b/optuna_dashboard/ts/components/AppDrawer.tsx @@ -24,7 +24,6 @@ import Brightness4Icon from "@mui/icons-material/Brightness4" import Brightness7Icon from "@mui/icons-material/Brightness7" import TableViewIcon from "@mui/icons-material/TableView" import RateReviewIcon from "@mui/icons-material/RateReview" -import ClearIcon from "@mui/icons-material/Clear" import MenuIcon from "@mui/icons-material/Menu" import GitHubIcon from "@mui/icons-material/GitHub" import OpenInNewIcon from "@mui/icons-material/OpenInNew" @@ -185,7 +184,7 @@ export const AppDrawer: FC<{ @@ -304,7 +303,7 @@ export const AppDrawer: FC<{ @@ -314,21 +313,6 @@ export const AppDrawer: FC<{ - - - - - - - - diff --git a/optuna_dashboard/ts/components/CompareStudies.tsx b/optuna_dashboard/ts/components/CompareStudies.tsx index 5e4d5f00..bae1f687 100644 --- a/optuna_dashboard/ts/components/CompareStudies.tsx +++ b/optuna_dashboard/ts/components/CompareStudies.tsx @@ -89,7 +89,7 @@ export const CompareStudies: FC<{ }, [studies, query]) const studyListWidth = 200 - const title = "Compare Studies" + const title = "Compare Studies (Experimental)" useEffect(() => { action.updateStudySummaries() @@ -99,7 +99,7 @@ export const CompareStudies: FC<{ <> - {studies.length} Studies + Compare studies with Shift+Click @@ -315,8 +315,8 @@ const StudiesGraph: FC<{ studies: StudySummary[] }> = ({ studies }) => { diff --git a/optuna_dashboard/ts/components/GraphEdf.tsx b/optuna_dashboard/ts/components/GraphEdf.tsx index ee1418fd..185dd889 100644 --- a/optuna_dashboard/ts/components/GraphEdf.tsx +++ b/optuna_dashboard/ts/components/GraphEdf.tsx @@ -27,7 +27,7 @@ interface EdfPlotInfo { trials: Trial[] } -export const GraphEdfBeta: FC<{ +export const GraphEdf: FC<{ study: StudyDetail | null objectiveId: number }> = ({ study, objectiveId }) => { @@ -37,7 +37,7 @@ export const GraphEdfBeta: FC<{ () => new Target("objective", objectiveId), [objectiveId] ) - const trials = useFilteredTrials(study, [target], false, false) + const trials = useFilteredTrials(study, [target], false) useEffect(() => { if (study !== null) { @@ -57,60 +57,6 @@ export const GraphEdfBeta: FC<{ ) } -export const GraphEdf: FC<{ - study: StudyDetail | null -}> = ({ study = null }) => { - const theme = useTheme() - const [targets, selected, setTarget] = useObjectiveTargets(study) - const trials = useFilteredTrials(study, [selected], false, false) - - const handleObjectiveChange = (event: SelectChangeEvent) => { - setTarget(event.target.value) - } - - useEffect(() => { - if (study != null) { - plotEdf(trials, selected, plotDomId, theme.palette.mode) - } - }, [trials, selected, theme.palette.mode]) - return ( - - - - EDF - - {study !== null && study.directions.length !== 1 ? ( - - Objective: - - - ) : null} - - - - - - ) -} - export const GraphEdfMultiStudies: FC<{ studies: StudyDetail[] }> = ({ studies }) => { @@ -119,7 +65,7 @@ export const GraphEdfMultiStudies: FC<{ studies.length !== 0 ? studies[0] : null ) - const trials = useFilteredTrialsFromStudies(studies, [selected], false, false) + const trials = useFilteredTrialsFromStudies(studies, [selected], false) const edfPlotInfos = studies.map((study, index) => { const e: EdfPlotInfo = { study_name: study?.name, diff --git a/optuna_dashboard/ts/components/GraphHistory.tsx b/optuna_dashboard/ts/components/GraphHistory.tsx index ae219850..2e78cd89 100644 --- a/optuna_dashboard/ts/components/GraphHistory.tsx +++ b/optuna_dashboard/ts/components/GraphHistory.tsx @@ -5,9 +5,7 @@ import { FormControl, FormLabel, FormControlLabel, - Checkbox, MenuItem, - Switch, Select, Radio, RadioGroup, @@ -34,24 +32,16 @@ interface HistoryPlotInfo { export const GraphHistory: FC<{ study: StudyDetail | null - betaLogScale?: boolean - betaIncludePruned?: boolean -}> = ({ study, betaLogScale, betaIncludePruned }) => { + logScale: boolean + includePruned: boolean +}> = ({ study, logScale, includePruned }) => { const theme = useTheme() const [xAxis, setXAxis] = useState< "number" | "datetime_start" | "datetime_complete" >("number") - const [logScale, setLogScale] = useState(false) - const [filterCompleteTrial, setFilterCompleteTrial] = useState(false) - const [filterPrunedTrial, setFilterPrunedTrial] = useState(false) const [targets, selected, setTarget] = useObjectiveAndUserAttrTargets(study) - const trials = useFilteredTrials( - study, - [selected], - filterCompleteTrial, - betaIncludePruned === undefined ? filterPrunedTrial : !betaIncludePruned - ) + const trials = useFilteredTrials(study, [selected], includePruned) useEffect(() => { if (study !== null) { @@ -60,7 +50,7 @@ export const GraphHistory: FC<{ study.directions, selected, xAxis, - betaLogScale === undefined ? logScale : betaLogScale, + logScale, theme.palette.mode, study?.objective_names ) @@ -70,7 +60,6 @@ export const GraphHistory: FC<{ study?.directions, selected, logScale, - betaLogScale, xAxis, theme.palette.mode, study?.objective_names, @@ -90,18 +79,6 @@ export const GraphHistory: FC<{ } } - const handleLogScaleChange = (e: ChangeEvent) => { - setLogScale(!logScale) - } - - const handleFilterCompleteChange = (e: ChangeEvent) => { - setFilterCompleteTrial(!filterCompleteTrial) - } - - const handleFilterPrunedChange = (e: ChangeEvent) => { - setFilterPrunedTrial(!filterPrunedTrial) - } - return ( ) : null} - {betaLogScale === undefined ? ( - - Log y scale: - - - ) : null} - {betaIncludePruned === undefined ? ( - - Filter state: - - } - label="Complete" - /> - - } - label="Pruned" - /> - - ) : null} = ({ studies, betaLogScale, betaIncludePruned }) => { + logScale: boolean + includePruned: boolean +}> = ({ studies, logScale, includePruned }) => { const theme = useTheme() const [xAxis, setXAxis] = useState< "number" | "datetime_start" | "datetime_complete" >("number") - const [logScale, setLogScale] = useState(false) - const [filterCompleteTrial, setFilterCompleteTrial] = useState(false) - const [filterPrunedTrial, setFilterPrunedTrial] = useState(false) // TODO(umezawa): Prepare targets with all studies. const [targets, selected, setTarget] = useObjectiveAndUserAttrTargets( @@ -232,8 +166,7 @@ export const GraphHistoryMultiStudies: FC<{ const trials = useFilteredTrialsFromStudies( studies, [selected], - filterCompleteTrial, - betaIncludePruned === undefined ? filterPrunedTrial : !betaIncludePruned + !includePruned ) const historyPlotInfos = studies.map((study, index) => { const h: HistoryPlotInfo = { @@ -250,10 +183,10 @@ export const GraphHistoryMultiStudies: FC<{ historyPlotInfos, selected, xAxis, - betaLogScale === undefined ? logScale : betaLogScale, + logScale, theme.palette.mode ) - }, [studies, selected, logScale, betaLogScale, xAxis, theme.palette.mode]) + }, [studies, selected, logScale, xAxis, theme.palette.mode]) const handleObjectiveChange = (event: SelectChangeEvent) => { setTarget(event.target.value) @@ -269,18 +202,6 @@ export const GraphHistoryMultiStudies: FC<{ } } - const handleLogScaleChange = (e: ChangeEvent) => { - setLogScale(!logScale) - } - - const handleFilterCompleteChange = (e: ChangeEvent) => { - setFilterCompleteTrial(!filterCompleteTrial) - } - - const handleFilterPrunedChange = (e: ChangeEvent) => { - setFilterPrunedTrial(!filterPrunedTrial) - } - return ( ) : null} - {betaLogScale === undefined ? ( - - Log y scale: - - - ) : null} - {betaIncludePruned === undefined ? ( - - Filter state: - - } - label="Complete" - /> - - } - label="Pruned" - /> - - ) : null} { if (importances !== null && nObjectives === importances.length) { - plotParamImportancesBeta(importances, objectiveNames, theme.palette.mode) + plotParamImportance(importances, objectiveNames, theme.palette.mode) } }, [nObjectives, importances, theme.palette.mode]) @@ -60,7 +48,7 @@ export const GraphHyperparameterImportanceBeta: FC<{ ) } -const plotParamImportancesBeta = ( +const plotParamImportance = ( importances: ParamImportance[][], objectiveNames: string[], mode: string @@ -111,110 +99,3 @@ const plotParamImportancesBeta = ( ) plotly.react(plotDomId, traces, layout) } - -export const GraphHyperparameterImportances: FC<{ - study: StudyDetail | null - studyId: number -}> = ({ study = null, studyId }) => { - const theme = useTheme() - const action = actionCreator() - const importances = useParamImportanceValue(studyId) - const [objectiveId, setObjectiveId] = useState(0) - const numCompletedTrials = - study?.trials.filter((t) => t.state === "Complete").length || 0 - - const handleObjectiveChange = (event: SelectChangeEvent) => { - setObjectiveId(event.target.value as number) - } - - useEffect(() => { - action.updateParamImportance(studyId) - }, [numCompletedTrials]) - - useEffect(() => { - if (importances !== null && importances.length > objectiveId) { - plotParamImportances(importances[objectiveId], theme.palette.mode) - } - }, [importances, objectiveId, theme.palette.mode]) - - return ( - - - - Hyperparameter importance - - {study !== null && study.directions.length !== 1 ? ( - - Objective ID: - - - ) : null} - - - - - - ) -} - -const plotParamImportances = (importance: ParamImportance[], mode: string) => { - if (document.getElementById(plotDomId) === null) { - return - } - const reversed = [...importance].reverse() - const importance_values = reversed.map((p) => p.importance) - const param_names = reversed.map((p) => p.name) - const param_hover_templates = reversed.map( - (p) => `${p.name} (${p.distribution}): ${p.importance} ` - ) - - const layout: Partial = { - xaxis: { - title: `Importance for the Objective Value`, - }, - yaxis: { - title: "Hyperparameter", - automargin: true, - }, - margin: { - l: 50, - t: 0, - r: 50, - b: 50, - }, - showlegend: false, - template: mode === "dark" ? plotlyDarkTemplate : {}, - } - - const plotData: Partial[] = [ - { - type: "bar", - orientation: "h", - x: importance_values, - y: param_names, - text: importance_values.map((v) => String(v.toFixed(2))), - textposition: "outside", - hovertemplate: param_hover_templates, - marker: { - color: "rgb(66,146,198)", - }, - }, - ] - - plotly.react(plotDomId, plotData, layout) -} diff --git a/optuna_dashboard/ts/components/GraphIntermediateValues.tsx b/optuna_dashboard/ts/components/GraphIntermediateValues.tsx index 49749584..ca0bfda0 100644 --- a/optuna_dashboard/ts/components/GraphIntermediateValues.tsx +++ b/optuna_dashboard/ts/components/GraphIntermediateValues.tsx @@ -1,22 +1,11 @@ import * as plotly from "plotly.js-dist-min" -import React, { ChangeEvent, FC, useEffect, useState } from "react" -import { - Box, - Checkbox, - FormControl, - FormLabel, - FormControlLabel, - Grid, - Typography, - useTheme, - CardContent, - Card, -} from "@mui/material" +import React, { FC, useEffect } from "react" +import { Box, Typography, useTheme, CardContent, Card } from "@mui/material" import { plotlyDarkTemplate } from "./PlotlyDarkMode" const plotDomId = "graph-intermediate-values" -export const GraphIntermediateValuesBeta: FC<{ +export const GraphIntermediateValues: FC<{ trials: Trial[] includePruned: boolean logScale: boolean @@ -48,78 +37,6 @@ export const GraphIntermediateValuesBeta: FC<{ ) } -export const GraphIntermediateValues: FC<{ - trials: Trial[] -}> = ({ trials = [] }) => { - const theme = useTheme() - const [filterCompleteTrial, setFilterCompleteTrial] = useState(false) - const [filterPrunedTrial, setFilterPrunedTrial] = useState(false) - - useEffect(() => { - plotIntermediateValue( - trials, - theme.palette.mode, - filterCompleteTrial, - filterPrunedTrial, - false - ) - }, [trials, theme.palette.mode, filterCompleteTrial, filterPrunedTrial]) - - const handleFilterCompleteChange = (e: ChangeEvent) => { - e.preventDefault() - setFilterCompleteTrial(!filterCompleteTrial) - } - const handleFilterPrunedChange = (e: ChangeEvent) => { - e.preventDefault() - setFilterPrunedTrial(!filterPrunedTrial) - } - return ( - - - - Intermediate values - - - Filter state: - - } - label="Complete" - /> - - } - label="Pruned" - /> - - - - - - - ) -} - const plotIntermediateValue = ( trials: Trial[], mode: string, diff --git a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx index 98c88264..4f945fe1 100644 --- a/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx +++ b/optuna_dashboard/ts/components/GraphParallelCoordinate.tsx @@ -90,7 +90,7 @@ export const GraphParallelCoordinate: FC<{ const theme = useTheme() const [targets, searchSpace, renderCheckBoxes] = useTargets(study) - const trials = useFilteredTrials(study, targets, false, false) + const trials = useFilteredTrials(study, targets, false) useEffect(() => { if (study !== null) { plotCoordinate(study, trials, targets, searchSpace, theme.palette.mode) diff --git a/optuna_dashboard/ts/components/GraphSlice.tsx b/optuna_dashboard/ts/components/GraphSlice.tsx index cfa86ba6..8ce335af 100644 --- a/optuna_dashboard/ts/components/GraphSlice.tsx +++ b/optuna_dashboard/ts/components/GraphSlice.tsx @@ -47,7 +47,6 @@ export const GraphSlice: FC<{ selectedParamTarget !== null ? [selectedObjective, selectedParamTarget] : [selectedObjective], - false, false ) diff --git a/optuna_dashboard/ts/components/PreferenceDialog.tsx b/optuna_dashboard/ts/components/PreferenceDialog.tsx deleted file mode 100644 index 79261f7f..00000000 --- a/optuna_dashboard/ts/components/PreferenceDialog.tsx +++ /dev/null @@ -1,172 +0,0 @@ -import React, { useEffect, useState } from "react" -import MuiDialogTitle from "@mui/material/DialogTitle" -import CloseIcon from "@mui/icons-material/Close" -import MuiDialogContent from "@mui/material/DialogContent" -import FormControlLabel from "@mui/material/FormControlLabel" -import { - Dialog, - Checkbox, - Typography, - IconButton, - FormGroup, - useTheme, - FormLabel, -} from "@mui/material" -import { useRecoilValue } from "recoil" -import { graphVisibilityState } from "../state" -import { actionCreator } from "../action" - -type UsePreferenceDialogReturn = [(open: boolean) => void, () => JSX.Element] - -export const usePreferenceDialog = ( - studyDetail: StudyDetail | null -): UsePreferenceDialogReturn => { - const theme = useTheme() - const action = actionCreator() - const globalGraphVisibility = - useRecoilValue(graphVisibilityState) - const [localGraphVisibility, setLocalGraphVisibility] = - useState(globalGraphVisibility) - - useEffect(() => { - action.getGraphVisibility() - }, []) - - useEffect(() => { - setLocalGraphVisibility(globalGraphVisibility) - }, [globalGraphVisibility]) - - const [prefOpen, setPrefOpen] = useState(false) - const handleClose = () => { - setPrefOpen(false) - action.saveGraphVisibility(localGraphVisibility) - } - const handlePreferenceOnChange = ( - event: React.ChangeEvent - ) => { - setLocalGraphVisibility({ - ...localGraphVisibility, - [event.target.name]: event.target.checked, - }) - } - - const renderPreferenceDialog = () => { - return ( - - - Preferences - - - - - - Charts - - - } - label="History" - /> - - } - label="Pareto Front" - /> - - } - label="Parallel Coordinate" - /> - 1 || - !studyDetail.has_intermediate_values) - } - control={ - - } - label="Intermediate Values" - /> - - } - label="EDF" - /> - - } - label="Contour" - /> - - } - label="Hyperparameter Importances" - /> - - } - label="Slice" - /> - - - - ) - } - return [setPrefOpen, renderPreferenceDialog] -} diff --git a/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx b/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx deleted file mode 100644 index 1c323f6f..00000000 --- a/optuna_dashboard/ts/components/ReloadIntervalSelect.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { FC } from "react" -import { styled } from "@mui/system" -import { useRecoilValue } from "recoil" -import { reloadIntervalState } from "../state" -import { MenuItem, TextField, alpha } from "@mui/material" -import { Cached } from "@mui/icons-material" -import { actionCreator } from "../action" - -export const ReloadIntervalSelect: FC = () => { - const action = actionCreator() - const reloadInterval = useRecoilValue(reloadIntervalState) - - const Wrapper = styled("div")(({ theme }) => ({ - position: "relative", - borderRadius: theme.shape.borderRadius, - backgroundColor: alpha(theme.palette.common.white, 0.15), - "&:hover": { - backgroundColor: alpha(theme.palette.common.white, 0.25), - }, - marginLeft: 0, - width: "100%", - [theme.breakpoints.up("sm")]: { - marginLeft: theme.spacing(1), - width: "auto", - }, - })) - - const IconWrapper = styled("div")(({ theme }) => ({ - padding: theme.spacing(0, 2), - height: "100%", - position: "absolute", - pointerEvents: "none", - display: "flex", - alignItems: "center", - justifyContent: "center", - })) - - const Select = styled(TextField)(({ theme }) => ({ - color: "inherit", - width: "14ch", - "& .MuiInput-underline:after": { - borderColor: "rgb(256,256,256,.1)", - }, - "& .MuiOutlinedInput-root": { - color: "inherit", - "& fieldset": { - borderColor: "rgb(256,256,256,.1)", - }, - "& .MuiSelect-icon": { - color: "white", - }, - "&:hover fieldset": { - borderColor: "rgb(256,256,256,.1)", - }, - "&.Mui-focused fieldset": { - borderColor: "rgb(256,256,256,.1)", - }, - }, - "& .MuiInputBase-input": { - // vertical padding + font size from searchIcon - paddingLeft: `calc(1em + ${theme.spacing(4)})`, - width: "100%", - }, - })) - - return ( - - - - - - - ) -} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 530125fb..255be7d2 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -1,249 +1,197 @@ -import React, { FC, useEffect } from "react" +import React, { FC, useEffect, useMemo } from "react" import { useRecoilValue } from "recoil" import { Link, useParams } from "react-router-dom" import { - AppBar, - Card, - Typography, - CardContent, - Container, - Toolbar, Box, - IconButton, + Card, + CardContent, + Typography, useTheme, + IconButton, } from "@mui/material" -import { Home, Settings } from "@mui/icons-material" -import Brightness4Icon from "@mui/icons-material/Brightness4" -import Brightness7Icon from "@mui/icons-material/Brightness7" +import Grid2 from "@mui/material/Unstable_Grid2" +import ChevronRightIcon from "@mui/icons-material/ChevronRight" +import HomeIcon from "@mui/icons-material/Home" -import { GraphParallelCoordinate } from "./GraphParallelCoordinate" -import { GraphHyperparameterImportances } from "./GraphHyperparameterImportances" -import { GraphEdf } from "./GraphEdf" -import { Contour } from "./GraphContour" -import { GraphIntermediateValues } from "./GraphIntermediateValues" -import { GraphSlice } from "./GraphSlice" -import { GraphHistory } from "./GraphHistory" -import { GraphParetoFront } from "./GraphParetoFront" import { StudyNote } from "./Note" import { actionCreator } from "../action" import { - graphVisibilityState, reloadIntervalState, - studyDetailsState, - studySummariesState, + useStudyDetailValue, + useStudyName, } from "../state" -import { usePreferenceDialog } from "./PreferenceDialog" -import { ReloadIntervalSelect } from "./ReloadIntervalSelect" import { TrialTable } from "./TrialTable" +import { AppDrawer, PageId } from "./AppDrawer" +import { GraphParallelCoordinate } from "./GraphParallelCoordinate" +import { Contour } from "./GraphContour" +import { GraphSlice } from "./GraphSlice" +import { GraphEdf } from "./GraphEdf" +import { TrialList } from "./TrialList" +import { StudyHistory } from "./StudyHistory" interface ParamTypes { studyId: string } -const useStudyDetailValue = (studyId: number): StudyDetail | null => { - const studyDetails = useRecoilValue(studyDetailsState) - return studyDetails[studyId] || null -} +export const useURLVars = (): number => { + const { studyId } = useParams() -const useStudySummaryValue = (studyId: number): StudySummary | null => { - const studySummaries = useRecoilValue(studySummariesState) - return studySummaries.find((s) => s.study_id == studyId) || null + return useMemo(() => parseInt(studyId, 10), [studyId]) } export const StudyDetail: FC<{ toggleColorMode: () => void -}> = ({ toggleColorMode }) => { + page: PageId +}> = ({ toggleColorMode, page }) => { const theme = useTheme() const action = actionCreator() - const { studyId } = useParams() - const studyIdNumber = parseInt(studyId, 10) - const studyDetail = useStudyDetailValue(studyIdNumber) - const studySummary = useStudySummaryValue(studyIdNumber) - const directions = studyDetail?.directions || studySummary?.directions || null - const graphVisibility = useRecoilValue(graphVisibilityState) + const studyId = useURLVars() + const studyDetail = useStudyDetailValue(studyId) const reloadInterval = useRecoilValue(reloadIntervalState) - const [openPreferenceDialog, renderPreferenceDialog] = - usePreferenceDialog(studyDetail) + const studyName = useStudyName(studyId) + + const title = + studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}` useEffect(() => { action.loadReloadInterval() - action.updateStudyDetail(studyIdNumber) + action.updateStudyDetail(studyId) + action.updateAPIMeta() }, []) useEffect(() => { if (reloadInterval < 0) { return } + const nTrials = studyDetail ? studyDetail.trials.length : 0 + let interval = reloadInterval * 1000 + + // For Human-in-the-loop Optimization, the interval is set to 2 seconds + // when the number of trials is small and the page is "trialList". + if (page === "trialList" && nTrials < 100) { + interval = 2000 + } else if (page === "trialList" && nTrials < 500) { + interval = 5000 + } + const intervalId = setInterval(function () { - action.updateStudyDetail(studyIdNumber) - }, reloadInterval * 1000) + action.updateStudyDetail(studyId) + }, interval) return () => clearInterval(intervalId) - }, [reloadInterval, studyDetail]) + }, [reloadInterval, studyDetail, page]) - // TODO(chenghuzi): Reduce the number of calls to setInterval and clearInterval. - const title = studyDetail !== null ? studyDetail.name : `Study #${studyId}` - const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] - - return ( -
- {renderPreferenceDialog()} - - - - {APP_BAR_TITLE} - - - { - toggleColorMode() - }} - color="inherit" - title={ - theme.palette.mode === "dark" - ? "Switch to light mode" - : "Switch to dark mode" - } - > - {theme.palette.mode === "dark" ? ( - - ) : ( - - )} - - { - openPreferenceDialog(true) - }} - title="Open preference panel" - > - - - - - - - - - + } else if (page === "analytics") { + content = ( + + + Hyperparameter Relationships + + + + + + + + + + + + + + + + + + Empirical Distribution of the Objective Value + + + {studyDetail !== null + ? studyDetail.directions.map((d, i) => ( + + + + + + + + )) + : null} + + + ) + } else if (page === "trialTable") { + content = ( + + + + + + ) + } else if (page === "trialList") { + content = + } else if (page === "note" && studyDetail !== null) { + content = ( + -
- - {title} - - {graphVisibility.history ? ( - - - - - - ) : null} + + Note + + + + ) + } - {directions !== null && - directions.length > 1 && - graphVisibility.paretoFront ? ( - - - - - - ) : null} - {graphVisibility.parallelCoordinate ? ( - - - - - - ) : null} + const toolbar = ( + <> + + + + + + {title} + + + ) - {studyDetail !== null && - studyDetail.directions.length == 1 && - studyDetail.has_intermediate_values && - graphVisibility.intermediateValues ? ( - - - - - - ) : null} - {graphVisibility.edf ? ( - - - - - - ) : null} - - {graphVisibility.contour ? ( - - - - - - ) : null} - - {graphVisibility.importances ? ( - - - - - - ) : null} - - {studyDetail !== null && graphVisibility.slice ? ( - - - - - - ) : null} - - - - {studyDetail !== null ? ( - - ) : null} -
-
-
+ return ( + + + {content} + + ) } diff --git a/optuna_dashboard/ts/components/StudyDetailBeta.tsx b/optuna_dashboard/ts/components/StudyDetailBeta.tsx deleted file mode 100644 index 6a7ab839..00000000 --- a/optuna_dashboard/ts/components/StudyDetailBeta.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import React, { FC, useEffect, useMemo } from "react" -import { useRecoilValue } from "recoil" -import { Link, useParams } from "react-router-dom" -import { - Box, - Card, - CardContent, - Typography, - useTheme, - IconButton, -} from "@mui/material" -import Grid2 from "@mui/material/Unstable_Grid2" -import ChevronRightIcon from "@mui/icons-material/ChevronRight" -import HomeIcon from "@mui/icons-material/Home" - -import { StudyNote } from "./Note" -import { actionCreator } from "../action" -import { - reloadIntervalState, - useStudyDetailValue, - useStudyName, -} from "../state" -import { TrialTable } from "./TrialTable" -import { AppDrawer, PageId } from "./AppDrawer" -import { GraphParallelCoordinate } from "./GraphParallelCoordinate" -import { Contour } from "./GraphContour" -import { GraphSlice } from "./GraphSlice" -import { GraphEdfBeta } from "./GraphEdf" -import { TrialList } from "./TrialList" -import { StudyHistory } from "./StudyHistory" - -interface ParamTypes { - studyId: string -} - -export const useURLVars = (): number => { - const { studyId } = useParams() - - return useMemo(() => parseInt(studyId, 10), [studyId]) -} - -export const StudyDetailBeta: FC<{ - toggleColorMode: () => void - page: PageId -}> = ({ toggleColorMode, page }) => { - const theme = useTheme() - const action = actionCreator() - const studyId = useURLVars() - const studyDetail = useStudyDetailValue(studyId) - const reloadInterval = useRecoilValue(reloadIntervalState) - const studyName = useStudyName(studyId) - - const title = - studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}` - - useEffect(() => { - action.loadReloadInterval() - action.updateStudyDetail(studyId) - action.updateAPIMeta() - }, []) - - useEffect(() => { - if (reloadInterval < 0) { - return - } - const nTrials = studyDetail ? studyDetail.trials.length : 0 - let interval = reloadInterval * 1000 - - // For Human-in-the-loop Optimization, the interval is set to 2 seconds - // when the number of trials is small and the page is "trialList". - if (page === "trialList" && nTrials < 100) { - interval = 2000 - } else if (page === "trialList" && nTrials < 500) { - interval = 5000 - } - - const intervalId = setInterval(function () { - action.updateStudyDetail(studyId) - }, interval) - return () => clearInterval(intervalId) - }, [reloadInterval, studyDetail, page]) - - let content = null - if (page === "history") { - content = - } else if (page === "analytics") { - content = ( - - - Hyperparameter Relationships - - - - - - - - - - - - - - - - - - Empirical Distribution of the Objective Value - - - {studyDetail !== null - ? studyDetail.directions.map((d, i) => ( - - - - - - - - )) - : null} - - - ) - } else if (page === "trialTable") { - content = ( - - - - - - ) - } else if (page === "trialList") { - content = - } else if (page === "note" && studyDetail !== null) { - content = ( - - - Note - - - - ) - } - - const toolbar = ( - <> - - - - - - {title} - - - ) - - return ( - - - {content} - - - ) -} diff --git a/optuna_dashboard/ts/components/StudyHistory.tsx b/optuna_dashboard/ts/components/StudyHistory.tsx index 7d81f848..938cfcf6 100644 --- a/optuna_dashboard/ts/components/StudyHistory.tsx +++ b/optuna_dashboard/ts/components/StudyHistory.tsx @@ -10,10 +10,10 @@ import { } from "@mui/material" import { GraphParetoFront } from "./GraphParetoFront" import { GraphHistory } from "./GraphHistory" -import { GraphIntermediateValuesBeta } from "./GraphIntermediateValues" +import { GraphIntermediateValues } from "./GraphIntermediateValues" import Grid2 from "@mui/material/Unstable_Grid2" import { DataGrid, DataGridColumn } from "./DataGrid" -import { GraphHyperparameterImportanceBeta } from "./GraphHyperparameterImportances" +import { GraphHyperparameterImportance } from "./GraphHyperparameterImportances" import { BestTrialsCard } from "./BestTrialsCard" import { useStudyDetailValue, @@ -96,8 +96,8 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => { @@ -106,7 +106,7 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => { studyDetail.directions.length == 1 && studyDetail.has_intermediate_values ? ( - = ({ studyId }) => { ) : null} - void }> = ({ toggleColorMode }) => { const theme = useTheme() + const action = actionCreator() const [studyFilterText, setStudyFilterText] = React.useState("") const studyFilter = (row: StudySummary) => { @@ -41,116 +49,69 @@ export const StudyList: FC<{ return row.study_name.indexOf(k) >= 0 }) } - const [openDeleteStudyDialog, renderDeleteStudyDialog] = - useDeleteStudyDialog() + const studies = useRecoilValue(studySummariesState) const [openCreateStudyDialog, renderCreateStudyDialog] = useCreateStudyDialog() + const [openDeleteStudyDialog, renderDeleteStudyDialog] = + useDeleteStudyDialog() + const [openRenameStudyDialog, renderRenameStudyDialog] = + useRenameStudyDialog(studies) + const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc") - const linkColor = useMemo( - () => - theme.palette.mode === "dark" - ? theme.palette.primary.light - : theme.palette.primary.dark, - [theme.palette.mode] - ) - - const action = actionCreator() - const studies = useRecoilValue(studySummariesState) + let filteredStudies = studies.filter((s) => !studyFilter(s)) + if (sortBy === "id-desc") { + filteredStudies = filteredStudies.reverse() + } useEffect(() => { action.updateStudySummaries() }, []) - const columns: DataGridColumn[] = [ - { - field: "study_id", - label: "Study ID", - sortable: true, + const Select = styled(TextField)(({ theme }) => ({ + "& .MuiInputBase-input": { + // vertical padding + font size from searchIcon + paddingLeft: `calc(1em + ${theme.spacing(4)})`, }, - { - field: "study_name", - label: "Name", - sortable: true, - toCellValue: (i) => ( - - {studies[i].study_name} - - ), - }, - { - field: "directions", - label: "Direction", - sortable: false, - toCellValue: (i) => studies[i].directions.join(), - }, - { - field: "study_name", - label: "", - sortable: false, - padding: "none", - toCellValue: (i) => ( - { - openDeleteStudyDialog(studies[i].study_id) - }} - > - - - ), - }, - ] + })) + const sortBySelect = ( + + + + + + + ) - const collapseAttrColumns: DataGridColumn[] = [ - { field: "key", label: "Key", sortable: true }, - { field: "value", label: "Value", sortable: true }, - ] - - const collapseBody = (index: number) => { - return ( - - - - - Study user attributes - - - columns={collapseAttrColumns} - rows={studies[index].user_attrs} - keyField={"key"} - dense={true} - initialRowsPerPage={5} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - - - - Study system attributes - - - columns={collapseAttrColumns} - rows={studies[index].system_attrs} - keyField={"key"} - dense={true} - initialRowsPerPage={5} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - - ) - } + const toolbar = return ( - <> - + + - - {APP_BAR_TITLE} - - { - toggleColorMode() - }} - color="inherit" - title={ - theme.palette.mode === "dark" - ? "Switch to light mode" - : "Switch to dark mode" - } - > - {theme.palette.mode === "dark" ? ( - - ) : ( - - )} - - { - action.updateStudySummaries("Success to reload") - }} - color="inherit" - title="Reload studies" - > - - - { - openCreateStudyDialog() - }} - color="inherit" - title="Create new study" - > - - - + + + + { + setStudyFilterText(s) + }} + delay={500} + textFieldProps={{ + fullWidth: true, + id: "search-study", + variant: "outlined", + placeholder: "Search study", + sx: { maxWidth: 500 }, + InputProps: { + startAdornment: ( + + + + + + ), + }, + }} + /> + {sortBySelect} + + + + + + + + + {filteredStudies.map((study) => ( + + + + + {study.study_id}. {study.study_name} + + + {"Direction: " + + study.directions + .map((d) => d.toString().toUpperCase()) + .join(", ")} + + + + + + { + openRenameStudyDialog(study.study_id, study.study_name) + }} + > + + + { + openDeleteStudyDialog(study.study_id) + }} + > + + + + + ))} + - - - - - - Announcement - - - {`Please go to `} - - our experimental new UI page - - {" and share your thoughts with us via "} - - the GitHub Discussion's post - - {"."} - - - - - - - { - setStudyFilterText(s) - }} - delay={500} - textFieldProps={{ - fullWidth: true, - id: "search-study", - variant: "outlined", - placeholder: "Search study", - InputProps: { - startAdornment: ( - - - - - - ), - }, - }} - /> - - - - - - columns={columns} - rows={studies} - keyField={"study_id"} - collapseBody={collapseBody} - initialRowsPerPage={10} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - defaultFilter={studyFilter} - /> - - + {renderCreateStudyDialog()} {renderDeleteStudyDialog()} - + {renderRenameStudyDialog()} +
) } diff --git a/optuna_dashboard/ts/components/StudyListBeta.tsx b/optuna_dashboard/ts/components/StudyListBeta.tsx deleted file mode 100644 index 9ed503d4..00000000 --- a/optuna_dashboard/ts/components/StudyListBeta.tsx +++ /dev/null @@ -1,255 +0,0 @@ -import React, { FC, useEffect, useState } from "react" -import { useRecoilValue } from "recoil" -import { Link } from "react-router-dom" -import { - Typography, - Container, - Card, - CardActionArea, - Box, - Button, - IconButton, - MenuItem, - useTheme, - InputAdornment, - SvgIcon, - CardContent, - TextField, - CardActions, -} from "@mui/material" -import MuiLink from "@mui/material/Link" -import { Delete, Refresh, Search } from "@mui/icons-material" -import SortIcon from "@mui/icons-material/Sort" -import HomeIcon from "@mui/icons-material/Home" -import AddBoxIcon from "@mui/icons-material/AddBox" -import CompareIcon from "@mui/icons-material/Compare" -import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline" - -import { actionCreator } from "../action" -import { DebouncedInputTextField } from "./Debounce" -import { studySummariesState } from "../state" -import { styled } from "@mui/system" -import { AppDrawer } from "./AppDrawer" -import { useCreateStudyDialog } from "./CreateStudyDialog" -import { useDeleteStudyDialog } from "./DeleteStudyDialog" -import { useRenameStudyDialog } from "./RenameStudyDialog" - -export const StudyListBeta: FC<{ - toggleColorMode: () => void -}> = ({ toggleColorMode }) => { - const theme = useTheme() - const action = actionCreator() - - const [studyFilterText, setStudyFilterText] = React.useState("") - const studyFilter = (row: StudySummary) => { - const keywords = studyFilterText.split(" ") - return !keywords.every((k) => { - if (k === "") { - return true - } - return row.study_name.indexOf(k) >= 0 - }) - } - const studies = useRecoilValue(studySummariesState) - const [openCreateStudyDialog, renderCreateStudyDialog] = - useCreateStudyDialog() - const [openDeleteStudyDialog, renderDeleteStudyDialog] = - useDeleteStudyDialog() - const [openRenameStudyDialog, renderRenameStudyDialog] = - useRenameStudyDialog(studies) - const [sortBy, setSortBy] = useState<"id-asc" | "id-desc">("id-asc") - - let filteredStudies = studies.filter((s) => !studyFilter(s)) - if (sortBy === "id-desc") { - filteredStudies = filteredStudies.reverse() - } - - useEffect(() => { - action.updateStudySummaries() - }, []) - - const Select = styled(TextField)(({ theme }) => ({ - "& .MuiInputBase-input": { - // vertical padding + font size from searchIcon - paddingLeft: `calc(1em + ${theme.spacing(4)})`, - }, - })) - const sortBySelect = ( - - - - - - - ) - - const toolbar = - - return ( - - - - - - - {`Thank you for testing the new UI! We would appreciate it if you could send us the feedback via `} - - this post - - {" on GitHub Discussions."} - - - - - - - { - setStudyFilterText(s) - }} - delay={500} - textFieldProps={{ - fullWidth: true, - id: "search-study", - variant: "outlined", - placeholder: "Search study", - sx: { maxWidth: 500 }, - InputProps: { - startAdornment: ( - - - - - - ), - }, - }} - /> - {sortBySelect} - - - - - - - - - {filteredStudies.map((study) => ( - - - - - {study.study_id}. {study.study_name} - - - {"Direction: " + - study.directions - .map((d) => d.toString().toUpperCase()) - .join(", ")} - - - - - - { - openRenameStudyDialog(study.study_id, study.study_name) - }} - > - - - { - openDeleteStudyDialog(study.study_id) - }} - > - - - - - ))} - - - - {renderCreateStudyDialog()} - {renderDeleteStudyDialog()} - {renderRenameStudyDialog()} - - ) -} diff --git a/optuna_dashboard/ts/components/TrialTable.tsx b/optuna_dashboard/ts/components/TrialTable.tsx index 32206e8a..ed930c9c 100644 --- a/optuna_dashboard/ts/components/TrialTable.tsx +++ b/optuna_dashboard/ts/components/TrialTable.tsx @@ -1,28 +1,16 @@ -import React, { createRef, FC, FormEvent, MouseEvent } from "react" -import { - Typography, - Grid, - Box, - Button, - IconButton, - Stack, - TextField, -} from "@mui/material" +import React, { FC } from "react" +import { IconButton } from "@mui/material" import LinkIcon from "@mui/icons-material/Link" import { DataGridColumn, DataGrid } from "./DataGrid" import { Link } from "react-router-dom" -import { actionCreator } from "../action" - export const TrialTable: FC<{ studyDetail: StudyDetail | null - isBeta: boolean initialRowsPerPage?: number -}> = ({ studyDetail, isBeta, initialRowsPerPage }) => { +}> = ({ studyDetail, initialRowsPerPage }) => { const trials: Trial[] = studyDetail !== null ? studyDetail.trials : [] const objectiveNames: string[] = studyDetail?.objective_names || [] - const action = actionCreator() const columns: DataGridColumn[] = [ { field: "number", label: "Number", sortable: true, padding: "none" }, @@ -103,48 +91,6 @@ export const TrialTable: FC<{ })) columns.push(...objectiveColumns) } - if (!isBeta) { - columns.push({ - field: "datetime_start", - label: "Duration(ms)", - toCellValue: (i) => { - const startMs = trials[i].datetime_start?.getTime() - const completeMs = trials[i].datetime_complete?.getTime() - if (startMs !== undefined && completeMs !== undefined) { - return (completeMs - startMs).toString() - } - return null - }, - sortable: true, - less: (firstEl, secondEl): number => { - const firstStartMs = firstEl.datetime_start?.getTime() - const firstCompleteMs = firstEl.datetime_complete?.getTime() - const firstDurationMs = - firstStartMs !== undefined && firstCompleteMs !== undefined - ? firstCompleteMs - firstStartMs - : undefined - const secondStartMs = secondEl.datetime_start?.getTime() - const secondCompleteMs = secondEl.datetime_complete?.getTime() - const secondDurationMs = - secondStartMs !== undefined && secondCompleteMs !== undefined - ? secondCompleteMs - secondStartMs - : undefined - - if (firstDurationMs === secondDurationMs) { - return 0 - } else if ( - firstDurationMs !== undefined && - secondDurationMs !== undefined - ) { - return firstDurationMs < secondDurationMs ? 1 : -1 - } else if (firstDurationMs !== undefined) { - return -1 - } else { - return 1 - } - }, - }) - } if ( studyDetail?.union_search_space.length === studyDetail?.intersection_search_space.length @@ -220,175 +166,24 @@ export const TrialTable: FC<{ }, }) }) - if (isBeta) { - columns.push({ - field: "trial_id", - label: "Detail", - toCellValue: (i) => ( - - - - ), - }) - } - - const collapseIntermediateValueColumns: DataGridColumn[] = - [ - { field: "step", label: "Step", sortable: true }, - { - field: "value", - label: "Value", - sortable: true, - less: (firstEl, secondEl): number => { - const firstVal = firstEl.value - const secondVal = secondEl.value - if (firstVal === secondVal) { - return 0 - } - if (firstVal === "nan") { - return -1 - } else if (secondVal === "nan") { - return 1 - } - if (firstVal === "-inf" || secondVal === "inf") { - return 1 - } else if (secondVal === "-inf" || firstVal === "inf") { - return -1 - } - return firstVal < secondVal ? 1 : -1 - }, - }, - ] - const collapseAttrColumns: DataGridColumn[] = [ - { field: "key", label: "Key", sortable: true }, - { field: "value", label: "Value", sortable: true }, - ] - - const collapseBody = (index: number) => { - const objectiveFormRefs = studyDetail?.directions.map((d) => - createRef() - ) - const handleSubmit = (e: FormEvent): void => { - if (objectiveFormRefs === undefined) { - return - } - if (studyDetail === null) { - return - } - - e.preventDefault() - const studyId = studyDetail.id - const trialId = trials[index].trial_id - const objectiveValues = objectiveFormRefs.map((ref) => - ref.current ? Number(ref.current.value) : NaN - ) - if (objectiveValues.includes(NaN)) { - return - } - - action.makeTrialComplete(studyId, trialId, objectiveValues) - } - - const handleFailTrial = (e: MouseEvent): void => { - if (studyDetail === null) { - return - } - const studyId = studyDetail.id - const trialId = trials[index].trial_id - action.makeTrialFail(studyId, trialId) - } - - return ( - - - - - Intermediate values - - - columns={collapseIntermediateValueColumns} - rows={trials[index].intermediate_values} - keyField={"step"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - - - - Trial system attributes - - - columns={collapseAttrColumns} - rows={trials[index].system_attrs} - keyField={"key"} - dense={true} - rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} - /> - - - {trials[index].state === "Running" ? ( - - - - Trial tell - -
- - - {objectiveFormRefs !== undefined && - objectiveFormRefs.map((ref, i) => ( - - ))} - - - - - - - - -
-
-
- ) : null} -
- ) - } + columns.push({ + field: "trial_id", + label: "Detail", + toCellValue: (i) => ( + + + + ), + }) return ( @@ -396,7 +191,6 @@ export const TrialTable: FC<{ rows={trials} keyField={"trial_id"} dense={true} - collapseBody={isBeta ? undefined : collapseBody} initialRowsPerPage={initialRowsPerPage} /> ) diff --git a/optuna_dashboard/ts/trialFilter.ts b/optuna_dashboard/ts/trialFilter.ts index 2cb95793..96fb308f 100644 --- a/optuna_dashboard/ts/trialFilter.ts +++ b/optuna_dashboard/ts/trialFilter.ts @@ -95,7 +95,6 @@ export class Target { const filterTrials = ( study: StudyDetail | null, targets: Target[], - filterComplete: boolean, filterPruned: boolean ): Trial[] => { if (study === null) { @@ -105,9 +104,6 @@ const filterTrials = ( if (t.state !== "Complete" && t.state !== "Pruned") { return false } - if (t.state === "Complete" && filterComplete) { - return false - } if (t.state === "Pruned" && filterPruned) { return false } @@ -118,24 +114,20 @@ const filterTrials = ( export const useFilteredTrials = ( study: StudyDetail | null, targets: Target[], - filterComplete: boolean, filterPruned: boolean ): Trial[] => useMemo(() => { - return filterTrials(study, targets, filterComplete, filterPruned) - }, [study?.trials, targets, filterComplete, filterPruned]) + return filterTrials(study, targets, filterPruned) + }, [study?.trials, targets, filterPruned]) export const useFilteredTrialsFromStudies = ( studies: StudyDetail[], targets: Target[], - filterComplete: boolean, filterPruned: boolean ): Trial[][] => useMemo(() => { - return studies.map((s) => - filterTrials(s, targets, filterComplete, filterPruned) - ) - }, [studies, targets, filterComplete, filterPruned]) + return studies.map((s) => filterTrials(s, targets, filterPruned)) + }, [studies, targets, filterPruned]) export const useObjectiveTargets = ( study: StudyDetail | null diff --git a/typescript_tests/TrialTable.test.tsx b/typescript_tests/TrialTable.test.tsx deleted file mode 100644 index 81e1178a..00000000 --- a/typescript_tests/TrialTable.test.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import React from "react" -global.URL.createObjectURL = jest.fn() - -import { SnackbarProvider } from "notistack" -import { RecoilRoot } from "recoil" -import { cleanup, render, within, fireEvent } from "@testing-library/react" -import { TrialTable } from "../optuna_dashboard/ts/components/TrialTable" - -afterEach(cleanup) - -const dummyDistribution: FloatDistribution = { - type: "FloatDistribution", - low: 0, - high: 10, - step: 1, - log: false, -} -const trials: Trial[] = [ - { - trial_id: 1, - study_id: 0, - number: 0, - state: "Complete" as TrialState, - values: [-1], - intermediate_values: [], - datetime_start: new Date("2021-06-15T00:00:00"), - datetime_complete: new Date("2021-06-15T00:00:01"), - params: [ - { - name: "x", - param_internal_value: 1, - param_external_value: "1", - param_external_type: "float", - distribution: dummyDistribution, - }, - { - name: "y", - param_internal_value: 2, - param_external_value: "2", - param_external_type: "float", - distribution: dummyDistribution, - }, - ], - fixed_params: [], - user_attrs: [], - system_attrs: [], - note: { - body: "", - version: 0, - }, - artifacts: [], - }, - { - trial_id: 2, - study_id: 0, - number: 1, - state: "Fail" as TrialState, - values: [-2], - intermediate_values: [], - datetime_start: new Date("2021-06-15T00:00:01"), - datetime_complete: new Date("2021-06-15T00:00:03"), - params: [ - { - name: "x", - param_internal_value: 1, - param_external_value: "1", - param_external_type: "float", - distribution: dummyDistribution, - }, - { - name: "y", - param_internal_value: 2, - param_external_value: "2", - param_external_type: "float", - distribution: dummyDistribution, - }, - ], - fixed_params: [], - user_attrs: [], - system_attrs: [], - note: { - body: "", - version: 0, - }, - artifacts: [], - }, -] - -const study_direction: StudyDirection = "minimize" as StudyDirection - -const studyDetail: StudyDetail = { - id: 1, - name: "study_0", - directions: [study_direction], - datetime_start: new Date("2021-06-15T00:00:00"), - best_trials: [trials[1]], - trials: trials, - intersection_search_space: [ - { - name: "x", - distribution: dummyDistribution, - }, - { - name: "y", - distribution: dummyDistribution, - }, - ], - union_search_space: [ - { - name: "x", - distribution: dummyDistribution, - }, - { - name: "y", - distribution: dummyDistribution, - }, - ], - union_user_attrs: [ - { key: "foo", sortable: false }, - { key: "bar", sortable: false }, - ], - has_intermediate_values: false, - note: { - version: 0, - body: "", - }, -} - -it("Sort TrialTable by trial number", () => { - const { getAllByRole, getByText } = render( - - - - - - ) - const rows = getAllByRole("row") - - expect(within(rows[1]).getByText("0")).toBeTruthy() - expect(within(rows[3]).getAllByText("1")[0]).toBeTruthy() - - fireEvent.click(getByText("Number")) - - const rows_updated = getAllByRole("row") - expect(within(rows_updated[1]).getAllByText("1")[0]).toBeTruthy() - expect(within(rows_updated[3]).getByText("0")).toBeTruthy() -}) - -it("Sort TrialTable by value", () => { - const { getAllByRole, getByText } = render( - - - - - - ) - fireEvent.click(getByText("Value")) - const rows = getAllByRole("row") - expect(within(rows[1]).getByText("-2")).toBeTruthy() - expect(within(rows[3]).getByText("-1")).toBeTruthy() - - fireEvent.click(getByText("Value")) - const rows_updated = getAllByRole("row") - expect(within(rows_updated[1]).getByText("-1")).toBeTruthy() - expect(within(rows_updated[3]).getByText("-2")).toBeTruthy() -}) - -it("Sort TrialTable by duration", () => { - const { getAllByRole, getByText } = render( - - - - - - ) - fireEvent.click(getByText("Duration(ms)")) - const rows = getAllByRole("row") - expect(within(rows[1]).getByText("1000")).toBeTruthy() - expect(within(rows[3]).getByText("2000")).toBeTruthy() - - fireEvent.click(getByText("Duration(ms)")) - const rows_updated = getAllByRole("row") - expect(within(rows_updated[1]).getByText("2000")).toBeTruthy() - expect(within(rows_updated[3]).getByText("1000")).toBeTruthy() -}) - -it("Sort TrialTable by state", () => { - const { getAllByRole, getByText } = render( - - - - - - ) - fireEvent.click(getByText("State")) - const rows = getAllByRole("row") - expect(within(rows[1]).getByText("Complete")).toBeTruthy() - expect(within(rows[3]).getByText("Fail")).toBeTruthy() - - fireEvent.click(getByText("State")) - const rows_updated = getAllByRole("row") - expect(within(rows_updated[1]).getByText("Fail")).toBeTruthy() - expect(within(rows_updated[3]).getByText("Complete")).toBeTruthy() -}) - -it("Filter trials by state", () => { - const { queryAllByText } = render( - - - - - - ) - expect(queryAllByText("Fail").length).toBe(1) - - // Click 'Complete' state - const completedRows = queryAllByText("Complete") - expect(completedRows.length).toBe(1) - fireEvent.click(completedRows[0]) - - expect(queryAllByText("Fail").length).toBe(0) -}) diff --git a/visual_regression_test.py b/visual_regression_test.py index 50e52703..77a795c3 100644 --- a/visual_regression_test.py +++ b/visual_regression_test.py @@ -182,10 +182,10 @@ def create_dummy_storage() -> optuna.storages.InMemoryStorage: async def contains_study_name(page: Page, study_name: str) -> bool: - h4_elements = await page.querySelectorAll("h4") - for element in h4_elements: - title = await page.evaluate("(element) => element.textContent", element) - if title == study_name: + typography_elements = await page.querySelectorAll("div.MuiTypography-root") + for element in typography_elements: + title = await page.evaluate("(element) => element.innerText", element) + if study_name in title: return True return False @@ -205,6 +205,7 @@ async def take_screenshots(storage: optuna.storages.BaseStorage) -> list[str]: summaries = get_all_study_summaries(storage) study_ids = {s._study_id: s.study_name for s in summaries} for study_id, study_name in study_ids.items(): + # TODO(c-bata): Check "Analysis" tab. await page.goto(f"http://{args.host}:{args.port}/dashboard/studies/{study_id}") time.sleep(args.sleep)