mirror of
https://github.com/wassname/optuna-dashboard.git
synced 2026-09-10 12:23:22 +08:00
Merge pull request #943 from porink0424/followup/feature/tslib-trialtable
Followup/feature/tslib trialtable
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight"
|
||||
import DownloadIcon from "@mui/icons-material/Download"
|
||||
import HomeIcon from "@mui/icons-material/Home"
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
IconButton,
|
||||
@@ -13,6 +15,8 @@ import React, { FC, useEffect, useMemo } from "react"
|
||||
import { Link, useParams } from "react-router-dom"
|
||||
import { useRecoilValue } from "recoil"
|
||||
|
||||
import { TrialTable } from "@optuna/react"
|
||||
import * as Optuna from "@optuna/types"
|
||||
import { actionCreator } from "../action"
|
||||
import { useConstants } from "../constantsProvider"
|
||||
import {
|
||||
@@ -34,7 +38,6 @@ import { PreferentialHistory } from "./Preferential/PreferentialHistory"
|
||||
import { PreferentialTrials } from "./Preferential/PreferentialTrials"
|
||||
import { StudyHistory } from "./StudyHistory"
|
||||
import { TrialList } from "./TrialList"
|
||||
import { TrialTable } from "./TrialTable"
|
||||
|
||||
export const useURLVars = (): number => {
|
||||
const { studyId } = useParams<{ studyId: string }>()
|
||||
@@ -59,6 +62,19 @@ export const StudyDetail: FC<{
|
||||
const reloadInterval = useRecoilValue<number>(reloadIntervalState)
|
||||
const studyName = useStudyName(studyId)
|
||||
const isPreferential = useStudyIsPreferential(studyId)
|
||||
const study: Optuna.Study | null = studyDetail
|
||||
? {
|
||||
id: studyDetail.id,
|
||||
name: studyDetail.name,
|
||||
directions: studyDetail.directions,
|
||||
union_search_space: studyDetail.union_search_space,
|
||||
intersection_search_space: studyDetail.intersection_search_space,
|
||||
union_user_attrs: studyDetail.union_user_attrs,
|
||||
datetime_start: studyDetail.datetime_start,
|
||||
trials: studyDetail.trials,
|
||||
metric_names: studyDetail.metric_names,
|
||||
}
|
||||
: null
|
||||
|
||||
const title =
|
||||
studyName !== null ? `${studyName} (id=${studyId})` : `Study #${studyId}`
|
||||
@@ -166,7 +182,10 @@ export const StudyDetail: FC<{
|
||||
)
|
||||
} else if (page === "trialList") {
|
||||
content = <TrialList studyDetail={studyDetail} />
|
||||
} else if (page === "trialTable") {
|
||||
} else if (page === "trialTable" && study !== null) {
|
||||
const linkURL = (studyId: number, trialNumber: number) => {
|
||||
return url_prefix + `/studies/${studyId}/trials?numbers=${trialNumber}`
|
||||
}
|
||||
content = (
|
||||
<Box
|
||||
component="div"
|
||||
@@ -174,7 +193,16 @@ export const StudyDetail: FC<{
|
||||
>
|
||||
<Card sx={{ margin: theme.spacing(2) }}>
|
||||
<CardContent>
|
||||
<TrialTable studyDetail={studyDetail} />
|
||||
<TrialTable study={study} linkComponent={Link} linkURL={linkURL} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<DownloadIcon />}
|
||||
download
|
||||
href={`/csv/${studyDetail?.id}`}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Download CSV File
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import DownloadIcon from "@mui/icons-material/Download"
|
||||
import LinkIcon from "@mui/icons-material/Link"
|
||||
import { Button, IconButton, useTheme } from "@mui/material"
|
||||
import React, { FC } from "react"
|
||||
|
||||
import { DataGrid } from "@optuna/react"
|
||||
|
||||
import { Link } from "react-router-dom"
|
||||
import { StudyDetail, Trial } from "ts/types/optuna"
|
||||
|
||||
import {
|
||||
ColumnDef,
|
||||
FilterFn,
|
||||
Row,
|
||||
createColumnHelper,
|
||||
} from "@tanstack/react-table"
|
||||
import { useConstants } from "../constantsProvider"
|
||||
|
||||
const multiValueFilter: FilterFn<Trial> = <D extends object>(
|
||||
row: Row<D>,
|
||||
columnId: string,
|
||||
filterValue: string[]
|
||||
) => {
|
||||
const rowValue = row.getValue(columnId) as string
|
||||
return !filterValue.includes(rowValue)
|
||||
}
|
||||
|
||||
export const TrialTable: FC<{
|
||||
studyDetail: StudyDetail | null
|
||||
}> = ({ studyDetail }) => {
|
||||
const { url_prefix } = useConstants()
|
||||
|
||||
const theme = useTheme()
|
||||
const trials: Trial[] = studyDetail !== null ? studyDetail.trials : []
|
||||
const metricNames: string[] = studyDetail?.metric_names || []
|
||||
|
||||
const columnHelper = createColumnHelper<Trial>()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const columns: ColumnDef<Trial, any>[] = [
|
||||
columnHelper.accessor("number", {
|
||||
header: "Number",
|
||||
enableColumnFilter: false,
|
||||
}),
|
||||
columnHelper.accessor("state", {
|
||||
header: "State",
|
||||
enableSorting: false,
|
||||
enableColumnFilter: true,
|
||||
filterFn: multiValueFilter,
|
||||
}),
|
||||
]
|
||||
if (studyDetail === null || studyDetail.directions.length === 1) {
|
||||
columns.push(
|
||||
columnHelper.accessor("values", {
|
||||
header: "Value",
|
||||
enableSorting: true,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
})
|
||||
)
|
||||
} else {
|
||||
columns.push(
|
||||
...studyDetail.directions.map((s, objectiveId) =>
|
||||
columnHelper.accessor((row) => row["values"]?.[objectiveId], {
|
||||
id: `values_${objectiveId}`,
|
||||
header:
|
||||
metricNames.length === studyDetail?.directions.length
|
||||
? metricNames[objectiveId]
|
||||
: `Objective ${objectiveId}`,
|
||||
enableSorting: true,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
const isDynamicSpace =
|
||||
studyDetail?.union_search_space.length !==
|
||||
studyDetail?.intersection_search_space.length
|
||||
studyDetail?.union_search_space.forEach((s) => {
|
||||
const sortable = s.distribution.type !== "CategoricalDistribution"
|
||||
const filterChoices: (string | null)[] | undefined =
|
||||
s.distribution.type === "CategoricalDistribution"
|
||||
? s.distribution.choices.map((c) => c?.toString() ?? "null")
|
||||
: undefined
|
||||
const hasMissingValue = trials.some(
|
||||
(t) => !t.params.some((p) => p.name === s.name)
|
||||
)
|
||||
if (filterChoices !== undefined && isDynamicSpace && hasMissingValue) {
|
||||
filterChoices.push(null)
|
||||
}
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row["params"].find((p) => p.name === s.name)?.param_external_value ||
|
||||
null,
|
||||
{
|
||||
id: `params_${s.name}`,
|
||||
header: `Param ${s.name}`,
|
||||
enableSorting: sortable,
|
||||
sortUndefined: "last",
|
||||
enableColumnFilter: filterChoices !== undefined,
|
||||
filterFn: multiValueFilter,
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
studyDetail?.union_user_attrs.forEach((attr_spec) => {
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
(row) =>
|
||||
row["user_attrs"].find((a) => a.key === attr_spec.key)?.value || null,
|
||||
{
|
||||
id: `user_attrs_${attr_spec.key}`,
|
||||
header: `UserAttribute ${attr_spec.key}`,
|
||||
enableSorting: attr_spec.sortable,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
}
|
||||
)
|
||||
)
|
||||
})
|
||||
columns.push(
|
||||
columnHelper.accessor((row) => row, {
|
||||
header: "Detail",
|
||||
cell: (info) => (
|
||||
<IconButton
|
||||
component={Link}
|
||||
to={
|
||||
url_prefix +
|
||||
`/studies/${info.getValue().study_id}/trials?numbers=${
|
||||
info.getValue().number
|
||||
}`
|
||||
}
|
||||
color="inherit"
|
||||
title="Go to the trial's detail page"
|
||||
size="small"
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableColumnFilter: false,
|
||||
})
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<DataGrid data={trials} columns={columns} />
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<DownloadIcon />}
|
||||
download
|
||||
href={`/csv/${studyDetail?.id}`}
|
||||
sx={{ marginRight: theme.spacing(2), minWidth: "120px" }}
|
||||
>
|
||||
Download CSV File
|
||||
</Button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Link as LinkIcon } from "@mui/icons-material"
|
||||
import { IconButton } from "@mui/material"
|
||||
import { FC } from "react"
|
||||
|
||||
import * as Optuna from "@optuna/types"
|
||||
@@ -22,8 +24,12 @@ const multiValueFilter: FilterFn<Optuna.Trial> = <D extends object>(
|
||||
export const TrialTable: FC<{
|
||||
study: Optuna.Study
|
||||
initialRowsPerPage?: number
|
||||
}> = ({ study, initialRowsPerPage }) => {
|
||||
// biome-ignore lint/suspicious/noExplicitAny: Any react component.
|
||||
linkComponent?: React.ComponentType<any>
|
||||
linkURL?: (studyId: number, trialNumber: number) => string
|
||||
}> = ({ study, initialRowsPerPage, linkComponent, linkURL }) => {
|
||||
const trials: Optuna.Trial[] = study.trials
|
||||
const metricNames: string[] = study.metric_names || []
|
||||
|
||||
const columnHelper = createColumnHelper<Optuna.Trial>()
|
||||
// biome-ignore lint/suspicious/noExplicitAny: It is difficult to specify this type.
|
||||
@@ -53,7 +59,10 @@ export const TrialTable: FC<{
|
||||
...study.directions.map((_s, objectiveId) =>
|
||||
columnHelper.accessor((row) => row.values?.[objectiveId], {
|
||||
id: `values_${objectiveId}`,
|
||||
header: `Objective ${objectiveId}`,
|
||||
header:
|
||||
metricNames.length === study.directions.length
|
||||
? metricNames[objectiveId]
|
||||
: `Objective ${objectiveId}`,
|
||||
enableSorting: true,
|
||||
enableColumnFilter: false,
|
||||
sortUndefined: "last",
|
||||
@@ -61,9 +70,22 @@ export const TrialTable: FC<{
|
||||
)
|
||||
)
|
||||
}
|
||||
const isDynamicSpace =
|
||||
study.union_search_space.length !== study.intersection_search_space.length
|
||||
|
||||
if (study?.union_search_space != null) {
|
||||
if (study.union_search_space != null) {
|
||||
for (const s of study.union_search_space) {
|
||||
const sortable = s.distribution.type !== "CategoricalDistribution"
|
||||
const filterChoices: (string | null)[] | undefined =
|
||||
s.distribution.type === "CategoricalDistribution"
|
||||
? s.distribution.choices.map((c) => c?.toString() ?? "null")
|
||||
: undefined
|
||||
const hasMissingValue = trials.some(
|
||||
(t) => !t.params.some((p) => p.name === s.name)
|
||||
)
|
||||
if (filterChoices !== undefined && isDynamicSpace && hasMissingValue) {
|
||||
filterChoices.push(null)
|
||||
}
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
(row) => {
|
||||
@@ -77,9 +99,9 @@ export const TrialTable: FC<{
|
||||
{
|
||||
id: `params_${s.name}`,
|
||||
header: `Param ${s.name}`,
|
||||
enableSorting: true,
|
||||
enableSorting: sortable,
|
||||
sortUndefined: "last",
|
||||
enableColumnFilter: false,
|
||||
enableColumnFilter: filterChoices !== undefined,
|
||||
filterFn: multiValueFilter,
|
||||
}
|
||||
)
|
||||
@@ -87,7 +109,7 @@ export const TrialTable: FC<{
|
||||
}
|
||||
}
|
||||
|
||||
if (study?.union_search_space != null) {
|
||||
if (study.union_search_space != null) {
|
||||
for (const attr_spec of study.union_user_attrs) {
|
||||
columns.push(
|
||||
columnHelper.accessor(
|
||||
@@ -108,6 +130,26 @@ export const TrialTable: FC<{
|
||||
)
|
||||
}
|
||||
}
|
||||
if (linkComponent !== undefined && linkURL !== undefined) {
|
||||
columns.push(
|
||||
columnHelper.accessor((row) => row, {
|
||||
header: "Detail",
|
||||
cell: (info) => (
|
||||
<IconButton
|
||||
component={linkComponent}
|
||||
to={linkURL(info.getValue().study_id, info.getValue().number)}
|
||||
color="inherit"
|
||||
title="Go to the trial's detail page"
|
||||
size="small"
|
||||
>
|
||||
<LinkIcon />
|
||||
</IconButton>
|
||||
),
|
||||
enableSorting: false,
|
||||
enableColumnFilter: false,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<DataGrid
|
||||
|
||||
Reference in New Issue
Block a user