Skip to content

Commit 2a9ae00

Browse files
committed
feat: enhance UpdateStats display with progress bar and error handling
Fix for #343
1 parent 67da574 commit 2a9ae00

File tree

3 files changed

+89
-38
lines changed

3 files changed

+89
-38
lines changed

Cargo.lock

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ rand = "0.9.2"
128128
indoc = "2.0.6"
129129
owo-colors = "4.2.2"
130130
json5 = "0.4.1"
131+
indicatif = "0.17.9"
131132
aws-config = "1.8.5"
132133
aws-sdk-s3 = "1.102.0"
133134
aws-sdk-sqs = "1.80.0"

src/execution/stats.rs

Lines changed: 88 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -193,55 +193,111 @@ impl OperationInProcessStats {
193193

194194
impl std::fmt::Display for UpdateStats {
195195
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196-
let mut messages = Vec::new();
197-
let num_errors = self.num_errors.get();
198-
if num_errors > 0 {
199-
messages.push(format!("{num_errors} source rows FAILED"));
200-
}
201-
202-
let num_skipped = self.num_no_change.get();
203-
if num_skipped > 0 {
204-
messages.push(format!("{num_skipped} source rows NO CHANGE"));
205-
}
206-
196+
let mut segments = Vec::new();
207197
let num_insertions = self.num_insertions.get();
208198
let num_deletions = self.num_deletions.get();
209199
let num_updates = self.num_updates.get();
210-
let num_reprocesses = self.num_reprocesses.get();
211-
let num_source_rows = num_insertions + num_deletions + num_updates + num_reprocesses;
212-
if num_source_rows > 0 {
213-
let mut sub_messages = Vec::new();
200+
let num_no_change = self.num_no_change.get();
201+
let num_errors = self.num_errors.get();
202+
let num_in_process = self.processing.get_in_process();
203+
let total = num_insertions + num_deletions + num_updates + num_no_change;
204+
205+
// Progress bar segments
206+
if total > 0 {
214207
if num_insertions > 0 {
215-
sub_messages.push(format!("{num_insertions} ADDED"));
208+
segments.push((num_insertions, "+", format!("\x1B[90m(+{} added)\x1B[0m", num_insertions)));
216209
}
217210
if num_deletions > 0 {
218-
sub_messages.push(format!("{num_deletions} REMOVED"));
219-
}
220-
if num_reprocesses > 0 {
221-
sub_messages.push(format!(
222-
"{num_reprocesses} REPROCESSED on flow/logic changes or reexport"
223-
));
211+
segments.push((num_deletions, "-", format!("\x1B[90m(-{} removed)\x1B[0m", num_deletions)));
224212
}
225213
if num_updates > 0 {
226-
sub_messages.push(format!("{num_updates} UPDATED in source content only"));
214+
segments.push((num_updates, "~", format!("\x1B[90m(~{} updated)\x1B[0m", num_updates)));
215+
}
216+
if num_no_change > 0 {
217+
segments.push((num_no_change, " ", "".to_string()));
227218
}
228-
messages.push(format!(
229-
"{num_source_rows} source rows processed ({})",
230-
sub_messages.join(", "),
231-
));
232219
}
233220

234-
let num_in_process = self.processing.get_in_process();
235-
if num_in_process > 0 {
236-
messages.push(format!("{num_in_process} source rows IN PROCESS"));
221+
// Error handling
222+
if num_errors > 0 {
223+
write!(f, "{} rows failed", num_errors)?;
224+
if !segments.is_empty() {
225+
write!(f, "; ")?;
226+
}
237227
}
238228

239-
if !messages.is_empty() {
240-
write!(f, "{}", messages.join("; "))?;
229+
// Progress bar
230+
if !segments.is_empty() {
231+
let mut sorted_segments = segments.clone();
232+
sorted_segments.sort_by_key(|s| s.0);
233+
sorted_segments.reverse();
234+
235+
let bar_width = 40;
236+
let mut bar = String::new();
237+
238+
let percentage = ((total - num_in_process) as f64 / total as f64 * 100.0) as i64;
239+
let mut remaining_width = bar_width;
240+
241+
for (count, segment_type, _) in sorted_segments.iter() {
242+
let segment_width = (*count * bar_width as i64 / total as i64) as usize;
243+
let width = std::cmp::min(segment_width, remaining_width);
244+
if width > 0 {
245+
// Calculate completed and remaining portions
246+
let completed_portion = (width as f64 * (total - num_in_process) as f64 / total as f64) as usize;
247+
let remaining_portion = width - completed_portion;
248+
249+
// Add segment with appropriate characters based on type
250+
if completed_portion > 0 {
251+
let completed_char = match *segment_type {
252+
"+" => "█",
253+
"-" => "▓",
254+
"~" => "▒",
255+
_ => "░"
256+
};
257+
bar.push_str(&completed_char.repeat(completed_portion));
258+
}
259+
260+
if remaining_portion > 0 {
261+
let remaining_char = match *segment_type {
262+
"+" => "▒",
263+
"-" => "░",
264+
"~" => "░",
265+
_ => " "
266+
};
267+
bar.push_str(&remaining_char.repeat(remaining_portion));
268+
}
269+
270+
remaining_width = remaining_width.saturating_sub(width);
271+
}
272+
}
273+
if remaining_width > 0 {
274+
bar.push_str(&" ".repeat(remaining_width));
275+
}
276+
write!(f, "[{}] {}/{} records ", bar, total - num_in_process, total)?;
277+
278+
// Add segment labels
279+
let mut first = true;
280+
for (_, _, label) in segments.iter() {
281+
if !label.is_empty() {
282+
if !first {
283+
write!(f, " ")?;
284+
}
285+
write!(f, "{}", label)?;
286+
first = false;
287+
}
288+
}
241289
} else {
242290
write!(f, "No changes")?;
243291
}
244292

293+
// In-process info
294+
if num_in_process > 0 {
295+
if !segments.is_empty() {
296+
write!(f, " ")?;
297+
}
298+
write!(f, "({} in process)", num_in_process)?;
299+
}
300+
245301
Ok(())
246302
}
247303
}

0 commit comments

Comments
 (0)