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