use std::fmt;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SweepStage {
Collect,
Correlate,
Classify,
JiraSync,
LinearSync,
Deployments,
Incidents,
Dora,
PrMetrics,
Report,
}
impl SweepStage {
pub fn as_str(self) -> &'static str {
match self {
Self::Collect => "collect",
Self::Correlate => "correlate",
Self::Classify => "classify",
Self::JiraSync => "jira sync",
Self::LinearSync => "linear sync",
Self::Deployments => "deployments collect",
Self::Incidents => "incidents collect",
Self::Dora => "dora",
Self::PrMetrics => "pr-metrics",
Self::Report => "report",
}
}
}
impl fmt::Display for SweepStage {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum StageStatus {
Succeeded,
Failed(String),
}
impl StageStatus {
pub fn is_failure(&self) -> bool {
matches!(self, Self::Failed(_))
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct StageOutcome {
pub stage: SweepStage,
pub status: StageStatus,
pub elapsed: Duration,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct StaleFetch {
pub repo: String,
pub remote: String,
pub error: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DeclaredSkip {
pub leg: String,
pub reason: String,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct AuditSweepStats {
pub outcomes: Vec<StageOutcome>,
pub stale_fetches: Vec<StaleFetch>,
pub declared_skips: Vec<DeclaredSkip>,
}
impl AuditSweepStats {
pub fn record(&mut self, stage: SweepStage, started: Instant, result: anyhow::Result<()>) {
let status = match result {
Ok(()) => StageStatus::Succeeded,
Err(e) => {
let message = format!("{e:#}");
tracing::warn!(stage = stage.as_str(), error = %message, "audit stage failed");
StageStatus::Failed(message)
}
};
self.outcomes.push(StageOutcome {
stage,
status,
elapsed: started.elapsed(),
});
}
pub fn record_stale_fetch(&mut self, fetch: StaleFetch) {
tracing::warn!(
repo = %fetch.repo,
remote = %fetch.remote,
error = %fetch.error,
"collected from stale local refs; data may be behind the remote"
);
self.stale_fetches.push(fetch);
}
pub fn record_declared_skip(&mut self, skip: DeclaredSkip) {
tracing::warn!(
leg = %skip.leg,
reason = %skip.reason,
"collection leg declared absent; its sections are unassessed"
);
self.declared_skips.push(skip);
}
pub fn failures(&self) -> impl Iterator<Item = &StageOutcome> {
self.outcomes.iter().filter(|o| o.status.is_failure())
}
pub fn any_failed(&self) -> bool {
self.outcomes.iter().any(|o| o.status.is_failure())
}
pub fn summary(&self) -> String {
let total = self.outcomes.len();
let ok = total - self.failures().count();
format!("{ok} of {total} stage(s) succeeded")
}
}