facett-demo/src/runa_tab.rs
//! **The `runa` tab** — a dwarves forge pipeline ([`dwarves_runa::Pipeline`]) rendered on
//! the REUSED animated node-DAG viewer ([`facett_pipeline::PipelineView`]).
//!
//! runa is the dwarves forge's typed pipeline authoring layer: a `*.runa.ron`
//! declares a **Stage DAG** (each stage `needs` earlier stages) over a typed
//! [`Step`](dwarves_runa::Step) catalog (CargoBuild / Nextest / Lint / Build{variant} /
//! Sbom / Xtask / …). This tab shows that pipeline as the **same** beautiful
//! animated DAG the Spark/SDP pipelines render on — because "show another engine's
//! pipeline = a new **ADAPTER**, not a new viewer". The adapter is
//! [`facett_pipeline::RunaPipelineAdapter`] (feature `runa-adapter`); this tab is
//! just a thin driver: seed a real pipeline, map it, hand the model to the viewer.
//!
//! Seeded with the design-doc **korp-ci** pipeline (`build → test → package`), with
//! a demo run-status overlay (`build` done · `test` running · `package` idle) so the
//! DAG animates a live run rather than sitting static.
use std::collections::HashMap;
use facett::pipeline::{runa_adapter, PipelineView, StageStatus};
use dwarves_runa::Pipeline;
/// The stable tab title (the deck/discovery-contract key + the LEGEND key).
pub const TITLE: &str = "runa";
/// The design-doc **korp-ci** pipeline, verbatim RON (`build → test → package`).
/// Parsed through [`dwarves_runa::Pipeline::from_ron_str`], which also *validates* it
/// (unknown-`needs` + DAG cycle check), so the seed is guaranteed sound.
const KORP_CI: &str = r#"Pipeline(
name: "korp-ci",
triggers: [ Push(branch: "main"), Cron("0 3 * * *") ],
stages: [
Stage(name: "build", steps: [
CargoBuild(features: ["iceberg", "containers"], profile: Release),
]),
Stage(name: "test", needs: ["build"], steps: [
Nextest(all_features: true),
Lint(tool: Clippy, deny_warnings: true),
Xtask(cmd: "verify-demo"),
]),
Stage(name: "package", needs: ["test"], steps: [
Build(variant: "holger-embedded"),
Build(variant: "holger-full"),
Sbom(policy: Block),
]),
],
)"#;
/// The seeded korp-ci [`dwarves_runa::Pipeline`] — a real 3-stage forge pipeline.
pub fn sample_pipeline() -> Pipeline {
Pipeline::from_ron_str(KORP_CI).expect("the seeded korp-ci pipeline must load")
}
/// A demo run-status overlay so the DAG animates a run rather than sitting static:
/// `build` done, `test` running, `package` left idle (not yet started).
fn sample_status() -> HashMap<String, StageStatus> {
HashMap::from([
("build".to_string(), StageStatus::Done),
("test".to_string(), StageStatus::Running),
])
}
/// The `runa` pipeline view: the seeded korp-ci pipeline mapped through the
/// [`RunaPipelineAdapter`](facett::pipeline::RunaPipelineAdapter) and handed to the
/// reused animated DAG viewer. Titled `"runa"` (the deck/LEGEND key).
pub fn pipeline_view() -> PipelineView {
let p = sample_pipeline();
let mut model = runa_adapter::to_pipeline_model(&p, &sample_status());
// Retitle the pane to the deck/LEGEND key (the model title is the pipeline's
// own name, "korp-ci").
model.title = TITLE.to_string();
PipelineView::from_model(model).with_animation(true)
}
/// An empty runa pipeline view (0 stages) — the mega-matrix's `Empty` data-state
/// cell (the empty render path).
pub fn empty_pipeline_view() -> PipelineView {
let empty = Pipeline {
name: TITLE.to_string(),
triggers: Vec::new(),
runs_on: Default::default(),
stages: Vec::new(),
};
let mut model = runa_adapter::to_pipeline_model(&empty, &HashMap::new());
model.title = TITLE.to_string();
PipelineView::from_model(model).with_animation(true)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn seeded_pipeline_maps_to_three_nodes_two_edges() {
let p = sample_pipeline();
let m = runa_adapter::to_pipeline_model(&p, &sample_status());
assert_eq!(m.stages.len(), 3, "korp-ci has build/test/package");
assert_eq!(m.edges.len(), 2, "build→test, test→package");
// The status overlay lights the DAG: build done, test running, package idle.
assert_eq!(m.status_counts(), (1, 1, 1, 0));
}
#[test]
fn the_view_renders_the_nodes_and_edges() {
use facett::Facet;
// The tab hands the model to the reused viewer; its state_json exposes the
// nodes/edges the painter drew — the proof it rendered the DAG, not a shell.
let view = pipeline_view();
let sj = view.state_json();
assert_eq!(sj["node_count"].as_u64(), Some(3), "3 runa stages → 3 nodes");
assert_eq!(sj["edge_count"].as_u64(), Some(2), "2 needs → 2 edges");
assert_eq!(view.title(), TITLE, "the pane is titled `runa`");
}
}