Skip to main content

elasticctl_api/dashboards_ops/
delete.rs

1//! Dashboard deletion planning and apply.
2
3use super::{
4    BTreeSet, DashboardSummary, DeleteOutcome, Error, ErrorKind, MutationPlan, Result, Transport,
5    dashboards, invalid_plan, resolve,
6};
7
8/// The immutable, guard-ready targets for dashboard deletion.
9#[derive(Debug, Clone, PartialEq)]
10pub struct DashboardDeletePlan {
11    pub preview: MutationPlan,
12    pub targets: Vec<DashboardSummary>,
13}
14/// Resolve every dashboard selector and build the exact delete guard preview.
15pub async fn plan_delete(
16    transport: &Transport,
17    selectors: &[String],
18) -> Result<DashboardDeletePlan> {
19    if selectors.is_empty() {
20        return Err(Error::new(
21            ErrorKind::Error,
22            "dashboard delete needs at least one dashboard",
23        ));
24    }
25    let mut seen = BTreeSet::new();
26    let mut targets = Vec::new();
27    for selector in selectors {
28        let dashboard = resolve(transport, selector).await?;
29        if seen.insert(dashboard.id.clone()) {
30            targets.push(dashboard);
31        }
32    }
33    Ok(DashboardDeletePlan {
34        preview: delete_preview(&targets),
35        targets,
36    })
37}
38
39/// Apply a guard-approved dashboard deletion without resolving selectors again.
40pub async fn apply_delete(
41    transport: &Transport,
42    plan: &DashboardDeletePlan,
43) -> Result<DeleteOutcome> {
44    validate_delete_plan(plan)?;
45    let mut deleted = Vec::new();
46    let mut failed = Vec::new();
47    for target in &plan.targets {
48        match dashboards::delete(transport, &target.id).await {
49            Ok(()) => deleted.push(serde_json::json!({"id": target.id})),
50            Err(error) => failed.push(serde_json::json!({
51                "id": target.id,
52                "error": error.message,
53            })),
54        }
55    }
56    Ok(DeleteOutcome {
57        applied: true,
58        deleted,
59        failed,
60        total: plan.targets.len(),
61    })
62}
63
64fn delete_preview(targets: &[DashboardSummary]) -> MutationPlan {
65    MutationPlan {
66        preview_action: format!("Delete {} dashboard(s)", targets.len()),
67        preview_details: targets
68            .iter()
69            .map(|dashboard| format!("{}  {}", dashboard.id, dashboard.title))
70            .collect(),
71        targets: targets
72            .iter()
73            .map(|dashboard| dashboard.id.clone())
74            .collect(),
75    }
76}
77
78fn validate_delete_plan(plan: &DashboardDeletePlan) -> Result<()> {
79    if plan.targets.is_empty() {
80        return invalid_plan("dashboard delete plan needs at least one target");
81    }
82    let mut ids = BTreeSet::new();
83    for target in &plan.targets {
84        if target.id.trim().is_empty() || target.title.trim().is_empty() {
85            return invalid_plan("dashboard delete target identity must not be empty");
86        }
87        if !ids.insert(target.id.clone()) {
88            return invalid_plan("dashboard delete targets must be unique by id");
89        }
90    }
91    if plan.preview != delete_preview(&plan.targets) {
92        return invalid_plan("dashboard delete preview does not match guarded targets");
93    }
94    Ok(())
95}