Skip to content

Commit 636e920

Browse files
add alert query mode metadata (#1707)
* add alert query mode metadata persist queryType for alerts: builder, code, or promql defaults old alerts to builder accepts legacy queryType=sql as code reserves old creationType keys treats builder/code as SQL modes for auth and dataset resolution. * fix get alert * fmt fix * resolve comments * deepsource fix * skip loading anomaly or forecast alerts * fix aggregate validation
1 parent 47f798f commit 636e920

6 files changed

Lines changed: 273 additions & 81 deletions

File tree

‎src/alerts/alert_enums.rs‎

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,26 @@ impl Display for AlertType {
116116
}
117117
}
118118

119+
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Default)]
120+
#[serde(rename_all = "camelCase")]
121+
pub enum AlertQueryType {
122+
#[default]
123+
Builder,
124+
#[serde(alias = "sql")]
125+
Code,
126+
Promql,
127+
}
128+
129+
impl Display for AlertQueryType {
130+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131+
match self {
132+
AlertQueryType::Builder => write!(f, "builder"),
133+
AlertQueryType::Code => write!(f, "code"),
134+
AlertQueryType::Promql => write!(f, "promql"),
135+
}
136+
}
137+
}
138+
119139
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
120140
#[serde(rename_all = "camelCase")]
121141
pub enum AlertOperator {
@@ -211,6 +231,67 @@ impl Display for WhereConfigOperator {
211231
}
212232
}
213233

