v0.12.6-hotfix

This commit is contained in:
Mateusz Gruszczyński
2026-09-04 14:50:18 +02:00
parent 63e6e13a5d
commit 11da46c4d6
12 changed files with 291 additions and 29 deletions
+133 -9
View File
@@ -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(