Skip to content

Commit 4115fd2

Browse files
committed
updates
added pagination
1 parent 8a4dd83 commit 4115fd2

File tree

5 files changed

+136
-31
lines changed

5 files changed

+136
-31
lines changed

src/alerts/alert_enums.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ impl From<&str> for AlertVersion {
5050
}
5151
}
5252

53-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
53+
#[derive(
54+
Debug,
55+
serde::Serialize,
56+
serde::Deserialize,
57+
Clone,
58+
Default,
59+
FromStr,
60+
PartialEq,
61+
PartialOrd,
62+
Eq,
63+
Ord,
64+
)]
5465
#[serde(rename_all = "camelCase")]
5566
pub enum Severity {
5667
Critical,
@@ -230,7 +241,19 @@ pub enum EvalConfig {
230241
RollingWindow(RollingWindow),
231242
}
232243

233-
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Default, FromStr)]
244+
#[derive(
245+
Debug,
246+
serde::Serialize,
247+
serde::Deserialize,
248+
Clone,
249+
Copy,
250+
PartialEq,
251+
Default,
252+
FromStr,
253+
Eq,
254+
PartialOrd,
255+
Ord,
256+
)]
234257
#[serde(rename_all = "camelCase")]
235258
pub enum AlertState {
236259
Triggered,

src/alerts/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -869,8 +869,8 @@ pub enum AlertError {
869869
ParserError(#[from] ParserError),
870870
#[error("Invalid alert query: {0}")]
871871
InvalidAlertQuery(String),
872-
#[error("Invalid query parameter")]
873-
InvalidQueryParameter,
872+
#[error("Invalid query parameter: {0}")]
873+
InvalidQueryParameter(String),
874874
#[error("{0}")]
875875
ArrowError(#[from] ArrowError),
876876
#[error("Upgrade to Parseable Enterprise for {0} type alerts")]
@@ -901,7 +901,7 @@ impl actix_web::ResponseError for AlertError {
901901
Self::TargetInUse => StatusCode::CONFLICT,
902902
Self::ParserError(_) => StatusCode::BAD_REQUEST,
903903
Self::InvalidAlertQuery(_) => StatusCode::BAD_REQUEST,
904-
Self::InvalidQueryParameter => StatusCode::BAD_REQUEST,
904+
Self::InvalidQueryParameter(_) => StatusCode::BAD_REQUEST,
905905
Self::ValidationFailure(_) => StatusCode::BAD_REQUEST,
906906
Self::ArrowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
907907
Self::Unimplemented(_) => StatusCode::INTERNAL_SERVER_ERROR,

src/handlers/http/alerts.rs

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::{collections::HashMap, str::FromStr};
2020

2121
use crate::{
2222
alerts::{
23-
ALERTS, AlertError, AlertState,
23+
ALERTS, AlertError, AlertState, Severity,
2424
alert_enums::{AlertType, NotificationState},
2525
alert_structs::{AlertConfig, AlertRequest, NotificationStateRequest},
2626
alert_traits::AlertTrait,
@@ -45,18 +45,48 @@ use ulid::Ulid;
4545
pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
4646
let session_key = extract_session_key_from_req(&req)?;
4747
let query_map = web::Query::<HashMap<String, String>>::from_query(req.query_string())
48-
.map_err(|_| AlertError::InvalidQueryParameter)?;
48+
.map_err(|_| AlertError::InvalidQueryParameter("malformed query parameters".to_string()))?;
49+
4950
let mut tags_list = Vec::new();
50-
if !query_map.is_empty()
51-
&& let Some(tags) = query_map.get("tags")
52-
{
53-
tags_list = tags
54-
.split(',')
55-
.map(|s| s.trim().to_string())
56-
.filter(|s| !s.is_empty())
57-
.collect();
58-
if tags_list.is_empty() {
59-
return Err(AlertError::InvalidQueryParameter);
51+
let mut offset = 0usize;
52+
let mut limit = 100usize; // Default limit
53+
const MAX_LIMIT: usize = 1000; // Maximum allowed limit
54+
55+
// Parse query parameters
56+
if !query_map.is_empty() {
57+
// Parse tags parameter
58+
if let Some(tags) = query_map.get("tags") {
59+
tags_list = tags
60+
.split(',')
61+
.map(|s| s.trim().to_string())
62+
.filter(|s| !s.is_empty())
63+
.collect();
64+
if tags_list.is_empty() {
65+
return Err(AlertError::InvalidQueryParameter(
66+
"empty tags not allowed with query param tags".to_string(),
67+
));
68+
}
69+
}
70+
71+
// Parse offset parameter
72+
if let Some(offset_str) = query_map.get("offset") {
73+
offset = offset_str.parse().map_err(|_| {
74+
AlertError::InvalidQueryParameter("offset is not a valid number".to_string())
75+
})?;
76+
}
77+
78+
// Parse limit parameter
79+
if let Some(limit_str) = query_map.get("limit") {
80+
limit = limit_str.parse().map_err(|_| {
81+
AlertError::InvalidQueryParameter("limit is not a valid number".to_string())
82+
})?;
83+
84+
// Validate limit bounds
85+
if limit == 0 || limit > MAX_LIMIT {
86+
return Err(AlertError::InvalidQueryParameter(
87+
"limit should be between 1 and 1000".to_string(),
88+
));
89+
}
6090
}
6191
}
6292
let guard = ALERTS.read().await;
@@ -67,11 +97,55 @@ pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
6797
};
6898

6999
let alerts = alerts.list_alerts_for_user(session_key, tags_list).await?;
70-
let alerts_summary = alerts
100+
let mut alerts_summary = alerts
71101
.iter()
72102
.map(|alert| alert.to_summary())
73103
.collect::<Vec<_>>();
74-
Ok(web::Json(alerts_summary))
104+
// Sort by state priority (Triggered > NotTriggered) then by severity (Critical > High > Medium > Low)
105+
alerts_summary.sort_by(|a, b| {
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::NotTriggered); // 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::NotTriggered);
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);
130+
131+
let title_a = a.get("title").and_then(|v| v.as_str()).unwrap_or("");
132+
133+
let title_b = b.get("title").and_then(|v| v.as_str()).unwrap_or("");
134+
135+
// First sort by state, then by severity
136+
state_a
137+
.cmp(&state_b)
138+
.then_with(|| severity_a.cmp(&severity_b))
139+
.then_with(|| title_a.cmp(title_b))
140+
});
141+
142+
let paginated_alerts = alerts_summary
143+
.into_iter()
144+
.skip(offset)
145+
.take(limit)
146+
.collect::<Vec<_>>();
147+
148+
Ok(web::Json(paginated_alerts))
75149
}
76150

77151
// POST /alerts

src/storage/object_storage.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use tracing::info;
4343
use tracing::{error, warn};
4444
use ulid::Ulid;
4545

46-
use crate::alerts::alert_structs::AlertConfig;
46+
use crate::alerts::AlertConfig;
4747
use crate::alerts::target::Target;
4848
use crate::catalog::{self, manifest::Manifest, snapshot::Snapshot};
4949
use crate::correlation::{CorrelationConfig, CorrelationError};
@@ -1010,15 +1010,23 @@ pub fn target_json_path(target_id: &Ulid) -> RelativePathBuf {
10101010

10111011
#[inline(always)]
10121012
pub fn manifest_path(prefix: &str) -> RelativePathBuf {
1013-
match &PARSEABLE.options.mode {
1014-
Mode::Ingest => {
1015-
let id = INGESTOR_META
1016-
.get()
1017-
.unwrap_or_else(|| panic!("{}", INGESTOR_EXPECT))
1018-
.get_node_id();
1019-
let manifest_file_name = format!("ingestor.{id}.{MANIFEST_FILE}");
1020-
RelativePathBuf::from_iter([prefix, &manifest_file_name])
1021-
}
1022-
_ => RelativePathBuf::from_iter([prefix, MANIFEST_FILE]),
1013+
let hostname = hostname::get()
1014+
.unwrap_or_else(|_| std::ffi::OsString::from(&Ulid::new().to_string()))
1015+
.into_string()
1016+
.unwrap_or_else(|_| Ulid::new().to_string())
1017+
.matches(|c: char| c.is_alphanumeric() || c == '-' || c == '_')
1018+
.collect::<String>();
1019+
1020+
if PARSEABLE.options.mode == Mode::Ingest {
1021+
let id = INGESTOR_META
1022+
.get()
1023+
.unwrap_or_else(|| panic!("{}", INGESTOR_EXPECT))
1024+
.get_node_id();
1025+
1026+
let manifest_file_name = format!("ingestor.{hostname}.{id}.{MANIFEST_FILE}");
1027+
RelativePathBuf::from_iter([prefix, &manifest_file_name])
1028+
} else {
1029+
let manifest_file_name = format!("{hostname}.{MANIFEST_FILE}");
1030+
RelativePathBuf::from_iter([prefix, &manifest_file_name])
10231031
}
10241032
}

src/sync.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ pub async fn alert_runtime(mut rx: mpsc::Receiver<AlertTask>) -> Result<(), anyh
340340
if let Some(handle) = alert_tasks.remove(&ulid) {
341341
// cancel the task
342342
handle.abort();
343-
warn!("Alert with id {} deleted from evaluation tasks list", ulid);
343+
trace!("Alert with id {} deleted from evaluation tasks list", ulid);
344344
} else {
345345
error!(
346346
"Alert with id {} does not exist in evaluation tasks list",

0 commit comments

Comments
 (0)