artifactize_core/monitor/model/
graph.rs1use std::collections::{BTreeMap, BTreeSet};
4
5use super::{
6 NodeId, Target,
7 states::{Activity, Busy, Completion, EvalView, NotRun},
8 tree::{Kind, Node},
9};
10use crate::types::ArtifactName;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Light {
17 Red,
19 Error,
21 Active,
23 Blocked,
25 Waiting,
28 Green,
30}
31
32impl From<Completion> for Light {
33 fn from(completion: Completion) -> Self {
35 match completion {
36 Completion::Complete => Self::Green,
37 Completion::Failed { verdict: true } => Self::Red,
38 Completion::Failed { verdict: false } => Self::Error,
39 Completion::InProgress(Busy::Running | Busy::Human) => Self::Active,
40 Completion::InProgress(Busy::Queued) => Self::Waiting,
41 Completion::Waiting { blocked: true } => Self::Blocked,
42 Completion::Waiting { blocked: false } => Self::Waiting,
43 }
44 }
45}
46
47impl From<&EvalView> for Light {
48 fn from(view: &EvalView) -> Self {
49 match view {
50 EvalView::Done(_) => Self::Green,
51 EvalView::Failed { verdict: true } => Self::Red,
52 EvalView::Failed { verdict: false } => Self::Error,
53 EvalView::InProgress(Activity::Running | Activity::Human(_)) => Self::Active,
54 EvalView::BlockedBy(_) => Self::Blocked,
55 EvalView::InProgress(Activity::Queued(_))
56 | EvalView::WaitingOn(_)
57 | EvalView::NotRun(
58 NotRun::Dependency(_)
59 | NotRun::Budget
60 | NotRun::Stale
61 | NotRun::Unreviewed
62 | NotRun::Absent,
63 ) => Self::Waiting,
64 }
65 }
66}
67
68fn eval_light(eval: &Node, view: &EvalView) -> Light {
70 if eval.held
71 && matches!(
72 view,
73 EvalView::WaitingOn(_) | EvalView::NotRun(NotRun::Dependency(_))
74 )
75 {
76 Light::Blocked
77 } else {
78 Light::from(view)
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct GraphNode {
84 pub id: NodeId,
86 pub name: ArtifactName,
87 pub light: Light,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub struct GraphEdge {
93 pub from: ArtifactName,
94 pub to: ArtifactName,
95 pub light: Light,
97}
98
99#[derive(Debug, Clone, Default, PartialEq, Eq)]
100pub struct Graph {
101 pub nodes: Vec<GraphNode>,
103 pub edges: Vec<GraphEdge>,
105}
106
107impl Graph {
108 pub fn same_shape(&self, other: &Self) -> bool {
111 self.nodes.len() == other.nodes.len()
112 && self.edges.len() == other.edges.len()
113 && self
114 .nodes
115 .iter()
116 .zip(&other.nodes)
117 .all(|(one, other)| one.id == other.id)
118 && self
119 .edges
120 .iter()
121 .zip(&other.edges)
122 .all(|(one, other)| one.from == other.from && one.to == other.to)
123 }
124}
125
126pub fn graph(tree: &[Node]) -> Graph {
129 let artifacts: Vec<(ArtifactName, &Node, Completion)> = tree
130 .iter()
131 .filter_map(|node| match node.kind {
132 Kind::Artifact { completion, .. } => match Target::from(&node.id) {
133 Target::Artifact(name) => Some((name, node, completion)),
134 _ => None,
135 },
136 Kind::Eval(_) => None,
137 })
138 .collect();
139 let mut lights: BTreeMap<ArtifactName, Light> = BTreeMap::new();
140 for (name, node, completion) in &artifacts {
141 let evals = node
143 .children
144 .iter()
145 .filter_map(|eval| match &eval.kind {
146 Kind::Eval(view) => Some(eval_light(eval, view)),
147 Kind::Artifact { .. } => None,
148 })
149 .min()
150 .unwrap_or(Light::Green);
151 let light = match (evals, completion) {
153 (Light::Green, Completion::Waiting { .. }) => Light::from(*completion),
154 (light, _) => light,
155 };
156 lights.insert(name.clone(), light);
157 }
158 let position: BTreeMap<&ArtifactName, usize> = artifacts
159 .iter()
160 .enumerate()
161 .map(|(index, (name, _, _))| (name, index))
162 .collect();
163 let mut edges = Vec::new();
164 for (name, node, _) in &artifacts {
165 let upstream: BTreeSet<_> = node
166 .children
167 .iter()
168 .flat_map(|eval| eval.upstream.iter().map(|dependency| &dependency.artifact))
169 .collect();
170 for from in upstream {
171 let Some(&own) = lights.get(from).filter(|_| from != name) else {
172 continue;
173 };
174 edges.push(GraphEdge {
175 from: from.clone(),
176 to: name.clone(),
177 light: own,
178 });
179 }
180 }
181 edges.sort_by_key(|edge| (position[&edge.to], position[&edge.from]));
182 Graph {
183 nodes: artifacts
184 .into_iter()
185 .map(|(name, node, _)| GraphNode {
186 id: node.id.clone(),
187 light: lights[&name],
188 name,
189 })
190 .collect(),
191 edges,
192 }
193}