Skip to main content

runnerd_core/api/insights/
shown.rs

1//! What insights and the digest mean as every client shows them: each insight's labels and tone, how many warn,
2//! the window they come from, what the page says without any, and the digest's lines. Plain derivations of
3//! [`Insights`] and [`Digest`]; the dashboard, `runnerctl`, the TUI and notifications only lay them out.
4
5use super::{Digest, Insight, InsightKind, Insights, LongWait, Severity, WaitSpread};
6use crate::api::Tone;
7
8impl InsightKind {
9    /// Its name as a label: `Slower`, `Flaky`, `Near its cap`, `Cold cache`, `Memory pressure` or `Waits`.
10    pub fn label(&self) -> &'static str {
11        match self {
12            InsightKind::Slower { .. } => "Slower",
13            InsightKind::Flaky { .. } => "Flaky",
14            InsightKind::NearCap { .. } => "Near its cap",
15            InsightKind::ColdCache { .. } => "Cold cache",
16            InsightKind::Throttled { .. } => "Memory pressure",
17            InsightKind::Waits { .. } => "Waits",
18        }
19    }
20
21    /// Its name as one word, the API's `kind`: `slower`, `flaky`, `near-cap`, `cold-cache`, `throttled` or
22    /// `waits`.
23    pub fn word(&self) -> &'static str {
24        match self {
25            InsightKind::Slower { .. } => "slower",
26            InsightKind::Flaky { .. } => "flaky",
27            InsightKind::NearCap { .. } => "near-cap",
28            InsightKind::ColdCache { .. } => "cold-cache",
29            InsightKind::Throttled { .. } => "throttled",
30            InsightKind::Waits { .. } => "waits",
31        }
32    }
33}
34
35impl Severity {
36    /// `warning` or `notice`.
37    pub fn label(self) -> &'static str {
38        match self {
39            Severity::Warning => "warning",
40            Severity::Notice => "notice",
41        }
42    }
43}
44
45impl Insight {
46    /// What it means for the reader, by its severity alone, whatever its kind: a warning is worth a look, a notice
47    /// a plain fact.
48    pub fn tone(&self) -> Tone {
49        match self.severity {
50            Severity::Warning => Tone::Warn,
51            Severity::Notice => Tone::Neutral,
52        }
53    }
54}
55
56impl Insights {
57    /// How many insights warn.
58    pub fn warning_count(&self) -> usize {
59        self.insights
60            .iter()
61            .filter(|insight| insight.severity == Severity::Warning)
62            .count()
63    }
64
65    /// The whole days its window covers.
66    pub fn days(&self) -> u64 {
67        self.until.saturating_sub(self.since) / 86_400
68    }
69
70    /// Where its insights come from: `from the last 30 days`.
71    pub fn window_line(&self) -> String {
72        format!("from the last {} days", self.days())
73    }
74
75    /// What it says without any insight: `nothing stands out from the last 30 days`.
76    pub fn empty_line(&self) -> String {
77        format!("nothing stands out {}", self.window_line())
78    }
79}
80
81impl WaitSpread {
82    /// `value`, one of its percentiles, when it has jobs to take it over; `None` to show none.
83    pub fn reading(&self, value: Option<u64>) -> Option<u64> {
84        value.filter(|_| self.jobs > 0)
85    }
86}
87
88impl Digest {
89    /// Its day in a line: `12 jobs finished, 2 failed`, then `, 1 new insight` when some are new.
90    pub fn summary_line(&self) -> String {
91        let jobs = if self.finished == 1 { "job" } else { "jobs" };
92        let line = format!("{} {jobs} finished, {} failed", self.finished, self.failed);
93        match self.new.len() {
94            0 => line,
95            1 => format!("{line}, 1 new insight"),
96            new => format!("{line}, {new} new insights"),
97        }
98    }
99}
100
101impl LongWait {
102    /// It in a line: `halyard/api CI / test waited 1m 30s`, or `halyard/api job 7 waited 1m 30s` until the daemon
103    /// knows the job's name.
104    pub fn line(&self) -> String {
105        format!(
106            "{} {} waited {}",
107            self.repository,
108            crate::api::job_title(self.workflow.as_deref(), &self.name, Some(self.job)),
109            crate::api::duration(self.waited)
110        )
111    }
112}
113
114#[cfg(test)]
115mod tests;