Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

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`");
    }
}