fix(webui): sp8 review round, an organization page starts its workspace read beside its own reads, a Dashboard tile with no figures read says so, the Organizations heading gets its jump target from secH (#1873, lane sp8-kpis)
This commit was merged in pull request #1972.
This commit is contained in:
1 parent
9400e0d94d
commit
c66f1ec00c
1 file changed
+13
-9
@@ -156,7 +156,7 @@
|
|||||||
const titleCase = s => String(s || '').replace(/(^|[-_\s])(\w)/g, (_, sep, c) => `${sep ? ' ' : ''}${c.toUpperCase()}`);
|
const titleCase = s => String(s || '').replace(/(^|[-_\s])(\w)/g, (_, sep, c) => `${sep ? ' ' : ''}${c.toUpperCase()}`);
|
||||||
const clock = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
const clock = () => new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
||||||
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
const plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
||||||
const secH = (title, note = '', link = '') => `<div class="ws-sec-h"><h2>${title}</h2>${note ? `<span class="note">${note}</span>` : ''}${link}</div>`;
|
const secH = (title, note = '', link = '', attrs = '') => `<div class="ws-sec-h"><h2${attrs ? ` ${attrs}` : ''}>${title}</h2>${note ? `<span class="note">${note}</span>` : ''}${link}</div>`;
|
||||||
|
|
||||||
function settingsList(org, ctx) {
|
function settingsList(org, ctx) {
|
||||||
const { business, settings, instances, orgCount } = ctx;
|
const { business, settings, instances, orgCount } = ctx;
|
||||||
@@ -256,15 +256,17 @@
|
|||||||
if (!org) return `<article class="ws ws-dash" data-org-dashboard data-org-unavailable><div class="ws-vh"><h1 tabindex="-1">Organization unavailable</h1><p class="sub">${list ? 'This organization is not on your list.' : 'The organization list could not be read.'}</p></div></article>`;
|
if (!org) return `<article class="ws ws-dash" data-org-dashboard data-org-unavailable><div class="ws-vh"><h1 tabindex="-1">Organization unavailable</h1><p class="sub">${list ? 'This organization is not on your list.' : 'The organization list could not be read.'}</p></div></article>`;
|
||||||
// E2E6 P2-8: a stopped bus is not read for the bus-backed editors; each says the bus is stopped.
|
// E2E6 P2-8: a stopped bus is not read for the bus-backed editors; each says the bus is stopped.
|
||||||
const stopped = !org.busHealthy && org.busStopped === true;
|
const stopped = !org.busHealthy && org.busStopped === true;
|
||||||
|
// SP8 (#1873): the cross-organization read scoped to this one gives the tiles' figures and, SP1,
|
||||||
|
// while the bus is down, the reason the banner above everything else says. It runs beside the
|
||||||
|
// organization's own reads, not after them.
|
||||||
|
const wsRead = data.workspace({ org: id, sections: ['sessions', 'projects', 'metrics'] }).catch(() => null);
|
||||||
const reads = await Promise.allSettled([data.business(id), data.settings(id), data.instances(id), data.threads(id), data.projects(id), data.queue(id), data.agents(id), data.claims(id), ...(stopped ? [] : [window.MosaicAutonomy?.load(id), window.MosaicPacks?.load(id), window.MosaicWorkflows?.load(id)])]);
|
const reads = await Promise.allSettled([data.business(id), data.settings(id), data.instances(id), data.threads(id), data.projects(id), data.queue(id), data.agents(id), data.claims(id), ...(stopped ? [] : [window.MosaicAutonomy?.load(id), window.MosaicPacks?.load(id), window.MosaicWorkflows?.load(id)])]);
|
||||||
const value = i => (reads[i].status === 'fulfilled' ? reads[i].value : null);
|
const value = i => (reads[i].status === 'fulfilled' ? reads[i].value : null);
|
||||||
const business = value(0), settings = value(1), instances = instancesOf(value(2));
|
const business = value(0), settings = value(1), instances = instancesOf(value(2));
|
||||||
const threads = rowsOf(value(3)), projectRows = rowsOf(value(4)), queue = rowsOf(value(5));
|
const threads = rowsOf(value(3)), projectRows = rowsOf(value(4)), queue = rowsOf(value(5));
|
||||||
const agents = rowsOf(value(6)), claims = rowsOf(value(7));
|
const agents = rowsOf(value(6)), claims = rowsOf(value(7));
|
||||||
const docRead = business?.business ? await docs(id, business.business.projects).catch(() => ({ rows: [], failed: true })) : null;
|
const docRead = business?.business ? await docs(id, business.business.projects).catch(() => ({ rows: [], failed: true })) : null;
|
||||||
// SP8 (#1873): the cross-organization read scoped to this one gives the tiles' figures and, SP1,
|
const ws = await wsRead;
|
||||||
// while the bus is down, the reason the banner above everything else says.
|
|
||||||
const ws = await data.workspace({ org: id, sections: ['sessions', 'projects', 'metrics'] }).catch(() => null);
|
|
||||||
const wsRow = arr(ws?.orgs).find(r => r?.org === id) ?? null;
|
const wsRow = arr(ws?.orgs).find(r => r?.org === id) ?? null;
|
||||||
const downRow = org.busHealthy ? null : wsRow;
|
const downRow = org.busHealthy ? null : wsRow;
|
||||||
await window.MosaicConsole?.ready;
|
await window.MosaicConsole?.ready;
|
||||||
@@ -653,6 +655,8 @@
|
|||||||
const parts = (key, f) => { const ok = readers(o, SRC[key]); return per.filter(x => ok.has(x.org)).map(x => ({ id: x.org, n: num(f(x)) })); };
|
const parts = (key, f) => { const ok = readers(o, SRC[key]); return per.filter(x => ok.has(x.org)).map(x => ({ id: x.org, n: num(f(x)) })); };
|
||||||
const sum = ps => ps.reduce((a, p) => a + p.n, 0);
|
const sum = ps => ps.reduce((a, p) => a + p.n, 0);
|
||||||
const fig = (key, ps) => (m && readers(o, SRC[key]).size ? sum(ps) : null);
|
const fig = (key, ps) => (m && readers(o, SRC[key]).size ? sum(ps) : null);
|
||||||
|
// A response with no metrics section leaves every figure tile quiet; it says so rather than a bare "Not read".
|
||||||
|
const noFig = m ? {} : { none: 'Not read: no figures were read' };
|
||||||
const answered = o.rows.filter(r => r.state === 'ok').length;
|
const answered = o.rows.filter(r => r.state === 'ok').length;
|
||||||
// Agents working of all agents: the system Agents page's rows, read the way that page reads them.
|
// Agents working of all agents: the system Agents page's rows, read the way that page reads them.
|
||||||
// An organization whose agent read failed is named, never counted as having none.
|
// An organization whose agent read failed is named, never counted as having none.
|
||||||
@@ -666,10 +670,10 @@
|
|||||||
const tiles = !o.rows.length ? '<p class="ws-panel tight note" data-ws-tiles>No organizations yet, so there is nothing to count.</p>' : `<div class="ws-tiles six" data-ws-tiles role="group" aria-label="Key figures">
|
const tiles = !o.rows.length ? '<p class="ws-panel tight note" data-ws-tiles>No organizations yet, so there is nothing to count.</p>' : `<div class="ws-tiles six" data-ws-tiles role="group" aria-label="Key figures">
|
||||||
${kpiTile(o, { key: 'agents', name: 'Agents working', href: '#/agents?state=working', big: agentOk.length ? sum(agentParts) : null, small: `of ${agentAll.length}`, parts: agentParts, delta: waitingOn ? `${waitingOn} waiting on you` : '', miss: [...notIn(['agents']), ...agentReads.filter(r => !r.inst).map(r => `${esc(o.name(r.id))} (${esc(instWhy(r))})`)] })}
|
${kpiTile(o, { key: 'agents', name: 'Agents working', href: '#/agents?state=working', big: agentOk.length ? sum(agentParts) : null, small: `of ${agentAll.length}`, parts: agentParts, delta: waitingOn ? `${waitingOn} waiting on you` : '', miss: [...notIn(['agents']), ...agentReads.filter(r => !r.inst).map(r => `${esc(o.name(r.id))} (${esc(instWhy(r))})`)] })}
|
||||||
${kpiTile(o, { key: 'orgs', name: 'Organizations', href: '#/dashboard', big: answered, small: `of ${o.rows.length} running`, delta: o.unread.length ? andList(o.unread.map(r => `${esc(o.name(r.org))} ${offWord(r)}`)) : 'every bus answered', attrs: ' data-ws-jump="orgs"' })}
|
${kpiTile(o, { key: 'orgs', name: 'Organizations', href: '#/dashboard', big: answered, small: `of ${o.rows.length} running`, delta: o.unread.length ? andList(o.unread.map(r => `${esc(o.name(r.org))} ${offWord(r)}`)) : 'every bus answered', attrs: ' data-ws-jump="orgs"' })}
|
||||||
${kpiTile(o, { key: 'needs', name: 'Needs you', href: '#/inbox', big: needsN, small: needsN === 1 ? 'item' : 'items', parts: ps.needs, delta: needsN && t?.oldestNeedsYouAt ? `oldest ${span(t.oldestNeedsYouAt)}` : 'nothing waiting', miss: notIn(SRC.needs) })}
|
${kpiTile(o, { key: 'needs', name: 'Needs you', href: '#/inbox', big: needsN, small: needsN === 1 ? 'item' : 'items', parts: ps.needs, delta: needsN && t?.oldestNeedsYouAt ? `oldest ${span(t.oldestNeedsYouAt)}` : 'nothing waiting', miss: notIn(SRC.needs), ...noFig })}
|
||||||
${kpiTile(o, { key: 'tasks', name: 'Open tasks', href: '#/tasks', big: fig('tasks', ps.tasks), parts: ps.tasks, miss: notIn(SRC.tasks) })}
|
${kpiTile(o, { key: 'tasks', name: 'Open tasks', href: '#/tasks', big: fig('tasks', ps.tasks), parts: ps.tasks, miss: notIn(SRC.tasks), ...noFig })}
|
||||||
${kpiTile(o, { key: 'done', name: 'Done this week', href: '#/tasks', big: fig('done', ps.done), parts: ps.done, miss: notIn(SRC.done) })}
|
${kpiTile(o, { key: 'done', name: 'Done this week', href: '#/tasks', big: fig('done', ps.done), parts: ps.done, miss: notIn(SRC.done), ...noFig })}
|
||||||
${kpiTile(o, { key: 'projects', name: 'Projects', href: selHref('/projects'), big: fig('projects', ps.projects), small: 'active', parts: ps.projects, delta: `${archived} archived`, miss: notIn(SRC.projects) })}
|
${kpiTile(o, { key: 'projects', name: 'Projects', href: selHref('/projects'), big: fig('projects', ps.projects), small: 'active', parts: ps.projects, delta: `${archived} archived`, miss: notIn(SRC.projects), ...noFig })}
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
// SP1 (sample r2-01): one row per organization. A figure shows only when its read gave it; an
|
// SP1 (sample r2-01): one row per organization. A figure shows only when its read gave it; an
|
||||||
@@ -726,7 +730,7 @@
|
|||||||
${tiles}
|
${tiles}
|
||||||
<div class="ws-split ws-home-split">
|
<div class="ws-split ws-home-split">
|
||||||
<section aria-label="Organizations, projects and tasks">
|
<section aria-label="Organizations, projects and tasks">
|
||||||
${secH('Organizations', `read ${esc(hm(body.readAt))}`).replace('<h2>', '<h2 tabindex="-1" data-ws-orgs-h data-focus-key="orgs-h">')}
|
${secH('Organizations', `read ${esc(hm(body.readAt))}`, '', 'tabindex="-1" data-ws-orgs-h data-focus-key="orgs-h"')}
|
||||||
${orgList}
|
${orgList}
|
||||||
${secH('Recent project activity', 'latest change per project; there is no activity feed yet', `<a href="${esc(selHref('/projects'))}" data-focus-key="home-projects">All projects</a>`)}
|
${secH('Recent project activity', 'latest change per project; there is no activity feed yet', `<a href="${esc(selHref('/projects'))}" data-focus-key="home-projects">All projects</a>`)}
|
||||||
${trail}${missLine(o, ['projects'], 'recent')}
|
${trail}${missLine(o, ['projects'], 'recent')}
|
||||||
|
|||||||
Reference in new issue
Block a user