From 656fdeba1422681e7847fec290df53342fd2fea3 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Wed, 8 May 2024 21:13:22 +0900 Subject: [PATCH 1/8] Add TableArtifactViewer --- .../components/Artifact/ArtifactCardMedia.tsx | 10 +++- .../Artifact/TableArtifactViewer.tsx | 58 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx diff --git a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx index 7ea83290..61935dba 100644 --- a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx +++ b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx @@ -2,6 +2,7 @@ import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" import { Box, CardMedia } from "@mui/material" import React, { FC } from "react" import { Artifact } from "ts/types/optuna" +import { TableArtifactViewer, isTableArtifact } from "./TableArtifactViewer" import { ThreejsArtifactViewer, isThreejsArtifact, @@ -13,7 +14,14 @@ export const ArtifactCardMedia: FC<{ urlPath: string height: string }> = ({ artifact, urlPath, height }) => { - if (isThreejsArtifact(artifact)) { + if (isTableArtifact(artifact)) { + return ( + + ) + } else if (isThreejsArtifact(artifact)) { return ( { + return ( + artifact.filename.endsWith(".csv") || artifact.filename.endsWith(".jsonl") + ) +} + +interface TableArtifactViewerProps { + src: string + filetype: string | undefined +} + +type Data = { + [key: string]: any +} + +export const TableArtifactViewer: React.FC = ( + props +) => { + const [data, setData] = useState([]) + const handleFileChange = async () => { + const loadedData = await loadCSV(props) + setData(loadedData) + } + handleFileChange() + console.log(data) + + const columns = React.useMemo(() => { + const keys = data[0] ? Object.keys(data[0]) : [] + console.log(keys) + return keys.map((key) => ({ + accessorKey: key, + header: key, + })) + }, [data]) + + return +} + +const loadCSV = (props: TableArtifactViewerProps): any => { + return new Promise((resolve, reject) => { + Papa.parse(props.src, { + header: true, + download: true, + complete: (results: any) => { + resolve(results?.data) + }, + error: () => { + reject(new Error("csv parse err")) + }, + }) + }) +} From 2fee6c0f5e4df3f31671b803ae58578b103436af Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Wed, 8 May 2024 21:37:38 +0900 Subject: [PATCH 2/8] Disable filtering --- .../ts/components/Artifact/TableArtifactViewer.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index 896dea8a..de6e5476 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -32,12 +32,14 @@ export const TableArtifactViewer: React.FC = ( const columns = React.useMemo(() => { const keys = data[0] ? Object.keys(data[0]) : [] - console.log(keys) return keys.map((key) => ({ - accessorKey: key, header: key, + accessorKey: key, + enableSorting: true, + enableColumnFilter: false, })) }, [data]) + console.log(columns) return } From 077fa831f2f0df10476e90c813b16e146551de83 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 12 May 2024 13:56:50 +0900 Subject: [PATCH 3/8] Reduce multiple calls --- .../components/Artifact/TableArtifactViewer.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index de6e5476..744585e4 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -1,5 +1,5 @@ import Papa from "papaparse" -import React, { useState } from "react" +import React, { useState, useEffect } from "react" import { DataGrid } from "../DataGrid" import { Artifact } from "ts/types/optuna" @@ -23,12 +23,14 @@ export const TableArtifactViewer: React.FC = ( props ) => { const [data, setData] = useState([]) - const handleFileChange = async () => { - const loadedData = await loadCSV(props) - setData(loadedData) - } - handleFileChange() - console.log(data) + + useEffect(() => { + const handleFileChange = async () => { + const loadedData = await loadCSV(props) + setData(loadedData) + } + handleFileChange() + }, [props]) const columns = React.useMemo(() => { const keys = data[0] ? Object.keys(data[0]) : [] @@ -39,7 +41,6 @@ export const TableArtifactViewer: React.FC = ( enableColumnFilter: false, })) }, [data]) - console.log(columns) return } From 804c2d54a3c918643ad0220bf8db5e9bd9aeeed3 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 12 May 2024 14:12:03 +0900 Subject: [PATCH 4/8] Add modal to open csv file --- .../Artifact/StudyArtifactCards.tsx | 17 +++++ .../Artifact/TableArtifactViewer.tsx | 66 +++++++++++++++++-- 2 files changed, 79 insertions(+), 4 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx b/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx index fc749b5e..9fd12cf4 100644 --- a/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx +++ b/optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx @@ -24,6 +24,7 @@ import { StudyDetail } from "ts/types/optuna" import { actionCreator } from "../../action" import { ArtifactCardMedia } from "./ArtifactCardMedia" import { useDeleteStudyArtifactDialog } from "./DeleteArtifactDialog" +import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer" import { isThreejsArtifact, useThreejsArtifactModal, @@ -35,6 +36,8 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => { useDeleteStudyArtifactDialog() const [openThreejsArtifactModal, renderThreejsArtifactModal] = useThreejsArtifactModal() + const [openTableArtifactModal, renderTableArtifactModal] = + useTableArtifactModal() const width = "200px" const height = "150px" @@ -96,6 +99,19 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => { ) : null} + {isTableArtifact(artifact) ? ( + { + openTableArtifactModal(urlPath, artifact) + }} + > + + + ) : null} = ({ study }) => { {renderDeleteArtifactDialog()} {renderThreejsArtifactModal()} + {renderTableArtifactModal()} ) } diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index 744585e4..75037457 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -1,13 +1,14 @@ +import ClearIcon from "@mui/icons-material/Clear" +import { Box, Modal, useTheme } from "@mui/material" +import IconButton from "@mui/material/IconButton" import Papa from "papaparse" -import React, { useState, useEffect } from "react" +import React, { useState, useEffect, ReactNode } from "react" import { DataGrid } from "../DataGrid" import { Artifact } from "ts/types/optuna" export const isTableArtifact = (artifact: Artifact): boolean => { - return ( - artifact.filename.endsWith(".csv") || artifact.filename.endsWith(".jsonl") - ) + return artifact.filename.endsWith(".csv") } interface TableArtifactViewerProps { @@ -45,6 +46,63 @@ export const TableArtifactViewer: React.FC = ( return } +export const useTableArtifactModal = (): [ + (path: string, artifact: Artifact) => void, + () => ReactNode, +] => { + const [open, setOpen] = useState(false) + const [target, setTarget] = useState<[string, Artifact | null]>(["", null]) + const theme = useTheme() + + const openModal = (artifactUrlPath: string, artifact: Artifact) => { + setTarget([artifactUrlPath, artifact]) + setOpen(true) + } + + const renderDeleteStudyDialog = () => { + return ( + { + setOpen(false) + setTarget(["", null]) + }} + > + + { + setOpen(false) + setTarget(["", null]) + }} + > + + + + + + ) + } + return [openModal, renderDeleteStudyDialog] +} + const loadCSV = (props: TableArtifactViewerProps): any => { return new Promise((resolve, reject) => { Papa.parse(props.src, { From 2114f961f7bb319171b40ecef241af1dc6b08027 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 12 May 2024 14:18:36 +0900 Subject: [PATCH 5/8] Fix type error --- .../ts/components/Artifact/TableArtifactViewer.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index 75037457..f1f03c96 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -17,7 +17,7 @@ interface TableArtifactViewerProps { } type Data = { - [key: string]: any + [key: string]: string | number } export const TableArtifactViewer: React.FC = ( @@ -103,12 +103,12 @@ export const useTableArtifactModal = (): [ return [openModal, renderDeleteStudyDialog] } -const loadCSV = (props: TableArtifactViewerProps): any => { +const loadCSV = (props: TableArtifactViewerProps): Promise => { return new Promise((resolve, reject) => { Papa.parse(props.src, { header: true, download: true, - complete: (results: any) => { + complete: (results: Papa.ParseResult) => { resolve(results?.data) }, error: () => { From a38092497414daf454a6f0f9f114d841400285d7 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 12 May 2024 14:24:56 +0900 Subject: [PATCH 6/8] Install papaparse --- optuna_dashboard/package-lock.json | 19 ++++++++++++++++--- optuna_dashboard/package.json | 2 ++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/optuna_dashboard/package-lock.json b/optuna_dashboard/package-lock.json index e4d44204..e7ee060d 100644 --- a/optuna_dashboard/package-lock.json +++ b/optuna_dashboard/package-lock.json @@ -19,10 +19,12 @@ "@tanstack/react-query": "^5.18.1", "@tanstack/react-table": "^8.16.0", "@tanstack/react-virtual": "^3.1.2", + "@types/papaparse": "^5.3.14", "@types/three": "^0.160.0", "axios": "^1.6.7", "elkjs": "^0.9.1", "notistack": "^3.0.1", + "papaparse": "^5.4.1", "plotly.js-dist-min": "^2.28.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -3496,7 +3498,6 @@ "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -3506,6 +3507,14 @@ "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==" }, + "node_modules/@types/papaparse": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz", + "integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -6939,6 +6948,11 @@ "node": ">=6" } }, + "node_modules/papaparse": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.4.1.tgz", + "integrity": "sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8523,8 +8537,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", diff --git a/optuna_dashboard/package.json b/optuna_dashboard/package.json index b81887c1..c0a3d271 100644 --- a/optuna_dashboard/package.json +++ b/optuna_dashboard/package.json @@ -24,10 +24,12 @@ "@tanstack/react-query": "^5.18.1", "@tanstack/react-table": "^8.16.0", "@tanstack/react-virtual": "^3.1.2", + "@types/papaparse": "^5.3.14", "@types/three": "^0.160.0", "axios": "^1.6.7", "elkjs": "^0.9.1", "notistack": "^3.0.1", + "papaparse": "^5.4.1", "plotly.js-dist-min": "^2.28.0", "react": "^18.2.0", "react-dom": "^18.2.0", From 2ace90eb096709285a3d0f7c95de9c229a0e5354 Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Thu, 30 May 2024 09:55:04 +0900 Subject: [PATCH 7/8] Make modal scrollable --- .../ts/components/Artifact/ArtifactCardMedia.tsx | 10 +--------- .../components/Artifact/TableArtifactViewer.tsx | 6 +++++- optuna_dashboard/ts/components/DataGrid.tsx | 16 +++++++++------- 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx index 61935dba..7ea83290 100644 --- a/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx +++ b/optuna_dashboard/ts/components/Artifact/ArtifactCardMedia.tsx @@ -2,7 +2,6 @@ import InsertDriveFileIcon from "@mui/icons-material/InsertDriveFile" import { Box, CardMedia } from "@mui/material" import React, { FC } from "react" import { Artifact } from "ts/types/optuna" -import { TableArtifactViewer, isTableArtifact } from "./TableArtifactViewer" import { ThreejsArtifactViewer, isThreejsArtifact, @@ -14,14 +13,7 @@ export const ArtifactCardMedia: FC<{ urlPath: string height: string }> = ({ artifact, urlPath, height }) => { - if (isTableArtifact(artifact)) { - return ( - - ) - } else if (isThreejsArtifact(artifact)) { + if (isThreejsArtifact(artifact)) { return ( = ( })) }, [data]) - return + return } export const useTableArtifactModal = (): [ @@ -77,6 +77,10 @@ export const useTableArtifactModal = (): [ transform: "translate(-50%, -50%)", bgcolor: "background.paper", borderRadius: "15px", + width: "80%", + maxHeight: "80%", + overflowY: "auto", + p: 2, }} > ({ 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: 50, + pageSize: + initialRowsPerPage && rowsPerPageOptions.includes(initialRowsPerPage) + ? initialRowsPerPage + : 50, }) const table = useReactTable({ @@ -236,12 +243,7 @@ function DataGrid({ Date: Thu, 6 Jun 2024 11:23:16 +0900 Subject: [PATCH 8/8] Add error handling --- .../ts/components/Artifact/TableArtifactViewer.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index e810536f..c0338259 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -1,6 +1,7 @@ import ClearIcon from "@mui/icons-material/Clear" import { Box, Modal, useTheme } from "@mui/material" import IconButton from "@mui/material/IconButton" +import { useSnackbar } from "notistack" import Papa from "papaparse" import React, { useState, useEffect, ReactNode } from "react" import { DataGrid } from "../DataGrid" @@ -24,11 +25,18 @@ export const TableArtifactViewer: React.FC = ( props ) => { const [data, setData] = useState([]) + const { enqueueSnackbar } = useSnackbar() useEffect(() => { const handleFileChange = async () => { - const loadedData = await loadCSV(props) - setData(loadedData) + try { + const loadedData = await loadCSV(props) + setData(loadedData) + } catch (error: unknown) { + enqueueSnackbar("Failed to load the csv file.", { + variant: "error", + }) + } } handleFileChange() }, [props])