pub mod insights;
use crate::approval;
use crate::gate::{self, GateResult};
use crate::paths::Paths;
use crate::pipeline::{self, Stage};
use crate::run::{Run, RunMeta};
use crate::trajectory::{self, Anomaly};
use anyhow::Result;
use serde::Serialize;
pub const SCHEMA: &str = "keel.report/1";
#[derive(Debug, Serialize)]
pub struct Report {
pub schema: &'static str,
pub generated_at: String,
pub keel_version: &'static str,
pub specs: Vec<SpecReport>,
}
#[derive(Debug, Serialize)]
pub struct SpecReport {
pub slug: String,
pub stage: &'static str,
pub complete: bool,
pub gates: Vec<GateResult>,
pub approvals: serde_json::Map<String, serde_json::Value>,
pub runs: Vec<RunReport>,
}
#[derive(Debug, Serialize)]
pub struct RunReport {
#[serde(flatten)]
pub meta: RunMeta,
pub gates: Vec<GateResult>,
pub events: usize,
pub tokens: usize,
pub anomalies: Vec<Anomaly>,
}
impl Report {
pub fn build(paths: &Paths, only: Option<&str>) -> Result<Self> {
let slugs: Vec<String> = match only {
Some(s) => {
crate::spec::Spec::load(paths, s)?;
vec![s.to_string()]
}
None => crate::spec::list(paths)?,
};
let runs = crate::run::list(paths).unwrap_or_default();
let specs = slugs
.into_iter()
.map(|slug| SpecReport::build(paths, &slug, &runs))
.collect::<Result<Vec<_>>>()?;
Ok(Self {
schema: SCHEMA,
generated_at: chrono::Local::now().to_rfc3339(),
keel_version: env!("CARGO_PKG_VERSION"),
specs,
})
}
}
impl SpecReport {
fn build(paths: &Paths, slug: &str, all_runs: &[String]) -> Result<Self> {
let pos = pipeline::position(paths, slug);
let gates = ["G0", "G1"]
.iter()
.filter_map(|g| gate::previous(paths, slug, g))
.collect();
let mut approvals = serde_json::Map::new();
for stage in approval::STAGES {
let standing = approval::standing(paths, slug, stage).unwrap_or(approval::Standing::Absent);
approvals.insert(stage.to_string(), approval::standing_json(&standing));
}
let runs = all_runs
.iter()
.filter_map(|id| Run::load(paths, id).ok())
.filter(|r| r.meta.spec == slug)
.map(RunReport::build)
.collect();
Ok(Self {
slug: slug.to_string(),
stage: pos.stage.key(),
complete: pos.stage == Stage::Complete,
gates,
approvals,
runs,
})
}
}
impl RunReport {
fn build(run: Run) -> Self {
let (events, tokens, anomalies) = match trajectory::scan(&run.trajectory_path()) {
Ok(s) => {
let tokens = trajectory::token_total(&s.events);
(s.events.len(), tokens, s.anomalies)
}
Err(_) => (0, 0, Vec::new()),
};
Self {
gates: run.gate_results().unwrap_or_default(),
meta: run.meta,
events,
tokens,
anomalies,
}
}
}