Skip to content

Commit ee637e1

Browse files
authored
feat(docker): add provisioning progress events (#1567)
1 parent db40831 commit ee637e1

3 files changed

Lines changed: 841 additions & 34 deletions

File tree

crates/openshell-cli/src/run.rs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ struct ProvisioningDisplay {
219219
completed_steps: Vec<ProvisioningStep>,
220220
/// Progress bars for completed steps (so they can be cleared).
221221
completed_bars: Vec<ProgressBar>,
222+
/// The currently active provisioning step.
223+
active_step: Option<ProvisioningStep>,
222224
/// The currently active step label (shown on the spinner).
223225
active_label: String,
224226
/// Detail text shown next to the active step (e.g. image name).
@@ -253,6 +255,7 @@ impl ProvisioningDisplay {
253255
spacer,
254256
completed_steps: Vec::new(),
255257
completed_bars: Vec::new(),
258+
active_step: None,
256259
active_label: ProvisioningStep::RequestingSandbox
257260
.active_label()
258261
.to_string(),
@@ -290,11 +293,15 @@ impl ProvisioningDisplay {
290293
self.step_start = Instant::now();
291294
self.spinner.reset_elapsed();
292295
self.active_detail.clear();
296+
if self.active_step == Some(step) {
297+
self.active_step = None;
298+
}
293299
}
294300

295301
/// Set the active (in-progress) step shown on the spinner.
296-
fn set_active(&mut self, label: &str) {
297-
self.active_label = label.to_string();
302+
fn set_active(&mut self, step: ProvisioningStep) {
303+
self.active_step = Some(step);
304+
self.active_label = step.active_label().to_string();
298305
self.active_detail.clear();
299306
// Reset the spinner's elapsed time for the new step.
300307
self.spinner.reset_elapsed();
@@ -304,11 +311,17 @@ impl ProvisioningDisplay {
304311

305312
/// Set the active step from a known provisioning step enum.
306313
fn set_active_step(&mut self, step: ProvisioningStep) {
307-
self.set_active(step.active_label());
314+
if self.active_step == Some(step) {
315+
return;
316+
}
317+
self.set_active(step);
308318
}
309319

310320
/// Set detail text shown alongside the active step (e.g. image name).
311321
fn set_active_detail(&mut self, detail: &str) {
322+
if self.active_detail == detail {
323+
return;
324+
}
312325
self.active_detail = detail.to_string();
313326
self.update_spinner();
314327
}
@@ -6949,7 +6962,7 @@ fn format_timestamp_ms(ms: i64) -> String {
69496962
#[cfg(test)]
69506963
mod tests {
69516964
use super::{
6952-
ProvisioningStep, TlsOptions, build_sandbox_resource_limits,
6965+
ProvisioningDisplay, ProvisioningStep, TlsOptions, build_sandbox_resource_limits,
69536966
dockerfile_sources_supported_for_gateway, format_endpoint, format_gateway_select_header,
69546967
format_gateway_select_items, format_provider_attachment_table, gateway_add,
69556968
gateway_auth_label, gateway_env_override_warning, gateway_select_with, gateway_type_label,
@@ -6970,6 +6983,7 @@ mod tests {
69706983
use std::path::{Path, PathBuf};
69716984
use std::process::Command;
69726985
use std::thread;
6986+
use std::time::{Duration, Instant};
69736987
use tonic::Status;
69746988

69756989
use openshell_bootstrap::GatewayMetadata;
@@ -7178,6 +7192,48 @@ mod tests {
71787192
assert_eq!(progress_step_from_metadata("driver-private-step"), None);
71797193
}
71807194

7195+
#[test]
7196+
fn provisioning_display_ignores_repeated_active_step_updates() {
7197+
let mut display = ProvisioningDisplay::new();
7198+
display.set_active_step(ProvisioningStep::PullingSandboxImage);
7199+
display.set_active_detail("Downloading layer-1 (1 MB/2 MB)");
7200+
7201+
let original_step_start = Instant::now()
7202+
.checked_sub(Duration::from_secs(5))
7203+
.expect("test duration should be representable");
7204+
display.step_start = original_step_start;
7205+
7206+
display.set_active_step(ProvisioningStep::PullingSandboxImage);
7207+
display.set_active_detail("Downloading layer-1 (1 MB/2 MB)");
7208+
7209+
assert_eq!(
7210+
display.active_step,
7211+
Some(ProvisioningStep::PullingSandboxImage)
7212+
);
7213+
assert_eq!(display.active_detail, "Downloading layer-1 (1 MB/2 MB)");
7214+
assert_eq!(display.step_start, original_step_start);
7215+
display.clear();
7216+
}
7217+
7218+
#[test]
7219+
fn provisioning_display_resets_detail_on_active_step_transition() {
7220+
let mut display = ProvisioningDisplay::new();
7221+
display.set_active_step(ProvisioningStep::PullingSandboxImage);
7222+
display.set_active_detail("Downloading layer-1 (1 MB/2 MB)");
7223+
7224+
let original_step_start = Instant::now()
7225+
.checked_sub(Duration::from_secs(5))
7226+
.expect("test duration should be representable");
7227+
display.step_start = original_step_start;
7228+
7229+
display.set_active_step(ProvisioningStep::StartingSandbox);
7230+
7231+
assert_eq!(display.active_step, Some(ProvisioningStep::StartingSandbox));
7232+
assert!(display.active_detail.is_empty());
7233+
assert!(display.step_start > original_step_start);
7234+
display.clear();
7235+
}
7236+
71817237
#[test]
71827238
fn refresh_status_table_includes_operational_fields() {
71837239
let header = refresh_status_header();

0 commit comments

Comments
 (0)