Skip to content

Commit

Permalink
Merge pull request #1040 from c-bata/fix-jupyterlab-artifact
Browse files Browse the repository at this point in the history
Fix ArtifactViewer in Jupyter Lab extension
  • Loading branch information
c-bata authored Feb 25, 2025
2 parents b938d26 + d0c6aa9 commit 5349d5f
Show file tree
Hide file tree
Showing 14 changed files with 134 additions and 48 deletions.
12 changes: 1 addition & 11 deletions .github/workflows/jupyterlab-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,13 @@ jobs:
node-version: '20'
cache: 'npm'

- run: make tslib

- name: Build JavaScript dependencies
working-directory: optuna_dashboard
run: |
npm install
npm run build:pkg
# Without a following step, it will be failed to build the jupyter lab extension by the below error
# Type Error Cannot read properties of undefined (reading .../jupyterlab/.pnp.cjs)
- name: Mysterious hack to fix the build error
working-directory: jupyterlab
run: yarn install

- name: Build package
working-directory: jupyterlab
- run: make jupyterlab-extension
env:
YARN_ENABLE_IMMUTABLE_INSTALLS: false
run: python -m build --sdist

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ venv/
dist/
build/
optuna_dashboard/public/
jupyterlab/jupyterlab_optuna/vendor/

# JS
node_modules/
Expand Down
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ vscode-extension: vscode/assets/bundle.js
.PHONY: jupyterlab-extension
jupyterlab-extension: tslib
cd optuna_dashboard && npm install && npm run build:pkg
cd jupyterlab && python -m build --sdist
rm -rf jupyterlab/jupyterlab_optuna/vendor/
mkdir -p jupyterlab/jupyterlab_optuna/vendor/
cp -r optuna_dashboard jupyterlab/jupyterlab_optuna/vendor/optuna_dashboard
rm -rf jupyterlab/jupyterlab_optuna/vendor/optuna_dashboard/node_modules
cd jupyterlab && jlpm install && jlpm run build && python -m build --wheel

.PHONY: python-package
python-package: pyproject.toml tslib
Expand Down
13 changes: 12 additions & 1 deletion jupyterlab/jupyterlab_optuna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@

warnings.warn("Importing 'jupyterlab_optuna' outside a proper installation.")
__version__ = "dev"
from .handlers import setup_handlers

import os
import sys


# Force import of `jupyterlab_optuna.vendor.optuna_dashboard`, instead of
# `optuna_dashboard` installed by pip.
vendor_path = os.path.join(os.path.dirname(__file__), "vendor")
if vendor_path not in sys.path:
sys.path.insert(0, vendor_path)

from .handlers import setup_handlers # NOQA


def _jupyter_labextension_paths():
Expand Down
11 changes: 9 additions & 2 deletions jupyterlab/jupyterlab_optuna/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
try:
from optuna.artifacts import FileSystemArtifactStore
except ImportError:
from optuna_dashboard.artifact.file_system import FileSystemBackend as FileSystemArtifactStore
from optuna_dashboard.artifact.file_system import (
FileSystemBackend as FileSystemArtifactStore,
)

from optuna_dashboard import wsgi
from optuna_dashboard._app import JupyterLabExtensionContext
from tornado.web import FallbackHandler
from tornado.wsgi import WSGIContainer

Expand Down Expand Up @@ -52,7 +55,11 @@ def post(self):
artifact_store = None

with threading_lock:
_dashboard_app = wsgi(storage=storage_url, artifact_store=artifact_store)
_dashboard_app = wsgi(
storage=storage_url,
artifact_store=artifact_store,
jupyterlab_extension_context=JupyterLabExtensionContext(base_url=_base_url),
)
_is_initialized = True
self.finish(json.dumps({"is_initialized": True}))

Expand Down
8 changes: 3 additions & 5 deletions jupyterlab/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"jupyter_server>=2.0.1,<3", "optuna-dashboard>=0.12.0"
]
dependencies = ["jupyter_server>=2.0.1,<3"]
dynamic = ["version", "description", "authors", "urls", "keywords"]

[project.optional-dependencies]
dev = ["jupyter"]
dev = ["jupyter", "optuna>=3.1.0", "bottle>=0.13.0", "packaging", "scikit-learn"]

[tool.hatch.version]
source = "nodejs"
Expand All @@ -36,7 +34,7 @@ fields = ["description", "authors", "urls"]

[tool.hatch.build.targets.sdist]
artifacts = ["jupyterlab_optuna/labextension"]
exclude = [".github", "binder", "node_modules"]
exclude = [".github", "binder"]

[tool.hatch.build.targets.wheel.shared-data]
"jupyterlab_optuna/labextension" = "share/jupyter/labextensions/jupyterlab-optuna"
Expand Down
21 changes: 19 additions & 2 deletions optuna_dashboard/_app.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import csv
from dataclasses import dataclass
import functools
import importlib
import io
Expand Down Expand Up @@ -75,10 +76,16 @@
cached_path_exists = functools.lru_cache(maxsize=10)(os.path.exists)


@dataclass
class JupyterLabExtensionContext:
base_url: str


