fix(webui): sp14 review, every drawer opened while its instances read is open repaints when that read answers: the pending drawer names the read it waits on, and each open repaints its own drawer if it still waits on that read, so a second agent opened before the answer no longer says Reading state until the next poll; the Settings card's Agents and roles row uses the same running-not-read check as the Active agent sessions heading, copies included; test opens chief while coder's read is held and sees chief's state at once (#1873, lane sp14-org-page-truth)
This commit was merged in pull request #1978.
This commit is contained in:
1 parent
2fc97a3edb
commit
c26879637a
2 files changed
+28
-12
No files matched your search
@@ -179,7 +179,7 @@
|
||||
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) {
|
||||
const { business, settings, instances, orgCount } = ctx;
|
||||
const { business, settings, instances, orgCount, noRuns } = ctx;
|
||||
const b = business?.business, s = settings;
|
||||
const unread = 'Could not be read';
|
||||
const names = instances ? Object.keys(instances) : null;
|
||||
@@ -192,7 +192,7 @@
|
||||
['business', 'Identity', b ? `${label(org)} · ${org.id} · kind not recorded` : unread, 'org file', 'business'],
|
||||
['flag', 'Appearance', `${titleCase(theme)} theme · ${initials(org)} avatar`, b?.appearance?.theme || org.theme ? 'org file' : 'system', 'appearance'],
|
||||
['folder', 'Projects', b ? (Array.isArray(b.projects) && b.projects.length ? plural(b.projects.length, 'project') : 'No projects') : unread, 'org file', '#/projects'],
|
||||
['people', 'Agents and roles', names ? (names.length ? `${plural(names.length, 'agent')} · ${runsUnread(instances) ? 'running not read' : `${Object.values(instances).filter(running).length} running`}` : 'No agents') : unread, 'org file', 'agents'],
|
||||
['people', 'Agents and roles', names ? (names.length ? `${plural(names.length, 'agent')} · ${noRuns ? 'running not read' : `${Object.values(instances).filter(running).length} running`}` : 'No agents') : unread, 'org file', 'agents'],
|
||||
['term', 'Models and routing', models ? (models.length ? models.join(' · ') : 'No models set') : unread, 'org file', 'routing'],
|
||||
['queue', 'Arbiters', b ? arbiters || 'None set' : unread, 'org file', 'business'],
|
||||
['tasks', 'Task tracker', b ? (b.vars?.['tracker.kind'] ? titleCase(b.vars['tracker.kind']) : 'Not set') : unread, 'org file', 'business'],
|
||||
@@ -374,7 +374,7 @@
|
||||
<section aria-label="Workflows and settings">
|
||||
${window.MosaicWorkflows?.card?.(id, { stopped, label: label(org) }) ?? ''}
|
||||
${secH('Settings', 'every setting, with where its value comes from')}
|
||||
${settingsList(org, { business, settings, instances, orgCount: list ? list.length : null })}
|
||||
${settingsList(org, { business, settings, instances, noRuns, orgCount: list ? list.length : null })}
|
||||
</section>
|
||||
</div>
|
||||
<div class="ws-wide">
|
||||
@@ -1143,10 +1143,10 @@
|
||||
// page for it: once the page's own reads have answered (`opts.page`), a drawer whose instances read
|
||||
// has not says its state is being read, and repaints when the read answers. That answer is kept for
|
||||
// the one repaint it asks for; a repaint while the read is still open waits on the same read.
|
||||
let drawerInst = null; // { org, done, value, p }
|
||||
let drawerInst = null, drawerReads = 0; // { org, n, done, value, p }
|
||||
const drawerInstRead = org => {
|
||||
if (drawerInst?.org === org) return drawerInst;
|
||||
const r = { org, done: false, value: null };
|
||||
const r = { org, n: ++drawerReads, done: false, value: null };
|
||||
r.p = data.instances(org).then(v => ({ inst: instancesOf(v), copies: copiesOf(v) }), err => ({ inst: null, copies: {}, err }))
|
||||
.then(v => { r.done = true; r.value = v; return v; });
|
||||
drawerInst = r;
|
||||
@@ -1166,12 +1166,12 @@
|
||||
await Promise.race([ir.p, opts.page ?? ir.p]);
|
||||
const pending = !ir.done;
|
||||
// SP14 (#1873): when the read answers, only the drawer repaints, from the reads it already has; the
|
||||
// page's own reads are not made again. A drawer closed before the read answered, or one whose read
|
||||
// a later open replaced, repaints nothing, and its answer is not kept for a later open.
|
||||
// page's own reads are not made again. Each open repaints its own drawer if it is still on screen and
|
||||
// still waits on this read (data-drawer-pending names the read), so a second agent opened while the
|
||||
// read was open repaints too. A closed drawer repaints nothing, and the answer is not kept for a later open.
|
||||
if (pending) ir.p.then(() => {
|
||||
const mine = drawerInst === ir;
|
||||
if (mine) drawerInst = null;
|
||||
const node = mine ? document.querySelector(`#bus-view [data-ws-drawer="${CSS.escape(id)}"][data-drawer-pending]`) : null;
|
||||
if (drawerInst === ir) drawerInst = null;
|
||||
const node = document.querySelector(`#bus-view [data-ws-drawer="${CSS.escape(id)}"][data-drawer-pending="${ir.n}"]`);
|
||||
if (!node) return;
|
||||
const key = focusKey(node), tpl = document.createElement('template');
|
||||
tpl.innerHTML = drawerHtml(pick, { body, business, config, pending: false, instRead: ir.value });
|
||||
@@ -1181,7 +1181,7 @@
|
||||
refocus(fresh, key);
|
||||
});
|
||||
else if (drawerInst === ir) drawerInst = null;
|
||||
return drawerHtml(pick, { body, business, config, pending, instRead: pending ? null : ir.value });
|
||||
return drawerHtml(pick, { body, business, config, pending: pending && ir.n, instRead: pending ? null : ir.value });
|
||||
}
|
||||
function drawerHtml(pick, { body, business, config, pending, instRead }) {
|
||||
const { org, key, id } = pick;
|
||||
@@ -1253,7 +1253,7 @@
|
||||
return shell(a.name, `<dl class="ws-drawer-fields">${fields}</dl>
|
||||
<section class="ws-drawer-sec" data-drawer-tasks aria-label="Open tasks assigned to ${esc(a.name)}"><h3>Open tasks</h3>${tasksBody}${tasksAll}</section>
|
||||
<section class="ws-drawer-sec" data-drawer-decisions aria-label="Decisions ${esc(a.name)} raised"><h3>Decisions waiting on you</h3>${decBody}</section>
|
||||
${actions}`, ` data-drawer-org-id="${esc(org)}" data-drawer-name="${esc(a.name)}" data-drawer-org-name="${esc(orgName)}"${pending ? ' data-drawer-pending' : ''}`);
|
||||
${actions}`, ` data-drawer-org-id="${esc(org)}" data-drawer-name="${esc(a.name)}" data-drawer-org-name="${esc(orgName)}"${pending ? ` data-drawer-pending="${pending}"` : ''}`);
|
||||
}
|
||||
// Closing goes Back when a row link on this page pushed the current entry; otherwise it drops
|
||||
// `agent` from the address in place. Either way the page repaints without it and focus returns to the row.
|
||||
|
||||
@@ -218,6 +218,22 @@ test('while the drawer\'s instances read is open it says the state is being read
|
||||
assert.equal(await b.evaluate('document.activeElement?.dataset?.focusKey'), 'drawer-close');
|
||||
});
|
||||
|
||||
test('SP14 review: another agent opened while the drawer\'s instances read is open repaints when that read answers', async t => {
|
||||
const { b, wait, text, has } = await setup(t, { hash: '#/orgs/acme/organization?agent=acme%3Acoder', inst: 'hold' });
|
||||
await wait(`${settled} && document.querySelector('[data-ws-drawer="acme:coder"][data-drawer-pending]')`);
|
||||
// Chief opens before the held read answers; its drawer waits on the same read.
|
||||
await b.evaluate('location.hash = "#/orgs/acme/organization?agent=acme%3Achief"');
|
||||
const d = '[data-ws-drawer="acme:chief"]';
|
||||
await wait(`document.querySelector('${d}[data-drawer-pending]')`);
|
||||
assert.equal(await text(`${d} [data-drawer-state]`), 'Reading state…');
|
||||
// The read answers. Chief's drawer repaints from it at once, not at the next poll.
|
||||
await b.evaluate('window.__release()');
|
||||
await b.evaluate('new Promise(r => setTimeout(r, 1000))');
|
||||
assert.equal(await has(`${d}[data-drawer-pending]`), false, 'chief\'s drawer still says Reading state… after its read answered');
|
||||
assert.match(await text(`${d} [data-drawer-state]`), /not running/);
|
||||
assert.equal(await has('[data-ws-drawer="acme:coder"]'), false);
|
||||
});
|
||||
|
||||
test('a drawer closed before its instances read answers keeps no answer for the next open', async t => {
|
||||
const idle = 'window.__heldIdle = true;';
|
||||
const { b, wait, text } = await setup(t, { hash: '#/orgs/acme/organization?agent=acme%3Acoder', inst: 'hold', scripts: [idle] });
|
||||
|
||||
Reference in new issue
Block a user