234+
#[cfg(test)]
235+
mod tests {
236+
use super::AlertQueryType;
237+
238+
#[test]
239+
fn alert_query_type_deserializes_supported_modes() {
240+
assert_eq!(
241+
serde_json::from_str::<AlertQueryType>("\"builder\"").unwrap(),
242+
AlertQueryType::Builder
243+
);
244+
assert_eq!(
245+
serde_json::from_str::<AlertQueryType>("\"code\"").unwrap(),
246+
AlertQueryType::Code
247+
);
248+
assert_eq!(
249+
serde_json::from_str::<AlertQueryType>("\"promql\"").unwrap(),
250+
AlertQueryType::Promql
251+
);
252+
}
253+
254+
#[test]
255+
fn alert_query_type_accepts_legacy_sql_as_code() {
256+
assert_eq!(
257+
serde_json::from_str::<AlertQueryType>("\"sql\"").unwrap(),
258+
AlertQueryType::Code
259+
);
260+
}
261+
262+
#[test]
263+
fn alert_query_type_rejects_unknown_mode() {
264+
assert!(serde_json::from_str::<AlertQueryType>("\"rawSql\"").is_err());
265+
}
266+
267+
#[test]
268+
fn alert_request_deserializes_promql_query_type() {
269+
let request: crate::alerts::alert_structs::AlertRequest =
270+
serde_json::from_value(serde_json::json!({
271+
"severity": "high",
272+
"title": "Test alert",
273+
"alertType": "threshold",
274+
"queryType": "promql",
275+
"query": "sum({\"k8s.pod.cpu.usage\"}) by (\"k8s.namespace.name\")",
276+
"thresholdConfig": {"operator": ">", "value": -1.0},
277+
"evalConfig": {
278+
"rollingWindow": {
279+
"evalStart": "10 minutes",
280+
"evalEnd": "now",
281+
"evalFrequency": 10
282+
}
283+
},
284+
"targets": [],
285+
"notificationConfig": {"interval": 1},
286+
"datasets": ["azure-prod-cluster-metrics"]
287+
}))
288+
.unwrap();
289+
290+
assert_eq!(request.query_type, AlertQueryType::Promql);
291+
assert_eq!(request.datasets, vec!["azure-prod-cluster-metrics"]);
292+
}
293+
}
294+
214295
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
215296
#[serde(rename_all = "camelCase")]
216297
pub enum AggregateFunction {

‎src/alerts/alert_structs.rs‎

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ use crate::{
2828
alerts::{
2929
AlertError, CURRENT_ALERTS_VERSION,
3030
alert_enums::{
31-
AlertOperator, AlertState, AlertTask, AlertType, AlertVersion, EvalConfig,
32-
LogicalOperator, NotificationState, Severity, WhereConfigOperator,
31+
AlertOperator, AlertQueryType, AlertState, AlertTask, AlertType, AlertVersion,
32+
EvalConfig, LogicalOperator, NotificationState, Severity, WhereConfigOperator,
3333
},
3434
alert_traits::AlertTrait,
3535
target::{NotificationConfig, TARGETS},
3636
},
3737
metastore::metastore_traits::MetastoreObject,
38+
parseable::PARSEABLE,
3839
query::resolve_stream_names,
3940
storage::object_storage::{alert_json_path, alert_state_json_path, mttr_json_path},
4041
};
@@ -45,6 +46,10 @@ const RESERVED_FIELDS: &[&str] = &[
4546
"severity",
4647
"title",
4748
"query",
49+
"queryType",
50+
"query_type",
51+
"creationType",
52+
"creation_type",
4853
"datasets",
4954
"alertType",
5055
"alert_type",
@@ -282,6 +287,10 @@ pub struct AlertRequest {
282287
pub severity: Severity,
283288
pub title: String,
284289
pub query: String,
290+
#[serde(default)]
291+
pub query_type: AlertQueryType,
292+
#[serde(default)]
293+
pub datasets: Vec<String>,
285294
pub alert_type: String,
286295
pub anomaly_config: Option<AnomalyConfig>,
287296
pub forecast_config: Option<ForecastConfig>,
@@ -326,11 +335,24 @@ impl AlertRequest {
326335
for id in &self.targets {
327336
TARGETS.get_target_by_id(id, &tenant_id).await?;
328337
}
329-
let datasets = resolve_stream_names(&self.query)?;
338+
let datasets = match self.query_type {
339+
AlertQueryType::Builder | AlertQueryType::Code => resolve_stream_names(&self.query)?,
340+
AlertQueryType::Promql => self.datasets,
341+
};
330342

331343
if datasets.len() != 1 {
332344
return Err(AlertError::ValidationFailure(format!(
333-
"Query should include only one dataset. Found: {datasets:?}"
345+
"Alert should include only one dataset. Found: {datasets:?}"
346+
)));
347+
}
348+
if self.query_type == AlertQueryType::Promql
349+
&& !PARSEABLE
350+
.check_or_load_stream(&datasets[0], &tenant_id)
351+
.await
352+
{
353+
return Err(AlertError::ValidationFailure(format!(
354+
"Invalid PromQL metrics stream: {}",
355+
datasets[0]
334356
)));
335357
}
336358

@@ -342,6 +364,7 @@ impl AlertRequest {
342364
severity: self.severity,
343365
title: self.title,
344366
query: self.query,
367+
query_type: self.query_type,
345368
datasets,
346369
alert_type: {
347370
match self.alert_type.as_str() {
@@ -393,6 +416,8 @@ pub struct AlertConfig {
393416
pub severity: Severity,
394417
pub title: String,
395418
pub query: String,
419+
#[serde(default)]
420+
pub query_type: AlertQueryType,
396421
pub datasets: Vec<String>,
397422
pub alert_type: AlertType,
398423
pub threshold_config: ThresholdConfig,
@@ -420,6 +445,7 @@ pub struct AlertConfigResponse {
420445
pub severity: Severity,
421446
pub title: String,
422447
pub query: String,
448+
pub query_type: AlertQueryType,
423449
pub datasets: Vec<String>,
424450
pub alert_type: &'static str,
425451
pub anomaly_config: Option<AnomalyConfig>,
@@ -466,6 +492,7 @@ impl AlertConfig {
466492
severity: self.severity,
467493
title: self.title,
468494
query: self.query,
495+
query_type: self.query_type,
469496
datasets: self.datasets,
470497
alert_type: {
471498
match self.alert_type {

‎src/alerts/alert_traits.rs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
use crate::{
2020
alerts::{
21-
AlertConfig, AlertError, AlertState, AlertType, EvalConfig, Severity,
21+
AlertConfig, AlertError, AlertQueryType, AlertState, AlertType, EvalConfig, Severity,
2222
alert_enums::NotificationState,
2323
alert_structs::{Context, ThresholdConfig},
2424
},
@@ -64,6 +64,7 @@ pub trait AlertTrait: Debug + Send + Sync + MetastoreObject {
6464
fn get_severity(&self) -> &Severity;
6565
fn get_title(&self) -> &str;
6666
fn get_query(&self) -> &str;
67+
fn get_query_type(&self) -> AlertQueryType;
6768
fn get_alert_type(&self) -> &AlertType;
6869
fn get_threshold_config(&self) -> &ThresholdConfig;
6970
fn get_eval_config(&self) -> &EvalConfig;

‎src/alerts/alert_types.rs‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use ulid::Ulid;
2727

2828
use crate::{
2929
alerts::{
30-
AlertConfig, AlertError, AlertState, AlertType, AlertVersion, EvalConfig, Severity,
31-
ThresholdConfig,
30+
AlertConfig, AlertError, AlertQueryType, AlertState, AlertType, AlertVersion, EvalConfig,
31+
Severity, ThresholdConfig,
3232
alert_enums::NotificationState,
3333
alert_structs::{AlertStateEntry, GroupResult},
3434
alert_traits::{AlertTrait, MessageCreation},
@@ -58,6 +58,8 @@ pub struct ThresholdAlert {
5858
pub severity: Severity,
5959
pub title: String,
6060
pub query: String,
61+
#[serde(default)]
62+
pub query_type: AlertQueryType,
6163
pub alert_type: AlertType,
6264
pub threshold_config: ThresholdConfig,
6365
pub eval_config: EvalConfig,
@@ -89,6 +91,8 @@ impl MetastoreObject for ThresholdAlert {
8991
#[async_trait]
9092
impl AlertTrait for ThresholdAlert {
9193
async fn eval_alert(&self) -> Result<Option<String>, AlertError> {
94+
self.validate_oss_query_type()?;
95+
9296
let time_range = extract_time_range(&self.eval_config)?;
9397

9498
let tenant = self.tenant_id.as_deref().unwrap_or(DEFAULT_TENANT);
@@ -188,6 +192,8 @@ impl AlertTrait for ThresholdAlert {
188192
return Err(AlertError::InvalidAlertQuery("Empty query".into()));
189193
}
190194

195+
self.validate_oss_query_type()?;
196+
191197
let tables = resolve_stream_names(&self.query)?;
192198
if tables.is_empty() {
193199
return Err(AlertError::InvalidAlertQuery(
@@ -315,6 +321,10 @@ impl AlertTrait for ThresholdAlert {
315321
&self.query
316322
}
317323

324+
fn get_query_type(&self) -> AlertQueryType {
325+
self.query_type
326+
}
327+
318328
fn get_severity(&self) -> &Severity {
319329
&self.severity
320330
}
@@ -436,6 +446,7 @@ impl From<AlertConfig> for ThresholdAlert {
436446
severity: value.severity,
437447
title: value.title,
438448
query: value.query,
449+
query_type: value.query_type,
439450
alert_type: value.alert_type,
440451
threshold_config: value.threshold_config,
441452
eval_config: value.eval_config,
@@ -461,6 +472,7 @@ impl From<ThresholdAlert> for AlertConfig {
461472
severity: val.severity,
462473
title: val.title,
463474
query: val.query,
475+
query_type: val.query_type,
464476
alert_type: val.alert_type,
465477
threshold_config: val.threshold_config,
466478
eval_config: val.eval_config,
@@ -479,6 +491,14 @@ impl From<ThresholdAlert> for AlertConfig {
479491
}
480492

481493
impl ThresholdAlert {
494+
pub(crate) fn validate_oss_query_type(&self) -> Result<(), AlertError> {
495+
if self.query_type == AlertQueryType::Promql {
496+
return Err(AlertError::NotPresentInOSS("promql alerts"));
497+
}
498+
499+
Ok(())
500+
}
501+
482502
fn create_group_message(&self, breached_groups: &[GroupResult]) -> Result<String, AlertError> {
483503
let header = self.get_message_header()?;
484504
let mut message = format!("{header}\n");

0 commit comments

Comments
 (0)