Skip to content

Commit 78bc08b

Browse files
committed
improvements
1 parent 161b46c commit 78bc08b

8 files changed

Lines changed: 69 additions & 37 deletions

File tree

plugins/ui/src/deephaven/ui/_internal/RenderContext.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,14 @@ def get_href(self) -> str:
455455
"""
456456
return self._root.get_href()
457457

458+
def get_base_url(self) -> str:
459+
"""
460+
Get the base URL from the frontend (import.meta.env.BASE_URL).
461+
Returns:
462+
The base URL string.
463+
"""
464+
return self._root.get_base_url()
465+
458466
def update_url_state(self, query_params: QueryParams) -> None:
459467
"""
460468
Update the URL query parameters.
@@ -665,6 +673,8 @@ def import_state(self, state: dict[str, Any]) -> None:
665673
self._root.set_fragment(state.pop("__fragment"))
666674
if "__href" in state:
667675
self._root.set_href(state.pop("__href"))
676+
if "__baseUrl" in state:
677+
self._root.set_base_url(state.pop("__baseUrl"))
668678

669679
if "state" in state:
670680
for key, value in state["state"].items():

plugins/ui/src/deephaven/ui/_internal/RootRenderContextProtocol.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,11 @@ def get_href(self) -> str:
6767
def set_href(self, href: str) -> None:
6868
"""Update the full URL href."""
6969
...
70+
71+
def get_base_url(self) -> str:
72+
"""Get the base URL (from import.meta.env.BASE_URL on the frontend)."""
73+
...
74+
75+
def set_base_url(self, base_url: str) -> None:
76+
"""Update the base URL."""
77+
...

plugins/ui/src/deephaven/ui/components/router.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ..hooks.use_path import use_path
99
from ..hooks.use_url_components import use_url_components
1010
from ..types import WidgetPath, EnterpriseWidgetPath, resolve_widget_path
11+
from .._internal import get_context
1112
from .make_component import make_component as component
1213
from .route import _Route
1314
from .text import text
@@ -98,6 +99,7 @@ def _compile_routes(
9899
routes: list[_Route],
99100
parent_path: str,
100101
current_url_path: str,
102+
base_url: str = "/",
101103
) -> list[_CompiledRoute]:
102104
"""
103105
Recursively compile route definitions into a flat list of _CompiledRoute objects.
@@ -106,6 +108,7 @@ def _compile_routes(
106108
routes: The list of _Route objects to compile.
107109
parent_path: The accumulated parent path prefix.
108110
current_url_path: The current absolute URL path (for WidgetPath resolution).
111+
base_url: The base URL from the frontend.
109112
110113
Returns:
111114
A flat list of compiled routes.
@@ -136,7 +139,7 @@ def _compile_routes(
136139
elif r.path is not None:
137140
# Resolve WidgetPath / EnterpriseWidgetPath
138141
if isinstance(r.path, (WidgetPath, EnterpriseWidgetPath)):
139-
resolved_path = resolve_widget_path(r.path, current_url_path)
142+
resolved_path = resolve_widget_path(r.path, current_url_path, base_url)
140143
else:
141144
path_str = r.path
142145
if not path_str.startswith("/"):
@@ -170,13 +173,15 @@ def _compile_routes(
170173
# Recurse into children
171174
if r.children:
172175
compiled.extend(
173-
_compile_routes(r.children, resolved_path, current_url_path)
176+
_compile_routes(
177+
r.children, resolved_path, current_url_path, base_url
178+
)
174179
)
175180
else:
176181
# No path and not index — just a grouping node, recurse children
177182
if r.children:
178183
compiled.extend(
179-
_compile_routes(r.children, parent_path, current_url_path)
184+
_compile_routes(r.children, parent_path, current_url_path, base_url)
180185
)
181186

182187
return compiled
@@ -237,9 +242,10 @@ def _router_render(*routes: _Route) -> Element:
237242
path = use_path()
238243
url_components = use_url_components()
239244
current_url_path = url_components.path
245+
base_url = get_context().get_base_url()
240246

241247
# Compile all routes
242-
compiled = _compile_routes(list(routes), "", current_url_path)
248+
compiled = _compile_routes(list(routes), "", current_url_path, base_url)
243249

244250
# Match
245251
matched, params = _match_route(compiled, path)

plugins/ui/src/deephaven/ui/hooks/use_widget_path.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from ..types import WidgetPath, EnterpriseWidgetPath, resolve_widget_path
44
from .use_url_components import use_url_components
5+
from .._internal import get_context
56

67

78
def use_widget_path(widget_path: WidgetPath | EnterpriseWidgetPath) -> str:
@@ -19,4 +20,5 @@ def use_widget_path(widget_path: WidgetPath | EnterpriseWidgetPath) -> str:
1920
The resolved absolute URL path string.
2021
"""
2122
url = use_url_components()
22-
return resolve_widget_path(widget_path, url.path)
23+
context = get_context()
24+
return resolve_widget_path(widget_path, url.path, context.get_base_url())