def create_app(
storage: BaseStorage,
artifact_store: Optional[ArtifactStore] = None,
debug: bool = False,
jupyterlab_extension_context: JupyterLabExtensionContext | None = None,
) -> Bottle:
app = Bottle()
app._inmemory_cache = InMemoryCache()
Expand All @@ -99,10 +106,15 @@ def dashboard() -> BottleViewReturn:
@app.get("/api/meta")
@json_api_view
def api_meta() -> dict[str, Any]:
return {
meta: dict[str, Any] = {
"artifact_is_available": artifact_store is not None,
"plotlypy_is_available": importlib.util.find_spec("plotly") is not None,
}
if jupyterlab_extension_context is not None:
meta["jupyterlab_extension_context"] = {
"base_url": jupyterlab_extension_context.base_url
}
return meta

@app.get("/api/studies")
@json_api_view
Expand Down Expand Up @@ -638,6 +650,7 @@ def wsgi(
artifact_store: Optional[ArtifactBackend | ArtifactStore] = None,
*,
artifact_backend: Optional[ArtifactBackend] = None,
jupyterlab_extension_context: JupyterLabExtensionContext | None = None,
) -> WSGIApplication:
"""This function exposes WSGI interface for people who want to run on the
production-class WSGI servers like Gunicorn or uWSGI.
Expand All @@ -654,4 +667,8 @@ def wsgi(
)
store = to_artifact_store(artifact_backend)

return create_app(get_storage(storage), artifact_store=store)
return create_app(
get_storage(storage),
artifact_store=store,
jupyterlab_extension_context=jupyterlab_extension_context,
)
3 changes: 3 additions & 0 deletions optuna_dashboard/ts/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
export type APIMeta = {
artifact_is_available: boolean
plotlypy_is_available: boolean
jupyterlab_extension_context?: {
base_url: string
}
}

export interface TrialResponse {
Expand Down
16 changes: 15 additions & 1 deletion optuna_dashboard/ts/components/Artifact/StudyArtifactCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import React, {

import { StudyDetail } from "ts/types/optuna"
import { actionCreator } from "../../action"
import { useArtifactBaseUrlPath } from "../../hooks/useArtifactBaseUrlPath"
import { ArtifactCardMedia } from "./ArtifactCardMedia"
import { useDeleteStudyArtifactDialog } from "./DeleteArtifactDialog"
import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer"
Expand All @@ -30,8 +31,17 @@ import {
useThreejsArtifactModal,
} from "./ThreejsArtifactViewer"

const getStudyArtifactUrlPath = (
baseUrlPath: string,
studyId: number,
artifactId: string
): string => {
return `${baseUrlPath}/artifacts/${studyId}/${artifactId}`
}

export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
const theme = useTheme()
const artifactBaseUrl = useArtifactBaseUrlPath()
const [openDeleteArtifactDialog, renderDeleteArtifactDialog] =
useDeleteStudyArtifactDialog()
const [openThreejsArtifactModal, renderThreejsArtifactModal] =
Expand All @@ -58,7 +68,11 @@ export const StudyArtifactCards: FC<{ study: StudyDetail }> = ({ study }) => {
sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }}
>
{artifacts.map((artifact) => {
const urlPath = `/artifacts/${study.id}/${artifact.artifact_id}`
const urlPath = getStudyArtifactUrlPath(
artifactBaseUrl,
study.id,
artifact.artifact_id
)
return (
<Card
key={artifact.artifact_id}
Expand Down
18 changes: 17 additions & 1 deletion optuna_dashboard/ts/components/Artifact/TrialArtifactCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import React, {

import { Trial } from "ts/types/optuna"
import { actionCreator } from "../../action"
import { useArtifactBaseUrlPath } from "../../hooks/useArtifactBaseUrlPath"
import { ArtifactCardMedia } from "./ArtifactCardMedia"
import { useDeleteTrialArtifactDialog } from "./DeleteArtifactDialog"
import { isTableArtifact, useTableArtifactModal } from "./TableArtifactViewer"
Expand All @@ -30,8 +31,18 @@ import {
useThreejsArtifactModal,
} from "./ThreejsArtifactViewer"

export const getTrialArtifactUrlPath = (
baseUrlPath: string,
studyId: number,
trialId: number,
artifactId: string
): string => {
return `${baseUrlPath}/artifacts/${studyId}/${trialId}/${artifactId}`
}

export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
const theme = useTheme()
const artifactBaseUrl = useArtifactBaseUrlPath()
const [openDeleteArtifactDialog, renderDeleteArtifactDialog] =
useDeleteTrialArtifactDialog()
const [openThreejsArtifactModal, renderThreejsArtifactModal] =
Expand Down Expand Up @@ -67,7 +78,12 @@ export const TrialArtifactCards: FC<{ trial: Trial }> = ({ trial }) => {
sx={{ display: "flex", flexWrap: "wrap", p: theme.spacing(1, 0) }}
>
{artifacts.map((artifact) => {
const urlPath = `/artifacts/${trial.study_id}/${trial.trial_id}/${artifact.artifact_id}`
const urlPath = getTrialArtifactUrlPath(
artifactBaseUrl,
trial.study_id,
trial.trial_id,
artifact.artifact_id
)
return (
<Card
key={artifact.artifact_id}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ import ReactFlow, {
import "reactflow/dist/style.css"

import { StudyDetail, Trial } from "ts/types/optuna"
import { useConstants } from "../../constantsProvider"
import { useArtifactBaseUrlPath } from "../../hooks/useArtifactBaseUrlPath"
import { useStudyDetailValue } from "../../state"
import { getTrialArtifactUrlPath } from "../Artifact/TrialArtifactCards"
import { PreferentialOutputComponent } from "./PreferentialOutputComponent"
import { getArtifactUrlPath } from "./PreferentialTrials"

const elk = new ELK()
const nodeWidth = 400
Expand All @@ -40,7 +40,7 @@ type NodeData = {
}
const GraphNode: FC<NodeProps<NodeData>> = ({ data, isConnectable }) => {
const theme = useTheme()
const { environment } = useConstants()
const artifactBaseUrl = useArtifactBaseUrlPath()
const trial = data.trial
if (trial === undefined) {
return null
Expand All @@ -58,8 +58,8 @@ const GraphNode: FC<NodeProps<NodeData>> = ({ data, isConnectable }) => {
const artifact = trial.artifacts.find((a) => a.artifact_id === artifactId)
const urlPath =
artifactId !== undefined
? getArtifactUrlPath(
environment,
? getTrialArtifactUrlPath(
artifactBaseUrl,
trial.study_id,
trial.trial_id,
artifactId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import React, { FC, useState } from "react"

import { PreferenceHistory, StudyDetail, Trial } from "ts/types/optuna"
import { actionCreator } from "../../action"
import { useConstants } from "../../constantsProvider"
import { formatDate } from "../../dateUtil"
import { useArtifactBaseUrlPath } from "../../hooks/useArtifactBaseUrlPath"
import { useStudyDetailValue } from "../../state"
import { getTrialArtifactUrlPath } from "../Artifact/TrialArtifactCards"
import { TrialListDetail } from "../TrialList"
import { PreferentialOutputComponent } from "./PreferentialOutputComponent"
import { getArtifactUrlPath } from "./PreferentialTrials"

type TrialType = "worst" | "none"

Expand All @@ -31,10 +31,10 @@ const CandidateTrial: FC<{
type: TrialType
}> = ({ trial, type }) => {
const theme = useTheme()
const { environment } = useConstants()
const trialWidth = 300
const trialHeight = 300
const studyDetail = useStudyDetailValue(trial.study_id)
const artifactBaseUrl = useArtifactBaseUrlPath()
const [detailShown, setDetailShown] = useState(false)

if (studyDetail === null) {
Expand All @@ -49,8 +49,8 @@ const CandidateTrial: FC<{
const artifact = trial.artifacts.find((a) => a.artifact_id === artifactId)
const urlPath =
artifactId !== undefined
? getArtifactUrlPath(
environment,
? getTrialArtifactUrlPath(
artifactBaseUrl,
trial.study_id,
trial.trial_id,
artifactId
Expand Down
19 changes: 5 additions & 14 deletions optuna_dashboard/ts/components/Preferential/PreferentialTrials.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ import {
Trial,
} from "ts/types/optuna"
import { actionCreator } from "../../action"
import { useConstants } from "../../constantsProvider"
import { useArtifactBaseUrlPath } from "../../hooks/useArtifactBaseUrlPath"
import {
isThreejsArtifact,
useThreejsArtifactModal,
} from "../Artifact/ThreejsArtifactViewer"
import { getTrialArtifactUrlPath } from "../Artifact/TrialArtifactCards"
import { TrialListDetail } from "../TrialList"
import { PreferentialOutputComponent } from "./PreferentialOutputComponent"

Expand Down Expand Up @@ -178,16 +179,6 @@ const isComparisonReady = (
return false
}

export const getArtifactUrlPath = (
environment: "jupyterlab" | "optuna-dashboard",
studyId: number,
trialId: number,
artifactId: string
): string => {
const apiNamespace = environment === "jupyterlab" ? "/jupyterlab-optuna" : ""
return `${apiNamespace}/artifacts/${studyId}/${trialId}/${artifactId}`
}

const PreferentialTrial: FC<{
trial?: Trial
studyDetail: StudyDetail
Expand All @@ -204,7 +195,7 @@ const PreferentialTrial: FC<{
openThreejsArtifactModal,
}) => {
const theme = useTheme()
const { environment } = useConstants()
const artifactBaseUrl = useArtifactBaseUrlPath()
const action = actionCreator()
const [buttonHover, setButtonHover] = useState(false)
const trialWidth = 400
Expand All @@ -218,8 +209,8 @@ const PreferentialTrial: FC<{
const artifact = trial?.artifacts.find((a) => a.artifact_id === artifactId)
const urlPath =
trial !== undefined && artifactId !== undefined
? getArtifactUrlPath(
environment,
? getTrialArtifactUrlPath(
artifactBaseUrl,
studyDetail.id,
trial?.trial_id,
artifactId
Expand Down
Loading

0 comments on commit 5349d5f

Please sign in to comment.