use std::collections::{BTreeMap, BTreeSet};
use super::{
NodeId, Target,
states::{Activity, Busy, Completion, EvalView, NotRun},
tree::{Kind, Node},
};
use crate::types::ArtifactName;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Light {
Red,
Error,
Active,
Blocked,
Waiting,
Green,
}
impl From<Completion> for Light {
fn from(completion: Completion) -> Self {
match completion {
Completion::Complete => Self::Green,
Completion::Failed { verdict: true } => Self::Red,
Completion::Failed { verdict: false } => Self::Error,
Completion::InProgress(Busy::Running | Busy::Human) => Self::Active,
Completion::InProgress(Busy::Queued) => Self::Waiting,
Completion::Waiting { blocked: true } => Self::Blocked,
Completion::Waiting { blocked: false } => Self::Waiting,
}
}
}
impl From<&EvalView> for Light {
fn from(view: &EvalView) -> Self {
match view {
EvalView::Done(_) => Self::Green,
EvalView::Failed { verdict: true } => Self::Red,
EvalView::Failed { verdict: false } => Self::Error,
EvalView::InProgress(Activity::Running | Activity::Human(_)) => Self::Active,
EvalView::BlockedBy(_) => Self::Blocked,
EvalView::InProgress(Activity::Queued(_))
| EvalView::WaitingOn(_)
| EvalView::NotRun(
NotRun::Dependency(_)
| NotRun::Budget
| NotRun::Stale
| NotRun::Unreviewed
| NotRun::Absent,
) => Self::Waiting,
}
}
}
fn eval_light(eval: &Node, view: &EvalView) -> Light {
if eval.held
&& matches!(
view,
EvalView::WaitingOn(_) | EvalView::NotRun(NotRun::Dependency(_))
)
{
Light::Blocked
} else {
Light::from(view)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphNode {
pub id: NodeId,
pub name: ArtifactName,
pub light: Light,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GraphEdge {
pub from: ArtifactName,
pub to: ArtifactName,
pub light: Light,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Graph {
pub nodes: Vec<GraphNode>,
pub edges: Vec<GraphEdge>,
}
pub fn graph(tree: &[Node]) -> Graph {
let artifacts: Vec<(ArtifactName, &Node, Completion)> = tree
.iter()
.filter_map(|node| match node.kind {
Kind::Artifact { completion, .. } => match Target::from(&node.id) {
Target::Artifact(name) => Some((name, node, completion)),
_ => None,
},
Kind::Eval(_) => None,
})
.collect();
let mut lights: BTreeMap<ArtifactName, Light> = BTreeMap::new();
for (name, node, completion) in &artifacts {
let evals = node
.children
.iter()
.filter_map(|eval| match &eval.kind {
Kind::Eval(view) => Some(eval_light(eval, view)),
Kind::Artifact { .. } => None,
})
.min()
.unwrap_or(Light::Green);
let light = match (evals, completion) {
(Light::Green, Completion::Waiting { .. }) => Light::from(*completion),
(light, _) => light,
};
lights.insert(name.clone(), light);
}
let position: BTreeMap<&ArtifactName, usize> = artifacts
.iter()
.enumerate()
.map(|(index, (name, _, _))| (name, index))
.collect();
let mut edges = Vec::new();
for (name, node, _) in &artifacts {
let upstream: BTreeSet<_> = node
.children
.iter()
.flat_map(|eval| eval.upstream.iter().map(|dependency| &dependency.artifact))
.collect();
for from in upstream {
let Some(&own) = lights.get(from).filter(|_| from != name) else {
continue;
};
edges.push(GraphEdge {
from: from.clone(),
to: name.clone(),
light: own,
});
}
}
edges.sort_by_key(|edge| (position[&edge.to], position[&edge.from]));
Graph {
nodes: artifacts
.into_iter()
.map(|(name, node, _)| GraphNode {
id: node.id.clone(),
light: lights[&name],
name,
})
.collect(),
edges,
}
}