plugins/ui/src/deephaven/ui/object_types/ElementMessageStream.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"__absolutePath": str,
4343
"__fragment": str,
4444
"__href": str,
45+
"__baseUrl": str,
4546
},
4647
total=False,
4748
)
@@ -213,6 +214,11 @@ class ElementMessageStream(MessageStream, RootRenderContextProtocol):
213214
The full URL href, populated from the frontend.
214215
"""
215216

217+
_base_url: str
218+
"""
219+
The base URL from import.meta.env.BASE_URL on the frontend.
220+
"""
221+
216222
def __init__(self, element: Element, connection: MessageStream):
217223
"""
218224
Create a new ElementMessageStream. Renders the element in a render context, and sends the rendered result to the
@@ -234,6 +240,7 @@ def __init__(self, element: Element, connection: MessageStream):
234240
self._absolute_path = "/"
235241
self._fragment = ""
236242
self._href = ""
243+
self._base_url = "/"
237244
self._context = RenderContext(self)
238245
self._event_context = EventContext(self._send_event)
239246
self._renderer = Renderer(self._context)
@@ -378,6 +385,12 @@ def get_href(self) -> str:
378385
def set_href(self, href: str) -> None:
379386
self._href = href
380387

388+
def get_base_url(self) -> str:
389+
return self._base_url
390+
391+
def set_base_url(self, base_url: str) -> None:
392+
self._base_url = base_url
393+
381394
def start(self) -> None:
382395
"""
383396
Start the message stream. All we do is send a blank message to start. Client will respond with the initial state.
@@ -492,6 +505,7 @@ def _set_url_state(self, url_state: _UrlState) -> None:
492505
self.set_absolute_path(url_state.get("__absolutePath", "/"))
493506
self.set_fragment(url_state.get("__fragment", ""))
494507
self.set_href(url_state.get("__href", ""))
508+
self.set_base_url(url_state.get("__baseUrl", "/"))
495509
self._mark_dirty()
496510

497511
def _serialize_callables(self, node: Any) -> Any:

plugins/ui/src/deephaven/ui/types/widget_path.py

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from __future__ import annotations
22

3+
from deephaven.plugin.utilities import is_enterprise_environment
4+
35
import re
46
from dataclasses import dataclass
57
from typing import TypedDict, Union
@@ -75,43 +77,27 @@ class NavigationTarget(TypedDict, total=False):
7577

7678
# Patterns for detecting embed/app/iframe routes in a URL path.
7779
_EMBED_WIDGET_RE = re.compile(r"/embed/widget/")
78-
_APP_WIDGET_RE = re.compile(r"/app/widget/")
80+
_DASHBOARD_RE = re.compile(r"/dashboard/")
7981
_IFRAME_WIDGET_RE = re.compile(r"/iframe/widget/")
8082

81-
# Patterns for extracting the base URL from a path.
82-
_WIDGET_ROUTE_RE = re.compile(r"^(.*?)(?:/embed/widget/|/app/widget/|/iframe/widget/)")
83-
84-
8583
try:
86-
from deephaven_enterprise.client.session_manager import (
84+
# Import SessionManager for enterprise widget path resolution.
85+
from deephaven_enterprise.client.session_manager import ( # pyright: ignore[reportMissingImports]
8786
SessionManager,
8887
)
89-
90-
_has_enterprise = True
9188
except ImportError:
92-
_has_enterprise = False
89+
pass
9390

9491

9592
def _get_serial_for_name(name: str) -> int:
96-
if not _has_enterprise:
93+
if not is_enterprise_environment():
9794
raise RuntimeError(
98-
"deephaven_enterprise is required for embed to app path conversion"
95+
"deephaven_enterprise is required for embed to dashboard path conversion"
9996
)
100-
sm = SessionManager()
97+
sm = SessionManager() # pyright: ignore[reportUnboundVariable]
10198
return sm.controller_client.get_serial_for_name(name=name)
10299

103100

104-
def _extract_base_url(current_url_path: str) -> str:
105-
"""
106-
Extract the base URL from a URL path by finding the portion before
107-
/embed/widget/, /app/widget/, or /iframe/widget/.
108-
109-
Returns an empty string if no known prefix is found.
110-
"""
111-
m = _WIDGET_ROUTE_RE.match(current_url_path)
112-
return m.group(1) if m else ""
113-
114-
115101
def _detect_embed(current_url_path: str) -> bool:
116102
"""
117103
Detect whether the current URL is an embed/iframe route (True) or
@@ -121,26 +107,31 @@ def _detect_embed(current_url_path: str) -> bool:
121107
current_url_path
122108
):
123109
return True
124-
if _APP_WIDGET_RE.search(current_url_path):
110+
if _DASHBOARD_RE.search(current_url_path):
125111
return False
126112
return True
127113

