Merge pull request #453 from c-bata/replace-with-experimental-ui

Replace Stable UI with Experimental UI.
This commit is contained in:
Masashi Shibata
2023-05-10 12:54:58 +09:00
committed by GitHub
20 changed files with 411 additions and 2051 deletions
+11 -21
View File
@@ -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 = () => {
<SnackbarProvider maxSnack={3}>
<Router>
<Switch>
<Route
path={URL_PREFIX + "/studies/:studyId/beta"}
children={
<StudyDetailBeta
toggleColorMode={toggleColorMode}
page={"history"}
/>
}
/>
<Route
path={URL_PREFIX + "/studies/:studyId/analytics"}
children={
<StudyDetailBeta
<StudyDetail
toggleColorMode={toggleColorMode}
page={"analytics"}
/>
@@ -74,7 +63,7 @@ export const App: FC = () => {
<Route
path={URL_PREFIX + "/studies/:studyId/trials"}
children={
<StudyDetailBeta
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialList"}
/>
@@ -83,7 +72,7 @@ export const App: FC = () => {
<Route
path={URL_PREFIX + "/studies/:studyId/trials"}
children={
<StudyDetailBeta
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialList"}
/>
@@ -92,7 +81,7 @@ export const App: FC = () => {
<Route
path={URL_PREFIX + "/studies/:studyId/trialTable"}
children={
<StudyDetailBeta
<StudyDetail
toggleColorMode={toggleColorMode}
page={"trialTable"}
/>
@@ -101,7 +90,7 @@ export const App: FC = () => {
<Route
path={URL_PREFIX + "/studies/:studyId/note"}
children={
<StudyDetailBeta
<StudyDetail
toggleColorMode={toggleColorMode}
page={"note"}
/>
@@ -109,7 +98,12 @@ export const App: FC = () => {
/>
<Route
path={URL_PREFIX + "/studies/:studyId"}
children={<StudyDetail toggleColorMode={toggleColorMode} />}
children={
<StudyDetail
toggleColorMode={toggleColorMode}
page={"history"}
/>
}
/>
<Route
path={URL_PREFIX + "/compare-studies"}
@@ -117,10 +111,6 @@ export const App: FC = () => {
<CompareStudies toggleColorMode={toggleColorMode} />
}
/>
<Route
path={URL_PREFIX + "/beta"}
children={<StudyListBeta toggleColorMode={toggleColorMode} />}
/>
<Route
path={URL_PREFIX + "/"}
children={<StudyList toggleColorMode={toggleColorMode} />}
+2 -18
View File
@@ -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<{
<ListItem key="History" disablePadding sx={styleListItem}>
<ListItemButton
component={Link}
to={`${URL_PREFIX}/studies/${studyId}/beta`}
to={`${URL_PREFIX}/studies/${studyId}`}
sx={styleListItemButton}
selected={page === "history"}
>
@@ -304,7 +303,7 @@ export const AppDrawer: FC<{
<ListItem key="Feedback" disablePadding sx={styleListItem}>
<ListItemButton
target="_blank"
href="https://github.com/optuna/optuna-dashboard/discussions/332"
href="https://github.com/optuna/optuna-dashboard/discussions/new/choose"
sx={styleListItemButton}
>
<ListItemIcon sx={styleListItemIcon}>
@@ -314,21 +313,6 @@ export const AppDrawer: FC<{
<OpenInNewIcon sx={styleSwitch} />
</ListItemButton>
</ListItem>
<ListItem key="BetaUI" disablePadding sx={styleListItem}>
<ListItemButton
component={Link}
to={URL_PREFIX}
sx={styleListItemButton}
>
<ListItemIcon sx={styleListItemIcon}>
<ClearIcon />
</ListItemIcon>
<ListItemText
primary="Switch to stable UI"
sx={styleListItemText}
/>
</ListItemButton>
</ListItem>
</List>
</Drawer>
<Box component="main" sx={{ flexGrow: 1 }}>
@@ -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<{
<>
<IconButton
component={Link}
to={URL_PREFIX + "/beta"}
to={URL_PREFIX + "/"}
sx={{ marginRight: theme.spacing(1) }}
color="inherit"
title="Return to the top page"
@@ -131,7 +131,7 @@ export const CompareStudies: FC<{
<List>
<ListSubheader sx={{ display: "flex", flexDirection: "row" }}>
<Typography sx={{ p: theme.spacing(1, 0) }}>
{studies.length} Studies
Compare studies with Shift+Click
</Typography>
<Box sx={{ flexGrow: 1 }} />
</ListSubheader>
@@ -315,8 +315,8 @@ const StudiesGraph: FC<{ studies: StudySummary[] }> = ({ studies }) => {
<CardContent>
<GraphHistoryMultiStudies
studies={showStudyDetails}
betaIncludePruned={includePruned}
betaLogScale={logScale}
includePruned={includePruned}
logScale={logScale}
/>
</CardContent>
</Card>
+3 -57
View File
@@ -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<string>) => {
setTarget(event.target.value)
}
useEffect(() => {
if (study != null) {
plotEdf(trials, selected, plotDomId, theme.palette.mode)
}
}, [trials, selected, theme.palette.mode])
return (
<Grid container direction="row">
<Grid
item
xs={3}
container
direction="column"
sx={{ paddingRight: theme.spacing(2) }}
>
<Typography
variant="h6"
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
>
EDF
</Typography>
{study !== null && study.directions.length !== 1 ? (
<FormControl component="fieldset">
<FormLabel component="legend">Objective:</FormLabel>
<Select
value={selected.identifier()}
onChange={handleObjectiveChange}
>
{targets.map((target, i) => (
<MenuItem value={target.identifier()} key={i}>
{target.toLabel(study?.objective_names)}
</MenuItem>
))}
</Select>
</FormControl>
) : null}
</Grid>
<Grid item xs={9}>
<Box id={plotDomId} sx={{ height: "450px" }} />
</Grid>
</Grid>
)
}
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,
+11 -130
View File
@@ -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<boolean>(false)
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(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<HTMLInputElement>) => {
setLogScale(!logScale)
}
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
setFilterCompleteTrial(!filterCompleteTrial)
}
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
setFilterPrunedTrial(!filterPrunedTrial)
}
return (
<Grid container direction="row">
<Grid
@@ -135,46 +112,6 @@ export const GraphHistory: FC<{
</Select>
</FormControl>
) : null}
{betaLogScale === undefined ? (
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
>
<FormLabel component="legend">Log y scale:</FormLabel>
<Switch
checked={logScale}
onChange={handleLogScaleChange}
value="enable"
/>
</FormControl>
) : null}
{betaIncludePruned === undefined ? (
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
>
<FormLabel component="legend">Filter state:</FormLabel>
<FormControlLabel
control={
<Checkbox
checked={!filterCompleteTrial}
onChange={handleFilterCompleteChange}
/>
}
label="Complete"
/>
<FormControlLabel
control={
<Checkbox
checked={!filterPrunedTrial}
disabled={!study?.has_intermediate_values}
onChange={handleFilterPrunedChange}
/>
}
label="Pruned"
/>
</FormControl>
) : null}
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
@@ -213,16 +150,13 @@ export const GraphHistory: FC<{
export const GraphHistoryMultiStudies: FC<{
studies: StudyDetail[]
betaLogScale?: boolean
betaIncludePruned?: boolean
}> = ({ 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<boolean>(false)
const [filterCompleteTrial, setFilterCompleteTrial] = useState<boolean>(false)
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(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<string>) => {
setTarget(event.target.value)
@@ -269,18 +202,6 @@ export const GraphHistoryMultiStudies: FC<{
}
}
const handleLogScaleChange = (e: ChangeEvent<HTMLInputElement>) => {
setLogScale(!logScale)
}
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
setFilterCompleteTrial(!filterCompleteTrial)
}
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
setFilterPrunedTrial(!filterPrunedTrial)
}
return (
<Grid container direction="row">
<Grid
@@ -314,46 +235,6 @@ export const GraphHistoryMultiStudies: FC<{
</Select>
</FormControl>
) : null}
{betaLogScale === undefined ? (
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
>
<FormLabel component="legend">Log y scale:</FormLabel>
<Switch
checked={logScale}
onChange={handleLogScaleChange}
value="enable"
/>
</FormControl>
) : null}
{betaIncludePruned === undefined ? (
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
>
<FormLabel component="legend">Filter state:</FormLabel>
<FormControlLabel
control={
<Checkbox
checked={!filterCompleteTrial}
onChange={handleFilterCompleteChange}
/>
}
label="Complete"
/>
<FormControlLabel
control={
<Checkbox
checked={!filterPrunedTrial}
disabled={!studies[0]?.has_intermediate_values}
onChange={handleFilterPrunedChange}
/>
}
label="Pruned"
/>
</FormControl>
) : null}
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
@@ -1,25 +1,13 @@
import * as plotly from "plotly.js-dist-min"
import React, { FC, useEffect, useState } from "react"
import {
Grid,
FormControl,
FormLabel,
MenuItem,
Select,
Typography,
SelectChangeEvent,
useTheme,
Box,
Card,
CardContent,
} from "@mui/material"
import React, { FC, useEffect } from "react"
import { Typography, useTheme, Box, Card, CardContent } from "@mui/material"
import { plotlyDarkTemplate } from "./PlotlyDarkMode"
import { actionCreator } from "../action"
import { useParamImportanceValue, useStudyDirections } from "../state"
const plotDomId = "graph-hyperparameter-importances"
export const GraphHyperparameterImportanceBeta: FC<{
export const GraphHyperparameterImportance: FC<{
studyId: number
study: StudyDetail | null
graphHeight: string
@@ -41,7 +29,7 @@ export const GraphHyperparameterImportanceBeta: FC<{
useEffect(() => {
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<number>(0)
const numCompletedTrials =
study?.trials.filter((t) => t.state === "Complete").length || 0
const handleObjectiveChange = (event: SelectChangeEvent<number>) => {
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 (
<Grid container direction="row">
<Grid
item
xs={3}
container
direction="column"
sx={{ paddingRight: theme.spacing(2) }}
>
<Typography
variant="h6"
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
>
Hyperparameter importance
</Typography>
{study !== null && study.directions.length !== 1 ? (
<FormControl component="fieldset">
<FormLabel component="legend">Objective ID:</FormLabel>
<Select value={objectiveId} onChange={handleObjectiveChange}>
{study.directions.map((d, i) => (
<MenuItem value={i} key={i}>
{i}
</MenuItem>
))}
</Select>
</FormControl>
) : null}
</Grid>
<Grid item xs={9}>
<Box id={plotDomId} sx={{ height: "450px" }} />
</Grid>
</Grid>
)
}
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} <extra></extra>`
)
const layout: Partial<plotly.Layout> = {
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<plotly.PlotData>[] = [
{
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)
}
@@ -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<boolean>(false)
const [filterPrunedTrial, setFilterPrunedTrial] = useState<boolean>(false)
useEffect(() => {
plotIntermediateValue(
trials,
theme.palette.mode,
filterCompleteTrial,
filterPrunedTrial,
false
)
}, [trials, theme.palette.mode, filterCompleteTrial, filterPrunedTrial])
const handleFilterCompleteChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setFilterCompleteTrial(!filterCompleteTrial)
}
const handleFilterPrunedChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault()
setFilterPrunedTrial(!filterPrunedTrial)
}
return (
<Grid container direction="row">
<Grid
item
xs={3}
container
direction="column"
sx={{ paddingRight: theme.spacing(2) }}
>
<Typography
variant="h6"
sx={{ margin: "1em 0", fontWeight: theme.typography.fontWeightBold }}
>
Intermediate values
</Typography>
<FormControl
component="fieldset"
sx={{ marginBottom: theme.spacing(2) }}
>
<FormLabel component="legend">Filter state:</FormLabel>
<FormControlLabel
control={
<Checkbox
checked={!filterCompleteTrial}
onChange={handleFilterCompleteChange}
/>
}
label="Complete"
/>
<FormControlLabel
control={
<Checkbox
checked={!filterPrunedTrial}
onChange={handleFilterPrunedChange}
/>
}
label="Pruned"
/>
</FormControl>
</Grid>
<Grid item xs={9}>
<Box id={plotDomId} sx={{ height: "450px" }} />
</Grid>
</Grid>
)
}
const plotIntermediateValue = (
trials: Trial[],
mode: string,
@@ -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)
@@ -47,7 +47,6 @@ export const GraphSlice: FC<{
selectedParamTarget !== null
? [selectedObjective, selectedParamTarget]
: [selectedObjective],
false,
false
)
@@ -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<GraphVisibility>(graphVisibilityState)
const [localGraphVisibility, setLocalGraphVisibility] =
useState<GraphVisibility>(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<HTMLInputElement>
) => {
setLocalGraphVisibility({
...localGraphVisibility,
[event.target.name]: event.target.checked,
})
}
const renderPreferenceDialog = () => {
return (
<Dialog onClose={handleClose} aria-labelledby="vis-pref" open={prefOpen}>
<MuiDialogTitle
sx={{
margin: 0,
padding: theme.spacing(2),
minWidth: 300,
}}
>
<Typography variant="h6">Preferences</Typography>
<IconButton
aria-label="close"
sx={{
position: "absolute",
right: theme.spacing(1),
top: theme.spacing(1),
color: theme.palette.grey[500],
}}
onClick={handleClose}
>
<CloseIcon />
</IconButton>
</MuiDialogTitle>
<MuiDialogContent dividers>
<FormLabel component="legend">Charts</FormLabel>
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.history}
onChange={handlePreferenceOnChange}
name="history"
/>
}
label="History"
/>
<FormControlLabel
disabled={studyDetail?.directions?.length === 1}
control={
<Checkbox
checked={localGraphVisibility.paretoFront}
onChange={handlePreferenceOnChange}
name="paretoFront"
/>
}
label="Pareto Front"
/>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.parallelCoordinate}
onChange={handlePreferenceOnChange}
name="parallelCoordinate"
/>
}
label="Parallel Coordinate"
/>
<FormControlLabel
disabled={
studyDetail !== null &&
(studyDetail.directions.length > 1 ||
!studyDetail.has_intermediate_values)
}
control={
<Checkbox
checked={localGraphVisibility.intermediateValues}
onChange={handlePreferenceOnChange}
name="intermediateValues"
/>
}
label="Intermediate Values"
/>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.edf}
onChange={handlePreferenceOnChange}
name="edf"
/>
}
label="EDF"
/>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.contour}
onChange={handlePreferenceOnChange}
name="contour"
/>
}
label="Contour"
/>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.importances}
onChange={handlePreferenceOnChange}
name="importances"
/>
}
label="Hyperparameter Importances"
/>
<FormControlLabel
control={
<Checkbox
checked={localGraphVisibility.slice}
onChange={handlePreferenceOnChange}
name="slice"
/>
}
label="Slice"
/>
</FormGroup>
</MuiDialogContent>
</Dialog>
)
}
return [setPrefOpen, renderPreferenceDialog]
}
@@ -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<number>(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 (
<Wrapper>
<IconWrapper>
<Cached />
</IconWrapper>
<Select
select
value={reloadInterval}
onChange={(e) => {
action.saveReloadInterval(e.target.value as unknown as number)
}}
>
<MenuItem value={-1}>stop</MenuItem>
<MenuItem value={5}>5s</MenuItem>
<MenuItem value={10}>10s</MenuItem>
<MenuItem value={30}>30s</MenuItem>
<MenuItem value={60}>60s</MenuItem>
</Select>
</Wrapper>
)
}
+152 -204
View File
@@ -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<StudyDetails>(studyDetailsState)
return studyDetails[studyId] || null
}
export const useURLVars = (): number => {
const { studyId } = useParams<ParamTypes>()
const useStudySummaryValue = (studyId: number): StudySummary | null => {
const studySummaries = useRecoilValue<StudySummary[]>(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<ParamTypes>()
const studyIdNumber = parseInt(studyId, 10)
const studyDetail = useStudyDetailValue(studyIdNumber)
const studySummary = useStudySummaryValue(studyIdNumber)
const directions = studyDetail?.directions || studySummary?.directions || null
const graphVisibility = useRecoilValue<GraphVisibility>(graphVisibilityState)
const studyId = useURLVars()
const studyDetail = useStudyDetailValue(studyId)
const reloadInterval = useRecoilValue<number>(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 (
<div>
{renderPreferenceDialog()}
<AppBar position="static">
<Container
sx={{
["@media (min-width: 1280px)"]: {
maxWidth: "100%",
},
}}
>
<Toolbar>
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
<Box sx={{ flexGrow: 1 }} />
<ReloadIntervalSelect />
<IconButton
onClick={() => {
toggleColorMode()
}}
color="inherit"
title={
theme.palette.mode === "dark"
? "Switch to light mode"
: "Switch to dark mode"
}
>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
<IconButton
color="inherit"
onClick={() => {
openPreferenceDialog(true)
}}
title="Open preference panel"
>
<Settings />
</IconButton>
<IconButton
aria-controls="menu-appbar"
aria-haspopup="true"
component={Link}
to={URL_PREFIX + "/"}
color="inherit"
title="Go to the top"
>
<Home />
</IconButton>
</Toolbar>
</Container>
</AppBar>
<Container
let content = null
if (page === "history") {
content = <StudyHistory studyId={studyId} />
} else if (page === "analytics") {
content = (
<Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}>
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
Hyperparameter Relationships
</Typography>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphSlice study={studyDetail} />
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphParallelCoordinate study={studyDetail} />
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Contour study={studyDetail} />
</CardContent>
</Card>
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
Empirical Distribution of the Objective Value
</Typography>
<Grid2 container spacing={2} sx={{ padding: theme.spacing(0, 2) }}>
{studyDetail !== null
? studyDetail.directions.map((d, i) => (
<Grid2 xs={6} key={i}>
<Card>
<CardContent>
<GraphEdf study={studyDetail} objectiveId={i} />
</CardContent>
</Card>
</Grid2>
))
: null}
</Grid2>
</Box>
)
} else if (page === "trialTable") {
content = (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<TrialTable studyDetail={studyDetail} initialRowsPerPage={50} />
</CardContent>
</Card>
)
} else if (page === "trialList") {
content = <TrialList studyDetail={studyDetail} />
} else if (page === "note" && studyDetail !== null) {
content = (
<Box
sx={{
["@media (min-width: 1280px)"]: {
maxWidth: "100%",
},
height: `calc(100vh - ${theme.spacing(8)})`,
display: "flex",
flexDirection: "column",
padding: theme.spacing(2),
}}
>
<div>
<Typography
variant="h4"
sx={{
margin: `${theme.spacing(4)} ${theme.spacing(2)}`,
fontWeight: theme.typography.fontWeightBold,
fontSize: "1.8rem",
...(theme.palette.mode === "dark" && {
color: theme.palette.primary.light,
}),
}}
>
{title}
</Typography>
{graphVisibility.history ? (
<Card
sx={{
margin: theme.spacing(2),
}}
>
<CardContent>
<GraphHistory study={studyDetail} />
</CardContent>
</Card>
) : null}
<Typography
variant="h5"
sx={{
fontWeight: theme.typography.fontWeightBold,
margin: theme.spacing(2, 0),
}}
>
Note
</Typography>
<StudyNote
studyId={studyId}
latestNote={studyDetail.note}
cardSx={{ flexGrow: 1 }}
/>
</Box>
)
}
{directions !== null &&
directions.length > 1 &&
graphVisibility.paretoFront ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphParetoFront study={studyDetail} />
</CardContent>
</Card>
) : null}
{graphVisibility.parallelCoordinate ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphParallelCoordinate study={studyDetail} />
</CardContent>
</Card>
) : null}
const toolbar = (
<>
<IconButton
component={Link}
to={URL_PREFIX + "/"}
sx={{ marginRight: theme.spacing(1) }}
color="inherit"
title="Return to the top page"
>
<HomeIcon />
</IconButton>
<ChevronRightIcon sx={{ marginRight: theme.spacing(1) }} />
<Typography
noWrap
component="div"
sx={{ fontWeight: theme.typography.fontWeightBold }}
>
{title}
</Typography>
</>
)
{studyDetail !== null &&
studyDetail.directions.length == 1 &&
studyDetail.has_intermediate_values &&
graphVisibility.intermediateValues ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphIntermediateValues trials={trials} />
</CardContent>
</Card>
) : null}
{graphVisibility.edf ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphEdf study={studyDetail} />
</CardContent>
</Card>
) : null}
{graphVisibility.contour ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Contour study={studyDetail} />
</CardContent>
</Card>
) : null}
{graphVisibility.importances ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphHyperparameterImportances
study={studyDetail}
studyId={studyIdNumber}
/>
</CardContent>
</Card>
) : null}
{studyDetail !== null && graphVisibility.slice ? (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphSlice study={studyDetail} />
</CardContent>
</Card>
) : null}
<Card sx={{ margin: theme.spacing(2) }}>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</Card>
{studyDetail !== null ? (
<StudyNote
studyId={studyIdNumber}
latestNote={studyDetail.note}
cardSx={{ margin: theme.spacing(2) }}
/>
) : null}
</div>
</Container>
</div>
return (
<Box sx={{ display: "flex" }}>
<AppDrawer
studyId={studyId}
page={page}
toggleColorMode={toggleColorMode}
toolbar={toolbar}
>
{content}
</AppDrawer>
</Box>
)
}
@@ -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<ParamTypes>()
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<number>(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 = <StudyHistory studyId={studyId} />
} else if (page === "analytics") {
content = (
<Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}>
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
Hyperparameter Relationships
</Typography>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphSlice study={studyDetail} />
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<GraphParallelCoordinate study={studyDetail} />
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Contour study={studyDetail} />
</CardContent>
</Card>
<Typography variant="h5" sx={{ margin: theme.spacing(2) }}>
Empirical Distribution of the Objective Value
</Typography>
<Grid2 container spacing={2} sx={{ padding: theme.spacing(0, 2) }}>
{studyDetail !== null
? studyDetail.directions.map((d, i) => (
<Grid2 xs={6} key={i}>
<Card>
<CardContent>
<GraphEdfBeta study={studyDetail} objectiveId={i} />
</CardContent>
</Card>
</Grid2>
))
: null}
</Grid2>
</Box>
)
} else if (page === "trialTable") {
content = (
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<TrialTable
studyDetail={studyDetail}
isBeta={true}
initialRowsPerPage={50}
/>
</CardContent>
</Card>
)
} else if (page === "trialList") {
content = <TrialList studyDetail={studyDetail} />
} else if (page === "note" && studyDetail !== null) {
content = (
<Box
sx={{
height: `calc(100vh - ${theme.spacing(8)})`,
display: "flex",
flexDirection: "column",
padding: theme.spacing(2),
}}
>
<Typography
variant="h5"
sx={{
fontWeight: theme.typography.fontWeightBold,
margin: theme.spacing(2, 0),
}}
>
Note
</Typography>
<StudyNote
studyId={studyId}
latestNote={studyDetail.note}
cardSx={{ flexGrow: 1 }}
/>
</Box>
)
}
const toolbar = (
<>
<IconButton
component={Link}
to={URL_PREFIX + "/beta"}
sx={{ marginRight: theme.spacing(1) }}
color="inherit"
title="Return to the top page"
>
<HomeIcon />
</IconButton>
<ChevronRightIcon sx={{ marginRight: theme.spacing(1) }} />
<Typography
noWrap
component="div"
sx={{ fontWeight: theme.typography.fontWeightBold }}
>
{title}
</Typography>
</>
)
return (
<Box sx={{ display: "flex" }}>
<AppDrawer
studyId={studyId}
page={page}
toggleColorMode={toggleColorMode}
toolbar={toolbar}
>
{content}
</AppDrawer>
</Box>
)
}
@@ -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 }) => {
<CardContent>
<GraphHistory
study={studyDetail}
betaIncludePruned={includePruned}
betaLogScale={logScale}
includePruned={includePruned}
logScale={logScale}
/>
</CardContent>
</Card>
@@ -106,7 +106,7 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => {
studyDetail.directions.length == 1 &&
studyDetail.has_intermediate_values ? (
<Grid2 xs={6}>
<GraphIntermediateValuesBeta
<GraphIntermediateValues
trials={trials}
includePruned={includePruned}
logScale={logScale}
@@ -114,7 +114,7 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => {
</Grid2>
) : null}
<Grid2 xs={6}>
<GraphHyperparameterImportanceBeta
<GraphHyperparameterImportance
studyId={studyId}
study={studyDetail}
graphHeight="450px"
+182 -219
View File
@@ -1,35 +1,43 @@
import React, { FC, useEffect, useMemo } from "react"
import React, { FC, useEffect, useState } from "react"
import { useRecoilValue } from "recoil"
import { Link } from "react-router-dom"
import {
AppBar,
Toolbar,
Typography,
Container,
Card,
Grid,
CardActionArea,
Box,
Button,
IconButton,
MenuItem,
useTheme,
InputAdornment,
SvgIcon,
CardContent,
TextField,
CardActions,
} from "@mui/material"
import { AddBox, Delete, Refresh, Search } from "@mui/icons-material"
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 { DataGrid, DataGridColumn } from "./DataGrid"
import { DebouncedInputTextField } from "./Debounce"
import { studySummariesState } from "../state"
import Brightness7Icon from "@mui/icons-material/Brightness7"
import Brightness4Icon from "@mui/icons-material/Brightness4"
import { useDeleteStudyDialog } from "./DeleteStudyDialog"
import { styled } from "@mui/system"
import { AppDrawer } from "./AppDrawer"
import { useCreateStudyDialog } from "./CreateStudyDialog"
import { useDeleteStudyDialog } from "./DeleteStudyDialog"
import { useRenameStudyDialog } from "./RenameStudyDialog"
export const StudyList: FC<{
toggleColorMode: () => void
}> = ({ toggleColorMode }) => {
const theme = useTheme()
const action = actionCreator()
const [studyFilterText, setStudyFilterText] = React.useState<string>("")
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<StudySummary[]>(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<StudySummary[]>(studySummariesState)
let filteredStudies = studies.filter((s) => !studyFilter(s))
if (sortBy === "id-desc") {
filteredStudies = filteredStudies.reverse()
}
useEffect(() => {
action.updateStudySummaries()
}, [])
const columns: DataGridColumn<StudySummary>[] = [
{
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) => (
<Link
to={`${URL_PREFIX}/studies/${studies[i].study_id}`}
style={{ color: linkColor }}
>
{studies[i].study_name}
</Link>
),
},
{
field: "directions",
label: "Direction",
sortable: false,
toCellValue: (i) => studies[i].directions.join(),
},
{
field: "study_name",
label: "",
sortable: false,
padding: "none",
toCellValue: (i) => (
<IconButton
aria-label="delete study"
size="small"
color="inherit"
onClick={() => {
openDeleteStudyDialog(studies[i].study_id)
}}
>
<Delete />
</IconButton>
),
},
]
}))
const sortBySelect = (
<Box
sx={{
position: "relative",
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(0, 2),
}}
>
<Box
sx={{
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<SortIcon />
</Box>
<Select
select
value={sortBy}
onChange={(e) => {
setSortBy(e.target.value as "id-asc" | "id-desc")
}}
>
<MenuItem value={"id-asc"}>Sort ascending</MenuItem>
<MenuItem value={"id-desc"}>Sort descending</MenuItem>
</Select>
</Box>
)
const collapseAttrColumns: DataGridColumn<Attribute>[] = [
{ field: "key", label: "Key", sortable: true },
{ field: "value", label: "Value", sortable: true },
]
const collapseBody = (index: number) => {
return (
<Grid container direction="row">
<Grid item xs={6}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Study user attributes
</Typography>
<DataGrid<Attribute>
columns={collapseAttrColumns}
rows={studies[index].user_attrs}
keyField={"key"}
dense={true}
initialRowsPerPage={5}
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
/>
</Box>
</Grid>
<Grid item xs={6}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Study system attributes
</Typography>
<DataGrid<Attribute>
columns={collapseAttrColumns}
rows={studies[index].system_attrs}
keyField={"key"}
dense={true}
initialRowsPerPage={5}
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
/>
</Box>
</Grid>
</Grid>
)
}
const toolbar = <HomeIcon sx={{ margin: theme.spacing(0, 1) }} />
return (
<>
<AppBar position="static">
<Box sx={{ display: "flex" }}>
<AppDrawer toggleColorMode={toggleColorMode} toolbar={toolbar}>
<Container
sx={{
["@media (min-width: 1280px)"]: {
@@ -158,120 +119,122 @@ export const StudyList: FC<{
},
}}
>
<Toolbar>
<Typography variant="h6">{APP_BAR_TITLE}</Typography>
<Box sx={{ flexGrow: 1 }} />
<IconButton
onClick={() => {
toggleColorMode()
}}
color="inherit"
title={
theme.palette.mode === "dark"
? "Switch to light mode"
: "Switch to dark mode"
}
>
{theme.palette.mode === "dark" ? (
<Brightness7Icon />
) : (
<Brightness4Icon />
)}
</IconButton>
<IconButton
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={() => {
action.updateStudySummaries("Success to reload")
}}
color="inherit"
title="Reload studies"
>
<Refresh />
</IconButton>
<IconButton
aria-controls="menu-appbar"
aria-haspopup="true"
onClick={(e) => {
openCreateStudyDialog()
}}
color="inherit"
title="Create new study"
>
<AddBox />
</IconButton>
</Toolbar>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Box sx={{ display: "flex" }}>
<DebouncedInputTextField
onChange={(s) => {
setStudyFilterText(s)
}}
delay={500}
textFieldProps={{
fullWidth: true,
id: "search-study",
variant: "outlined",
placeholder: "Search study",
sx: { maxWidth: 500 },
InputProps: {
startAdornment: (
<InputAdornment position="start">
<SvgIcon fontSize="small" color="action">
<Search />
</SvgIcon>
</InputAdornment>
),
},
}}
/>
{sortBySelect}
<Box sx={{ flexGrow: 1 }} />
<Button
variant="outlined"
startIcon={<Refresh />}
onClick={(e) => {
action.updateStudySummaries("Success to reload")
}}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Reload
</Button>
<Button
variant="outlined"
startIcon={<AddBoxIcon />}
onClick={(e) => {
openCreateStudyDialog()
}}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Create
</Button>
<Button
variant="outlined"
startIcon={<CompareIcon />}
component={Link}
to={`${URL_PREFIX}/compare-studies`}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Compare
</Button>
</Box>
</CardContent>
</Card>
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
{filteredStudies.map((study) => (
<Card
key={study.study_id}
sx={{ margin: theme.spacing(2), width: "500px" }}
>
<CardActionArea
component={Link}
to={`${URL_PREFIX}/studies/${study.study_id}`}
>
<CardContent>
<Typography variant="h5">
{study.study_id}. {study.study_name}
</Typography>
<Typography
variant="subtitle1"
color="text.secondary"
component="div"
>
{"Direction: " +
study.directions
.map((d) => d.toString().toUpperCase())
.join(", ")}
</Typography>
</CardContent>
</CardActionArea>
<CardActions disableSpacing sx={{ paddingTop: 0 }}>
<Box sx={{ flexGrow: 1 }} />
<IconButton
aria-label="rename study"
size="small"
color="inherit"
onClick={() => {
openRenameStudyDialog(study.study_id, study.study_name)
}}
>
<DriveFileRenameOutlineIcon />
</IconButton>
<IconButton
aria-label="delete study"
size="small"
color="inherit"
onClick={() => {
openDeleteStudyDialog(study.study_id)
}}
>
<Delete />
</IconButton>
</CardActions>
</Card>
))}
</Box>
</Container>
</AppBar>
<Container
sx={{
["@media (min-width: 1280px)"]: {
maxWidth: "100%",
},
}}
>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Typography
variant="h5"
style={{ paddingBottom: theme.spacing(1) }}
>
Announcement
</Typography>
<Typography>
{`Please go to `}
<Link to={`${URL_PREFIX}/beta`} style={{ color: linkColor }}>
our experimental new UI page
</Link>
{" and share your thoughts with us via "}
<Link to={`${URL_PREFIX}/beta`} style={{ color: linkColor }}>
the GitHub Discussion's post
</Link>
{"."}
</Typography>
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Box sx={{ maxWidth: 500 }}>
<DebouncedInputTextField
onChange={(s) => {
setStudyFilterText(s)
}}
delay={500}
textFieldProps={{
fullWidth: true,
id: "search-study",
variant: "outlined",
placeholder: "Search study",
InputProps: {
startAdornment: (
<InputAdornment position="start">
<SvgIcon fontSize="small" color="action">
<Search />
</SvgIcon>
</InputAdornment>
),
},
}}
/>
</Box>
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<DataGrid<StudySummary>
columns={columns}
rows={studies}
keyField={"study_id"}
collapseBody={collapseBody}
initialRowsPerPage={10}
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
defaultFilter={studyFilter}
/>
</Card>
</Container>
</AppDrawer>
{renderCreateStudyDialog()}
{renderDeleteStudyDialog()}
</>
{renderRenameStudyDialog()}
</Box>
)
}
@@ -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<string>("")
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<StudySummary[]>(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 = (
<Box
sx={{
position: "relative",
borderRadius: theme.shape.borderRadius,
margin: theme.spacing(0, 2),
}}
>
<Box
sx={{
padding: theme.spacing(0, 2),
height: "100%",
position: "absolute",
pointerEvents: "none",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<SortIcon />
</Box>
<Select
select
value={sortBy}
onChange={(e) => {
setSortBy(e.target.value as "id-asc" | "id-desc")
}}
>
<MenuItem value={"id-asc"}>Sort ascending</MenuItem>
<MenuItem value={"id-desc"}>Sort descending</MenuItem>
</Select>
</Box>
)
const toolbar = <HomeIcon sx={{ margin: theme.spacing(0, 1) }} />
return (
<Box sx={{ display: "flex" }}>
<AppDrawer toggleColorMode={toggleColorMode} toolbar={toolbar}>
<Container
sx={{
["@media (min-width: 1280px)"]: {
maxWidth: "100%",
},
}}
>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Typography>
{`Thank you for testing the new UI! We would appreciate it if you could send us the feedback via `}
<MuiLink
target="_blank"
href="https://github.com/optuna/optuna-dashboard/discussions/332"
>
this post
</MuiLink>
{" on GitHub Discussions."}
</Typography>
</CardContent>
</Card>
<Card sx={{ margin: theme.spacing(2) }}>
<CardContent>
<Box sx={{ display: "flex" }}>
<DebouncedInputTextField
onChange={(s) => {
setStudyFilterText(s)
}}
delay={500}
textFieldProps={{
fullWidth: true,
id: "search-study",
variant: "outlined",
placeholder: "Search study",
sx: { maxWidth: 500 },
InputProps: {
startAdornment: (
<InputAdornment position="start">
<SvgIcon fontSize="small" color="action">
<Search />
</SvgIcon>
</InputAdornment>
),
},
}}
/>
{sortBySelect}
<Box sx={{ flexGrow: 1 }} />
<Button
variant="outlined"
startIcon={<Refresh />}
onClick={(e) => {
action.updateStudySummaries("Success to reload")
}}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Reload
</Button>
<Button
variant="outlined"
startIcon={<AddBoxIcon />}
onClick={(e) => {
openCreateStudyDialog()
}}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Create
</Button>
<Button
variant="outlined"
startIcon={<CompareIcon />}
component={Link}
to={`${URL_PREFIX}/compare-studies`}
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
>
Compare
</Button>
</Box>
</CardContent>
</Card>
<Box sx={{ display: "flex", flexWrap: "wrap" }}>
{filteredStudies.map((study) => (
<Card
key={study.study_id}
sx={{ margin: theme.spacing(2), width: "500px" }}
>
<CardActionArea
component={Link}
to={`${URL_PREFIX}/studies/${study.study_id}/beta`}
>
<CardContent>
<Typography variant="h5">
{study.study_id}. {study.study_name}
</Typography>
<Typography
variant="subtitle1"
color="text.secondary"
component="div"
>
{"Direction: " +
study.directions
.map((d) => d.toString().toUpperCase())
.join(", ")}
</Typography>
</CardContent>
</CardActionArea>
<CardActions disableSpacing sx={{ paddingTop: 0 }}>
<Box sx={{ flexGrow: 1 }} />
<IconButton
aria-label="rename study"
size="small"
color="inherit"
onClick={() => {
openRenameStudyDialog(study.study_id, study.study_name)
}}
>
<DriveFileRenameOutlineIcon />
</IconButton>
<IconButton
aria-label="delete study"
size="small"
color="inherit"
onClick={() => {
openDeleteStudyDialog(study.study_id)
}}
>
<Delete />
</IconButton>
</CardActions>
</Card>
))}
</Box>
</Container>
</AppDrawer>
{renderCreateStudyDialog()}
{renderDeleteStudyDialog()}
{renderRenameStudyDialog()}
</Box>
)
}
+21 -227
View File
@@ -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<Trial>[] = [
{ 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) => (
<IconButton
component={Link}
to={
URL_PREFIX +
`/studies/${trials[i].study_id}/trials?numbers=${trials[i].number}`
}
color="inherit"
title="Go to the trial's detail page"
size="small"
>
<LinkIcon />
</IconButton>
),
})
}
const collapseIntermediateValueColumns: DataGridColumn<TrialIntermediateValue>[] =
[
{ 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<Attribute>[] = [
{ field: "key", label: "Key", sortable: true },
{ field: "value", label: "Value", sortable: true },
]
const collapseBody = (index: number) => {
const objectiveFormRefs = studyDetail?.directions.map((d) =>
createRef<HTMLInputElement>()
)
const handleSubmit = (e: FormEvent<HTMLFormElement>): 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<HTMLButtonElement>): void => {
if (studyDetail === null) {
return
}
const studyId = studyDetail.id
const trialId = trials[index].trial_id
action.makeTrialFail(studyId, trialId)
}
return (
<Grid container direction="row">
<Grid item xs={6}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Intermediate values
</Typography>
<DataGrid<TrialIntermediateValue>
columns={collapseIntermediateValueColumns}
rows={trials[index].intermediate_values}
keyField={"step"}
dense={true}
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
/>
</Box>
</Grid>
<Grid item xs={6}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Trial system attributes
</Typography>
<DataGrid<Attribute>
columns={collapseAttrColumns}
rows={trials[index].system_attrs}
keyField={"key"}
dense={true}
rowsPerPageOption={[5, 10, { label: "All", value: -1 }]}
/>
</Box>
</Grid>
{trials[index].state === "Running" ? (
<Grid item xs={12}>
<Box margin={1}>
<Typography variant="h6" gutterBottom component="div">
Trial tell
</Typography>
<form onSubmit={handleSubmit}>
<Box margin={1}>
<Stack direction="row" spacing={1}>
{objectiveFormRefs !== undefined &&
objectiveFormRefs.map((ref, i) => (
<TextField
required
id={`objective-${i}`}
key={`objective-${i}`}
label={
objectiveNames.length ===
studyDetail?.directions.length
? objectiveNames[i]
: `Objective ${i}`
}
inputProps={{
inputMode: "numeric",
pattern: "[-+]?[0-9]*.?[0-9]+([eE][-+]?[0-9]+)?",
title: "Please input a float number",
}}
inputRef={ref}
/>
))}
</Stack>
</Box>
<Box margin={1}>
<Stack direction="row" spacing={1}>
<Button variant="contained" type="submit">
Submit
</Button>
<Button
variant="outlined"
color="error"
onClick={handleFailTrial}
>
Fail Trial
</Button>
</Stack>
</Box>
</form>
</Box>
</Grid>
) : null}
</Grid>
)
}
columns.push({
field: "trial_id",
label: "Detail",
toCellValue: (i) => (
<IconButton
component={Link}
to={
URL_PREFIX +
`/studies/${trials[i].study_id}/trials?numbers=${trials[i].number}`
}
color="inherit"
title="Go to the trial's detail page"
size="small"
>
<LinkIcon />
</IconButton>
),
})
return (
<DataGrid<Trial>
@@ -396,7 +191,6 @@ export const TrialTable: FC<{
rows={trials}
keyField={"trial_id"}
dense={true}
collapseBody={isBeta ? undefined : collapseBody}
initialRowsPerPage={initialRowsPerPage}
/>
)
+4 -12
View File
@@ -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<Trial[]>(() => {
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<Trial[][]>(() => {
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
-222
View File
@@ -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(
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
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(
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
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(
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
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(
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
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(
<RecoilRoot>
<SnackbarProvider>
<TrialTable studyDetail={studyDetail} isBeta={false} />
</SnackbarProvider>
</RecoilRoot>
)
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)
})
+5 -4
View File
@@ -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)