Skip to main content

ironflow_cli/output/
pause.rs

1//! Output of `ironflow run pause`, `ironflow run resume`,
2//! `ironflow workflow pause` and `ironflow workflow resume`.
3
4use comfy_table::{Cell, CellAlignment, Table};
5use ironflow_sdk::types::{PauseRunResponse, ResumeRunResponse, WorkflowPauseResponse};
6
7use super::{base_table, format_optional_datetime, status_color};
8
9/// Render a paused run, the state it resumes to, and the number of sub-runs
10/// paused along with it.
11pub fn paused_table(pause: &PauseRunResponse) -> Table {
12    let mut table = base_table();
13    table.set_header(vec![
14        "ID",
15        "Workflow",
16        "Status",
17        "Resumes to",
18        "Sub-runs paused",
19    ]);
20    let resumes_to = pause
21        .resume_status
22        .map_or_else(|| "-".to_string(), |status| status.to_string());
23    table.add_row(vec![
24        Cell::new(pause.id),
25        Cell::new(&pause.workflow_name),
26        Cell::new(pause.status)
27            .fg(status_color(&pause.status))
28            .set_alignment(CellAlignment::Center),
29        Cell::new(resumes_to),
30        Cell::new(pause.paused_descendants.len()),
31    ]);
32    table
33}
34
35/// Render a resumed run with the number of sub-runs resumed along with it.
36pub fn resumed_table(resume: &ResumeRunResponse) -> Table {
37    let mut table = base_table();
38    table.set_header(vec!["ID", "Workflow", "Status", "Sub-runs resumed"]);
39    table.add_row(vec![
40        Cell::new(resume.id),
41        Cell::new(&resume.workflow_name),
42        Cell::new(resume.status)
43            .fg(status_color(&resume.status))
44            .set_alignment(CellAlignment::Center),
45        Cell::new(resume.resumed_descendants.len()),
46    ]);
47    table
48}
49
50/// Render the pause state of a workflow.
51pub fn workflow_pause_table(pause: &WorkflowPauseResponse) -> Table {
52    let mut table = base_table();
53    table.set_header(vec!["Workflow", "Paused", "Paused at"]);
54    table.add_row(vec![
55        Cell::new(&pause.workflow_name),
56        Cell::new(if pause.paused_at.is_some() {
57            "yes"
58        } else {
59            "no"
60        }),
61        Cell::new(format_optional_datetime(&pause.paused_at)),
62    ]);
63    table
64}
65
66#[cfg(test)]
67mod tests {
68    use serde_json::{Value, from_value, json};
69    use uuid::Uuid;
70
71    use super::*;
72
73    fn run_fields(id: Uuid, status: &str) -> Value {
74        json!({
75            "id": id,
76            "workflow_name": "deploy",
77            "status": status,
78            "trigger": {"kind": "manual"},
79            "retry_count": 0,
80            "max_retries": 0,
81            "cost_usd": 0.0,
82            "duration_ms": 0,
83            "created_at": "2026-10-06T10:00:00Z",
84            "updated_at": "2026-10-06T10:00:00Z",
85            "created_by": {"kind": "system", "label": "cron"},
86        })
87    }
88
89    fn row_of(output: &str) -> &str {
90        output
91            .lines()
92            .find(|line| line.contains("deploy"))
93            .expect("the row")
94    }
95
96    #[test]
97    fn paused_table_shows_the_resume_target_and_the_sub_runs_paused() {
98        let id = Uuid::now_v7();
99        let mut fields = run_fields(id, "paused");
100        fields["resume_status"] = json!("sleeping");
101        fields["paused_descendants"] = json!([Uuid::now_v7()]);
102        let pause: PauseRunResponse = from_value(fields).expect("a pause response");
103
104        let output = paused_table(&pause).to_string();
105
106        assert!(output.contains("Resumes to"), "{output}");
107        assert!(output.contains(&id.to_string()), "{output}");
108        let row = row_of(&output);
109        assert!(row.contains("sleeping"), "{row}");
110        assert!(row.contains(" 1 "), "{row}");
111    }
112
113    #[test]
114    fn resumed_table_shows_the_sub_runs_resumed() {
115        let id = Uuid::now_v7();
116        let mut fields = run_fields(id, "pending");
117        fields["resumed_descendants"] = json!([Uuid::now_v7(), Uuid::now_v7()]);
118        let resume: ResumeRunResponse = from_value(fields).expect("a resume response");
119
120        let output = resumed_table(&resume).to_string();
121
122        assert!(output.contains("Sub-runs resumed"), "{output}");
123        let row = row_of(&output);
124        assert!(row.contains("pending"), "{row}");
125        assert!(row.contains(" 2 "), "{row}");
126    }
127
128    #[test]
129    fn workflow_pause_table_shows_when_the_workflow_was_paused() {
130        let pause: WorkflowPauseResponse = from_value(json!({
131            "workflow_name": "deploy",
132            "paused_at": "2026-10-06T10:00:00Z",
133        }))
134        .expect("a workflow pause response");
135
136        let output = workflow_pause_table(&pause).to_string();
137
138        let row = row_of(&output);
139        assert!(row.contains("yes"), "{row}");
140        assert!(row.contains("2026-10-06 10:00:00"), "{row}");
141    }
142
143    #[test]
144    fn workflow_pause_table_shows_a_resumed_workflow() {
145        let pause: WorkflowPauseResponse = from_value(json!({
146            "workflow_name": "deploy",
147        }))
148        .expect("a workflow pause response");
149
150        let output = workflow_pause_table(&pause).to_string();
151
152        let row = row_of(&output);
153        assert!(row.contains("no"), "{row}");
154        assert!(row.contains('-'), "{row}");
155    }
156}