elasticctl_api/compose/
alert_activity.rs1use crate::alerts::{self, AlertHit};
2use elasticctl_core::{Error, ErrorKind, Result, Transport};
3use serde_json::json;
4#[derive(Debug)]
5pub struct AlertActivity {
6 pub window: String,
7 pub total: u64,
8 pub open: u64,
9 pub acknowledged: u64,
10 pub closed: u64,
11 pub other: u64,
12 pub recent: Vec<AlertHit>,
13}
14pub fn validate_since(value: &str) -> Result<()> {
15 let b = value.as_bytes();
16 if !(2..=5).contains(&b.len())
17 || b[0] == b'0'
18 || !b[..b.len() - 1].iter().all(u8::is_ascii_digit)
19 || !matches!(b[b.len() - 1], b's' | b'm' | b'h' | b'd' | b'w')
20 {
21 return Err(Error::new(ErrorKind::Error, "invalid since"));
22 }
23 Ok(())
24}
25pub async fn alert_activity(
26 t: &Transport,
27 rule_id: &str,
28 since: &str,
29 recent: usize,
30) -> Result<AlertActivity> {
31 validate_since(since)?;
32 let size = recent.min(super::RECENT_ALERTS);
33 let body = json!({"query":{"bool":{"filter":[{"term":{"kibana.alert.rule.rule_id":rule_id}},{"range":{"@timestamp":{"gte":format!("now-{since}")}}}]}},"sort":[{"@timestamp":{"order":"desc"}},{"kibana.alert.uuid":{"order":"asc"}}],"size":size,"track_total_hits":true,"aggs":{"status":{"terms":{"field":"kibana.alert.workflow_status","size":8}}}});
34 let page = alerts::search_activity(t, &body).await?;
35 let mut out = AlertActivity {
36 window: since.into(),
37 total: page.total,
38 open: 0,
39 acknowledged: 0,
40 closed: 0,
41 other: page.other,
42 recent: page.hits.into_iter().take(size).collect(),
43 };
44 for (key, n) in page.buckets {
45 let counter = match key.as_str() {
46 "open" => &mut out.open,
47 "acknowledged" => &mut out.acknowledged,
48 "closed" => &mut out.closed,
49 _ => &mut out.other,
50 };
51 *counter = counter
52 .checked_add(n)
53 .ok_or_else(|| Error::new(ErrorKind::Http, "activity count overflow"))?;
54 }
55 Ok(out)
56}