ricecoder_workflows/
progress.rs1use crate::models::{WorkflowState, WorkflowStatus};
4use chrono::{DateTime, Duration, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ProgressTracker {
10 pub total_steps: usize,
12 pub step_durations: Vec<u64>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct StatusReport {
19 pub current_step: Option<String>,
21 pub progress_percentage: u32,
23 pub estimated_completion_time: Option<DateTime<Utc>>,
25 pub workflow_status: WorkflowStatus,
27 pub completed_steps_count: usize,
29 pub total_steps: usize,
31}
32
33impl ProgressTracker {
34 pub fn new(total_steps: usize) -> Self {
36 ProgressTracker {
37 total_steps,
38 step_durations: Vec::new(),
39 }
40 }
41
42 pub fn record_step_duration(&mut self, duration_ms: u64) {
44 self.step_durations.push(duration_ms);
45 }
46
47 pub fn calculate_progress(&self, completed_steps: usize) -> u32 {
49 if self.total_steps == 0 {
50 return 0;
51 }
52
53 ((completed_steps as u32 * 100) / self.total_steps as u32).min(100)
54 }
55
56 pub fn estimate_completion_time(
58 &self,
59 state: &WorkflowState,
60 now: DateTime<Utc>,
61 ) -> Option<DateTime<Utc>> {
62 if self.step_durations.is_empty() || self.total_steps == 0 {
63 return None;
64 }
65
66 let total_duration: u64 = self.step_durations.iter().sum();
68 let avg_duration_ms = total_duration / self.step_durations.len() as u64;
69
70 let remaining_steps = self.total_steps.saturating_sub(state.completed_steps.len());
72
73 let estimated_remaining_ms = remaining_steps as u64 * avg_duration_ms;
75
76 now.checked_add_signed(Duration::milliseconds(estimated_remaining_ms as i64))
78 }
79
80 pub fn generate_status_report(
82 &self,
83 state: &WorkflowState,
84 now: DateTime<Utc>,
85 ) -> StatusReport {
86 let completed_steps_count = state.completed_steps.len();
87 let progress_percentage = self.calculate_progress(completed_steps_count);
88 let estimated_completion_time = self.estimate_completion_time(state, now);
89
90 StatusReport {
91 current_step: state.current_step.clone(),
92 progress_percentage,
93 estimated_completion_time,
94 workflow_status: state.status,
95 completed_steps_count,
96 total_steps: self.total_steps,
97 }
98 }
99
100 pub fn get_average_step_duration(&self) -> Option<u64> {
102 if self.step_durations.is_empty() {
103 return None;
104 }
105
106 let total: u64 = self.step_durations.iter().sum();
107 Some(total / self.step_durations.len() as u64)
108 }
109
110 pub fn get_min_step_duration(&self) -> Option<u64> {
112 self.step_durations.iter().copied().min()
113 }
114
115 pub fn get_max_step_duration(&self) -> Option<u64> {
117 self.step_durations.iter().copied().max()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124
125 fn create_test_workflow_state() -> WorkflowState {
126 WorkflowState {
127 workflow_id: "test-workflow".to_string(),
128 status: WorkflowStatus::Running,
129 current_step: Some("step1".to_string()),
130 completed_steps: vec!["step0".to_string()],
131 step_results: Default::default(),
132 started_at: Utc::now(),
133 updated_at: Utc::now(),
134 }
135 }
136
137 #[test]
138 fn test_create_progress_tracker() {
139 let tracker = ProgressTracker::new(10);
140 assert_eq!(tracker.total_steps, 10);
141 assert!(tracker.step_durations.is_empty());
142 }
143
144 #[test]
145 fn test_record_step_duration() {
146 let mut tracker = ProgressTracker::new(10);
147 tracker.record_step_duration(100);
148 tracker.record_step_duration(200);
149
150 assert_eq!(tracker.step_durations.len(), 2);
151 assert_eq!(tracker.step_durations[0], 100);
152 assert_eq!(tracker.step_durations[1], 200);
153 }
154
155 #[test]
156 fn test_calculate_progress() {
157 let tracker = ProgressTracker::new(10);
158
159 assert_eq!(tracker.calculate_progress(0), 0);
160 assert_eq!(tracker.calculate_progress(5), 50);
161 assert_eq!(tracker.calculate_progress(10), 100);
162 assert_eq!(tracker.calculate_progress(15), 100); }
164
165 #[test]
166 fn test_calculate_progress_zero_steps() {
167 let tracker = ProgressTracker::new(0);
168 assert_eq!(tracker.calculate_progress(0), 0);
169 }
170
171 #[test]
172 fn test_get_average_step_duration() {
173 let mut tracker = ProgressTracker::new(10);
174 tracker.record_step_duration(100);
175 tracker.record_step_duration(200);
176 tracker.record_step_duration(300);
177
178 assert_eq!(tracker.get_average_step_duration(), Some(200));
179 }
180
181 #[test]
182 fn test_get_average_step_duration_empty() {
183 let tracker = ProgressTracker::new(10);
184 assert_eq!(tracker.get_average_step_duration(), None);
185 }
186
187 #[test]
188 fn test_get_min_step_duration() {
189 let mut tracker = ProgressTracker::new(10);
190 tracker.record_step_duration(100);
191 tracker.record_step_duration(200);
192 tracker.record_step_duration(50);
193
194 assert_eq!(tracker.get_min_step_duration(), Some(50));
195 }
196
197 #[test]
198 fn test_get_max_step_duration() {
199 let mut tracker = ProgressTracker::new(10);
200 tracker.record_step_duration(100);
201 tracker.record_step_duration(200);
202 tracker.record_step_duration(50);
203
204 assert_eq!(tracker.get_max_step_duration(), Some(200));
205 }
206
207 #[test]
208 fn test_estimate_completion_time() {
209 let mut tracker = ProgressTracker::new(10);
210 tracker.record_step_duration(100);
211 tracker.record_step_duration(100);
212
213 let state = create_test_workflow_state();
214 let now = Utc::now();
215
216 let estimated = tracker.estimate_completion_time(&state, now);
217 assert!(estimated.is_some());
218
219 let estimated_time = estimated.unwrap();
222 let diff = estimated_time.signed_duration_since(now);
223 assert!(diff.num_milliseconds() > 800 && diff.num_milliseconds() < 1000);
224 }
225
226 #[test]
227 fn test_estimate_completion_time_no_durations() {
228 let tracker = ProgressTracker::new(10);
229 let state = create_test_workflow_state();
230 let now = Utc::now();
231
232 let estimated = tracker.estimate_completion_time(&state, now);
233 assert!(estimated.is_none());
234 }
235
236 #[test]
237 fn test_generate_status_report() {
238 let mut tracker = ProgressTracker::new(10);
239 tracker.record_step_duration(100);
240
241 let state = create_test_workflow_state();
242 let now = Utc::now();
243
244 let report = tracker.generate_status_report(&state, now);
245
246 assert_eq!(report.current_step, Some("step1".to_string()));
247 assert_eq!(report.progress_percentage, 10); assert_eq!(report.completed_steps_count, 1);
249 assert_eq!(report.total_steps, 10);
250 assert_eq!(report.workflow_status, WorkflowStatus::Running);
251 assert!(report.estimated_completion_time.is_some());
252 }
253}