Skip to main content

kaizen/shell/
review.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2use crate::core_loop::{ReviewStatus, review, time};
3use crate::shell::cli::workspace_path;
4use crate::store::Store;
5use anyhow::Result;
6use std::path::Path;
7
8pub fn cmd_review_list(workspace: Option<&Path>, status: Option<String>, json: bool) -> Result<()> {
9    output(
10        &review::list(&open(workspace)?, parse_status(status.as_deref()))?,
11        json,
12    )
13}
14
15pub fn cmd_review_show(workspace: Option<&Path>, id: &str, json: bool) -> Result<()> {
16    let row = review::get(&open(workspace)?, id)?;
17    if json {
18        println!("{}", json_one(&row)?);
19        return Ok(());
20    }
21    output(&[row], false)
22}
23
24pub fn cmd_review_resolve(workspace: Option<&Path>, id: &str) -> Result<()> {
25    set(workspace, id, ReviewStatus::Resolved, "resolved")
26}
27
28pub fn cmd_review_dismiss(workspace: Option<&Path>, id: &str) -> Result<()> {
29    set(workspace, id, ReviewStatus::Dismissed, "dismissed")
30}
31
32fn set(workspace: Option<&Path>, id: &str, status: ReviewStatus, label: &str) -> Result<()> {
33    review::set_status(&open(workspace)?, id, status, time::now_ms())?;
34    println!("{label} review {id}");
35    Ok(())
36}
37
38fn open(workspace: Option<&Path>) -> Result<Store> {
39    let ws = workspace_path(workspace)?;
40    Store::open(&crate::core::workspace::db_path(&ws)?)
41}
42
43fn output(rows: &[crate::core_loop::ReviewItem], json: bool) -> Result<()> {
44    if json {
45        println!("{}", serde_json::to_string_pretty(rows)?);
46    } else {
47        rows.iter().for_each(|r| {
48            println!(
49                "{} session={} status={} {}",
50                r.id,
51                r.session_id,
52                r.status.as_str(),
53                r.title
54            )
55        });
56    }
57    Ok(())
58}
59
60fn json_one(row: &crate::core_loop::ReviewItem) -> Result<String> {
61    Ok(serde_json::to_string_pretty(row)?)
62}
63
64fn parse_status(raw: Option<&str>) -> Option<ReviewStatus> {
65    raw.map(|s| match s {
66        "resolved" => ReviewStatus::Resolved,
67        "dismissed" => ReviewStatus::Dismissed,
68        _ => ReviewStatus::Open,
69    })
70}
71
72#[cfg(test)]
73mod tests {
74    use super::*;
75    use crate::core_loop::ReviewItem;
76
77    #[test]
78    fn review_show_json_renders_single_object() {
79        let text = json_one(&item()).unwrap();
80        assert!(
81            serde_json::from_str::<serde_json::Value>(&text)
82                .unwrap()
83                .is_object()
84        );
85    }
86
87    fn item() -> ReviewItem {
88        ReviewItem {
89            id: "r1".into(),
90            source_key: "k".into(),
91            session_id: "s1".into(),
92            title: "Review".into(),
93            status: ReviewStatus::Open,
94            created_at_ms: 1,
95            resolved_at_ms: None,
96        }
97    }
98}