1use std::collections::BTreeMap;
12use std::io::Cursor;
13use tiny_http::Response;
14
15use crate::handlers::{error_response, json_response, State};
16use lex_store::issues::{
17 all_issue_status, issue_status, issue_verdicts, issues_in_progress, IssueState, IssueStatus,
18};
19use lex_vcs::IssueLog;
20
21pub fn issues_state_handler(state: &State) -> Response<Cursor<Vec<u8>>> {
23 let store = state.store.lock().unwrap();
24 match all_issue_status(&store) {
25 Ok(issues) => json_response(200, &serde_json::json!({ "issues": issues })),
26 Err(e) => error_response(500, format!("deriving issue state: {e}")),
27 }
28}
29
30pub fn issue_detail_handler(state: &State, id: &str) -> Response<Cursor<Vec<u8>>> {
33 let store = state.store.lock().unwrap();
34 let log = match IssueLog::open(store.root()) {
35 Ok(l) => l,
36 Err(e) => return error_response(500, format!("opening issue log: {e}")),
37 };
38 let issue = match log.get(&id.to_string()) {
39 Ok(Some(i)) => i,
40 Ok(None) => return error_response(404, format!("unknown issue `{id}`")),
41 Err(e) => return error_response(500, format!("reading issue {id}: {e}")),
42 };
43 let in_progress = match issues_in_progress(&store) {
44 Ok(s) => s,
45 Err(e) => return error_response(500, format!("scanning provenance: {e}")),
46 };
47 let status = match issue_status(&store, &issue, &in_progress) {
48 Ok(s) => s,
49 Err(e) => return error_response(500, format!("deriving state for {id}: {e}")),
50 };
51 let verdicts = match issue_verdicts(&store, id) {
52 Ok(v) => v,
53 Err(e) => return error_response(500, format!("reading verdicts for {id}: {e}")),
54 };
55 json_response(200, &serde_json::json!({
56 "issue": status.issue,
57 "state": status.state,
58 "blocked_on": status.blocked_on,
59 "has_work": status.has_work,
60 "verdicts": verdicts,
61 }))
62}
63
64pub fn projects_handler(state: &State) -> Response<Cursor<Vec<u8>>> {
68 let store = state.store.lock().unwrap();
69 let all = match all_issue_status(&store) {
70 Ok(v) => v,
71 Err(e) => return error_response(500, format!("deriving issue state: {e}")),
72 };
73 let mut by_project: BTreeMap<String, Vec<&IssueStatus>> = BTreeMap::new();
74 for s in &all {
75 if let Some(p) = &s.issue.project {
76 by_project.entry(p.clone()).or_default().push(s);
77 }
78 }
79 let projects: Vec<serde_json::Value> = by_project
80 .into_iter()
81 .map(|(name, issues)| {
82 let count = |st: IssueState| issues.iter().filter(|s| s.state == st).count();
83 serde_json::json!({
84 "name": name,
85 "counts": {
86 "open": count(IssueState::Open),
87 "in_progress": count(IssueState::InProgress),
88 "verified": count(IssueState::Verified),
89 "blocked": count(IssueState::Blocked),
90 },
91 "issues": issues.iter().map(|s| serde_json::json!({
92 "issue_id": s.issue.issue_id,
93 "title": s.issue.title,
94 "shape": s.issue.acceptance.shape(),
95 "state": s.state,
96 "blocked_on": s.blocked_on,
97 })).collect::<Vec<_>>(),
98 })
99 })
100 .collect();
101 json_response(200, &serde_json::json!({ "projects": projects }))
102}