diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..26e44f99 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,3 @@ +[run] +concurrency = multiprocessing,thread +source = optuna_dashboard/ diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 00000000..311d606f --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,49 @@ +on: + push: + branches: ["main"] + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + publish-gh-pages: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install Rust toolchains + uses: dtolnay/rust-toolchain@stable + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: '18' + - name: Build standalone_app + env: + PUBLIC_PATH: /optuna-dashboard/public/ + run: make MODE="prd" standalone_app/public/bundle.js + - name: Create publish directory + run: | + mkdir gh-publish + cp -r standalone_app/public gh-publish/public + cp standalone_app/favicon.ico gh-publish/favicon.ico + cp standalone_app/github-pages.html gh-publish/index.html + - name: Upload artifact + uses: actions/upload-pages-artifact@v2 + with: + path: './gh-publish' + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 diff --git a/.github/workflows/python-coverage.yml b/.github/workflows/python-coverage.yml new file mode 100644 index 00000000..4bda6350 --- /dev/null +++ b/.github/workflows/python-coverage.yml @@ -0,0 +1,48 @@ +name: python coverage + +on: + push: + branches: + - master + pull_request: {} + +jobs: + coverage: + runs-on: ubuntu-latest + + # Not intended for forks. + if: github.repository == 'optuna/optuna-dashboard' + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + architecture: x64 + + - name: Install dependencies + run: | + python -m pip install --progress-bar off --upgrade pip setuptools + pip install --progress-bar off .[optional] + pip install --progress-bar off .[test] + pip install --progress-bar off "optuna>=3.0.0" + pip install --progress-bar off . + echo 'import coverage; coverage.process_startup()' > sitecustomize.py + - name: Tests + env: + PYTHONPATH: . # To invoke sitecutomize.py + COVERAGE_PROCESS_START: .coveragerc # https://coverage.readthedocs.io/en/6.4.1/subprocess.html + COVERAGE_COVERAGE: yes # https://github.com/nedbat/coveragepy/blob/65bf33fc03209ffb01bbbc0d900017614645ee7a/coverage/control.py#L255-L261 + run: | + coverage run --source=optuna_dashboard -m pytest python_tests + coverage combine + coverage xml + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: ./coverage.xml + fail_ci_if_error: true diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 612ddb59..8fee4e44 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -45,7 +45,8 @@ jobs: # python_tests requires optuna>=3.0.0 since it imports FloatDistribution run: | python -m pip install --progress-bar off --upgrade pip setuptools - pip install streamlit boto3 moto[s3] pytest + pip install --progress-bar off .[optional] + pip install --progress-bar off .[test] pip install --progress-bar off "optuna>=3.0.0" pip install --progress-bar off . - run: pytest python_tests @@ -61,7 +62,8 @@ jobs: - name: Install dependencies run: | python -m pip install --progress-bar off --upgrade pip setuptools - pip install streamlit boto3 moto[s3] pytest + pip install --progress-bar off .[optional] + pip install --progress-bar off .[test] pip install --progress-bar off . python -m pip install --progress-bar off --upgrade git+https://github.com/optuna/optuna.git - run: pytest python_tests diff --git a/.gitignore b/.gitignore index dde468fc..49c137e0 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,16 @@ docs/_generated/ rustlib/target/ rustlib/pkg/ +# Test +.coverage +.coverage.* +coverage.xml + # Others .envrc .idea/ .vscode/ .DS_Store tmp/ +examples/preferential-optimization/artifact/ + diff --git a/README.md b/README.md index 4b38f863..62edd6c8 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ Listening on http://localhost:8080/ Hit Ctrl-C to quit. ``` +VSCode Extension + Please check out [our documentation](https://optuna-dashboard.readthedocs.io/en/latest/getting-started.html) for more details. ## Using an official Docker image @@ -75,27 +77,26 @@ $ docker run -it --rm -p 8080:8080 ghcr.io/optuna/optuna-dashboard postgresql+ps +## Browser-only version (Experimental) + +Browser-only version + +We’ve developed the version that operates solely within your web browser, which internally uses SQLite3 Wasm and Rust. +There’s no need to install Python or any other dependencies. +Simply open the following URL in your browser, drag and drop your SQLite3 file onto the page, and you’re ready to view your Optuna studies! + +https://optuna.github.io/optuna-dashboard/ + +*Please note that only a subset of features is available. However, you can still check the optimization history, hyperparameter importances, and etc. in graphs and tables.* + ## VS Code Extension (Experimental) You can install the VS Code extension via [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=Optuna.optuna-dashboard#overview). -![vscode-extension](./docs/_static/vscode-extension.png) +VSCode Extension Please right-click the SQLite3 files (`*.db` or `*.sqlite3`) in the VS Code file explorer and select the "Open in Optuna Dashboard" command from the dropdown menu. - -## Features - -### Manage Studies - -You can create and delete studies from Dashboard. - -![optuna-dashboard-create-delete-study](https://user-images.githubusercontent.com/5564044/205545958-305f2354-c7cd-4687-be2f-9e46e7401838.gif) - -### Visualize with Interactive Graphs & Rich Trials Data Grid - -You can check the optimization history, hyperparameter importances, etc. in graphs and tables. - -![optuna-dashboard-realtime-graph](https://user-images.githubusercontent.com/5564044/205545965-278cd7f4-da7d-4e2e-ac31-6d81b106cada.gif) +This extension leverages the browser-only version of Optuna Dashboard, so the same limitations apply. ## Submitting patches diff --git a/docs/_static/browser-app.gif b/docs/_static/browser-app.gif new file mode 100644 index 00000000..a4f6c464 Binary files /dev/null and b/docs/_static/browser-app.gif differ diff --git a/docs/_static/optuna-dashboard.gif b/docs/_static/optuna-dashboard.gif new file mode 100644 index 00000000..aea24d5a Binary files /dev/null and b/docs/_static/optuna-dashboard.gif differ diff --git a/docs/getting-started.rst b/docs/getting-started.rst index e45b6f86..295dd55c 100644 --- a/docs/getting-started.rst +++ b/docs/getting-started.rst @@ -1,6 +1,13 @@ Getting Started =============== +.. figure:: _static/optuna-dashboard.gif + :alt: Optuna Dashboard + :align: center + :width: 800px + + Optuna Dashboard + Installation ------------ @@ -172,6 +179,42 @@ or $ uwsgi --http :8080 --workeers 4 --wsgi-file wsgi.py +Browser-only version (Experimental) +----------------------------------- + +.. figure:: _static/browser-app.gif + :alt: GIF animation for the browser-only version + :align: center + :width: 800px + + Browser-only version of Optuna Dashboard, powered by Wasm. + +We've developed the version that operates solely within your web browser. +There's no need to install Python or any other dependencies. +Simply open the following URL in your browser, drag and drop your SQLite3 file onto the page, and you're ready to view your Optuna studies! + +https://optuna.github.io/optuna-dashboard/ + +.. warning:: + + Currently, only a subset of features is available. However, you can still check the optimization history, hyperparameter importances, and etc. in graphs and tables. + +VS Code Extension (Experimental) +-------------------------------- + +You can install the VS Code extension via `Visual Studio Marketplace `_. + +.. figure:: _static/vscode-extension.png + :alt: Screenshot for the VS Code Extension + :align: center + :width: 800px + + VS Code Extension + +To use, right-click the SQLite3 files (``*.db`` or ``*.sqlite3``) in the file explorer and select the "Open in Optuna Dashboard" from the dropdown menu. +This extension leverages the browser-only version of Optuna Dashboard, so the same limitations apply. + + Google Colaboratory ------------------- @@ -201,14 +244,3 @@ you can use ``google.colab.output()`` function as follows: output.serve_kernel_port_as_window(port, path='/dashboard/') Then please open http://localhost:8081/dashboard to browse. - -VS Code (Experimental) ----------------------- - -You can install the VS Code extension via `Visual Studio Marketplace `_. - -.. image:: _static/vscode-extension.png - :alt: Screenshot for the VS Code Extension - :align: center - -Please right-click the SQLite3 files (`*.db` or `*.sqlite3`) in the file explorer and select the "Open in Optuna Dashboard" command from the dropdown menu. diff --git a/optuna_dashboard/ts/components/AppDrawer.tsx b/optuna_dashboard/ts/components/AppDrawer.tsx index cdf331bc..184ec606 100644 --- a/optuna_dashboard/ts/components/AppDrawer.tsx +++ b/optuna_dashboard/ts/components/AppDrawer.tsx @@ -233,21 +233,19 @@ export const AppDrawer: FC<{ )} - {!isPreferential && ( - - - - - - - - - )} + + + + + + + + Trial {trial.number} } /> - - Objective Values = [{trial.values?.join(", ")}] - + {studyDetail?.is_preferential ? null : ( + + Objective Values = [{trial.values?.join(", ")}] + + )} Params = [ {trial.params diff --git a/optuna_dashboard/ts/components/GraphContour.tsx b/optuna_dashboard/ts/components/GraphContour.tsx index d7e2aa13..c75e1500 100644 --- a/optuna_dashboard/ts/components/GraphContour.tsx +++ b/optuna_dashboard/ts/components/GraphContour.tsx @@ -11,6 +11,7 @@ import { useTheme, Box, } from "@mui/material" +import blue from "@mui/material/colors/blue" import { plotlyDarkTemplate } from "./PlotlyDarkMode" import { useMergedUnionSearchSpace } from "../searchSpace" @@ -211,32 +212,62 @@ const plotContour = ( } }) + if (!study.is_preferential) { + const plotData: Partial[] = [ + { + type: "contour", + x: xIndices, + y: yIndices, + z: zValues, + colorscale: "Blues", + connectgaps: true, + hoverinfo: "none", + line: { + smoothing: 1.3, + }, + reversescale: study.directions[objectiveId] !== "minimize", + // https://github.com/plotly/react-plotly.js/issues/251 + // @ts-ignore + contours: { + coloring: "heatmap", + }, + }, + { + type: "scatter", + x: xValues, + y: yValues, + marker: { line: { width: 2.0, color: "Grey" }, color: "black" }, + mode: "markers", + showlegend: false, + }, + ] + plotly.react(plotDomId, plotData, layout) + return + } + + layout.legend = { + y: 0.8, + } + const bestTrialIndices = study.best_trials.map((trial) => trial.number) const plotData: Partial[] = [ { - type: "contour", - x: xIndices, - y: yIndices, - z: zValues, - colorscale: "Blues", - connectgaps: true, - hoverinfo: "none", - line: { - smoothing: 1.3, - }, - reversescale: study.directions[objectiveId] !== "minimize", - // https://github.com/plotly/react-plotly.js/issues/251 - // @ts-ignore - contours: { - coloring: "heatmap", + type: "scatter", + x: xValues.filter((_, i) => bestTrialIndices.includes(i)), + y: yValues.filter((_, i) => bestTrialIndices.includes(i)), + marker: { + line: { width: 2.0, color: "Grey" }, + color: blue[200], }, + name: "best trials", + mode: "markers", }, { type: "scatter", - x: xValues, - y: yValues, + x: xValues.filter((_, i) => !bestTrialIndices.includes(i)), + y: yValues.filter((_, i) => !bestTrialIndices.includes(i)), marker: { line: { width: 2.0, color: "Grey" }, color: "black" }, + name: "others", mode: "markers", - showlegend: false, }, ] plotly.react(plotDomId, plotData, layout) diff --git a/optuna_dashboard/ts/components/PreferentialAnalytics.tsx b/optuna_dashboard/ts/components/PreferentialAnalytics.tsx new file mode 100644 index 00000000..d8623f97 --- /dev/null +++ b/optuna_dashboard/ts/components/PreferentialAnalytics.tsx @@ -0,0 +1,68 @@ +import React, { FC } from "react" +import { + Box, + Card, + CardContent, + Paper, + Typography, + useTheme, +} from "@mui/material" +import Grid2 from "@mui/material/Unstable_Grid2" +import { DataGrid, DataGridColumn } from "./DataGrid" +import { BestTrialsCard } from "./BestTrialsCard" +import { useStudyDetailValue, useStudySummaryValue } from "../state" +import { Contour } from "./GraphContour" + +export const PreferentialAnalytics: FC<{ studyId: number }> = ({ studyId }) => { + const theme = useTheme() + const studySummary = useStudySummaryValue(studyId) + const studyDetail = useStudyDetailValue(studyId) + + const userAttrs = studySummary?.user_attrs || studyDetail?.user_attrs || [] + const userAttrColumns: DataGridColumn[] = [ + { field: "key", label: "Key", sortable: true }, + { field: "value", label: "Value", sortable: true }, + ] + return ( + + + + + + + + + + + + + + + Study User Attributes + + + columns={userAttrColumns} + rows={userAttrs} + keyField={"key"} + dense={true} + initialRowsPerPage={5} + rowsPerPageOption={[5, 10, { label: "All", value: -1 }]} + /> + + + + + + ) +} diff --git a/optuna_dashboard/ts/components/StudyDetail.tsx b/optuna_dashboard/ts/components/StudyDetail.tsx index c3f9da0a..15cb7cf8 100644 --- a/optuna_dashboard/ts/components/StudyDetail.tsx +++ b/optuna_dashboard/ts/components/StudyDetail.tsx @@ -31,6 +31,7 @@ import { TrialList } from "./TrialList" import { StudyHistory } from "./StudyHistory" import { PreferentialTrials } from "./PreferentialTrials" import { PreferenceHistory } from "./PreferenceHistory" +import { PreferentialAnalytics } from "./PreferentialAnalytics" interface ParamTypes { studyId: string @@ -99,7 +100,9 @@ export const StudyDetail: FC<{ ) } else if (page === "analytics") { - content = ( + content = isPreferential ? ( + + ) : ( Hyperparameter Relationships diff --git a/pyproject.toml b/pyproject.toml index d9c0082c..0555f701 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,18 @@ docs = [ "sphinx_rtd_theme", ] +test = [ + "coverage", + "pytest", + "moto[s3]", +] + +optional = [ + "streamlit", + "boto3", +] + + [project.scripts] optuna-dashboard = "optuna_dashboard._cli:main" diff --git a/standalone_app/github-pages.html b/standalone_app/github-pages.html new file mode 100644 index 00000000..609c5c30 --- /dev/null +++ b/standalone_app/github-pages.html @@ -0,0 +1,19 @@ + + + + + Optuna Dashboard (Wasm ver.) + + + + + + + + + + + +
+ + diff --git a/standalone_app/package-lock.json b/standalone_app/package-lock.json index fcf2a381..dee5635d 100644 --- a/standalone_app/package-lock.json +++ b/standalone_app/package-lock.json @@ -28,6 +28,7 @@ "@types/react": "^18.2.0", "@types/react-dom": "^18.2.1", "@types/react-router-dom": "^5.3.3", + "compression-webpack-plugin": "^10.0.0", "esbuild-loader": "^3.0.1", "prettier": "^2.8.8", "ts-loader": "^9.4.2", @@ -1366,6 +1367,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -1552,6 +1592,79 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/compression-webpack-plugin": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-10.0.0.tgz", + "integrity": "sha512-wLXLIBwpul/ALcm7Aj+69X0pYT3BYt6DdPn3qrgBIh9YejV9Bju9ShhlAsjujLyWMo6SAweFIWaUoFmXZNuNrg==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/compression-webpack-plugin/node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/compression-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/compression-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/compression-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -2498,6 +2611,15 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -3997,6 +4119,35 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -4123,6 +4274,57 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "compression-webpack-plugin": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-10.0.0.tgz", + "integrity": "sha512-wLXLIBwpul/ALcm7Aj+69X0pYT3BYt6DdPn3qrgBIh9YejV9Bju9ShhlAsjujLyWMo6SAweFIWaUoFmXZNuNrg==", + "dev": true, + "requires": { + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + } + } + }, "convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", @@ -4829,6 +5031,12 @@ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", diff --git a/standalone_app/package.json b/standalone_app/package.json index ae01f27f..05b5390f 100644 --- a/standalone_app/package.json +++ b/standalone_app/package.json @@ -14,6 +14,7 @@ "@types/react": "^18.2.0", "@types/react-dom": "^18.2.1", "@types/react-router-dom": "^5.3.3", + "compression-webpack-plugin": "^10.0.0", "esbuild-loader": "^3.0.1", "prettier": "^2.8.8", "ts-loader": "^9.4.2", @@ -30,11 +31,11 @@ "@sqlite.org/sqlite-wasm": "^3.41.2-build11", "module-workers-polyfill": "^0.3.2", "notistack": "^3.0.1", + "optuna": "../rustlib/pkg", "plotly.js-dist-min": "^2.22.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.11.0", - "recoil": "^0.7.7", - "optuna": "../rustlib/pkg" + "recoil": "^0.7.7" } } diff --git a/standalone_app/webpack.config.js b/standalone_app/webpack.config.js index 39caaf7e..1e876959 100644 --- a/standalone_app/webpack.config.js +++ b/standalone_app/webpack.config.js @@ -3,6 +3,10 @@ const path = require('path'); const mode = process.env.NODE_ENV === 'production' ? 'production' : 'development'; const isDev = mode === 'development'; +const PUBLIC_PATH = process.env.PUBLIC_PATH ?? '/public/'; +if (!PUBLIC_PATH.endsWith('/')) { + PUBLIC_PATH += '/'; +} const typeScriptLoader = process.env.TYPESCRIPT_LOADER === "esbuild-loader" ? { test: /\.tsx?$/, @@ -34,7 +38,7 @@ var config = [ output: { path: __dirname + '/public/', filename: 'bundle.js', - publicPath: '/public/' + publicPath: PUBLIC_PATH }, module: { rules: [{ oneOf: [typeScriptLoader] }]