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 {
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 {
75                                "latest run failed".to_string()
76                            },
77                        });
78                    }
79                }
80            }
81            Ok(_) => reached = true,
82            Err(e) => {
83                let msg = format!("{e:#}");
84                first_err
85                    .get_or_insert_with(|| msg.lines().next().unwrap_or("unreachable").to_string());
86            }
87        }
88    }
89    if !reached {
90        out.push(RemoteProblem {
91            profile: profile.clone(),
92            panel: None,
93            name: profile,
94            status: Status::Failed,
95            note: first_err.unwrap_or_else(|| "workspace unreachable".to_string()),
96        });
97    }
98    out
99}