Skip to main content

origin_domain/
alert.rs

1//! Alerts are a universal concept: a product decides what raises one, the platform
2//! decides how it is deduplicated, surfaced and resolved.
3
4use crate::ids::{AccountId, AlertId, ConnectorId};
5use serde::{Deserialize, Serialize};
6use time::OffsetDateTime;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
9#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
10#[serde(rename_all = "snake_case")]
11pub enum Severity {
12    Info,
13    Warning,
14    Critical,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
19#[serde(rename_all = "snake_case")]
20pub enum AlertState {
21    Active,
22    Acknowledged,
23    Resolved,
24    /// Suppressed by the user; still tracked, but never surfaced.
25    Silenced,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29#[cfg_attr(feature = "ts", derive(ts_rs::TS))]
30pub struct Alert {
31    pub id: AlertId,
32    /// Stable identity of *the problem*, not of this occurrence. Two raises with the
33    /// same fingerprint are the same alert, so the user is not notified twice.
34    pub fingerprint: String,
35    pub severity: Severity,
36    pub title: String,
37    pub body: Option<String>,
38    pub connector: Option<ConnectorId>,
39    pub account: Option<AccountId>,
40    pub state: AlertState,
41    #[serde(with = "time::serde::rfc3339")]
42    #[cfg_attr(feature = "ts", ts(type = "string"))]
43    pub raised_at: OffsetDateTime,
44    #[serde(with = "time::serde::rfc3339::option")]
45    #[cfg_attr(feature = "ts", ts(type = "string | null"))]
46    pub resolved_at: Option<OffsetDateTime>,
47}
48
49impl Alert {
50    pub fn new(
51        fingerprint: impl Into<String>,
52        severity: Severity,
53        title: impl Into<String>,
54        raised_at: OffsetDateTime,
55    ) -> Self {
56        Self {
57            id: AlertId::generate(),
58            fingerprint: fingerprint.into(),
59            severity,
60            title: title.into(),
61            body: None,
62            connector: None,
63            account: None,
64            state: AlertState::Active,
65            raised_at,
66            resolved_at: None,
67        }
68    }
69
70    pub fn with_body(mut self, body: impl Into<String>) -> Self {
71        self.body = Some(body.into());
72        self
73    }
74
75    pub fn with_connector(mut self, connector: ConnectorId) -> Self {
76        self.connector = Some(connector);
77        self
78    }
79
80    /// Whether this alert should currently be shown to the user.
81    pub fn is_visible(&self) -> bool {
82        matches!(self.state, AlertState::Active | AlertState::Acknowledged)
83    }
84
85    pub fn resolve(&mut self, at: OffsetDateTime) {
86        self.state = AlertState::Resolved;
87        self.resolved_at = Some(at);
88    }
89}