Skip to main content

databricks_tui/fetchers/
problems.rs

1//! Cross-workspace problem scan: fetches the core panes of every other
2//! configured profile and keeps what's failing, so `!` can show trouble
3//! anywhere, not just in the current workspace.
4
5use crate::cli::DatabricksCli;
6use crate::shape::{Shape, Status};
7
8/// One unhealthy resource found in another workspace.
9pub struct RemoteProblem {
10    pub profile: String,
11    /// Index into `Panel::ALL`; None when the workspace itself is the
12    /// problem (unreachable / auth expired).
13    pub panel: Option<usize>,
14    pub name: String,
15    pub status: Status,
16    pub note: String,
17}
18
19/// Scans every profile except `current`: clusters, jobs, pipelines and
20/// warehouses per workspace, all in parallel. A workspace where every
21/// fetch fails yields a single "unreachable" row instead of vanishing.
22pub async fn fetch(profiles: Vec<String>, current: String) -> Vec<RemoteProblem> {
23    let handles: Vec<_> = profiles
24        .into_iter()
25        .filter(|name| *name != current)
26        .map(|name| tokio::spawn(scan_one(name)))
27        .collect();
28    let mut out = Vec::new();
29    for h in handles {
30        if let Ok(mut v) = h.await {
31            out.append(&mut v);
32        }
33    }
34    out
35}
36
37async fn scan_one(profile: String) -> Vec<RemoteProblem> {
38    let arg = if profile == "DEFAULT" {
39        None
40    } else {
41        Some(profile.clone())
42    };
43    let cli = DatabricksCli::new(arg);
44    let (clusters, jobs, pipelines, warehouses) = tokio::join!(
45        crate::fetchers::clusters::fetch(&cli),
46        crate::fetchers::jobs::fetch(&cli),
47        crate::fetchers::pipelines::fetch(&cli),
48        crate::fetchers::warehouses::fetch(&cli),
49    );
50
51    let mut out = Vec::new();
52    let mut reached = false;
53    let mut first_err: Option<String> = None;
54    // Panel indices match Panel::ALL: clusters 0, jobs 1, pipelines 2,
55    // warehouses 3.
56    for (panel, fetched) in [(0, clusters), (1, jobs), (2, pipelines), (3, warehouses)] {
57        match fetched {
58            Ok(Shape::List(items)) => {
59                reached = true;
60                for it in items {
61                    let failed_now = matches!(it.status, Status::Failed);
62                    let failed_last = it
63                        .history
64                        .last()
65                        .is_some_and(|s| matches!(s, Status::Failed));
66                    if failed_now || failed_last || it.alert.is_some() {
67                        out.push(RemoteProblem {
68                            profile: profile.clone(),
69                            panel: Some(panel),
70                            name: it.name,
71                            status: it.status,
72                            note: if failed_now {
73                                it.detail.unwrap_or_default()
74                            } else if let Some(alert) = it.alert {
75                                alert
76                            } else {
77                                "latest run failed".to_string()
78                            },
79                        });
80                    }
81                }
82            }
83            Ok(_) => reached = true,
84            Err(e) => {
85                let msg = format!("{e:#}");
86                first_err
87                    .get_or_insert_with(|| msg.lines().next().unwrap_or("unreachable").to_string());
88            }
89        }
90    }
91    if !reached {
92        out.push(RemoteProblem {
93            profile: profile.clone(),
94            panel: None,
95            name: profile,
96            status: Status::Failed,
97            note: first_err.unwrap_or_else(|| "workspace unreachable".to_string()),
98        });
99    }
100    out
101}