Merge pull request 'Make actionable delivery recovery a first-class mobile queue' (#1013) from timmy/1012-mobile-delivery-recovery-queue into main
This commit is contained in:
commit
c0a02a05d8
|
|
@ -1531,6 +1531,7 @@
|
|||
<div class="mobile-queue-list">
|
||||
<button data-mobile-queue="today" type="button"><span><strong>Today</strong><small>Planned work</small></span><span data-mobile-queue-count="today">0</span></button>
|
||||
<button data-mobile-queue="agenda" type="button"><span><strong>Agenda</strong><small>Upcoming deadlines</small></span><span data-mobile-queue-count="agenda">0</span></button>
|
||||
<button data-mobile-queue="delivery" type="button"><span><strong>Delivery</strong><small>Needs recovery</small></span><span data-mobile-queue-count="delivery">0</span></button>
|
||||
<button data-mobile-queue="attention" type="button"><span><strong>Attention</strong><small>Needs a response</small></span><span data-mobile-queue-count="attention">0</span></button>
|
||||
<button data-mobile-queue="update" type="button" aria-label="Updates, 0 unread conversations"><span><strong>Updates</strong><small>Unread conversations</small></span><span data-mobile-queue-count="update">0</span></button>
|
||||
<button data-mobile-queue="filed" type="button"><span><strong>Filed</strong><small>Issues you delegated</small></span><span data-mobile-queue-count="filed">0</span></button>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
draft: 'No drafts are ready to open.',
|
||||
};
|
||||
const continuation = [
|
||||
['delivery', 'Recover Delivery'],
|
||||
['attention', 'Start Attention'],
|
||||
['today', 'Continue Today'],
|
||||
['update', 'Resume Updates'],
|
||||
|
|
|
|||
|
|
@ -66,9 +66,9 @@
|
|||
function updateWork(mode) {
|
||||
const text = {
|
||||
continue:'Continue', resume:'Resume', start:'Start', plan:'Plan', find:'Find',
|
||||
attention:'Attention', update:'Updates', agenda:'Agenda', filed:'Filed', later:'Later', draft:'Drafts',
|
||||
delivery:'Delivery', attention:'Attention', update:'Updates', agenda:'Agenda', filed:'Filed', later:'Later', draft:'Drafts',
|
||||
}[mode] || 'Work';
|
||||
const queue = ['Attention', 'Updates', 'Agenda', 'Filed', 'Later', 'Drafts'].includes(text);
|
||||
const queue = ['Delivery', 'Attention', 'Updates', 'Agenda', 'Filed', 'Later', 'Drafts'].includes(text);
|
||||
if (options.workLabel) options.workLabel.textContent = text;
|
||||
if (buttons.work) buttons.work.setAttribute('aria-label', queue
|
||||
? (mode === 'update' ? 'Resume ' : 'Open ') + text
|
||||
|
|
@ -91,9 +91,9 @@
|
|||
}
|
||||
|
||||
function updateQueues(counts) {
|
||||
const names = 'today agenda attention update filed later draft'.split(' ');
|
||||
const names = 'today agenda delivery attention update filed later draft'.split(' ');
|
||||
const normalized = Object.fromEntries(names.map(name => [name, Math.max(0, Number(counts?.[name]) || 0)]));
|
||||
const actionableNames = ['today', 'attention', 'update', 'filed', 'later', 'draft'];
|
||||
const actionableNames = ['today', 'delivery', 'attention', 'update', 'filed', 'later', 'draft'];
|
||||
const active = actionableNames.reduce((total, name) => total + (normalized[name] > 0 ? 1 : 0), 0);
|
||||
Object.entries(options.queueCounts || {}).forEach(([name, element]) => {
|
||||
element.textContent = String(normalized[name] || 0);
|
||||
|
|
@ -117,6 +117,7 @@
|
|||
? 'Queues: no active queues; no upcoming deadlines'
|
||||
: 'Queues: Today ' + normalized.today
|
||||
+ ', Agenda ' + normalized.agenda + ' due'
|
||||
+ ', Delivery ' + normalized.delivery
|
||||
+ ', Attention ' + normalized.attention
|
||||
+ ', Updates ' + normalized.update
|
||||
+ ', Filed ' + normalized.filed
|
||||
|
|
|
|||
|
|
@ -101,6 +101,15 @@ def test_release_artifact_bootstraps_mobile_home_and_returns_from_insights(
|
|||
bounds = control.bounding_box()
|
||||
assert bounds and bounds["height"] >= 44
|
||||
|
||||
page.locator('[data-mobile-task="queues"]').click()
|
||||
delivery_queue = page.locator('[data-mobile-queue="delivery"]')
|
||||
expect(delivery_queue).to_be_visible()
|
||||
expect(delivery_queue).to_contain_text("Delivery")
|
||||
delivery_bounds = delivery_queue.bounding_box()
|
||||
assert delivery_bounds and delivery_bounds["height"] >= 44
|
||||
assert page.evaluate("document.documentElement.scrollWidth <= window.innerWidth")
|
||||
page.locator("#close-mobile-queues").click()
|
||||
|
||||
page.locator("#app-menu-toggle").click()
|
||||
page.locator("#open-insights").click()
|
||||
expect(page.locator("#insights-sheet")).to_be_visible()
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@ sheet.close = function () {{ this.open = false; this.listeners.close?.(); }};
|
|||
const close = new FakeElement();
|
||||
const badge = new FakeElement();
|
||||
const deadline = new FakeElement();
|
||||
const rows = Object.fromEntries(['today','agenda','attention','update','filed','later','draft','recaps'].map(name => [name, new FakeElement()]));
|
||||
const rows = Object.fromEntries(['today','agenda','delivery','attention','update','filed','later','draft','recaps'].map(name => [name, new FakeElement()]));
|
||||
const counts = Object.fromEntries(Object.keys(rows).map(name => [name, new FakeElement()]));
|
||||
const selected = [];
|
||||
const utilities = [];
|
||||
|
|
@ -366,7 +366,7 @@ const dock = createDock({{
|
|||
observe() {{}},
|
||||
}});
|
||||
dock.start();
|
||||
dock.updateQueues({{today:2, agenda:5, attention:1, update:5, filed:2, later:3, draft:4}});
|
||||
dock.updateQueues({{today:2, agenda:5, delivery:1, attention:1, update:5, filed:2, later:3, draft:4}});
|
||||
queues.click();
|
||||
const opened = sheet.open;
|
||||
rows.update.click();
|
||||
|
|
@ -411,9 +411,9 @@ process.stdout.write(JSON.stringify({{
|
|||
"utilities": ["recaps:trigger"],
|
||||
"badge": "0 active",
|
||||
"populated": {
|
||||
"badge": "6 active",
|
||||
"badge": "7 active",
|
||||
"badgeHidden": False,
|
||||
"badgeLabel": "Queues: Today 2, Agenda 5 due, Attention 1, Updates 5, Filed 2, Later 3, Drafts 4; 6 active queues",
|
||||
"badgeLabel": "Queues: Today 2, Agenda 5 due, Delivery 1, Attention 1, Updates 5, Filed 2, Later 3, Drafts 4; 7 active queues",
|
||||
"deadline": "5 due",
|
||||
"deadlineHidden": False,
|
||||
"agendaDue": "true",
|
||||
|
|
@ -422,13 +422,13 @@ process.stdout.write(JSON.stringify({{
|
|||
"badge": "1 active",
|
||||
"badgeHidden": False,
|
||||
"deadlineHidden": True,
|
||||
"badgeLabel": "Queues: Today 0, Agenda 0 due, Attention 0, Updates 7, Filed 0, Later 0, Drafts 0; 1 active queue",
|
||||
"badgeLabel": "Queues: Today 0, Agenda 0 due, Delivery 0, Attention 0, Updates 7, Filed 0, Later 0, Drafts 0; 1 active queue",
|
||||
},
|
||||
"clearedBadgeHidden": True,
|
||||
"clearedDeadlineHidden": True,
|
||||
"clearedAgendaDue": None,
|
||||
"clearedBadgeLabel": "Queues: no active queues; no upcoming deadlines",
|
||||
"counts": {"today": "0", "agenda": "0", "attention": "0", "update": "0", "filed": "0", "later": "0", "draft": "0", "recaps": "0"},
|
||||
"counts": {"today": "0", "agenda": "0", "delivery": "0", "attention": "0", "update": "0", "filed": "0", "later": "0", "draft": "0", "recaps": "0"},
|
||||
"updateLabel": "Updates, 0 unread conversations",
|
||||
"queueFocuses": 3,
|
||||
"columnState": None,
|
||||
|
|
@ -503,6 +503,37 @@ process.stdout.write(JSON.stringify({{result, calls}}));
|
|||
assert json.loads(result.stdout) == {"result": "opened", "calls": ["delivery-recovery"]}
|
||||
|
||||
|
||||
def test_mobile_work_prioritizes_and_revalidates_actionable_delivery_recovery():
|
||||
script = f"""
|
||||
const createLauncher = require({json.dumps(str(QUEUE_LAUNCHER))});
|
||||
const calls = [];
|
||||
let counts = {{delivery:2, attention:3, today:1, update:4}};
|
||||
const launcher = createLauncher({{
|
||||
getCounts: () => counts,
|
||||
openDelivery: () => {{ calls.push('delivery-recovery'); return 'opened-delivery'; }},
|
||||
openUpdates: () => {{ calls.push('updates'); return 'opened-updates'; }},
|
||||
selectFilter: name => calls.push('filter:' + name),
|
||||
firstAction: () => null,
|
||||
announce: () => {{}},
|
||||
openFindWork: () => calls.push('find'),
|
||||
}});
|
||||
const delivery = launcher.recommend();
|
||||
const opened = launcher.continueWork();
|
||||
counts = {{delivery:0, attention:0, today:0, update:4}};
|
||||
const afterRecovery = launcher.recommend();
|
||||
process.stdout.write(JSON.stringify({{delivery, opened, afterRecovery, calls}}));
|
||||
"""
|
||||
result = subprocess.run(["node", "-e", script], capture_output=True, text=True)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"delivery": {"name": "delivery", "count": 2, "label": "Recover Delivery (2)"},
|
||||
"opened": "opened-delivery",
|
||||
"afterRecovery": {"name": "update", "count": 4, "label": "Resume Updates (4)"},
|
||||
"calls": ["delivery-recovery"],
|
||||
}
|
||||
|
||||
|
||||
def test_mobile_work_continues_into_filed_follow_up_before_later_work():
|
||||
script = f"""
|
||||
const createLauncher = require({json.dumps(str(QUEUE_LAUNCHER))});
|
||||
|
|
@ -600,7 +631,7 @@ const work = {{attributes: {{}}, setAttribute(name, value) {{ this.attributes[na
|
|||
const workLabel = {{textContent:''}};
|
||||
const dock = createDock({{nav:{{}}, buttons:{{work}}, workLabel}});
|
||||
const labels = {{}};
|
||||
for (const mode of ['attention','update','agenda','filed','later','draft']) {{
|
||||
for (const mode of ['delivery','attention','update','agenda','filed','later','draft']) {{
|
||||
dock.updateWork(mode);
|
||||
labels[mode] = [workLabel.textContent, work.attributes['aria-label']];
|
||||
}}
|
||||
|
|
@ -610,6 +641,7 @@ process.stdout.write(JSON.stringify(labels));
|
|||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"delivery": ["Delivery", "Open Delivery"],
|
||||
"attention": ["Attention", "Open Attention"],
|
||||
"update": ["Updates", "Resume Updates"],
|
||||
"agenda": ["Agenda", "Open Agenda"],
|
||||
|
|
@ -646,6 +678,9 @@ async def test_dashboard_renders_and_wires_mobile_queue_switcher():
|
|||
assert "counts.agenda = agendaItems.length;" in html
|
||||
assert 'data-agenda-group' in html
|
||||
assert 'data-mobile-queue="attention"' in html
|
||||
assert 'data-mobile-queue="delivery"' in html
|
||||
assert '<strong>Delivery</strong><small>Needs recovery</small>' in html
|
||||
assert 'data-mobile-queue-count="delivery"' in html
|
||||
assert 'data-mobile-queue="update"' in html
|
||||
assert '<strong>Updates</strong><small>Unread conversations</small>' in html
|
||||
assert 'data-mobile-queue="filed"' in html
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user