Skip to main content

ironflow_cli/commands/
schedule.rs

1//! Schedule subcommands: list, create, pause, resume, delete, trigger.
2
3use anyhow::Result;
4use clap::{Args, Subcommand};
5use comfy_table::{ContentArrangement, Table};
6use ironflow_sdk::IronflowClient;
7use ironflow_sdk::types::{CreateScheduleRequest, ScheduleResponse};
8use uuid::Uuid;
9
10use crate::confirm::confirm;
11use crate::output;
12
13/// Arguments for the `schedule` command group.
14#[derive(Debug, Args)]
15pub struct ScheduleArgs {
16    /// Schedule subcommand.
17    #[command(subcommand)]
18    pub command: ScheduleCommands,
19}
20
21/// Available schedule subcommands.
22#[derive(Debug, Subcommand)]
23pub enum ScheduleCommands {
24    /// List all schedules.
25    List,
26    /// Create a new schedule.
27    Create {
28        /// Workflow name.
29        workflow: String,
30        /// Cron expression (5 or 6 field format).
31        cron: String,
32        /// JSON inputs for the workflow (defaults to `{}`).
33        #[arg(long, default_value = "{}")]
34        inputs: String,
35    },
36    /// Pause a schedule (disable automatic triggers).
37    Pause {
38        /// Schedule ID.
39        id: Uuid,
40    },
41    /// Resume a paused schedule.
42    Resume {
43        /// Schedule ID.
44        id: Uuid,
45    },
46    /// Delete a schedule.
47    Delete {
48        /// Schedule ID.
49        id: Uuid,
50        /// Skip the interactive confirmation.
51        #[arg(long)]
52        yes: bool,
53    },
54    /// Trigger a schedule manually, creating a run immediately.
55    Trigger {
56        /// Schedule ID.
57        id: Uuid,
58    },
59}
60
61fn schedules_table(schedules: &[ScheduleResponse]) -> Table {
62    let mut table = Table::new();
63    table.set_content_arrangement(ContentArrangement::Dynamic);
64    table.set_header(vec![
65        "ID",
66        "WORKFLOW",
67        "CRON",
68        "SOURCE",
69        "ENABLED",
70        "NEXT TRIGGER",
71    ]);
72    for s in schedules {
73        let source = format!("{:?}", s.source).to_lowercase();
74        table.add_row(vec![
75            s.id.to_string(),
76            s.workflow_name.clone(),
77            s.cron_expression.clone(),
78            source.to_string(),
79            if s.disabled_at.is_none() {
80                "active".to_string()
81            } else {
82                "paused".to_string()
83            },
84            s.next_trigger_at
85                .as_ref()
86                .map(|d| d.to_string())
87                .unwrap_or_else(|| "-".to_string()),
88        ]);
89    }
90    table
91}
92
93/// Execute a schedule subcommand.
94///
95/// # Errors
96///
97/// Returns an error on API failure, invalid JSON inputs, or an unconfirmed
98/// destructive command.
99pub async fn execute(client: &IronflowClient, args: &ScheduleArgs, json_mode: bool) -> Result<()> {
100    match &args.command {
101        ScheduleCommands::List => {
102            let response = client.list_schedules().await?;
103            if json_mode {
104                output::print_json(&response)?;
105            } else {
106                println!("{}", schedules_table(&response.data));
107            }
108            Ok(())
109        }
110        ScheduleCommands::Create {
111            workflow,
112            cron,
113            inputs,
114        } => {
115            let parsed_inputs: serde_json::Value =
116                serde_json::from_str(inputs).map_err(|e| anyhow::anyhow!("invalid JSON: {e}"))?;
117            let response = client
118                .create_schedule(&CreateScheduleRequest {
119                    workflow_name: workflow.clone(),
120                    cron_expression: cron.clone(),
121                    inputs: Some(parsed_inputs),
122                })
123                .await?;
124            if json_mode {
125                output::print_json(&response)?;
126            } else {
127                println!("Schedule {} created", response.data.id);
128            }
129            Ok(())
130        }
131        ScheduleCommands::Pause { id } => {
132            let response = client.pause_schedule(*id).await?;
133            if json_mode {
134                output::print_json(&response)?;
135            } else {
136                println!("Schedule {} paused", response.data.id);
137            }
138            Ok(())
139        }
140        ScheduleCommands::Resume { id } => {
141            let response = client.resume_schedule(*id).await?;
142            if json_mode {
143                output::print_json(&response)?;
144            } else {
145                println!("Schedule {} resumed", response.data.id);
146            }
147            Ok(())
148        }
149        ScheduleCommands::Delete { id, yes } => {
150            let prompt = format!("Delete schedule {id}?");
151            confirm(&prompt, *yes)?;
152            client.delete_schedule(*id).await?;
153            if json_mode {
154                output::print_json(&serde_json::json!({"deleted": id.to_string()}))?;
155            } else {
156                println!("Schedule {id} deleted");
157            }
158            Ok(())
159        }
160        ScheduleCommands::Trigger { id } => {
161            let response = client.trigger_schedule(*id).await?;
162            if json_mode {
163                output::print_json(&response)?;
164            } else {
165                println!("Schedule {} triggered", response.data.id);
166            }
167            Ok(())
168        }
169    }
170}