Skip to content

Commit 7df81df

Browse files
update: add tags, created to alerts
similar to dashboards, server supports adding tags while creating alerts list alerts by tags by providing query param tags=<list of tags> list_tags endpoint provides list of unique tags from all alerts
1 parent 7017c28 commit 7df81df

File tree

4 files changed

+119
-5
lines changed

4 files changed

+119
-5
lines changed

src/alerts/mod.rs

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
use actix_web::http::header::ContentType;
2020
use arrow_schema::{DataType, Schema};
2121
use async_trait::async_trait;
22-
use chrono::Utc;
22+
use chrono::{DateTime, Utc};
2323
use datafusion::logical_expr::{LogicalPlan, Projection};
2424
use datafusion::sql::sqlparser::parser::ParserError;
2525
use derive_more::FromStrError;
@@ -197,6 +197,14 @@ pub enum AlertType {
197197
Threshold,
198198
}
199199

200+
impl Display for AlertType {
201+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202+
match self {
203+
AlertType::Threshold => write!(f, "Threshold"),
204+
}
205+
}
206+
}
207+
200208
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
201209
#[serde(rename_all = "camelCase")]
202210
pub enum AlertOperator {
@@ -528,6 +536,7 @@ pub struct AlertRequest {
528536
pub threshold_config: ThresholdConfig,
529537
pub eval_config: EvalConfig,
530538
pub targets: Vec<Ulid>,
539+
pub tags: Option<Vec<String>>,
531540
}
532541

533542
impl AlertRequest {
@@ -547,6 +556,8 @@ impl AlertRequest {
547556
eval_config: self.eval_config,
548557
targets: self.targets,
549558
state: AlertState::default(),
559+
created: Utc::now(),
560+
tags: self.tags,
550561
};
551562
Ok(config)
552563
}
@@ -568,6 +579,8 @@ pub struct AlertConfig {
568579
// for new alerts, state should be resolved
569580
#[serde(default)]
570581
pub state: AlertState,
582+
pub created: DateTime<Utc>,
583+
pub tags: Option<Vec<String>>,
571584
}
572585

573586
impl AlertConfig {
@@ -597,6 +610,8 @@ impl AlertConfig {
597610
eval_config,
598611
targets,
599612
state,
613+
created: Utc::now(),
614+
tags: None,
600615
};
601616

602617
// Save the migrated alert back to storage
@@ -1183,6 +1198,45 @@ impl AlertConfig {
11831198
}
11841199
Ok(())
11851200
}
1201+
1202+
/// create a summary of the dashboard
1203+
/// used for listing dashboards
1204+
pub fn to_summary(&self) -> serde_json::Map<String, serde_json::Value> {
1205+
let mut map = serde_json::Map::new();
1206+
1207+
map.insert(
1208+
"title".to_string(),
1209+
serde_json::Value::String(self.title.clone()),
1210+
);
1211+
1212+
map.insert(
1213+
"created".to_string(),
1214+
serde_json::Value::String(self.created.to_string()),
1215+
);
1216+
1217+
map.insert(
1218+
"alertType".to_string(),
1219+
serde_json::Value::String(self.alert_type.to_string()),
1220+
);
1221+
1222+
map.insert(
1223+
"id".to_string(),
1224+
serde_json::Value::String(self.id.to_string()),
1225+
);
1226+
1227+
if let Some(tags) = &self.tags {
1228+
map.insert(
1229+
"tags".to_string(),
1230+
serde_json::Value::Array(
1231+
tags.iter()
1232+
.map(|tag| serde_json::Value::String(tag.clone()))
1233+
.collect(),
1234+
),
1235+
);
1236+
}
1237+
1238+
map
1239+
}
11861240
}
11871241

