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()
|
reducers = _reducer_choices()
|
||||||
default_reducer = reducers[0]["key"] if reducers else None
|
default_reducer = reducers[0]["key"] if reducers else None
|
||||||
default_spec = REDUCERS.get(default_reducer) if default_reducer 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:
|
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)
|
dep_id = await PREFECT.deployment_id(client)
|
||||||
views = [_run_view(r) for r in runs]
|
views = [_run_view(r) for r in runs]
|
||||||
_mark_stale_views(views)
|
_mark_stale_views(views)
|
||||||
# Pre-resolve the two <details> sections' open state from the URL so
|
# Pre-resolve the two <details> sections' open state from the URL so
|
||||||
# first paint matches (no flash). Intro defaults closed, picker open.
|
# first paint matches (no flash). Intro defaults closed, picker open.
|
||||||
q = request.query_params
|
|
||||||
intro_open = q.get("intro") == "1"
|
intro_open = q.get("intro") == "1"
|
||||||
picker_open = q.get("picker") != "0"
|
picker_open = q.get("picker") != "0"
|
||||||
# Also pre-resolve the radio-group selections so n/f/j render with the
|
# 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
|
// the query string so a refresh restores the page. Intro defaults to
|
||||||
// closed (no param); picker defaults to open (no param).
|
// closed (no param); picker defaults to open (no param).
|
||||||
function updateUrlState() {
|
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 introEl = document.getElementById('intro');
|
||||||
const pickerEl = document.getElementById('picker');
|
const pickerEl = document.getElementById('picker');
|
||||||
if (introEl && introEl.open) p.set('intro', '1');
|
if (introEl && introEl.open) p.set('intro', '1');
|
||||||
|
|||||||
@ -37,6 +37,26 @@
|
|||||||
slot.setAttribute('hx-vals', JSON.stringify(stateAsQuery()));
|
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) {
|
function paint(ax) {
|
||||||
if (!ax.el) return;
|
if (!ax.el) return;
|
||||||
ax.el.innerHTML = '';
|
ax.el.innerHTML = '';
|
||||||
@ -101,6 +121,7 @@
|
|||||||
ax.selected = (ax.selected === v) ? null : v;
|
ax.selected = (ax.selected === v) ? null : v;
|
||||||
paint(ax);
|
paint(ax);
|
||||||
syncHtmxVals();
|
syncHtmxVals();
|
||||||
|
syncUrl();
|
||||||
updateCounter();
|
updateCounter();
|
||||||
triggerRunsRefresh();
|
triggerRunsRefresh();
|
||||||
});
|
});
|
||||||
@ -115,9 +136,15 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
applyUrlState();
|
||||||
syncHtmxVals();
|
syncHtmxVals();
|
||||||
refreshUniverse();
|
refreshUniverse();
|
||||||
updateCounter();
|
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.
|
// Periodically refresh the universe so newly-introduced values appear.
|
||||||
setInterval(refreshUniverse, 30_000);
|
setInterval(refreshUniverse, 30_000);
|
||||||
})();
|
})();
|
||||||
|
|||||||
@ -500,7 +500,7 @@
|
|||||||
<script type="module" src="/static/dataset-picker.js?v=12"></script>
|
<script type="module" src="/static/dataset-picker.js?v=12"></script>
|
||||||
<script type="module" src="/static/metrics.js?v=11"></script>
|
<script type="module" src="/static/metrics.js?v=11"></script>
|
||||||
<script src="/static/compare-select.js?v=4"></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 type="module" src="/static/run-modal.js?v=3"></script>
|
||||||
<script>
|
<script>
|
||||||
// Anchor-links alone don't expand <details>; force it.
|
// Anchor-links alone don't expand <details>; force it.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user