feat: assign and start available work (#411)
This commit is contained in:
1 parent
985472eb06
commit
4afe4dd3b9
7 files changed
+229
-7
No files matched your search
@@ -0,0 +1,144 @@
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ASSIGN_AND_START = Path(__file__).parents[1] / "frontend" / "assign-and-start.js"
|
||||
DASHBOARD = Path(__file__).parents[1] / "frontend" / "dashboard.js"
|
||||
CSS = Path(__file__).parents[1] / "frontend" / "dashboard.css"
|
||||
WORKER = Path(__file__).parents[1] / "frontend" / "service-worker.js"
|
||||
|
||||
|
||||
def run_node(script):
|
||||
return json.loads(subprocess.run(
|
||||
["node", "-e", script], check=True, capture_output=True, text=True
|
||||
).stdout)
|
||||
|
||||
|
||||
def test_repeated_assign_and_start_taps_claim_once_then_start_confirmed_issue():
|
||||
script = f"""
|
||||
const fs=require('fs');
|
||||
if (!fs.existsSync({json.dumps(str(ASSIGN_AND_START))})) {{
|
||||
process.stdout.write(JSON.stringify({{available:false}}));
|
||||
}} else {{
|
||||
const createAssignAndStart=require({json.dumps(str(ASSIGN_AND_START))});
|
||||
const calls=[];
|
||||
let release;
|
||||
const claimResult=new Promise(resolve=>{{release=resolve;}});
|
||||
const flow=createAssignAndStart({{
|
||||
available:()=>true,
|
||||
claim:item=>{{calls.push('claim:'+item.number);return claimResult;}},
|
||||
start:item=>{{calls.push('start:'+item.number);return 'started';}},
|
||||
recover:item=>calls.push('recover:'+item.number),
|
||||
announce:message=>calls.push('announce:'+message),
|
||||
}});
|
||||
const issue={{repository:'stackchain/dashboard',number:411}};
|
||||
const first=flow.run(issue);
|
||||
const second=flow.run(issue);
|
||||
release({{...issue,assignees:['timmy']}});
|
||||
Promise.all([first,second]).then(results=>process.stdout.write(JSON.stringify({{available:true,calls,results,same:first===second}})));
|
||||
}}
|
||||
"""
|
||||
|
||||
assert run_node(script) == {
|
||||
"available": True,
|
||||
"calls": [
|
||||
"claim:411",
|
||||
"start:411",
|
||||
"announce:Assigned, added to Today, and ready to work.",
|
||||
],
|
||||
"results": ["started", "started"],
|
||||
"same": True,
|
||||
}
|
||||
|
||||
|
||||
def test_full_today_blocks_before_assignment_and_explains_recovery():
|
||||
script = f"""
|
||||
const createAssignAndStart=require({json.dumps(str(ASSIGN_AND_START))});
|
||||
const calls=[];
|
||||
const flow=createAssignAndStart({{
|
||||
available:()=>false,
|
||||
claim:()=>{{calls.push('claim');return Promise.resolve({{}});}},
|
||||
start:()=>calls.push('start'),
|
||||
recover:()=>calls.push('recover'),
|
||||
announce:message=>calls.push('announce:'+message),
|
||||
}});
|
||||
flow.run({{repository:'stackchain/dashboard',number:411}}).then(result=>
|
||||
process.stdout.write(JSON.stringify({{result,calls}}))
|
||||
);
|
||||
"""
|
||||
|
||||
assert run_node(script) == {
|
||||
"result": "full",
|
||||
"calls": ["announce:Today is full—remove an item before assigning this issue."],
|
||||
}
|
||||
|
||||
|
||||
def test_confirmed_assignment_recovers_in_issue_detail_when_today_start_fails():
|
||||
script = f"""
|
||||
const createAssignAndStart=require({json.dumps(str(ASSIGN_AND_START))});
|
||||
const calls=[];
|
||||
const issue={{repository:'stackchain/dashboard',number:411,assignees:['timmy']}};
|
||||
const flow=createAssignAndStart({{
|
||||
available:()=>true,
|
||||
claim:()=>Promise.resolve(issue),
|
||||
start:()=> 'sync-unavailable',
|
||||
recover:item=>calls.push('recover:'+item.number),
|
||||
announce:message=>calls.push('announce:'+message),
|
||||
}});
|
||||
flow.run(issue).then(result=>process.stdout.write(JSON.stringify({{result,calls}})));
|
||||
"""
|
||||
|
||||
assert run_node(script) == {
|
||||
"result": "recovery",
|
||||
"calls": [
|
||||
"announce:Assigned to you, but Today could not start. The issue is open so you can recover.",
|
||||
"recover:411",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_failed_assignment_does_not_start_today_and_can_be_retried():
|
||||
script = f"""
|
||||
const createAssignAndStart=require({json.dumps(str(ASSIGN_AND_START))});
|
||||
const calls=[];let attempt=0;
|
||||
const issue={{repository:'stackchain/dashboard',number:411}};
|
||||
const flow=createAssignAndStart({{
|
||||
available:()=>true,
|
||||
claim:()=>{{attempt+=1;calls.push('claim:'+attempt);return attempt===1 ? Promise.reject(new Error('conflict')) : Promise.resolve(issue);}},
|
||||
start:()=>{{calls.push('start');return 'started';}},
|
||||
recover:()=>calls.push('recover'),
|
||||
announce:message=>calls.push('announce:'+message),
|
||||
}});
|
||||
flow.run(issue).catch(error=>{{
|
||||
calls.push('error:'+error.message);
|
||||
return flow.run(issue);
|
||||
}}).then(result=>process.stdout.write(JSON.stringify({{result,calls}})));
|
||||
"""
|
||||
|
||||
assert run_node(script) == {
|
||||
"result": "started",
|
||||
"calls": [
|
||||
"claim:1",
|
||||
"error:conflict",
|
||||
"claim:2",
|
||||
"start",
|
||||
"announce:Assigned, added to Today, and ready to work.",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_find_work_renders_phone_safe_assign_and_start_and_wires_offline_shell():
|
||||
dashboard = DASHBOARD.read_text()
|
||||
css = CSS.read_text()
|
||||
worker = WORKER.read_text()
|
||||
|
||||
assert "data-claim-start-index" in dashboard
|
||||
assert ">Assign & start</button>" in dashboard
|
||||
assert "const assignAndStart = createAssignAndStart({" in dashboard
|
||||
assert "assignAndStart.run(item)" in dashboard
|
||||
assert "createAndStart.available" in dashboard
|
||||
assert "createAndStart.complete" in dashboard
|
||||
assert ".find-work-claim-actions" in css
|
||||
assert "grid-template-columns:repeat(2,minmax(0,1fr))" in css
|
||||
assert "BASE + 'static/assign-and-start.js'" in worker
|
||||
@@ -377,6 +377,7 @@ def test_install_precaches_complete_subpath_scoped_app_shell():
|
||||
"/dashboard/static/issue-sheet.js",
|
||||
"/dashboard/static/create-issue-sheet.js",
|
||||
"/dashboard/static/create-and-start.js",
|
||||
"/dashboard/static/assign-and-start.js",
|
||||
"/dashboard/static/pull-sheet.js",
|
||||
"/dashboard/static/review-sheet.js",
|
||||
"/dashboard/static/work-route.js",
|
||||
|
||||
Reference in new issue
Block a user