Add smiley feedback collector with comment after a completed run (RBT-WP-0004-T12)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 295952@bnt-lap001 Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
parent
7d0249ed51
commit
c17b43264d
6 changed files with 167 additions and 2 deletions
28
rtut/ui.py
28
rtut/ui.py
|
|
@ -34,7 +34,7 @@ def page(title: str, body: str) -> bytes:
|
|||
"const f=b.closest('form'),n=f.querySelector('[name=note]'),w=f.querySelector('[data-warn]');"
|
||||
"if(n&&!n.value.trim()&&!b.dataset.warned){ev.preventDefault();b.dataset.warned='1';w.hidden=false;n.focus();}}));"
|
||||
# keep the operator on the current step after every action
|
||||
"const c=document.getElementById('current');if(c)c.scrollIntoView({block:'center'});"
|
||||
"const c=document.getElementById(location.hash==='#feedback'?'feedback':'current');if(c)c.scrollIntoView({block:'center'});"
|
||||
"</script>").encode()
|
||||
|
||||
|
||||
|
|
@ -42,6 +42,9 @@ def e(s) -> str:
|
|||
return html.escape(str(s))
|
||||
|
||||
|
||||
SMILEYS = (("good", "🙂", "Went well"), ("neutral", "😐", "So-so"), ("poor", "🙁", "Poor"))
|
||||
|
||||
|
||||
class App:
|
||||
def __init__(self, roots: list[Path]):
|
||||
self.token = secrets.token_urlsafe(16)
|
||||
|
|
@ -83,6 +86,23 @@ class App:
|
|||
"<p><label>Your name (one word)<br><input name=operator required></label></p>"
|
||||
"<button>Start run</button></form>")
|
||||
|
||||
def feedback_form(self, i: int, rid: str, r) -> str:
|
||||
fb = R.latest_feedback(r)
|
||||
thanks = ""
|
||||
if fb:
|
||||
face = next(f for k, f, _ in SMILEYS if k == fb["rating"])
|
||||
thanks = (f"<p><b>Thank you.</b> Recorded: {face} {e(fb['comment'] or '(no comment)')} "
|
||||
"<span class=note>You can change it below.</span></p>")
|
||||
btns = " ".join(f"<button name=rating value={k} title='{e(t)}' aria-label='{e(t)}'"
|
||||
f" style='font-size:1.6rem;padding:.1rem .6rem'>{face}</button>"
|
||||
for k, face, t in SMILEYS)
|
||||
return (f"<div id=feedback class=step><h2>How was this runbook?</h2>{thanks}"
|
||||
f"<form method=post action='/run/{i}/{e(rid)}/feedback'>{self.hidden()}"
|
||||
"<p class=note>Practicable? Anything that felt strange or unclear, or an idea to make it better. "
|
||||
"Never include secrets.</p>"
|
||||
f"<p><textarea name=comment rows=3 style='width:100%' placeholder='optional comment'>"
|
||||
f"{e(fb['comment']) if fb else ''}</textarea></p><p class=row>{btns}</p></form></div>")
|
||||
|
||||
def run_page(self, i: int, rid: str, msg: str = "") -> bytes:
|
||||
p = self.packs[i]; r = R.load(p, rid); d = p.data
|
||||
out = [f"<h1>{e(d['title'])}</h1><p class=pill>{e(d['exercise_status'])}</p>"
|
||||
|
|
@ -157,6 +177,8 @@ class App:
|
|||
if r.complete:
|
||||
out.append("<p><b>All steps done.</b> To record this run as exercising the pack, review and apply: "
|
||||
f"<code>python -m rtut run finish {e(p.path)} {e(rid)} --apply</code></p>")
|
||||
if r.complete:
|
||||
out.append(self.feedback_form(i, rid, r))
|
||||
out.append("<h2>Threat checks</h2><ul>" + "".join(f"<li>{e(t)}</li>" for t in d["threat_checks"]) + "</ul>")
|
||||
return page(d["title"], "".join(out))
|
||||
|
||||
|
|
@ -205,6 +227,10 @@ def make_handler(app: App, port_holder: list):
|
|||
params = {k[2:]: v for k, v in form.items() if k.startswith("p_")}
|
||||
r = R.create(p, form.get("operator", ""), params)
|
||||
return self._redirect(f"/run/{i}/{r.data['run_id']}#current")
|
||||
if len(parts) == 4 and parts[0] == "run" and parts[3] == "feedback":
|
||||
i, rid = int(parts[1]), parts[2]
|
||||
R.add_feedback(R.load(app.packs[i], rid), form.get("rating", ""), form.get("comment", ""))
|
||||
return self._redirect(f"/run/{i}/{rid}#feedback")
|
||||
i, rid, sid, action = int(parts[1]), parts[2], parts[3], parts[4]
|
||||
p = app.packs[i]; r = R.load(p, rid)
|
||||
if action == "start": R.start(r, sid)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue