runs filter: persist chip state in URL + server-render initial slice
- runs-filter.js mirrors its chip selections into the query string (dataset/algorithm/N/T/J). Empty selections are omitted entirely. Reads URL on init; triggers an immediate /runs refresh if any filter was present so the polled slice catches up instantly. - dataset-picker.js's updateUrlState now merges into the existing query instead of rebuilding, so the two scripts don't stomp each other's keys. - Index route applies the same chip filter to its initial server-side run listing, so a filter-bearing deep-link renders the right slice on first paint — no flash of unfiltered runs.
This commit is contained in:
parent
3a951b387a
commit
4f6e900c05
@ -742,14 +742,16 @@ async def index(request: Request) -> HTMLResponse:
|
||||
reducers = _reducer_choices()
|
||||
default_reducer = reducers[0]["key"] if reducers else None
|
||||
default_spec = REDUCERS.get(default_reducer) if default_reducer else None
|
||||
q = request.query_params
|
||||
required = _chip_filter_tags(q)
|
||||
initial_limit = 50 if required else 10
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
runs = await PREFECT.recent_runs(client, limit=10)
|
||||
runs = await PREFECT.recent_runs(client, limit=initial_limit, required_tags=required)
|
||||
dep_id = await PREFECT.deployment_id(client)
|
||||
views = [_run_view(r) for r in runs]
|
||||
_mark_stale_views(views)
|
||||
# Pre-resolve the two <details> sections' open state from the URL so
|
||||
# first paint matches (no flash). Intro defaults closed, picker open.
|
||||
q = request.query_params
|
||||
intro_open = q.get("intro") == "1"
|
||||
picker_open = q.get("picker") != "0"
|
||||
# Also pre-resolve the radio-group selections so n/f/j render with the
|
||||
|
||||
@ -298,7 +298,10 @@ async function main() {
|
||||
// the query string so a refresh restores the page. Intro defaults to
|
||||
// closed (no param); picker defaults to open (no param).
|
||||
function updateUrlState() {
|
||||
const p = new URLSearchParams();
|
||||
// Merge into the current query — other scripts (e.g. runs-filter.js)
|
||||
// also own some keys, so wipe only ours before repopulating.
|
||||
const p = new URLSearchParams(window.location.search);
|
||||
for (const k of ['intro', 'picker', 'ds', 'n', 'f', 'j']) p.delete(k);
|
||||
const introEl = document.getElementById('intro');
|
||||
const pickerEl = document.getElementById('picker');
|
||||
if (introEl && introEl.open) p.set('intro', '1');
|
||||
|
||||
@ -37,6 +37,26 @@
|
||||
slot.setAttribute('hx-vals', JSON.stringify(stateAsQuery()));
|
||||
}
|
||||
|
||||
function syncUrl() {
|
||||
// Mirror chip state to URL query — merge so we don't stomp on other
|
||||
// scripts' keys (dataset-picker writes intro/picker/ds/n/f/j).
|
||||
const p = new URLSearchParams(window.location.search);
|
||||
for (const ax of AXES) p.delete(ax.key);
|
||||
for (const ax of AXES) {
|
||||
if (ax.selected != null) p.set(ax.key, ax.selected);
|
||||
}
|
||||
const qs = p.toString();
|
||||
history.replaceState(null, '', qs ? `?${qs}` : window.location.pathname);
|
||||
}
|
||||
|
||||
function applyUrlState() {
|
||||
const u = new URLSearchParams(window.location.search);
|
||||
for (const ax of AXES) {
|
||||
const v = u.get(ax.key);
|
||||
ax.selected = v || null;
|
||||
}
|
||||
}
|
||||
|
||||
function paint(ax) {
|
||||
if (!ax.el) return;
|
||||
ax.el.innerHTML = '';
|
||||
@ -101,6 +121,7 @@
|
||||
ax.selected = (ax.selected === v) ? null : v;
|
||||
paint(ax);
|
||||
syncHtmxVals();
|
||||
syncUrl();
|
||||
updateCounter();
|
||||
triggerRunsRefresh();
|
||||
});
|
||||
@ -115,9 +136,15 @@
|
||||
}
|
||||
});
|
||||
|
||||
applyUrlState();
|
||||
syncHtmxVals();
|
||||
refreshUniverse();
|
||||
updateCounter();
|
||||
// If the URL had filter params, kick a refresh so #runs-slot's initial
|
||||
// HTML (rendered unfiltered server-side) gets replaced with the matching
|
||||
// slice. Without this the user would see the full list until the 3s
|
||||
// poll tick.
|
||||
if (AXES.some((ax) => ax.selected != null)) triggerRunsRefresh();
|
||||
// Periodically refresh the universe so newly-introduced values appear.
|
||||
setInterval(refreshUniverse, 30_000);
|
||||
})();
|
||||
|
||||
@ -500,7 +500,7 @@
|
||||
<script type="module" src="/static/dataset-picker.js?v=12"></script>
|
||||
<script type="module" src="/static/metrics.js?v=11"></script>
|
||||
<script src="/static/compare-select.js?v=4"></script>
|
||||
<script src="/static/runs-filter.js?v=6"></script>
|
||||
<script src="/static/runs-filter.js?v=7"></script>
|
||||
<script type="module" src="/static/run-modal.js?v=3"></script>
|
||||
<script>
|
||||
// Anchor-links alone don't expand <details>; force it.
|
||||
|
||||
Loading…
Reference in New Issue
Block a user