From 5feff79296b4d381ba0db85abec545c6b9add23b Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Thu, 13 Jun 2024 13:07:07 +0900 Subject: [PATCH 1/4] Implement jsonl tables viewer --- .../Artifact/TableArtifactViewer.tsx | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index c0338259..9887a7c5 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -8,8 +8,12 @@ import { DataGrid } from "../DataGrid" import { Artifact } from "ts/types/optuna" +import axios from "axios" + export const isTableArtifact = (artifact: Artifact): boolean => { - return artifact.filename.endsWith(".csv") + return ( + artifact.filename.endsWith(".csv") || artifact.filename.endsWith(".jsonl") + ) } interface TableArtifactViewerProps { @@ -30,10 +34,12 @@ export const TableArtifactViewer: React.FC = ( useEffect(() => { const handleFileChange = async () => { try { - const loadedData = await loadCSV(props) + const loadedData = await loadData(props) + console.log("data") + console.log(loadedData) setData(loadedData) } catch (error: unknown) { - enqueueSnackbar("Failed to load the csv file.", { + enqueueSnackbar("Failed to load the file.", { variant: "error", }) } @@ -42,7 +48,7 @@ export const TableArtifactViewer: React.FC = ( }, [props]) const columns = React.useMemo(() => { - const keys = data[0] ? Object.keys(data[0]) : [] + const keys = data.length > 0 ? Object.keys(data[0]) : [] return keys.map((key) => ({ header: key, accessorKey: key, @@ -115,6 +121,16 @@ export const useTableArtifactModal = (): [ return [openModal, renderDeleteStudyDialog] } +const loadData = (props: TableArtifactViewerProps): Promise => { + if (props.filetype === "csv") { + return loadCSV(props) + } else if (props.filetype === "jsonl") { + return loadJsonl(props) + } else { + return Promise.reject(new Error("Unsupported file type")) + } +} + const loadCSV = (props: TableArtifactViewerProps): Promise => { return new Promise((resolve, reject) => { Papa.parse(props.src, { @@ -123,9 +139,32 @@ const loadCSV = (props: TableArtifactViewerProps): Promise => { complete: (results: Papa.ParseResult) => { resolve(results?.data) }, - error: () => { - reject(new Error("csv parse err")) + error: (error) => { + reject(new Error("CSV parse error: " + error)) }, }) }) } + +const loadJsonl = async (props: TableArtifactViewerProps): Promise => { + try { + const response = await axios.get(props.src, { responseType: "text" }) + const data = response.data + const lines = data.split("\n") + const jsonObjects = lines + .filter((line) => line.trim().length > 0) + .map((line) => { + try { + return JSON.parse(line) + } catch (e) { + console.error("JSON parse error on line:", line, e) + return null + } + }) + .filter(Boolean) as Data[] + + return jsonObjects + } catch (error) { + throw new Error("JSONL parse error: " + error) + } +} From 2ef9409b1ac280d4d3ff38fc4c0d134d78c4d69c Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 23 Jun 2024 15:43:04 +0900 Subject: [PATCH 2/4] Fix error handling --- .../Artifact/TableArtifactViewer.tsx | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index 9887a7c5..5018bd0f 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -22,7 +22,7 @@ interface TableArtifactViewerProps { } type Data = { - [key: string]: string | number + [key: string]: Object } export const TableArtifactViewer: React.FC = ( @@ -35,11 +35,9 @@ export const TableArtifactViewer: React.FC = ( const handleFileChange = async () => { try { const loadedData = await loadData(props) - console.log("data") - console.log(loadedData) setData(loadedData) } catch (error: unknown) { - enqueueSnackbar("Failed to load the file.", { + enqueueSnackbar("Failed to load the file. " + error, { variant: "error", }) } @@ -51,7 +49,8 @@ export const TableArtifactViewer: React.FC = ( const keys = data.length > 0 ? Object.keys(data[0]) : [] return keys.map((key) => ({ header: key, - accessorKey: key, + accessorFn: (info) => + typeof info[key] === "object" ? JSON.stringify(info[key]) : info[key], enableSorting: true, enableColumnFilter: false, })) @@ -139,32 +138,26 @@ const loadCSV = (props: TableArtifactViewerProps): Promise => { complete: (results: Papa.ParseResult) => { resolve(results?.data) }, - error: (error) => { - reject(new Error("CSV parse error: " + error)) + error: () => { + reject(new Error("CSV parse error")) }, }) }) } const loadJsonl = async (props: TableArtifactViewerProps): Promise => { + const response = await axios.get(props.src, { responseType: "text" }) + const data = response.data try { - const response = await axios.get(props.src, { responseType: "text" }) - const data = response.data - const lines = data.split("\n") - const jsonObjects = lines + const jsons = data + .split("\n") .filter((line) => line.trim().length > 0) .map((line) => { - try { - return JSON.parse(line) - } catch (e) { - console.error("JSON parse error on line:", line, e) - return null - } + return JSON.parse(line) }) .filter(Boolean) as Data[] - - return jsonObjects + return jsons } catch (error) { - throw new Error("JSONL parse error: " + error) + throw new Error("JSONL parse error") } } From 4e1006a4fd737785decb04dfc06e6718d2aed71f Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 23 Jun 2024 15:51:43 +0900 Subject: [PATCH 3/4] Fix type error --- .../ts/components/Artifact/TableArtifactViewer.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index 5018bd0f..c398c02b 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -22,7 +22,8 @@ interface TableArtifactViewerProps { } type Data = { - [key: string]: Object + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [key: string]: any } export const TableArtifactViewer: React.FC = ( @@ -49,7 +50,7 @@ export const TableArtifactViewer: React.FC = ( const keys = data.length > 0 ? Object.keys(data[0]) : [] return keys.map((key) => ({ header: key, - accessorFn: (info) => + accessorFn: (info: Data) => typeof info[key] === "object" ? JSON.stringify(info[key]) : info[key], enableSorting: true, enableColumnFilter: false, @@ -151,8 +152,8 @@ const loadJsonl = async (props: TableArtifactViewerProps): Promise => { try { const jsons = data .split("\n") - .filter((line) => line.trim().length > 0) - .map((line) => { + .filter((line: string) => line.trim().length > 0) + .map((line: string) => { return JSON.parse(line) }) .filter(Boolean) as Data[] From cc0ab0fc39b316e85c15e4eb31a0d53a356217ce Mon Sep 17 00:00:00 2001 From: keisuke-umezawa Date: Sun, 30 Jun 2024 13:00:49 +0900 Subject: [PATCH 4/4] Follow review comments --- .../ts/components/Artifact/TableArtifactViewer.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx index c398c02b..bb346fe4 100644 --- a/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx +++ b/optuna_dashboard/ts/components/Artifact/TableArtifactViewer.tsx @@ -38,7 +38,7 @@ export const TableArtifactViewer: React.FC = ( const loadedData = await loadData(props) setData(loadedData) } catch (error: unknown) { - enqueueSnackbar("Failed to load the file. " + error, { + enqueueSnackbar(`Failed to load the file. ${error}`, { variant: "error", }) } @@ -47,7 +47,13 @@ export const TableArtifactViewer: React.FC = ( }, [props]) const columns = React.useMemo(() => { - const keys = data.length > 0 ? Object.keys(data[0]) : [] + const unionSet: Set = new Set() + data.forEach((d) => { + Object.keys(d).forEach((key) => { + unionSet.add(key) + }) + }) + const keys = Array.from(unionSet) return keys.map((key) => ({ header: key, accessorFn: (info: Data) =>