Skip to content

Commit 1c34b0f

Browse files
committed
changes
1 parent 2f577de commit 1c34b0f

3 files changed

Lines changed: 196 additions & 60 deletions

File tree

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
from urllib.parse import urlencode, urlparse, urlunparse
2+
3+
import trackio
4+
from playwright.sync_api import expect, sync_playwright
5+
6+
7+
def _url_with_query(base_url: str, params: dict[str, str]) -> str:
8+
parsed = urlparse(base_url)
9+
path = parsed.path if parsed.path else "/"
10+
query = urlencode(params)
11+
return urlunparse(
12+
(parsed.scheme, parsed.netloc, path, "", query, "")
13+
)
14+
15+
16+
def test_share_view_query_params_apply(temp_dir):
17+
project = "test_share_qp"
18+
for name in ("run-alpha", "run-beta"):
19+
trackio.init(project=project, name=name)
20+
for _ in range(3):
21+
trackio.log(metrics={"loss": 0.1, "accuracy": 0.9})
22+
trackio.finish()
23+
24+
app, _, _, full_url = trackio.show(
25+
project=project, block_thread=False, open_browser=False
26+
)
27+
28+
try:
29+
with sync_playwright() as p:
30+
browser = p.chromium.launch()
31+
page = browser.new_page()
32+
page.set_default_timeout(15000)
33+
34+
primary = _url_with_query(
35+
full_url,
36+
{
37+
"project": project,
38+
"runs": "run-alpha",
39+
"metric_filter": "^loss$",
40+
"sidebar": "hidden",
41+
"navbar": "hidden",
42+
"accordion": "hidden",
43+
},
44+
)
45+
page.goto(primary)
46+
page.wait_for_load_state("networkidle")
47+
48+
expect(page.locator(".navbar")).to_have_count(0)
49+
expect(page.locator(".sidebar")).to_have_count(0)
50+
expect(page.locator(".accordion")).to_have_count(0)
51+
52+
expect(page.locator(".metrics-page")).to_be_visible()
53+
expect(page.locator(".vega-embed")).to_have_count(1)
54+
expect(page.locator(".metrics-page .legend-dot")).to_have_count(1)
55+
56+
legacy = _url_with_query(
57+
full_url,
58+
{
59+
"project": project,
60+
"runs": "run-alpha",
61+
"metrics": "loss",
62+
"sidebar": "hidden",
63+
"navbar": "hidden",
64+
"accordion": "hidden",
65+
},
66+
)
67+
page.goto(legacy)
68+
page.wait_for_load_state("networkidle")
69+
70+
expect(page.locator(".navbar")).to_have_count(0)
71+
expect(page.locator(".vega-embed")).to_have_count(1)
72+
expect(page.locator(".metrics-page .legend-dot")).to_have_count(1)
73+
74+
browser.close()
75+
finally:
76+
trackio.delete_project(project, force=True)
77+
app.close()

trackio/frontend/src/App.svelte

