--- title: Workstreams --- ```js const API = "http://127.0.0.1:8000"; const POLL = 15_000; ``` ```js // Fetch workstreams + topics in parallel, join on topic_id → domain/title const wsState = (async function*() { while (true) { let data = [], ok = false; try { const [rw, rt] = await Promise.all([ fetch(`${API}/workstreams/`), fetch(`${API}/topics/`), ]); ok = rw.ok && rt.ok; if (ok) { const [wsList, topicList] = await Promise.all([rw.json(), rt.json()]); const topicMap = Object.fromEntries(topicList.map(t => [t.id, t])); data = wsList.map(w => ({ ...w, domain: topicMap[w.topic_id]?.domain ?? "unknown", topic_title: topicMap[w.topic_id]?.title ?? "—", })); } } catch {} yield {data, ok, ts: new Date()}; await new Promise(res => setTimeout(res, POLL)); } })(); ``` ```js const data = wsState.data ?? []; const _ok = wsState.ok ?? false; const _ts = wsState.ts; ``` # Workstreams ```js display(html`
${_ok ? `Live · updated ${_ts?.toLocaleTimeString()}` : `Offline — run: make api`}
`); ``` ```js const domainOpts = ["(all)", ...new Set(data.map(w => w.domain))].sort(); const statusOpts = ["(all)", "active", "blocked", "completed", "archived"]; const domainFilter = view(Inputs.select(domainOpts, {label: "Domain"})); const statusFilter = view(Inputs.select(statusOpts, {label: "Status"})); const ownerFilter = view(Inputs.text({label: "Owner contains"})); ``` ```js const filtered = data.filter(w => (domainFilter === "(all)" || w.domain === domainFilter) && (statusFilter === "(all)" || w.status === statusFilter) && (!ownerFilter || (w.owner ?? "").toLowerCase().includes(ownerFilter.toLowerCase())) ); display(Inputs.table(filtered.map(w => ({ Title: w.title, Domain: w.domain, Status: w.status, Owner: w.owner ?? "—", Due: w.due_date ?? "—", Updated: new Date(w.updated_at).toLocaleDateString(), })), {rows: 20})); ``` ## Status Distribution ```js import * as Plot from "npm:@observablehq/plot"; const byStatus = Object.entries( filtered.reduce((acc, w) => { acc[w.status] = (acc[w.status] ?? 0) + 1; return acc; }, {}) ).map(([status, count]) => ({status, count})); display(Plot.plot({ marks: [ Plot.barX(byStatus, {y: "status", x: "count", fill: "status", tip: true}), Plot.ruleX([0]), ], marginLeft: 80, width: 500, })); ```