1use chrono::{DateTime, Duration as ChronoDuration, Utc};
4use serde::{Deserialize, Serialize};
5
6use crate::TemporalId;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Sequence {
11 pub id: TemporalId,
13
14 pub label: String,
16
17 pub steps: Vec<SequenceStep>,
19
20 pub current_step: usize,
22
23 pub status: SequenceStatus,
25
26 pub started_at: Option<DateTime<Utc>>,
28
29 pub completed_at: Option<DateTime<Utc>>,
31
32 pub allow_parallel: bool,
34
35 pub created_at: DateTime<Utc>,
37
38 pub tags: Vec<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct SequenceStep {
45 pub id: TemporalId,
47
48 pub label: String,
50
51 pub order: u32,
53
54 pub duration_secs: Option<i64>,
56
57 pub status: StepStatus,
59
60 pub parallel_with_next: bool,
62
63 pub depends_on: Vec<u32>,
65
66 pub started_at: Option<DateTime<Utc>>,
68
69 pub completed_at: Option<DateTime<Utc>>,
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
75pub enum SequenceStatus {
76 NotStarted,
78 InProgress,
80 Completed,
82 Failed,
84 Cancelled,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
90pub enum StepStatus {
91 Pending,
93 Ready,
95 InProgress,
97 Completed,
99 Skipped,
101 Failed,
103}
104
105impl Sequence {
106 pub fn new(label: impl Into<String>, steps: Vec<SequenceStep>) -> Self {
108 Self {
109 id: TemporalId::new(),
110 label: label.into(),
111 steps,
112 current_step: 0,
113 status: SequenceStatus::NotStarted,
114 started_at: None,
115 completed_at: None,
116 allow_parallel: false,
117 created_at: Utc::now(),
118 tags: Vec::new(),
119 }
120 }
121
122 pub fn ready_steps(&self) -> Vec<&SequenceStep> {
124 self.steps
125 .iter()
126 .filter(|s| {
127 s.status == StepStatus::Ready
128 || (s.status == StepStatus::Pending && self.dependencies_met(s))
129 })
130 .collect()
131 }
132
133 pub fn dependencies_met(&self, step: &SequenceStep) -> bool {
135 step.depends_on.iter().all(|dep_order| {
136 self.steps
137 .iter()
138 .find(|s| s.order == *dep_order)
139 .map(|s| s.status == StepStatus::Completed)
140 .unwrap_or(true)
141 })
142 }
143
144 pub fn total_duration(&self) -> ChronoDuration {
146 if self.allow_parallel {
147 self.steps
148 .iter()
149 .filter_map(|s| s.duration_secs)
150 .map(ChronoDuration::seconds)
151 .max()
152 .unwrap_or(ChronoDuration::zero())
153 } else {
154 self.steps
155 .iter()
156 .filter_map(|s| s.duration_secs)
157 .map(ChronoDuration::seconds)
158 .fold(ChronoDuration::zero(), |acc, d| acc + d)
159 }
160 }
161
162 pub fn complete_current_step(&mut self) {
164 if self.status == SequenceStatus::NotStarted {
165 self.status = SequenceStatus::InProgress;
166 self.started_at = Some(Utc::now());
167 }
168
169 if let Some(step) = self.steps.get_mut(self.current_step) {
170 step.status = StepStatus::Completed;
171 step.completed_at = Some(Utc::now());
172 }
173
174 self.current_step += 1;
175
176 if self.current_step >= self.steps.len() {
177 self.status = SequenceStatus::Completed;
178 self.completed_at = Some(Utc::now());
179 }
180
181 self.update_ready_steps();
182 }
183
184 pub fn update_ready_steps(&mut self) {
186 let completed_orders: Vec<u32> = self
187 .steps
188 .iter()
189 .filter(|s| s.status == StepStatus::Completed)
190 .map(|s| s.order)
191 .collect();
192
193 for step in &mut self.steps {
194 if step.status == StepStatus::Pending {
195 let deps_met = step
196 .depends_on
197 .iter()
198 .all(|dep| completed_orders.contains(dep));
199 if deps_met {
200 step.status = StepStatus::Ready;
201 }
202 }
203 }
204 }
205}
206
207impl SequenceStep {
208 pub fn new(label: impl Into<String>, order: u32) -> Self {
210 Self {
211 id: TemporalId::new(),
212 label: label.into(),
213 order,
214 duration_secs: None,
215 status: StepStatus::Pending,
216 parallel_with_next: false,
217 depends_on: Vec::new(),
218 started_at: None,
219 completed_at: None,
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_sequence_advance() {
230 let steps = vec![
231 SequenceStep::new("Step 1", 0),
232 SequenceStep::new("Step 2", 1),
233 ];
234 let mut seq = Sequence::new("Deploy", steps);
235 assert_eq!(seq.status, SequenceStatus::NotStarted);
236
237 seq.complete_current_step();
238 assert_eq!(seq.status, SequenceStatus::InProgress);
239 assert_eq!(seq.steps[0].status, StepStatus::Completed);
240
241 seq.complete_current_step();
242 assert_eq!(seq.status, SequenceStatus::Completed);
243 }
244
245 #[test]
246 fn test_total_duration_sequential() {
247 let mut steps = vec![
248 SequenceStep::new("Step 1", 0),
249 SequenceStep::new("Step 2", 1),
250 ];
251 steps[0].duration_secs = Some(60);
252 steps[1].duration_secs = Some(120);
253
254 let seq = Sequence::new("Pipeline", steps);
255 assert_eq!(seq.total_duration().num_seconds(), 180);
256 }
257}