Lines changed: 59 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@
3131
import Settings from "./pages/Settings.svelte";
3232
import { initTheme, isDark, onThemeChange } from "./lib/theme.js";
3333
34+
function metricFilterFromLegacyMetricsParam(metricsParam) {
35+
if (!metricsParam) return "";
36+
const trimmed = metricsParam.trim();
37+
const parts = trimmed.split(",").map((s) => s.trim()).filter(Boolean);
38+
if (parts.length === 0) return "";
39+
const isSimpleToken = (p) => /^[\w./-]+$/.test(p);
40+
if (parts.every(isSimpleToken)) {
41+
return parts
42+
.map((p) => {
43+
const esc = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44+
return `^${esc}$`;
45+
})
46+
.join("|");
47+
}
48+
return trimmed;
49+
}
50+
3451
initTheme();
3552
3653
let darkMode = $state(isDark());
@@ -52,6 +69,7 @@
5269
let metricColumns = $state([]);
5370
let sidebarOpen = $state(true);
5471
let sidebarHidden = $state(false);
72+
let navbarHidden = $state(false);
5573
let urlTick = $state(0);
5674
let alerts = $state([]);
5775
let pollTimer = $state(null);
@@ -209,6 +227,11 @@
209227
refreshRuns();
210228
});
211229
230+
$effect(() => {
231+
urlTick;
232+
navbarHidden = getQueryParam("navbar") === "hidden";
233+
});
234+
212235
onMount(() => {
213236
const sidebarParam = getQueryParam("sidebar");
214237
if (sidebarParam === "hidden") {
@@ -227,17 +250,12 @@
227250
if (!Number.isNaN(s)) smoothing = s;
228251
}
229252
230-
const metricsParam = getQueryParam("metrics");
231-
if (metricsParam) {
232-
const parts = metricsParam.split(",").map((s) => s.trim()).filter(Boolean);
233-
if (parts.length) {
234-
metricFilter = parts
235-
.map((p) => {
236-
const esc = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
237-
return `^${esc}$`;
238-
})
239-
.join("|");
240-
}
253+
const metricFilterParam = getQueryParam("metric_filter");
254+
const metricsLegacyParam = getQueryParam("metrics");
255+
if (metricFilterParam) {
256+
metricFilter = metricFilterParam;
257+
} else if (metricsLegacyParam) {
258+
metricFilter = metricFilterFromLegacyMetricsParam(metricsLegacyParam);
241259
}
242260
243261
if (getQueryParam("accordion") === "hidden") {
@@ -283,6 +301,7 @@
283301
}
284302
await refreshProjects();
285303
await refreshRuns();
304+
286305
await refreshAlerts();
287306
} catch (e) {
288307
console.error("Failed to load projects:", e);
@@ -314,6 +333,32 @@
314333
if (projectLocked) applyLockedProject();
315334
});
316335
336+
let urlRunsFromQueryApplied = $state(false);
337+
338+
$effect(() => {
339+
if (urlRunsFromQueryApplied) return;
340+
if (!appBootstrapReady) return;
341+
if (!selectedProject) return;
342+
const runsParam = getQueryParam("runs");
343+
if (!runsParam) {
344+
urlRunsFromQueryApplied = true;
345+
return;
346+
}
347+
if (!runs.length) {
348+
urlRunsFromQueryApplied = true;
349+
return;
350+
}
351+
const names = runsParam.split(",").map((s) => s.trim()).filter(Boolean);
352+
const nameToKey = new Map(runs.map((r) => [r.name, runKey(r)]));
353+
const wanted = names
354+
.map((n) => nameToKey.get(n))
355+
.filter((k) => k != null);
356+
if (wanted.length) {
357+
selectedRuns = wanted;
358+
}
359+
urlRunsFromQueryApplied = true;
360+
});
361+
317362
let showSidebar = $derived(
318363
currentPage === "metrics" ||
319364
currentPage === "system" ||
@@ -362,7 +407,9 @@
362407
{/if}
363408
364409
<div class="main">
365-
<Navbar {currentPage} onNavigate={handleNavigate} />
410+
{#if !navbarHidden}
411+
<Navbar {currentPage} onNavigate={handleNavigate} />
412+
{/if}
366413
367414
<div class="page-content">
368415
{#if currentPage === "metrics"}

trackio/frontend/src/components/Sidebar.svelte

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@
8282
8383
let latestOnly = $state(false);
8484
let shareTab = $state("share");
85+
let copyFeedback = $state(null);
86+
let copyFeedbackTimer = null;
8587
8688
function toggleLatestOnly() {
8789
latestOnly = !latestOnly;
@@ -143,7 +145,7 @@
143145
const params = new URLSearchParams();
144146
params.set("project", selectedProject);
145147
if (metricFilter?.trim()) {
146-
params.set("metrics", metricFilter.trim());
148+
params.set("metric_filter", metricFilter.trim());
147149
}
148150
const runNames = selectedRunNamesFromIds(selectedRuns, runs);
149151
if (runNames.length) {
@@ -164,7 +166,7 @@
164166
return `<iframe src="${shareUrl}" style="width:1600px; height:500px; border:0;"></iframe>`;
165167
});
166168
167-
async function copyText(value) {
169+
async function copyText(value, feedbackKey) {
168170
if (!value) return;
169171
try {
170172
await navigator.clipboard.writeText(value);
@@ -179,6 +181,14 @@
179181
document.execCommand("copy");
180182
document.body.removeChild(textarea);
181183
}
184+
copyFeedback = feedbackKey;
185+
if (copyFeedbackTimer) {
186+
clearTimeout(copyFeedbackTimer);
187+
}
188+
copyFeedbackTimer = setTimeout(() => {
189+
copyFeedback = null;
190+
copyFeedbackTimer = null;
191+
}, 2000);
182192
}
183193
</script>
184194
@@ -221,53 +231,55 @@
221231
</div>
222232
223233
{#if variant === "full"}
224-
<div class="section">
225-
<div class="share-tabs">
226-
<button
227-
class="share-tab-btn"
228-
class:active={shareTab === "share"}
229-
onclick={() => { shareTab = "share"; }}
230-
>
231-
Share
232-
</button>
233-
<button
234-
class="share-tab-btn"
235-
class:active={shareTab === "embed"}
236-
onclick={() => { shareTab = "embed"; }}
237-
>
238-
Embed
239-
</button>
240-
</div>
241-
{#if shareTab === "share"}
242-
<div class="share-field">
243-
<span class="section-label">Share this view</span>
244-
{#if shareUrl}
245-
<div class="share-input-row">
246-
<input type="text" value={shareUrl} readonly />
247-
<button class="copy-btn" onclick={() => copyText(shareUrl)}>
248-
Copy
249-
</button>
250-
</div>
251-
{:else}
252-
<p class="share-hint">Select a project to generate a share link.</p>
253-
{/if}
254-
</div>
255-
{:else}
256-
<div class="share-field">
257-
<span class="section-label">Embed this view</span>
258-
{#if embedCode}
259-
<div class="share-input-row">
260-
<textarea readonly rows="2" value={embedCode}></textarea>
261-
<button class="copy-btn" onclick={() => copyText(embedCode)}>
262-
Copy
263-
</button>
264-
</div>
265-
{:else}
266-
<p class="share-hint">Select a project to generate embed HTML.</p>
267-
{/if}
234+
{#if currentPage === "metrics"}
235+
<div class="section">
236+
<div class="share-tabs">
237+
<button
238+
class="share-tab-btn"
239+
class:active={shareTab === "share"}
240+
onclick={() => { shareTab = "share"; }}
241+
>
242+
Share
243+
</button>
244+
<button
245+
class="share-tab-btn"
246+
class:active={shareTab === "embed"}
247+
onclick={() => { shareTab = "embed"; }}
248+
>
249+
Embed
250+
</button>
268251
</div>
269-
{/if}
270-
</div>
252+
{#if shareTab === "share"}
253+
<div class="share-field">
254+
<span class="section-label">Share this view</span>
255+
{#if shareUrl}
256+
<div class="share-input-row">
257+
<input type="text" value={shareUrl} readonly />
258+
<button class="copy-btn" onclick={() => copyText(shareUrl, "share")}>
259+
{copyFeedback === "share" ? "Copied" : "Copy"}
260+
</button>
261+
</div>
262+
{:else}
263+
<p class="share-hint">Select a project to generate a share link.</p>
264+
{/if}
265+
</div>
266+
{:else}
267+
<div class="share-field">
268+
<span class="section-label">Embed this view</span>
269+
{#if embedCode}
270+
<div class="share-input-row">
271+
<textarea readonly rows="2" value={embedCode}></textarea>
272+
<button class="copy-btn" onclick={() => copyText(embedCode, "embed")}>
273+
{copyFeedback === "embed" ? "Copied" : "Copy"}
274+
</button>
275+
</div>
276+
{:else}
277+
<p class="share-hint">Select a project to generate embed HTML.</p>
278+
{/if}
279+
</div>
280+
{/if}
281+
</div>
282+
{/if}
271283
272284
<div class="section">
273285
<div class="runs-header">

0 commit comments

Comments
 (0)