generated from coulomb/repo-seed
feat(dashboard): poll optimisation — T4, T5, T6
T4: workstreams.md and dependencies.md now call /state/deps instead of the
full /state/summary — removes 2 heavy 10-table queries per 60 s cycle.
T5: index.md's 4 independent polling loops (summaryState, sbomSnapState,
regsState, wsChartState) consolidated into a single pageState generator
with one Promise.all batch and a shared backoff counter.
T6: config.js gains waitForVisible(ms) — pauses polling entirely while the
tab is hidden and fires immediately on visibilitychange. pollDelay()
simplified (hidden-tab POLL_HIDDEN logic removed). All 16 polling pages
migrated from await sleep(pollDelay(...)) to await waitForVisible(pollDelay(...)).
CUST-WP-0039 complete — all 6 tasks done.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,117 +3,39 @@ title: Overview
|
||||
---
|
||||
|
||||
```js
|
||||
import {API, POLL, POLL_HEAVY, apiFetch, pollDelay, sleep} from "./components/config.js";
|
||||
import {API, POLL_HEAVY, apiFetch, pollDelay, waitForVisible} from "./components/config.js";
|
||||
```
|
||||
|
||||
```js
|
||||
// Live polling — yields {data, ok, ts}; backs off when the API is slow/offline.
|
||||
const summaryState = (async function*() {
|
||||
// Single polling loop — fetches all data in one Promise.all batch, backs off uniformly.
|
||||
const pageState = (async function*() {
|
||||
let failures = 0;
|
||||
while (true) {
|
||||
let data, ok = false;
|
||||
let summary = {}, snapshots = [], totalPkgs = 0, milestones = [], wsAll = [], ok = false;
|
||||
try {
|
||||
const r = await apiFetch("/state/summary", {timeout: 20_000});
|
||||
ok = r.ok;
|
||||
data = ok ? await r.json() : {error: `HTTP ${r.status}`};
|
||||
} catch (e) {
|
||||
data = {error: "API unreachable"};
|
||||
}
|
||||
failures = ok ? 0 : failures + 1;
|
||||
yield {data, ok, ts: new Date()};
|
||||
await sleep(pollDelay({ok, base: POLL_HEAVY, failures}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
```js
|
||||
const summary = summaryState.data ?? {};
|
||||
const _ok = summaryState.ok ?? false;
|
||||
const _ts = summaryState.ts;
|
||||
const totals = summary.totals ?? {};
|
||||
const ws = totals.workstreams ?? {};
|
||||
const tasks = totals.tasks ?? {};
|
||||
const decisions = totals.decisions ?? {};
|
||||
```
|
||||
|
||||
```js
|
||||
// Blocking decisions — fetched once on load, refreshed only after a resolve action.
|
||||
// Kept separate from the summary poll so in-progress form inputs aren't wiped every 15 s.
|
||||
const blockingDecisions = Mutable([]);
|
||||
const refreshDecisions = async () => {
|
||||
const r = await fetch(`${API}/decisions/?decision_type=pending`).catch(() => null);
|
||||
const all = r?.ok ? await r.json() : [];
|
||||
blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status));
|
||||
};
|
||||
refreshDecisions();
|
||||
```
|
||||
|
||||
```js
|
||||
// SBOM snapshots — repo coverage and total package count
|
||||
const sbomSnapState = (async function*() {
|
||||
let failures = 0;
|
||||
while (true) {
|
||||
let snapshots = [], totalPkgs = 0, ok = false;
|
||||
try {
|
||||
const r = await apiFetch("/sbom/snapshots/");
|
||||
ok = r.ok;
|
||||
if (r.ok) {
|
||||
snapshots = await r.json();
|
||||
totalPkgs = snapshots.reduce((s, sn) => s + (sn.entry_count ?? 0), 0);
|
||||
}
|
||||
} catch {}
|
||||
failures = ok ? 0 : failures + 1;
|
||||
yield {snapshots, totalPkgs};
|
||||
await sleep(pollDelay({ok, base: POLL_HEAVY, failures}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
```js
|
||||
// Registered projects — milestone events tagged with registration
|
||||
const regsState = (async function*() {
|
||||
let failures = 0;
|
||||
while (true) {
|
||||
let rows = [], ok = false;
|
||||
try {
|
||||
const r = await apiFetch("/progress/?event_type=milestone&limit=500");
|
||||
ok = r.ok;
|
||||
if (r.ok) {
|
||||
const all = await r.json();
|
||||
rows = all.filter(e => e.summary?.startsWith("Project registered with State Hub:"));
|
||||
}
|
||||
} catch {}
|
||||
failures = ok ? 0 : failures + 1;
|
||||
yield rows;
|
||||
await sleep(pollDelay({ok, base: POLL_HEAVY, failures}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
```js
|
||||
// All-workstreams + all-tasks poll — drives the multi-mode chart
|
||||
const wsChartState = (async function*() {
|
||||
let failures = 0;
|
||||
while (true) {
|
||||
let wsAll = [], ok = false;
|
||||
try {
|
||||
const [rw, rt, rto, rr, rwi] = await Promise.all([
|
||||
const [rSum, rSnap, rRegs, rw, rt, rto, rr, rwi] = await Promise.all([
|
||||
apiFetch("/state/summary", {timeout: 20_000}),
|
||||
apiFetch("/sbom/snapshots/"),
|
||||
apiFetch("/progress/?event_type=milestone&limit=500"),
|
||||
apiFetch("/workstreams/"),
|
||||
apiFetch("/tasks/?limit=2000"),
|
||||
apiFetch("/topics/"),
|
||||
apiFetch("/repos/"),
|
||||
apiFetch("/workstreams/workplan-index"),
|
||||
]);
|
||||
ok = rw.ok && rt.ok && rto.ok && rr.ok;
|
||||
ok = rSum.ok && rSnap.ok && rRegs.ok && rw.ok && rt.ok && rto.ok && rr.ok;
|
||||
if (ok) {
|
||||
const [wsList, taskList, topicList, repoList] = await Promise.all([
|
||||
rw.json(), rt.json(), rto.json(), rr.json(),
|
||||
const [summaryData, snapList, allEvents, wsList, taskList, topicList, repoList] = await Promise.all([
|
||||
rSum.json(), rSnap.json(), rRegs.json(), rw.json(), rt.json(), rto.json(), rr.json(),
|
||||
]);
|
||||
summary = summaryData;
|
||||
snapshots = snapList;
|
||||
totalPkgs = snapshots.reduce((s, sn) => s + (sn.entry_count ?? 0), 0);
|
||||
milestones = allEvents.filter(e => e.summary?.startsWith("Project registered with State Hub:"));
|
||||
const workplanIndex = rwi.ok ? await rwi.json() : {workstreams: {}};
|
||||
const workplanMap = workplanIndex.workstreams ?? {};
|
||||
const topicMap = Object.fromEntries(topicList.map(t => [t.id, t]));
|
||||
const repoMap = Object.fromEntries(repoList.map(r => [r.id, r]));
|
||||
// Aggregate task counts per workstream
|
||||
const counts = {};
|
||||
for (const t of taskList) {
|
||||
const wid = t.workstream_id;
|
||||
@@ -139,17 +61,40 @@ const wsChartState = (async function*() {
|
||||
...(counts[w.id] ?? {done: 0, in_progress: 0, blocked: 0, todo: 0, total: 0}),
|
||||
};
|
||||
});
|
||||
} else {
|
||||
summary = {error: "API unreachable"};
|
||||
}
|
||||
} catch {}
|
||||
} catch (e) {
|
||||
summary = {error: "API unreachable"};
|
||||
}
|
||||
failures = ok ? 0 : failures + 1;
|
||||
yield {wsAll, ok};
|
||||
await sleep(pollDelay({ok, base: POLL_HEAVY, failures}));
|
||||
yield {summary, snapshots, totalPkgs, milestones, wsAll, ok, ts: new Date()};
|
||||
await waitForVisible(pollDelay({ok, base: POLL_HEAVY, failures}));
|
||||
}
|
||||
})();
|
||||
```
|
||||
|
||||
```js
|
||||
const wsAll = wsChartState.wsAll ?? [];
|
||||
const summary = pageState.summary ?? {};
|
||||
const _ok = pageState.ok ?? false;
|
||||
const _ts = pageState.ts;
|
||||
const totals = summary.totals ?? {};
|
||||
const ws = totals.workstreams ?? {};
|
||||
const tasks = totals.tasks ?? {};
|
||||
const decisions = totals.decisions ?? {};
|
||||
const wsAll = pageState.wsAll ?? [];
|
||||
```
|
||||
|
||||
```js
|
||||
// Blocking decisions — fetched once on load, refreshed only after a resolve action.
|
||||
// Kept separate from the main poll so in-progress form inputs aren't wiped every 60 s.
|
||||
const blockingDecisions = Mutable([]);
|
||||
const refreshDecisions = async () => {
|
||||
const r = await fetch(`${API}/decisions/?decision_type=pending`).catch(() => null);
|
||||
const all = r?.ok ? await r.json() : [];
|
||||
blockingDecisions.value = all.filter(d => ["open", "escalated"].includes(d.status));
|
||||
};
|
||||
refreshDecisions();
|
||||
```
|
||||
|
||||
# Custodian State Hub
|
||||
@@ -390,8 +335,8 @@ const contribCounts = summary.contribution_counts ?? {};
|
||||
const licenceRisk = summary.licence_risk_count ?? 0;
|
||||
const totalContribs = ["br","fr","ep","upr"].reduce((s, t) => s + (contribCounts[t] ?? 0), 0);
|
||||
const needsFollowUp = (contribCounts["submitted"] ?? 0) + (contribCounts["acknowledged"] ?? 0);
|
||||
const sbomSnaps = sbomSnapState.snapshots ?? [];
|
||||
const totalPkgs = sbomSnapState.totalPkgs ?? 0;
|
||||
const sbomSnaps = pageState.snapshots ?? [];
|
||||
const totalPkgs = pageState.totalPkgs ?? 0;
|
||||
|
||||
display(html`<div class="grid grid-cols-3" style="gap:1rem;margin-bottom:1.5rem">
|
||||
<a class="card card-link" href="./contributions">
|
||||
@@ -508,7 +453,7 @@ if (nextSteps.length === 0) {
|
||||
## Registered Projects
|
||||
|
||||
```js
|
||||
const regs = regsState ?? [];
|
||||
const regs = pageState.milestones ?? [];
|
||||
if (regs.length === 0) {
|
||||
display(html`<p style="color:gray">No projects registered yet. Run <code>custodian register-project</code> inside a repo.</p>`);
|
||||
} else {
|
||||
@@ -523,7 +468,7 @@ if (regs.length === 0) {
|
||||
|
||||
```js
|
||||
// Registered domains with no workstreams yet — show a getting-started hint
|
||||
const regs = regsState ?? [];
|
||||
const regs = pageState.milestones ?? [];
|
||||
const registeredDomains = new Set(regs.map(e => e.detail?.domain).filter(Boolean));
|
||||
const emptyRegistered = (summary.topics ?? []).filter(t =>
|
||||
registeredDomains.has(t.domain_slug) && (t.workstreams ?? []).length === 0
|
||||
|
||||
Reference in New Issue
Block a user