v0.12.6-hotfix
This commit is contained in:
+133
-9
@@ -64,14 +64,28 @@ fn alert_type_enabled(cfg: &NotificationSettings, kind: &str) -> bool {
|
||||
types.other
|
||||
}
|
||||
|
||||
fn should_send(cfg: &NotificationSettings, level: &str, kind: &str) -> bool {
|
||||
if !cfg.enabled || !alert_type_enabled(cfg, kind) {
|
||||
return false;
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum DeliveryDecision {
|
||||
NotApplicable,
|
||||
Silent(&'static str),
|
||||
Send,
|
||||
}
|
||||
|
||||
fn delivery_decision(cfg: &NotificationSettings, level: &str, kind: &str) -> DeliveryDecision {
|
||||
let candidate = level == "error" || level == "warn" || important_kind(kind);
|
||||
if !candidate {
|
||||
return DeliveryDecision::NotApplicable;
|
||||
}
|
||||
if level == "error" || level == "warn" {
|
||||
return true;
|
||||
if !cfg.enabled {
|
||||
return DeliveryDecision::Silent("notifications_disabled");
|
||||
}
|
||||
cfg.mode == "important" && important_kind(kind)
|
||||
if !alert_type_enabled(cfg, kind) {
|
||||
return DeliveryDecision::Silent("alert_type_disabled");
|
||||
}
|
||||
if level != "error" && level != "warn" && cfg.mode != "important" {
|
||||
return DeliveryDecision::Silent("mode_filtered");
|
||||
}
|
||||
DeliveryDecision::Send
|
||||
}
|
||||
|
||||
fn cooldown_key(cfg: &NotificationSettings, kind: &str, metadata: &Value) -> String {
|
||||
@@ -100,15 +114,85 @@ fn take_cooldown(cfg: &NotificationSettings, kind: &str, metadata: &Value) -> bo
|
||||
true
|
||||
}
|
||||
|
||||
fn with_notification_status(
|
||||
metadata: &Value,
|
||||
status: &str,
|
||||
reason: Option<&str>,
|
||||
provider: &str,
|
||||
) -> Value {
|
||||
let mut updated = match metadata {
|
||||
Value::Object(map) => Value::Object(map.clone()),
|
||||
other => json!({"data": other}),
|
||||
};
|
||||
let mut notification = serde_json::Map::new();
|
||||
notification.insert("status".into(), Value::String(status.into()));
|
||||
if let Some(reason) = reason {
|
||||
notification.insert("reason".into(), Value::String(reason.into()));
|
||||
}
|
||||
if !provider.trim().is_empty() {
|
||||
notification.insert("provider".into(), Value::String(provider.into()));
|
||||
}
|
||||
if let Some(object) = updated.as_object_mut() {
|
||||
object.insert("notification".into(), Value::Object(notification));
|
||||
}
|
||||
updated
|
||||
}
|
||||
|
||||
fn persist_notification_status(
|
||||
state: &AppState,
|
||||
event_id: Option<i64>,
|
||||
metadata: &Value,
|
||||
status: &str,
|
||||
reason: Option<&str>,
|
||||
provider: &str,
|
||||
) {
|
||||
let Some(event_id) = event_id else {
|
||||
return;
|
||||
};
|
||||
let updated = with_notification_status(metadata, status, reason, provider);
|
||||
match state.db.update_event_metadata(event_id, &updated) {
|
||||
Ok(true) => state.broadcast(
|
||||
"log.updated",
|
||||
json!({"id": event_id, "metadata": updated}),
|
||||
),
|
||||
Ok(false) => tracing::warn!(event_id, "cannot update missing event log metadata"),
|
||||
Err(err) => tracing::warn!(event_id, error=?err, "cannot update event log metadata"),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dispatch(
|
||||
state: AppState,
|
||||
event_id: Option<i64>,
|
||||
level: String,
|
||||
kind: String,
|
||||
message: String,
|
||||
metadata: Value,
|
||||
) {
|
||||
let cfg = state.settings.read().await.notifications.clone();
|
||||
if !should_send(&cfg, &level, &kind) || !take_cooldown(&cfg, &kind, &metadata) {
|
||||
match delivery_decision(&cfg, &level, &kind) {
|
||||
DeliveryDecision::NotApplicable => return,
|
||||
DeliveryDecision::Silent(reason) => {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"silent",
|
||||
Some(reason),
|
||||
&cfg.provider,
|
||||
);
|
||||
return;
|
||||
}
|
||||
DeliveryDecision::Send => {}
|
||||
}
|
||||
if !take_cooldown(&cfg, &kind, &metadata) {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"silent",
|
||||
Some("cooldown"),
|
||||
&cfg.provider,
|
||||
);
|
||||
return;
|
||||
}
|
||||
let title = format!(
|
||||
@@ -139,8 +223,26 @@ pub async fn dispatch(
|
||||
}
|
||||
_ => Err("unsupported notification provider".into()),
|
||||
};
|
||||
if let Err(err) = result {
|
||||
tracing::warn!(provider=%cfg.provider, error=%err, "notification delivery failed");
|
||||
match result {
|
||||
Ok(()) => persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"sent",
|
||||
None,
|
||||
&cfg.provider,
|
||||
),
|
||||
Err(err) => {
|
||||
persist_notification_status(
|
||||
&state,
|
||||
event_id,
|
||||
&metadata,
|
||||
"failed",
|
||||
Some("delivery_failed"),
|
||||
&cfg.provider,
|
||||
);
|
||||
tracing::warn!(provider=%cfg.provider, error=%err, "notification delivery failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +263,28 @@ mod tests {
|
||||
assert!(alert_type_enabled(&cfg, "zone.sensor_discrepancy"));
|
||||
assert!(!alert_type_enabled(&cfg, "zone.action_error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_alert_type_is_recorded_as_silent() {
|
||||
let mut cfg = NotificationSettings::default();
|
||||
cfg.enabled = true;
|
||||
cfg.mode = "important".into();
|
||||
cfg.alert_types.sensor_discrepancy = false;
|
||||
|
||||
assert_eq!(
|
||||
delivery_decision(&cfg, "warn", "zone.sensor_discrepancy"),
|
||||
DeliveryDecision::Silent("alert_type_disabled")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ordinary_info_event_has_no_notification_status() {
|
||||
let cfg = NotificationSettings::default();
|
||||
assert_eq!(
|
||||
delivery_decision(&cfg, "info", "zone.quick_control"),
|
||||
DeliveryDecision::NotApplicable
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_pushover(
|
||||
|
||||
Reference in New Issue
Block a user