128114

129115
def resolve_widget_path(
130116
widget_path: WidgetPath | EnterpriseWidgetPath,
131117
current_url_path: str,
118+
base_url: str = "/",
132119
) -> str:
133120
"""
134121
Resolve a WidgetPath or EnterpriseWidgetPath to an absolute URL path string.
135122
136123
Args:
137124
widget_path: The widget path descriptor.
138125
current_url_path: The current absolute URL path from the browser.
126+
base_url: The base URL from the frontend (import.meta.env.BASE_URL).
127+
Defaults to "/".
139128
140129
Returns:
141130
The resolved absolute URL path string.
142131
"""
143-
base_url = _extract_base_url(current_url_path)
132+
# Normalize base_url: ensure no trailing slash for clean concatenation
133+
base = base_url.rstrip("/")
134+
144135
embed = widget_path.embed
145136
if embed is None:
146137
embed = _detect_embed(current_url_path)
@@ -152,11 +143,9 @@ def resolve_widget_path(
152143

153144
if isinstance(widget_path, WidgetPath):
154145
if embed:
155-
# Non-enterprise embed route
156-
path = f"{base_url}/iframe/widget/{widget_path.widget}{local_suffix}"
146+
path = f"{base}/iframe/widget/{widget_path.widget}{local_suffix}"
157147
else:
158-
# Non-enterprise app route (future work — use iframe for now)
159-
path = f"{base_url}/iframe/widget/{widget_path.widget}{local_suffix}"
148+
path = f"{base}{local_suffix}" if local_suffix else (base or "/")
160149
return path
161150

162151
# EnterpriseWidgetPath
@@ -168,14 +157,14 @@ def resolve_widget_path(
168157
if widget_path.replica_slot is not None
169158
else ""
170159
)
171-
path = f"{base_url}/embed/widget/{query_part}/{widget_path.widget}{replica}{local_suffix}"
160+
path = f"{base}/embed/widget/{query_part}/{widget_path.widget}{replica}{local_suffix}"
172161
else:
173-
# Enterprise app route: <base>/app/widget/<serial>-<widget>[/local/...]
162+
# Enterprise app route: <base>/dashboard/<serial>_<widget>[/local/...]
174163
query = widget_path.query
175164
if isinstance(query, str):
176165
serial = _get_serial_for_name(query)
177166
else:
178167
serial = query
179-
path = f"{base_url}/app/widget/{serial}-{widget_path.widget}{local_suffix}"
168+
path = f"{base}/dashboard/{serial}_{widget_path.widget}{local_suffix}"
180169

181170
return path

plugins/ui/src/js/src/events/Navigate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const PATH_PARAM = '__path';
1919
export const ABSOLUTE_PATH_PARAM = '__absolutePath';
2020
export const FRAGMENT_PARAM = '__fragment';
2121
export const HREF_PARAM = '__href';
22+
export const BASE_URL_PARAM = '__baseUrl';
2223

2324
/** The local path prefix that separates platform routing from user routing. */
2425
const LOCAL_PREFIX = '/local/';

plugins/ui/src/js/src/widget/WidgetHandler.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import Navigate, {
5959
QUERY_PARAM,
6060
PATH_PARAM,
6161
ABSOLUTE_PATH_PARAM,
62+
BASE_URL_PARAM,
6263
FRAGMENT_PARAM,
6364
HREF_PARAM,
6465
getLocalPath,
@@ -177,6 +178,7 @@ function WidgetHandler({
177178
[ABSOLUTE_PATH_PARAM]: window.location.pathname,
178179
[FRAGMENT_PARAM]: window.location.hash.replace(/^#/, ''),
179180
[HREF_PARAM]: window.location.href,
181+
[BASE_URL_PARAM]: import.meta.env.BASE_URL,
180182
};
181183
}, []);
182184

0 commit comments

Comments
 (0)