@@ -31,28 +31,63 @@ use actix_web::{
31
31
use bytes:: Bytes ;
32
32
use ulid:: Ulid ;
33
33
34
- use crate :: alerts:: { ALERTS , AlertConfig , AlertError , AlertRequest , AlertState } ;
34
+ use crate :: alerts:: { ALERTS , AlertConfig , AlertError , AlertRequest , AlertState , Severity } ;
35
35
36
36
// GET /alerts
37
37
/// User needs at least a read access to the stream(s) that is being referenced in an alert
38
38
/// 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)
39
43
pub async fn list ( req : HttpRequest ) -> Result < impl Responder , AlertError > {
40
44
let session_key = extract_session_key_from_req ( & req) ?;
41
45
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
+
43
48
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
44
54
if !query_map. is_empty ( ) {
55
+ // Parse tags parameter
45
56
if let Some ( tags) = query_map. get ( "tags" ) {
46
57
tags_list = tags
47
58
. split ( ',' )
48
59
. map ( |s| s. trim ( ) . to_string ( ) )
49
60
. filter ( |s| !s. is_empty ( ) )
50
61
. collect ( ) ;
51
62
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
+ ) ) ;
53
87
}
54
88
}
55
89
}
90
+
56
91
let guard = ALERTS . read ( ) . await ;
57
92
let alerts = if let Some ( alerts) = guard. as_ref ( ) {
58
93
alerts
@@ -68,36 +103,44 @@ pub async fn list(req: HttpRequest) -> Result<impl Responder, AlertError> {
68
103
69
104
// Sort by state priority (Triggered > Silenced > Resolved) then by severity (Critical > High > Medium > Low)
70
105
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 ) ;
93
130
94
131
// 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) )
98
135
} ) ;
99
136
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) )
101
144
}
102
145
103
146
// POST /alerts
0 commit comments