diff --git a/.github/workflows/typescript-tests.yml b/.github/workflows/typescript-tests.yml index 01c99470..26c98501 100644 --- a/.github/workflows/typescript-tests.yml +++ b/.github/workflows/typescript-tests.yml @@ -9,7 +9,14 @@ on: - '**.tsx' - 'optuna_dashboard/package.json' - 'optuna_dashboard/package-lock.json' + - 'vscode/package.json' + - 'vscode/package-lock.json' + - 'tslib/react/package.json' + - 'tslib/react/package-lock.json' + - 'tslib/storage/package.json' + - 'tslib/storage/package-lock.json' - 'optuna_dashboard/tsconfig.json' + - 'optuna_dashboard/tsconfig.pkg.json' - 'optuna_dashboard/webpack.config.js' jobs: lint: @@ -112,6 +119,11 @@ jobs: npm run build:dev npm run build:prd + - name: Build optuna_dashboard package (for Jupyter Lab extension) + working-directory: optuna_dashboard + run: | + npm run build:pkg + test-tslib: name: Run tests runs-on: ubuntu-latest diff --git a/optuna_dashboard/.gitignore b/optuna_dashboard/.gitignore new file mode 100644 index 00000000..c8beb626 --- /dev/null +++ b/optuna_dashboard/.gitignore @@ -0,0 +1,2 @@ +pkg +types diff --git a/optuna_dashboard/package-lock.json b/optuna_dashboard/package-lock.json index d039bfe3..0513b6c2 100644 --- a/optuna_dashboard/package-lock.json +++ b/optuna_dashboard/package-lock.json @@ -1,11 +1,11 @@ { - "name": "optuna-dashboard", + "name": "@optuna/optuna-dashboard", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "optuna-dashboard", + "name": "@optuna/optuna-dashboard", "version": "0.0.1", "license": "MIT", "dependencies": { @@ -19,7 +19,7 @@ "@react-three/drei": "^9.96.4", "@react-three/fiber": "^8.15.15", "@tanstack/react-query": "^5.18.1", - "@tanstack/react-table": "^8.16.0", + "@tanstack/react-table": "file:../tslib/react/node_modules/@tanstack/react-table", "@tanstack/react-virtual": "^3.1.2", "@types/papaparse": "^5.3.14", "@types/three": "^0.160.0", @@ -14278,22 +14278,8 @@ } }, "node_modules/@tanstack/react-table": { - "version": "8.17.3", - "license": "MIT", - "dependencies": { - "@tanstack/table-core": "8.17.3" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } + "resolved": "../tslib/react/node_modules/@tanstack/react-table", + "link": true }, "node_modules/@tanstack/react-virtual": { "version": "3.5.1", @@ -14310,17 +14296,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@tanstack/table-core": { - "version": "8.17.3", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, "node_modules/@tanstack/virtual-core": { "version": "3.5.1", "license": "MIT", diff --git a/optuna_dashboard/package.json b/optuna_dashboard/package.json index 12dadc4e..6953ee7b 100644 --- a/optuna_dashboard/package.json +++ b/optuna_dashboard/package.json @@ -1,14 +1,16 @@ { - "name": "optuna-dashboard", + "name": "@optuna/optuna-dashboard", "private": true, "version": "0.0.1", "description": "Dashboard for Optuna", - "main": "index.js", + "main": "pkg/pkg_index.js", + "types": "types/pkg_index.d.ts", "scripts": { "watch": "NODE_ENV=development TYPESCRIPT_LOADER=esbuild-loader webpack --watch", "build": "webpack", "build:dev": "NODE_ENV=development TYPESCRIPT_LOADER=esbuild-loader webpack", "build:prd": "NODE_ENV=production webpack", + "build:pkg": "tsc -d --project tsconfig.pkg.json", "type-check": "tsc --noEmit" }, "author": "Masashi Shibata", @@ -24,7 +26,7 @@ "@react-three/drei": "^9.96.4", "@react-three/fiber": "^8.15.15", "@tanstack/react-query": "^5.18.1", - "@tanstack/react-table": "^8.16.0", + "@tanstack/react-table": "file:../tslib/react/node_modules/@tanstack/react-table", "@tanstack/react-virtual": "^3.1.2", "@types/papaparse": "^5.3.14", "@types/three": "^0.160.0", diff --git a/optuna_dashboard/ts/components/App.tsx b/optuna_dashboard/ts/components/App.tsx index 1204ae8f..c915f7c7 100644 --- a/optuna_dashboard/ts/components/App.tsx +++ b/optuna_dashboard/ts/components/App.tsx @@ -13,14 +13,10 @@ import { BrowserRouter as Router, Route, Routes } from "react-router-dom" import { RecoilRoot } from "recoil" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { APIClientProvider } from "../apiClientProvider" -import { AxiosClient } from "../axiosClient" import { CompareStudies } from "./CompareStudies" import { StudyDetail } from "./StudyDetail" import { StudyList } from "./StudyList" -const axiosAPIClient = new AxiosClient() - const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -54,102 +50,100 @@ export const App: FC = () => { } return ( - - - - - - - - - - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - - } - /> - } - /> - - - - - - - - + + + + + + + + + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + + } + /> + } + /> + + + + + + + ) } diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index bb346fe4..6fc01f02 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -1,10 +1,10 @@ import ClearIcon from "@mui/icons-material/Clear" import { Box, Modal, useTheme } from "@mui/material" import IconButton from "@mui/material/IconButton" +import { DataGrid } from "@optuna/react" import { useSnackbar } from "notistack" import Papa from "papaparse" import React, { useState, useEffect, ReactNode } from "react" -import { DataGrid } from "../DataGrid" import { Artifact } from "ts/types/optuna" diff --git a/optuna_dashboard/ts/components/CompareStudies.tsx b/optuna_dashboard/ts/components/CompareStudies.tsx index 8d376240..49f62b97 100644 --- a/optuna_dashboard/ts/components/CompareStudies.tsx +++ b/optuna_dashboard/ts/components/CompareStudies.tsx @@ -13,12 +13,12 @@ import { import Chip from "@mui/material/Chip" import Divider from "@mui/material/Divider" import FormControlLabel from "@mui/material/FormControlLabel" +import Grid from "@mui/material/Grid" import List from "@mui/material/List" import ListItem from "@mui/material/ListItem" import ListItemButton from "@mui/material/ListItemButton" import ListItemText from "@mui/material/ListItemText" import ListSubheader from "@mui/material/ListSubheader" -import Grid2 from "@mui/material/Unstable_Grid2" import * as Optuna from "@optuna/types" import { useSnackbar } from "notistack" import React, { FC, useEffect, useMemo, useState } from "react" @@ -336,21 +336,21 @@ const StudiesGraph: FC<{ studies: StudySummary[] }> = ({ studies }) => { ) : null} - + {showStudyDetails !== null && showStudyDetails.length > 0 && showStudyDetails.every((s) => s) ? showStudyDetails[0].directions.map((d, i) => ( - + - + )) : null} - + ) } diff --git a/optuna_dashboard/ts/components/DataGrid.tsx b/optuna_dashboard/ts/components/DataGrid.tsx deleted file mode 100644 index 607f580c..00000000 --- a/optuna_dashboard/ts/components/DataGrid.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import CheckBoxIcon from "@mui/icons-material/CheckBox" -import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank" -import FilterListIcon from "@mui/icons-material/FilterList" -import FirstPageIcon from "@mui/icons-material/FirstPage" -import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft" -import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight" -import LastPageIcon from "@mui/icons-material/LastPage" -import { - Box, - IconButton, - Menu, - MenuItem, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TablePagination, - TableRow, - TableSortLabel, - TextField, - useTheme, -} from "@mui/material" -import ListItemIcon from "@mui/material/ListItemIcon" -import Paper from "@mui/material/Paper" -import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions" -import { styled } from "@mui/system" -import React from "react" - -import { - ColumnDef, - ColumnFiltersState, - Header, - PaginationState, - SortingState, - flexRender, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table" - -const TableHeaderCellSpan = styled("span")({ - display: "inline-flex", -}) - -const HiddenSpan = styled("span")({ - border: 0, - clip: "rect(0 0 0 0)", - height: 1, - margin: -1, - overflow: "hidden", - padding: 0, - position: "absolute", - top: 20, - width: 1, -}) - -function FilterMenu({ - header, - filterChoices, -}: { - header: Header - filterChoices: string[] -}): React.ReactElement { - const [filterMenuAnchorEl, setFilterMenuAnchorEl] = - React.useState(null) - return ( - <> - { - setFilterMenuAnchorEl(e.currentTarget) - }} - > - - - { - setFilterMenuAnchorEl(null) - }} - > - {filterChoices.map((choice) => ( - { - const skippedValues = header.column.getFilterValue() as string[] - const isSkipped = skippedValues.includes(choice) - const newSkippedValues = isSkipped - ? skippedValues.filter((v) => v !== choice) - : skippedValues.concat(choice) - header.column.setFilterValue(newSkippedValues) - }} - > - - {header.column.getFilterValue() !== undefined ? ( - (header.column.getFilterValue() as string[]).includes( - choice - ) ? ( - - ) : ( - - ) - ) : null} - - {choice ?? "(missing value)"} - - ))} - - - ) -} - -function DataGrid({ - data, - columns, - initialRowsPerPage, -}: { - data: T[] - columns: ColumnDef[] - initialRowsPerPage?: number -}): React.ReactElement { - const [sorting, setSorting] = React.useState([]) - const [columnFilters, setColumnFilters] = React.useState( - [] - ) - const rowsPerPageOptions = [10, 50, 100, { label: "All", value: data.length }] - - const [pagination, setPagination] = React.useState({ - pageIndex: 0, - pageSize: - initialRowsPerPage && rowsPerPageOptions.includes(initialRowsPerPage) - ? initialRowsPerPage - : 50, - }) - - const table = useReactTable({ - data, - columns, - state: { - columnFilters, - sorting, - pagination, - }, - onColumnFiltersChange: setColumnFilters, - onSortingChange: setSorting, - onPaginationChange: setPagination, - getCoreRowModel: getCoreRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - getSortedRowModel: getSortedRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getFilteredRowModel: getFilteredRowModel(), - autoResetPageIndex: false, - }) - - return ( - - - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - if ( - header.column.getCanFilter() && - !header.column.getIsFiltered() - ) { - header.column.setFilterValue([]) - } - const order = header.column.getIsSorted() - const filterChoices = header.column.getCanFilter() - ? Array.from( - header.column.getFacetedUniqueValues().keys() - ).sort() - : null - return ( - - {header.isPlaceholder ? null : ( - - {header.column.getCanSort() ? ( - - {flexRender( - header.column.columnDef.header, - header.getContext() - )} - {order !== null ? ( - - {order === "desc" - ? "sorted descending" - : "sorted ascending"} - - ) : null} - - ) : ( - flexRender( - header.column.columnDef.header, - header.getContext() - ) - )} - {filterChoices !== null ? ( - - ) : null} - - )} - - ) - })} - - ))} - - - {table.getRowModel().rows.map((row) => { - return ( - - {row.getVisibleCells().map((cell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ) - })} - - ) - })} - -
-
- - { - table.setPageIndex(page) - }} - onRowsPerPageChange={(e) => { - const size = e.target.value ? Number(e.target.value) : 10 - table.setPageSize(size) - }} - ActionsComponent={TablePaginationActions} - /> - {table.getPageCount() > 2 ? ( - table.setPageIndex(page)} - maxPageNumber={table.getPageCount()} - /> - ) : null} - -
- ) -} - -const TablePaginationActions = ({ - count, - page, - rowsPerPage, - onPageChange, -}: TablePaginationActionsProps) => { - const theme = useTheme() - const handleFirstPageButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, 0) - } - - const handleBackButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, page - 1) - } - - const handleNextButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, page + 1) - } - - const handleLastPageButtonClick = ( - event: React.MouseEvent - ) => { - onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1)) - } - - return ( - - - {theme.direction === "rtl" ? : } - - - {theme.direction === "rtl" ? ( - - ) : ( - - )} - - = Math.ceil(count / rowsPerPage) - 1} - aria-label="next page" - > - {theme.direction === "rtl" ? ( - - ) : ( - - )} - - = Math.ceil(count / rowsPerPage) - 1} - aria-label="last page" - > - {theme.direction === "rtl" ? : } - - - ) -} - -const PaginationForm1: React.FC<{ - onPageNumberSubmit: (value: number) => void - maxPageNumber: number -}> = ({ onPageNumberSubmit, maxPageNumber }) => { - // This component is separated from DataGrid to prevent `DataGrid` from re-rendering the page, - // every time any letters are input. - const [specifiedPageText, setSpecifiedPageText] = React.useState("") - - const handleSubmitPageNumber = (event: React.FormEvent) => { - event.preventDefault() - const newPageNumber = parseInt(specifiedPageText, 10) - // Page is 0-indexed in `TablePagination`. - onPageNumberSubmit(newPageNumber - 1) - setSpecifiedPageText("") // reset the input field - } - - return ( -
- { - setSpecifiedPageText(e.target.value) - }} - /> - - ) -} - -export { DataGrid } diff --git a/optuna_dashboard/ts/components/Preferential/PreferentialAnalytics.tsx b/optuna_dashboard/ts/components/Preferential/PreferentialAnalytics.tsx index 554e6ea4..e0f1560e 100644 --- a/optuna_dashboard/ts/components/Preferential/PreferentialAnalytics.tsx +++ b/optuna_dashboard/ts/components/Preferential/PreferentialAnalytics.tsx @@ -6,12 +6,12 @@ import { Typography, useTheme, } from "@mui/material" -import Grid2 from "@mui/material/Unstable_Grid2" +import Grid from "@mui/material/Grid" +import { DataGrid } from "@optuna/react" import * as Optuna from "@optuna/types" import React, { FC } from "react" import { useStudyDetailValue, useStudySummaryValue } from "../../state" import { BestTrialsCard } from "../BestTrialsCard" -import { DataGrid } from "../DataGrid" import { Contour } from "../GraphContour" import { ColumnDef, createColumnHelper } from "@tanstack/react-table" @@ -41,16 +41,16 @@ export const PreferentialAnalytics: FC<{ studyId: number }> = ({ studyId }) => { component="div" sx={{ display: "flex", width: "100%", flexDirection: "column" }} > - - + + - - + + - - + + = ({ studyId }) => { - - + + ) } diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index 55a1332a..f49f2839 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -8,7 +8,7 @@ import { Typography, useTheme, } from "@mui/material" -import Grid2 from "@mui/material/Unstable_Grid2" +import Grid from "@mui/material/Grid" import React, { FC, useEffect, useMemo } from "react" import { Link, useParams } from "react-router-dom" import { useRecoilValue } from "recoil" @@ -146,19 +146,19 @@ export const StudyDetail: FC<{ > Empirical Distribution of the Objective Value - + {studyDetail !== null ? studyDetail.directions.map((d, i) => ( - + - + )) : null} - + ) } else if (page === "trialList") { diff --git a/optuna_dashboard/ts/components/StudyHistory.tsx b/optuna_dashboard/ts/components/StudyHistory.tsx index 6577f050..bcf161a4 100644 --- a/optuna_dashboard/ts/components/StudyHistory.tsx +++ b/optuna_dashboard/ts/components/StudyHistory.tsx @@ -8,7 +8,8 @@ import { useTheme, } from "@mui/material" import FormControlLabel from "@mui/material/FormControlLabel" -import Grid2 from "@mui/material/Unstable_Grid2" +import Grid from "@mui/material/Grid" +import { DataGrid } from "@optuna/react" import * as Optuna from "@optuna/types" import React, { FC, useState } from "react" import { useRecoilValue } from "recoil" @@ -21,7 +22,6 @@ import { import { artifactIsAvailable } from "../state" import { StudyArtifactCards } from "./Artifact/StudyArtifactCards" import { BestTrialsCard } from "./BestTrialsCard" -import { DataGrid } from "./DataGrid" import { GraphHistory } from "./GraphHistory" import { GraphHyperparameterImportance } from "./GraphHyperparameterImportances" import { GraphIntermediateValues } from "./GraphIntermediateValues" @@ -124,31 +124,31 @@ export const StudyHistory: FC<{ studyId: number }> = ({ studyId }) => { /> - - + + - - + + - + {studyDetail !== null && studyDetail.plotly_graph_objects.map((go) => ( - + - + ))} - + - - + + = ({ studyId }) => { - + {studyDetail !== null && studyDetail.directions.length === 1 && studyDetail.has_intermediate_values ? ( - + - + ) : null} - + {artifactEnabled && studyDetail !== null && ( - - + + = ({ studyId }) => { - - + + )} ) diff --git a/optuna_dashboard/ts/components/TrialTable.tsx b/optuna_dashboard/ts/components/TrialTable.tsx index 55fdc2e0..df62cf97 100644 --- a/optuna_dashboard/ts/components/TrialTable.tsx +++ b/optuna_dashboard/ts/components/TrialTable.tsx @@ -3,9 +3,10 @@ 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 { DataGrid } from "./DataGrid" import { ColumnDef, diff --git a/optuna_dashboard/ts/index.tsx b/optuna_dashboard/ts/index.tsx index fd63f378..9fe11370 100644 --- a/optuna_dashboard/ts/index.tsx +++ b/optuna_dashboard/ts/index.tsx @@ -1,9 +1,15 @@ import React from "react" import ReactDOM from "react-dom/client" +import { APIClientProvider } from "./apiClientProvider" +import { AxiosClient } from "./axiosClient" import { App } from "./components/App" +const axiosAPIClient = new AxiosClient() + ReactDOM.createRoot(document.getElementById("dashboard") as HTMLElement).render( - + + + ) diff --git a/optuna_dashboard/ts/pkg_index.tsx b/optuna_dashboard/ts/pkg_index.tsx new file mode 100644 index 00000000..c8e04aa6 --- /dev/null +++ b/optuna_dashboard/ts/pkg_index.tsx @@ -0,0 +1,5 @@ +import { APIClientProvider } from "./apiClientProvider" +import { AxiosClient } from "./axiosClient" +import { App } from "./components/App" + +export { AxiosClient, APIClientProvider, App } diff --git a/optuna_dashboard/tsconfig.pkg.json b/optuna_dashboard/tsconfig.pkg.json new file mode 100644 index 00000000..0e65b770 --- /dev/null +++ b/optuna_dashboard/tsconfig.pkg.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "./ts", + "outDir": "./pkg", + "declaration": true, + "declarationDir": "./types", + }, + "files": [ + "./ts/pkg_index.tsx", + ], + "include": [ + "./ts/types/**/*", + ], +} \ No newline at end of file diff --git a/standalone_app/src/components/StudyDetail.tsx b/standalone_app/src/components/StudyDetail.tsx index 7d137f5b..ff9b176c 100644 --- a/standalone_app/src/components/StudyDetail.tsx +++ b/standalone_app/src/components/StudyDetail.tsx @@ -12,7 +12,7 @@ import { Typography, useTheme, } from "@mui/material" -import Grid2 from "@mui/material/Unstable_Grid2" +import Grid from "@mui/material/Grid" import { PlotHistory, PlotImportance, @@ -176,8 +176,8 @@ export const StudyDetail: FC<{ - - + + {!!study && ( @@ -185,8 +185,8 @@ export const StudyDetail: FC<{ )} - - + + {!!study && ( @@ -198,8 +198,8 @@ export const StudyDetail: FC<{ )} - - + + {!!study && } diff --git a/tslib/react/src/components/DataGrid.tsx b/tslib/react/src/components/DataGrid.tsx index 4462768b..15d55bea 100644 --- a/tslib/react/src/components/DataGrid.tsx +++ b/tslib/react/src/components/DataGrid.tsx @@ -1,15 +1,19 @@ -import CheckBoxIcon from "@mui/icons-material/CheckBox" -import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank" -import FilterListIcon from "@mui/icons-material/FilterList" -import FirstPageIcon from "@mui/icons-material/FirstPage" -import KeyboardArrowLeft from "@mui/icons-material/KeyboardArrowLeft" -import KeyboardArrowRight from "@mui/icons-material/KeyboardArrowRight" -import LastPageIcon from "@mui/icons-material/LastPage" +import { + CheckBox as CheckBoxIcon, + CheckBoxOutlineBlank as CheckBoxOutlineBlankIcon, + FilterList as FilterListIcon, + FirstPage as FirstPageIcon, + KeyboardArrowLeft, + KeyboardArrowRight, + LastPage as LastPageIcon, +} from "@mui/icons-material" import { Box, IconButton, + ListItem as ListItemIcon, Menu, MenuItem, + Paper, Table, TableBody, TableCell, @@ -21,8 +25,6 @@ import { TextField, useTheme, } from "@mui/material" -import ListItemIcon from "@mui/material/ListItemIcon" -import Paper from "@mui/material/Paper" import { TablePaginationActionsProps } from "@mui/material/TablePagination/TablePaginationActions" import { styled } from "@mui/system" import React from "react"