fix(webui): sp11 review 2, the Sign out form drops kept pane messages as an expired session does, and the page it unloads keeps none; when neither bus read gives agent activity, an organization page's sessions heading says working was not read instead of a figure (#1873, lane sp11-fixes)
This commit is contained in:
1 parent
f73c761ad3
commit
2d7d7be5ca
4 files changed
+43
-7
No files matched your search
@@ -311,16 +311,19 @@
|
||||
})();
|
||||
const saveKept = () => { try { if (kept.size) sessionStorage.setItem(DRAFTS, JSON.stringify(Object.fromEntries(kept))); else sessionStorage.removeItem(DRAFTS); } catch { /* the Map still holds it */ } };
|
||||
const typedIn = p => p.chat?.draft?.() || p.draft || p.el?.querySelector('form textarea')?.value || '';
|
||||
// Signed out: the kept drafts go and nothing is kept again, so the next person to sign in on this
|
||||
// tab never sees text typed before.
|
||||
// Signed out, by an expired session or by the Sign out form: the kept drafts go and nothing is kept
|
||||
// again, not even by the pagehide the form's own submit causes, so the next person to sign in on
|
||||
// this tab never sees text typed before.
|
||||
let draftsOff = false;
|
||||
function keep(p) { if (draftsOff) return; const text = typedIn(p); if (text.trim()) kept.set(p.key, text); else kept.delete(p.key); saveKept(); }
|
||||
function drop(p) { if (kept.delete(p.key)) saveKept(); }
|
||||
window.addEventListener('pagehide', () => { if (mounted) for (const p of mounted.panes) keep(p); });
|
||||
document.addEventListener('mosaic-signed-out', () => {
|
||||
function forgetDrafts() {
|
||||
draftsOff = true; kept.clear(); saveKept();
|
||||
if (mounted) for (const p of mounted.panes) { p.draft = ''; const t = p.el?.querySelector('form textarea'); if (t) t.value = ''; }
|
||||
});
|
||||
}
|
||||
document.addEventListener('mosaic-signed-out', forgetDrafts);
|
||||
document.addEventListener('submit', e => { if (e.target?.id === 'sign-out') forgetDrafts(); }, true);
|
||||
function makePane(key) {
|
||||
const { org, id } = split(key);
|
||||
const p = { key, org, id, n: ++paneSeq, el: null, chat: null, first: true, title: null, draft: kept.get(key) || '', held: false, seq: 0 };
|
||||
|
||||
@@ -317,7 +317,8 @@
|
||||
const copyLive = Object.entries(copiesOf(value(2))).filter(([, c]) => c && typeof c.name === 'string' && !copyBlocked(c) && running(c)).map(([key, c]) => [key, c, c.name]);
|
||||
const live = instances ? [...Object.entries(instances).filter(([, item]) => running(item)).map(([key, item]) => [key, item, key]), ...copyLive] : null;
|
||||
// The sessions heading and rows take their activity from the session list the tile counts, so the
|
||||
// two never disagree. When the tile shows no figure they use the bus agents read instead.
|
||||
// two never disagree. When the tile shows no figure they use the bus agents read instead; when
|
||||
// that failed too, the heading says working was not read rather than a figure.
|
||||
const tileAgents = orgAgents && !agentsWhy ? arr(ws.sessions).filter(x => x?.kind === 'role' && x.org === id && typeof x.role === 'string').map(x => ({ role: x.role, holder_run: x.run, state: x.state })) : agents;
|
||||
const liveWorking = live ? live.filter(([key, item]) => activity(key, item, tileAgents)[2] === 'working').length : 0;
|
||||
// A session's project and task is the thread its live run holds a claim on (thread.claims), not
|
||||
@@ -352,7 +353,7 @@
|
||||
${docrow}
|
||||
<div class="ws-split">
|
||||
<section aria-label="Measures and sessions">
|
||||
${secH('Active agent sessions', live ? (live.length ? `${live.length} running, ${liveWorking} working` : 'none running') : '', `<a href="#/agents?${oq}" data-focus-key="agents-all">This organization's agents</a>`)}
|
||||
${secH('Active agent sessions', live ? (live.length ? `${live.length} running${tileAgents ? `, ${liveWorking} working` : ', working not read'}` : 'none running') : '', `<a href="#/agents?${oq}" data-focus-key="agents-all">This organization's agents</a>`)}
|
||||
${sessions}
|
||||
${secH('Queue', queueNote, '<a href="#/queue" data-org-queue data-focus-key="queue-open">Open queue</a>')}
|
||||
${queueWhy}
|
||||
|
||||
@@ -438,6 +438,30 @@ test('SP11: signing out drops kept pane messages, and a reload after it opens th
|
||||
assert.deepEqual(state.sent, []);
|
||||
});
|
||||
|
||||
// SP11 review: the Sign out form is a plain POST that unloads the page; its submit drops the kept
|
||||
// messages and the pagehide it causes keeps nothing, so a sign-in after it opens the pane empty.
|
||||
test('SP11: the Sign out form drops kept pane messages, and the page it unloads keeps none', async t => {
|
||||
const state = { down: new Set(), sent: [], created: [] };
|
||||
const a1 = `alpha:${T.a1}`, hash = `#/assistant?open=${a1}`;
|
||||
const { b, base, wait } = await open(t, { hash, state });
|
||||
await wait(`${ready(a1)} && !${field(a1)}.disabled`);
|
||||
await type(b, a1, 'private note');
|
||||
await b.evaluate(`location.hash = '#/tasks'`);
|
||||
await wait(`!document.querySelector('[data-assist-pane]')`);
|
||||
await b.evaluate(`location.hash = ${JSON.stringify(hash)}`);
|
||||
await wait(`${field(a1)}?.value === 'private note'`);
|
||||
await b.evaluate(`document.getElementById('sign-out').requestSubmit(), true`);
|
||||
// The POST unloads the page; poll from here, since the old page's context goes away under it.
|
||||
let left = false;
|
||||
for (let i = 0; i < 200 && !left; i++) { try { left = await b.evaluate(`location.pathname === '/auth/logout'`); } catch { /* navigating */ } if (!left) await new Promise(r => setTimeout(r, 30)); }
|
||||
assert.equal(left, true, 'the Sign out form posted');
|
||||
await b.navigate(base + '?again=1' + hash);
|
||||
await wait(`${ready(a1)} && !!${field(a1)} && !${field(a1)}.disabled`);
|
||||
assert.equal(await b.evaluate(`${field(a1)}.value`), '');
|
||||
assert.equal(await b.evaluate(`sessionStorage.getItem('mosaic-assistant-drafts')`), null);
|
||||
assert.deepEqual(state.sent, []);
|
||||
});
|
||||
|
||||
// SP11 (#1873): a pane whose message was sent but not yet shown in the conversation is not free: a
|
||||
// fourth thread replaces the first pane that holds nothing, and that pane only once its receipt is in.
|
||||
test('SP11: a pane whose send still waits for its receipt is not replaced by a fourth thread', async t => {
|
||||
|
||||
@@ -65,7 +65,7 @@ function fixture({ fail = {}, none = false, split = {} } = {}) {
|
||||
busHealthy: true,
|
||||
reads: { business: async () => ({ business: { id, human: 'jason', projects: DATA[id].projects.map(p => p.repo), appearance: { theme: org.theme } } }), settings: async () => ({}), queue: async () => ({ rows: [] }), busStatus: async () => ({}) },
|
||||
bus: {
|
||||
messages: async () => ({ messages: [], unread: 0 }), agents: async () => split[id] ?? DATA[id].agents, 'thread.claims': async () => [], inbox: async () => [], tasks: async () => [],
|
||||
messages: async () => ({ messages: [], unread: 0 }), agents: async () => { if (fail[id]?.includes('agents')) throw Object.assign(new Error('off'), { code: 'channel-disabled' }); return split[id] ?? DATA[id].agents; }, 'thread.claims': async () => [], inbox: async () => [], tasks: async () => [],
|
||||
instances: () => (fail[id]?.includes('instances') ? Promise.reject(Object.assign(new Error('off'), { code: 'bus-unavailable' })) : Promise.resolve({ revision: 'a'.repeat(64), definitions: {}, instances: DATA[id].instances })),
|
||||
},
|
||||
chat: { projects: async () => DATA[id].projects, threads: async () => [] },
|
||||
@@ -252,6 +252,14 @@ test('An organization page counts working from one read: the sessions heading an
|
||||
assert.match(await text('[data-org-dashboard] .ws-split tbody'), /coder[\s\S]*working/);
|
||||
});
|
||||
|
||||
test('When neither bus read gives agent activity, the sessions heading says working was not read, never a figure', async t => {
|
||||
const { b, wait, text } = await setup(t, { hash: '#/orgs/acme', fail: { acme: ['agents'] } });
|
||||
await wait('document.querySelector("[data-org-dashboard] [data-org-tiles] [data-ws-tile=agents]")');
|
||||
assert.match(await text('[data-org-tiles] [data-ws-tile=agents]'), /Not read/);
|
||||
assert.match(await text('[data-org-dashboard] .ws-split'), /Active agent sessions\s*1 running, working not read/);
|
||||
assert.doesNotMatch(await text('[data-org-dashboard] .ws-split'), /\d+ working/);
|
||||
});
|
||||
|
||||
test('A stopped organization\'s page shows its tiles as not read, still linking to its filtered pages', async t => {
|
||||
const { b, wait, text } = await setup(t, { hash: '#/orgs/gamma' });
|
||||
await wait('document.querySelector("[data-org-dashboard] [data-org-tiles]")');
|
||||
|
||||
Reference in new issue
Block a user