11881242
#[derive(Debug, thiserror::Error)]
@@ -1221,6 +1275,8 @@ pub enum AlertError {
12211275
ParserError(#[from] ParserError),
12221276
#[error("Invalid alert query")]
12231277
InvalidAlertQuery,
1278+
#[error("Invalid query parameter")]
1279+
InvalidQueryParameter,
12241280
}
12251281

12261282
impl actix_web::ResponseError for AlertError {
@@ -1243,6 +1299,7 @@ impl actix_web::ResponseError for AlertError {
12431299
Self::TargetInUse => StatusCode::CONFLICT,
12441300
Self::ParserError(_) => StatusCode::BAD_REQUEST,
12451301
Self::InvalidAlertQuery => StatusCode::BAD_REQUEST,
1302+
Self::InvalidQueryParameter => StatusCode::BAD_REQUEST,
12461303
}
12471304
}
12481305

@@ -1350,6 +1407,7 @@ impl Alerts {
13501407
pub async fn list_alerts_for_user(
13511408
&self,
13521409
session: SessionKey,
1410+
tags: Vec<String>,
13531411
) -> Result<Vec<AlertConfig>, AlertError> {
13541412
let mut alerts: Vec<AlertConfig> = Vec::new();
13551413
for (_, alert) in self.alerts.read().await.iter() {
@@ -1358,6 +1416,17 @@ impl Alerts {
13581416
alerts.push(alert.to_owned());
13591417
}
13601418
}
1419+
if tags.is_empty() {
1420+
return Ok(alerts);
1421+
}
1422+
// filter alerts based on tags
1423+
alerts.retain(|alert| {
1424+
if let Some(alert_tags) = &alert.tags {
1425+
alert_tags.iter().any(|tag| tags.contains(tag))
1426+
} else {
1427+
false
1428+
}
1429+
});
13611430

13621431
Ok(alerts)
13631432
}
@@ -1456,6 +1525,20 @@ impl Alerts {
14561525

14571526
Ok(())
14581527
}
1528+
1529+
/// List tags from all alerts
1530+
/// This function returns a list of unique tags from all alerts
1531+
pub async fn list_tags(&self) -> Vec<String> {
1532+
let alerts = self.alerts.read().await;
1533+
let mut tags = alerts
1534+
.iter()
1535+
.filter_map(|(_, alert)| alert.tags.as_ref())
1536+
.flat_map(|t| t.iter().cloned())
1537+
.collect::<Vec<String>>();
1538+
tags.sort();
1539+
tags.dedup();
1540+
tags
1541+
}
14591542
}
14601543

14611544
#[derive(Debug, Serialize)]

src/handlers/http/alerts.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
*
1717
*/
1818

19-
use std::str::FromStr;
19+
use std::{collections::HashMap, str::FromStr};
2020

2121
use crate::{
2222
parseable::PARSEABLE,
@@ -38,9 +38,28 @@ use crate::alerts::{ALERTS, AlertConfig, AlertError, AlertRequest, AlertState};
3838
/// Read all alerts then return alerts which satisfy the condition
3939
pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
4040
let session_key = extract_session_key_from_req(&req)?;
41-
let alerts = ALERTS.list_alerts_for_user(session_key).await?;
41+
let query_map = web::Query::<HashMap<String, String>>::from_query(req.query_string())
42+
.map_err(|_| AlertError::InvalidQueryParameter)?;
43+
let mut tags_list = Vec::new();
44+
if !query_map.is_empty() {
45+
if let Some(tags) = query_map.get("tags") {
46+
tags_list = tags
47+
.split(',')
48+
.map(|s| s.trim().to_string())
49+
.filter(|s| !s.is_empty())
50+
.collect();
51+
if tags_list.is_empty() {
52+
return Err(AlertError::InvalidQueryParameter);
53+
}
54+
}
55+
}
4256

43-
Ok(web::Json(alerts))
57+
let alerts = ALERTS.list_alerts_for_user(session_key, tags_list).await?;
58+
let alerts_summary = alerts
59+
.iter()
60+
.map(|alert| alert.to_summary())
61+
.collect::<Vec<_>>();
62+
Ok(web::Json(alerts_summary))
4463
}
4564

4665
// POST /alerts
@@ -154,3 +173,8 @@ pub async fn update_state(
154173
let alert = ALERTS.get_alert_by_id(alert_id).await?;
155174
Ok(web::Json(alert))
156175
}
176+
177+
pub async fn list_tags() -> Result<impl Responder, AlertError> {
178+
let tags = ALERTS.list_tags().await;
179+
Ok(web::Json(tags))
180+
}

src/handlers/http/modal/server.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,13 @@ impl Server {
269269
.authorize(Action::DeleteAlert),
270270
),
271271
)
272+
.service(
273+
web::resource("/list_tags").route(
274+
web::get()
275+
.to(alerts::list_tags)
276+
.authorize(Action::ListDashboard),
277+
),
278+
)
272279
}
273280

274281
pub fn get_targets_webscope() -> Scope {

src/prism/home/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ async fn get_alert_titles(
356356
query_value: &str,
357357
) -> Result<Vec<Resource>, PrismHomeError> {
358358
let alerts = ALERTS
359-
.list_alerts_for_user(key.clone())
359+
.list_alerts_for_user(key.clone(), vec![])
360360
.await?
361361
.iter()
362362
.filter_map(|alert| {

0 commit comments

Comments
 (0)