diff --git a/src/logs/status/time.rs b/src/logs/status/time.rs index c719b188..fb9949b2 100644 --- a/src/logs/status/time.rs +++ b/src/logs/status/time.rs @@ -5,6 +5,7 @@ use anyhow::Result; use chrono::{DateTime, Local}; use crate::config::UserConfig; +use crate::logs::command_record::StageName; use crate::logs::reading::{Command, Entry, Observation, Snapshot}; pub(crate) struct Views { @@ -111,11 +112,15 @@ fn run(command: &Command, now: DateTime, cutoff: i64) -> Run { seconds: seconds .map(|seconds| seconds.to_string()) .unwrap_or_default(), - usual: command - .usual_ranges - .get(&stage.name) - .map(|range| range.text()) - .unwrap_or_else(|| "Not enough samples".into()), + usual: if StageName::is_dispatch(&stage.name) { + "—".into() + } else { + command + .usual_ranges + .get(&stage.name) + .map(|range| range.text()) + .unwrap_or_else(|| "Not enough samples".into()) + }, left, width, } diff --git a/tests/console.rs b/tests/console.rs index 47870d8f..99e9c375 100644 --- a/tests/console.rs +++ b/tests/console.rs @@ -501,6 +501,55 @@ fn time_pages_pair_with_all_status_stages_and_repository_usual_ranges() { assert_eq!(console.get("/?page=3").status(), 200); } +#[test] +#[cfg(target_os = "linux")] +fn stages_distinguishes_dispatch_from_insufficient_usual_range_samples() { + let home = tempfile::tempdir().unwrap(); + configure(home.path()); + let now = chrono::Local::now(); + let stat = fs::read_to_string(format!("/proc/{}/stat", std::process::id())).unwrap(); + let ticks = stat + .rsplit_once(") ") + .unwrap() + .1 + .split_whitespace() + .nth(19) + .unwrap(); + let boot = fs::read_to_string("/proc/sys/kernel/random/boot_id").unwrap(); + for (number, kind, stage) in [ + (1, "Pickup run", "dispatched Run"), + (2, "Security run", "dispatched Spec run"), + ] { + let mut value = record(home.path(), number, None, 0); + value["kind"] = json!(kind); + value["name"] = json!(format!("pass-{number}")); + value["issue"] = Value::Null; + value["process_id"] = json!(std::process::id()); + value["process_start"] = json!(format!("{}:{ticks}", boot.trim())); + value["started_at"] = json!((now - chrono::Duration::minutes(2)).to_rfc3339()); + value["stages"] = json!([ + {"name": "implement", "began_at": value["started_at"], "session_log": null}, + {"name": stage, "began_at": (now - chrono::Duration::minutes(1)).to_rfc3339(), "session_log": null} + ]); + write_record(home.path(), number, &value); + } + let console = Console::start(Path::new(env!("CARGO_BIN_EXE_thirdshift")), home.path()); + let html = console.get("/?page=stages").text().unwrap(); + for name in ["dispatched Run", "dispatched Spec run"] { + let row = html + .split("") + .find(|row| row.starts_with(&format!("{name}
"))) + .unwrap(); + assert!(row.contains("Usual range: —"), "{row}"); + assert!(row.contains("class=\"running-stage\""), "{row}"); + assert!(row.contains("Running · 1 min"), "{row}"); + assert!(row.contains("Not reached · —"), "{row}"); + } + assert!( + html.contains("implement
Usual range: Not enough samples") + ); +} + #[test] fn security_lists_findings_and_sessions_from_the_cli_inventory() { let home = tempfile::tempdir().unwrap();