fix(templates): set the resolved event on alert recoveries

build_notification_context and the alert sample never set event, so a
recovered alert rendered {notification_event} as firing next to
alert_status=resolved. Both now follow resolved. Also moves the
saveCustomDestination comment back above its function.
This commit is contained in:
Loakesh Indiran
2026-11-03 23:28:33 +05:30
parent 6d81f347e1
commit 430218fc1c
3 changed files with 50 additions and 5 deletions
+31
View File
@@ -4232,6 +4232,11 @@ async fn build_notification_context(
crate::alerts::notifications::STATUS_FIRING
}
.to_string(),
event: if resolved {
config::meta::alerts::notification_kind::NotificationEvent::Resolved
} else {
config::meta::alerts::notification_kind::NotificationEvent::Firing
},
episode_id,
recovery_reason: String::new(),
alert_priority: alert.priority.map(|p| p.to_string()).unwrap_or_default(),
@@ -9338,4 +9343,30 @@ mod rule_notification_tests {
assert!(!url.contains("sql_mode"));
assert!(!url.contains("query="));
}
#[tokio::test]
async fn recovered_alert_context_carries_the_resolved_event() {
use config::meta::alerts::notification_kind::NotificationEvent;
let mut alert = Alert::default();
alert.org_id = "org".into();
alert.stream_name = "default".into();
for (resolved, want) in [
(false, NotificationEvent::Firing),
(true, NotificationEvent::Resolved),
] {
let options = ProcessTemplateOptions {
rows_end_time: 0,
start_time: None,
evaluation_timestamp: 0,
is_email: false,
level: None,
actual_value: None,
episode_id: None,
resolved,
};
let ctx = build_notification_context("org", &alert, &[], &[], options, None).await;
assert_eq!(ctx.event, want);
}
}
}
@@ -198,6 +198,7 @@ pub fn example_context(kind: NotificationKind, resolved: bool) -> NotificationCo
};
let mut ctx = super::preview::synthetic_context(Some(level));
ctx.kind = kind;
ctx.event = event_of(resolved);
ctx
}
}
@@ -625,6 +626,19 @@ mod tests {
}
/// Regenerate `render/testdata/examples/`; hand-check every file before committing it.
#[test]
fn alert_example_event_follows_resolved() {
let kind = NotificationKind::Alert;
assert_eq!(
example_context(kind, false).event,
NotificationEvent::Firing
);
assert_eq!(
example_context(kind, true).event,
NotificationEvent::Resolved
);
}
#[test]
#[ignore]
fn capture_examples() {
+5 -5
View File
@@ -1476,11 +1476,6 @@ function headersFromRows(rows: { key: string; value: string }[]): Headers {
return headers;
}
// The custom/pipeline save path (dispatched from saveDestination for non-prebuilt
// types). OForm calls the submit only once the schema (incl. the type-keyed
// superRefine) passes — the schema, not a manual guard, gates the save. `value`
// is the validated payload source of truth; the payload is built with explicit
// keys so no schema-only field (e.g. `credentials`) leaks into the request.
/** The stored metadata.payload_format as the select's value; anything else is automatic. */
function storedPayloadFormat(
metadata: Record<string, unknown> | null,
@@ -1505,6 +1500,11 @@ function withPayloadFormat(
return metadata;
}
// The custom/pipeline save path (dispatched from saveDestination for non-prebuilt
// types). OForm calls the submit only once the schema (incl. the type-keyed
// superRefine) passes — the schema, not a manual guard, gates the save. `value`
// is the validated payload source of truth; the payload is built with explicit
// keys so no schema-only field (e.g. `credentials`) leaks into the request.
function saveCustomDestination(value: AddDestinationForm) {
const dismiss = toast({
variant: "loading",