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
107pub fn graph(tree: &[Node]) -> Graph {
110 let artifacts: Vec<(ArtifactName, &Node, Completion)> = tree
111 .iter()
112 .filter_map(|node| match node.kind {
113 Kind::Artifact { completion, .. } => match Target::from(&node.id) {
114 Target::Artifact(name) => Some((name, node, completion)),
115 _ => None,
116 },
117 Kind::Eval(_) => None,
118 })
119 .collect();
120 let mut lights: BTreeMap<ArtifactName, Light> = BTreeMap::new();
121 for (name, node, completion) in &artifacts {
122 let evals = node
124 .children
125 .iter()
126 .filter_map(|eval| match &eval.kind {
127 Kind::Eval(view) => Some(eval_light(eval, view)),
128 Kind::Artifact { .. } => None,
129 })
130 .min()
131 .unwrap_or(Light::Green);
132 let light = match (evals, completion) {
134 (Light::Green, Completion::Waiting { .. }) => Light::from(*completion),
135 (light, _) => light,
136 };
137 lights.insert(name.clone(), light);
138 }
139 let position: BTreeMap<&ArtifactName, usize> = artifacts
140 .iter()
141 .enumerate()
142 .map(|(index, (name, _, _))| (name, index))
143 .collect();
144 let mut edges = Vec::new();
145 for (name, node, _) in &artifacts {
146 let upstream: BTreeSet<_> = node
147 .children
148 .iter()
149 .flat_map(|eval| eval.upstream.iter().map(|dependency| &dependency.artifact))
150 .collect();
151 for from in upstream {
152 let Some(&own) = lights.get(from).filter(|_| from != name) else {
153 continue;
154 };
155 edges.push(GraphEdge {
156 from: from.clone(),
157 to: name.clone(),
158 light: own,
159 });
160 }
161 }
162 edges.sort_by_key(|edge| (position[&edge.to], position[&edge.from]));
163 Graph {
164 nodes: artifacts
165 .into_iter()
166 .map(|(name, node, _)| GraphNode {
167 id: node.id.clone(),
168 light: lights[&name],
169 name,
170 })
171 .collect(),
172 edges,
173 }
174}