Skip to content

Commit 4d872cd

Browse files
add pagination to alerts list api
1 parent 7755a23 commit 4d872cd

File tree

2 files changed

+104
-37
lines changed

2 files changed

+104
-37
lines changed

src/alerts/mod.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,19 @@ pub enum EvalConfig {
469469
#[serde(rename_all = "camelCase")]
470470
pub struct AlertEval {}
471471

472-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Default, FromStr)]
472+
#[derive(
473+
Debug,
474+
serde::Serialize,
475+
serde::Deserialize,
476+
Clone,
477+
Copy,
478+
PartialEq,
479+
Eq,
480+
PartialOrd,
481+
Ord,
482+
Default,
483+
FromStr,
484+
)]
473485
#[serde(rename_all = "camelCase")]
474486
pub enum AlertState {
475487
Triggered,
@@ -488,7 +500,19 @@ impl Display for AlertState {
488500
}
489501
}
490502

491-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
503+
#[derive(
504+
Debug,
505+
serde::Serialize,
506+
serde::Deserialize,
507+
Clone,
508+
Copy,
509+
PartialEq,
510+
Eq,
511+
PartialOrd,
512+
Ord,
513+
Default,
514+
FromStr,
515+
)]
492516
#[serde(rename_all = "camelCase")]
493517
pub enum Severity {
494518
Critical,
@@ -1308,8 +1332,8 @@ pub enum AlertError {
13081332
ParserError(#[from] ParserError),
13091333
#[error("Invalid alert query")]
13101334
InvalidAlertQuery,
1311-
#[error("Invalid query parameter")]
1312-
InvalidQueryParameter,
1335+
#[error("Invalid query parameter: {0}")]
1336+
InvalidQueryParameter(String),
13131337
#[error("{0}")]
13141338
ArrowError(#[from] ArrowError),
13151339
#[error("Upgrade to Parseable Enterprise for {0} type alerts")]
@@ -1336,7 +1360,7 @@ impl actix_web::ResponseError for AlertError {
13361360
Self::TargetInUse => StatusCode::CONFLICT,
13371361
Self::ParserError(_) => StatusCode::BAD_REQUEST,
13381362
Self::InvalidAlertQuery => StatusCode::BAD_REQUEST,
1339-
Self::InvalidQueryParameter => StatusCode::BAD_REQUEST,
1363+
Self::InvalidQueryParameter(_) => StatusCode::BAD_REQUEST,
13401364
Self::ArrowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
13411365
Self::NotPresentInOSS(_) => StatusCode::BAD_REQUEST,
13421366
}
@@ -1650,23 +1674,23 @@ pub async fn get_alerts_summary() -> Result<AlertsSummary, AlertError> {
16501674
triggered_alerts.push(AlertsInfo {
16511675
title: alert.get_title().to_string(),
16521676
id: *alert.get_id(),
1653-
severity: alert.get_severity().clone(),
1677+
severity: *alert.get_severity(),
16541678
});
16551679
}
16561680
AlertState::Silenced => {
16571681
silenced += 1;
16581682
silenced_alerts.push(AlertsInfo {
16591683
title: alert.get_title().to_string(),
16601684
id: *alert.get_id(),
1661-
severity: alert.get_severity().clone(),
1685+
severity: *alert.get_severity(),
16621686
});
16631687
}
16641688
AlertState::Resolved => {
16651689
resolved += 1;
16661690
resolved_alerts.push(AlertsInfo {
16671691
title: alert.get_title().to_string(),
16681692
id: *alert.get_id(),
1669-
severity: alert.get_severity().clone(),
1693+
severity: *alert.get_severity(),
16701694
});
16711695
}
16721696
}

src/handlers/http/alerts.rs

Lines changed: 72 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -31,28 +31,63 @@ use actix_web::{
3131
use bytes::Bytes;
3232
use ulid::Ulid;
3333

34-
use crate::alerts::{ALERTS, AlertConfig, AlertError, AlertRequest, AlertState};
34+
use crate::alerts::{ALERTS, AlertConfig, AlertError, AlertRequest, AlertState, Severity};
3535

3636
// GET /alerts
3737
/// User needs at least a read access to the stream(s) that is being referenced in an alert
3838
/// Read all alerts then return alerts which satisfy the condition
39+
/// Supports pagination with optional query parameters:
40+
/// - tags: comma-separated list of tags to filter alerts
41+
/// - offset: number of alerts to skip (default: 0)
42+
/// - limit: maximum number of alerts to return (default: 100, max: 1000)
3943
pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
4044
let session_key = extract_session_key_from_req(&req)?;
4145
let query_map = web::Query::<HashMap<String, String>>::from_query(req.query_string())
42-
.map_err(|_| AlertError::InvalidQueryParameter)?;
46+
.map_err(|_| AlertError::InvalidQueryParameter("malformed query parameters".to_string()))?;
47+
4348
let mut tags_list = Vec::new();
49+
let mut offset = 0usize;
50+
let mut limit = 100usize; // Default limit
51+
const MAX_LIMIT: usize = 1000; // Maximum allowed limit
52+
53+
// Parse query parameters
4454
if !query_map.is_empty() {
55+
// Parse tags parameter
4556
if let Some(tags) = query_map.get("tags") {
4657
tags_list = tags
4758
.split(',')
4859
.map(|s| s.trim().to_string())
4960
.filter(|s| !s.is_empty())
5061
.collect();
5162
if tags_list.is_empty() {
52-
return Err(AlertError::InvalidQueryParameter);
63+
return Err(AlertError::InvalidQueryParameter(
64+
"empty tags not allowed with query param tags".to_string(),
65+
));
66+
}
67+
}
68+
69+
// Parse offset parameter
70+
if let Some(offset_str) = query_map.get("offset") {
71+
offset = offset_str.parse().map_err(|_| {
72+
AlertError::InvalidQueryParameter("offset is not a valid number".to_string())
73+
})?;
74+
}
75+
76+
// Parse limit parameter
77+
if let Some(limit_str) = query_map.get("limit") {
78+
limit = limit_str.parse().map_err(|_| {
79+
AlertError::InvalidQueryParameter("limit is not a valid number".to_string())
80+
})?;
81+
82+
// Validate limit bounds
83+
if limit == 0 || limit > MAX_LIMIT {
84+
return Err(AlertError::InvalidQueryParameter(
85+
"limit should be between 1 and 1000".to_string(),
86+
));
5387
}
5488
}
5589
}
90+
5691
let guard = ALERTS.read().await;
5792
let alerts = if let Some(alerts) = guard.as_ref() {
5893
alerts
@@ -68,36 +103,44 @@ pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
68103

69104
// Sort by state priority (Triggered > Silenced > Resolved) then by severity (Critical > High > Medium > Low)
70105
alerts_summary.sort_by(|a, b| {
71-
// Helper function to convert state to priority number (lower number = higher priority)
72-
let state_priority = |state: &str| match state {
73-
"Triggered" => 0,
74-
"Silenced" => 1,
75-
"Resolved" => 2,
76-
_ => 3, // Unknown state gets lowest priority
77-
};
78-
79-
// Helper function to convert severity to priority number (lower number = higher priority)
80-
let severity_priority = |severity: &str| match severity {
81-
"Critical" => 0,
82-
"High" => 1,
83-
"Medium" => 2,
84-
"Low" => 3,
85-
_ => 4, // Unknown severity gets lowest priority
86-
};
87-
88-
let state_a = a.get("state").and_then(|v| v.as_str()).unwrap_or("");
89-
let state_b = b.get("state").and_then(|v| v.as_str()).unwrap_or("");
90-
91-
let severity_a = a.get("severity").and_then(|v| v.as_str()).unwrap_or("");
92-
let severity_b = b.get("severity").and_then(|v| v.as_str()).unwrap_or("");
106+
// Parse state and severity from JSON values back to enums
107+
let state_a = a
108+
.get("state")
109+
.and_then(|v| v.as_str())
110+
.and_then(|s| s.parse::<AlertState>().ok())
111+
.unwrap_or(AlertState::Resolved); // Default to lowest priority
112+
113+
let state_b = b
114+
.get("state")
115+
.and_then(|v| v.as_str())
116+
.and_then(|s| s.parse::<AlertState>().ok())
117+
.unwrap_or(AlertState::Resolved);
118+
119+
let severity_a = a
120+
.get("severity")
121+
.and_then(|v| v.as_str())
122+
.and_then(|s| s.parse::<Severity>().ok())
123+
.unwrap_or(Severity::Low); // Default to lowest priority
124+
125+
let severity_b = b
126+
.get("severity")
127+
.and_then(|v| v.as_str())
128+
.and_then(|s| s.parse::<Severity>().ok())
129+
.unwrap_or(Severity::Low);
93130

94131
// First sort by state, then by severity
95-
state_priority(state_a)
96-
.cmp(&state_priority(state_b))
97-
.then_with(|| severity_priority(severity_a).cmp(&severity_priority(severity_b)))
132+
state_a
133+
.cmp(&state_b)
134+
.then_with(|| severity_a.cmp(&severity_b))
98135
});
99136

100-
Ok(web::Json(alerts_summary))
137+
let paginated_alerts = alerts_summary
138+
.into_iter()
139+
.skip(offset)
140+
.take(limit)
141+
.collect::<Vec<_>>();
142+
143+
Ok(web::Json(paginated_alerts))
101144
}
102145

103146
// POST /alerts

0 commit comments

Comments
 (0)