Skip to content

Commit

Permalink
Feat/monitor names in alert configs (#95)
Browse files Browse the repository at this point in the history
* feat: Add migration to add `monitor_name` to `monitor_alert_config`, mirroring `monitor.name`

* feat: Make `AlertConfig` domain model include Monitor name as well as monitor ID

* feat: Infra changes to support `AlertConfig` domain model including names of monitors

* chore: Add comment to explain how trigger keeps `monitor_alert_config.monitor_name` up to date with `monitor.name`
  • Loading branch information
howamith authored Dec 14, 2024
1 parent 37f403b commit 833a06e
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 52 deletions.
85 changes: 62 additions & 23 deletions api/src/domain/models/alert_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ pub struct AlertConfig {
/// The type of alert.
#[serde(rename = "type")]
pub type_: AlertType,
/// A list of IDs for Monitors that this alert configuration is associated with.
pub monitor_ids: Vec<Uuid>,
/// A list of Monitors that this alert configuration is applied on.
pub monitors: Vec<AppliedMonitor>,
}

/// The different types of alerts that can be configured.
Expand All @@ -43,6 +43,15 @@ pub struct SlackAlertConfig {
pub token: String,
}

/// Brief info on a Monitor using an alert configuration.
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct AppliedMonitor {
/// The ID of a Monitor using an alert configuration.
pub monitor_id: Uuid,
/// The name of a Monitor using an alert configuration.
pub name: String,
}

impl AlertConfig {
/// Create a new `AlertConfig` for Slack.
pub fn new_slack_config(
Expand All @@ -62,7 +71,7 @@ impl AlertConfig {
on_late,
on_error,
type_: AlertType::Slack(SlackAlertConfig { channel, token }),
monitor_ids: Vec::new(),
monitors: Vec::new(),
}
}

Expand All @@ -75,7 +84,10 @@ impl AlertConfig {
monitor.monitor_id, self.alert_config_id
)));
}
self.monitor_ids.push(monitor.monitor_id);
self.monitors.push(AppliedMonitor {
monitor_id: monitor.monitor_id,
name: monitor.name.clone(),
});
Ok(())
}

Expand All @@ -89,13 +101,19 @@ impl AlertConfig {
monitor.monitor_id, self.alert_config_id
)));
}
self.monitor_ids.retain(|&id| id != monitor.monitor_id);
self.monitors
.retain(|monitor_info| monitor_info.monitor_id != monitor.monitor_id);
Ok(())
}

/// Check if the alert configuration is associated with a monitor.
pub fn is_associated_with_monitor(&self, monitor: &Monitor) -> bool {
self.monitor_ids.contains(&monitor.monitor_id)
let monitor_ids: Vec<_> = self
.monitors
.iter()
.map(|monitor_info| monitor_info.monitor_id)
.collect();
monitor_ids.contains(&monitor.monitor_id)
}
}

Expand Down Expand Up @@ -134,26 +152,33 @@ mod tests {
token: "test-token".to_string(),
})
);
assert!(alert_config.monitor_ids.is_empty());
assert!(alert_config.monitors.is_empty());
}

#[test]
fn test_serialisation() {
let alert_config = AlertConfig::new_slack_config(
"test-name".to_string(),
"test-tenant".to_string(),
true,
true,
true,
"test-channel".to_string(),
"test-token".to_string(),
);
let alert_config = AlertConfig {
alert_config_id: gen_uuid("3867e53d-9c17-4ce9-b153-eff3d8c9edec"),
name: "test-name".to_string(),
tenant: "test-tenant".to_string(),
active: true,
on_late: true,
on_error: true,
type_: AlertType::Slack(SlackAlertConfig {
channel: "test-channel".to_string(),
token: "test-token".to_string(),
}),
monitors: vec![AppliedMonitor {
monitor_id: gen_uuid("ba0cd705-4a5b-4635-9def-611b1143e4aa"),
name: "test-name".to_string(),
}],
};

let value = serde_json::to_value(&alert_config).unwrap();
assert_eq!(
value,
json!({
"alert_config_id": alert_config.alert_config_id.to_string(),
"alert_config_id": "3867e53d-9c17-4ce9-b153-eff3d8c9edec",
"name": "test-name",
"tenant": "test-tenant",
"active": true,
Expand All @@ -165,7 +190,12 @@ mod tests {
"token": "test-token"
}
},
"monitor_ids": []
"monitors": [
{
"monitor_id": "ba0cd705-4a5b-4635-9def-611b1143e4aa",
"name": "test-name"
}
]
})
);
}
Expand All @@ -184,17 +214,23 @@ mod tests {
let monitor = Monitor::new("test-tenant".to_string(), "test-name".to_string(), 200, 100);

// Sanity check to make sure we start from a clean slate.
assert_eq!(alert_config.monitor_ids, vec![]);
assert_eq!(alert_config.monitors, vec![]);
assert!(!alert_config.is_associated_with_monitor(&monitor));

alert_config.associate_monitor(&monitor).unwrap();

assert_eq!(alert_config.monitor_ids, vec![monitor.monitor_id]);
assert_eq!(
alert_config.monitors,
vec![AppliedMonitor {
monitor_id: monitor.monitor_id,
name: monitor.name.clone()
}]
);
assert!(alert_config.is_associated_with_monitor(&monitor));

alert_config.disassociate_monitor(&monitor).unwrap();

assert_eq!(alert_config.monitor_ids, vec![]);
assert_eq!(alert_config.monitors, vec![]);
assert!(!alert_config.is_associated_with_monitor(&monitor));
}

Expand All @@ -219,7 +255,10 @@ mod tests {
channel: "test-channel".to_string(),
token: "test-token".to_string(),
}),
monitor_ids: vec![gen_uuid("ba0cd705-4a5b-4635-9def-611b1143e4aa")],
monitors: vec![AppliedMonitor {
monitor_id: gen_uuid("ba0cd705-4a5b-4635-9def-611b1143e4aa"),
name: "test-name".to_string(),
}],
};

let result = alert_config.associate_monitor(&monitor);
Expand Down Expand Up @@ -255,7 +294,7 @@ mod tests {
channel: "test-channel".to_string(),
token: "test-token".to_string(),
}),
monitor_ids: vec![],
monitors: vec![],
};

let result = alert_config.disassociate_monitor(&monitor);
Expand Down
1 change: 1 addition & 0 deletions api/src/infrastructure/db_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ diesel::table! {
monitor_alert_config (alert_config_id, monitor_id) {
alert_config_id -> Uuid,
monitor_id -> Uuid,
monitor_name -> Varchar,
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Drop trigger and function
DROP TRIGGER IF EXISTS monitor_name_update ON monitor;
DROP FUNCTION IF EXISTS update_monitor_alert_config_monitor_name;

ALTER TABLE monitor_alert_config DROP monitor_name;
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Add name to monitor_alert_config, nullable for now.
ALTER TABLE monitor_alert_config
ADD monitor_name VARCHAR NULL;

-- Set the names to what they are currently, then make the column non-nullable.
UPDATE monitor_alert_config
SET monitor_name = monitor.name
FROM monitor
WHERE monitor_alert_config.monitor_id = monitor.monitor_id;

ALTER TABLE monitor_alert_config
ALTER COLUMN monitor_name SET NOT NULL;

-- Create a trigger function for updating the monitor name in monitor_alert_config.
CREATE OR REPLACE FUNCTION update_monitor_alert_config_monitor_name()
RETURNS TRIGGER AS $$
BEGIN
UPDATE monitor_alert_config
SET monitor_name = NEW.name
WHERE monitor_id = NEW.monitor_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Apply the trigger to the monitor table so that when name changes, the changes are
-- reflected in the monitor_alert_config table.
CREATE TRIGGER monitor_name_update
AFTER UPDATE OF name ON monitor
FOR EACH ROW
EXECUTE FUNCTION update_monitor_alert_config_monitor_name();
48 changes: 36 additions & 12 deletions api/src/infrastructure/models/alert_config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use diesel::prelude::*;
use uuid::Uuid;

use crate::domain::models::alert_config::{AlertConfig, AlertType, SlackAlertConfig};
use crate::domain::models::alert_config::{
AlertConfig, AlertType, AppliedMonitor, SlackAlertConfig,
};
use crate::errors::Error;
use crate::infrastructure::db_schema::{alert_config, monitor_alert_config, slack_alert_config};

Expand Down Expand Up @@ -31,6 +33,10 @@ pub struct AlertConfigData {
pub struct MonitorAlertConfigData {
pub alert_config_id: Uuid,
pub monitor_id: Uuid,
// Note that this column will be kept up to date with the `name` of the corresponding `monitor`
// record by the `monitor_name_update` trigger, added in the
// `2024-12-13-222551_add-name-to-monitor-alert-config` migration.
pub monitor_name: String,
}

// Only used for writing data.
Expand Down Expand Up @@ -89,9 +95,12 @@ impl AlertConfigData {
}
_ => return Err(Error::InvalidAlertConfig("Unknown alert type".to_owned())),
},
monitor_ids: monitor_alert_configs
monitors: monitor_alert_configs
.iter()
.map(|mac| mac.monitor_id)
.map(|mac| AppliedMonitor {
monitor_id: mac.monitor_id,
name: mac.monitor_name.clone(),
})
.collect(),
})
}
Expand Down Expand Up @@ -127,11 +136,12 @@ impl NewAlertConfigData {
on_error: alert_config.on_error,
},
alert_config
.monitor_ids
.monitors
.iter()
.map(|monitor_id| MonitorAlertConfigData {
.map(|monitor| MonitorAlertConfigData {
alert_config_id: alert_config.alert_config_id,
monitor_id: *monitor_id,
monitor_id: monitor.monitor_id,
monitor_name: monitor.name.clone(),
})
.collect(),
specific_data,
Expand All @@ -154,10 +164,12 @@ mod tests {
MonitorAlertConfigData {
alert_config_id: gen_uuid("41ebffb4-a188-48e9-8ec1-61380085cde3"),
monitor_id: gen_uuid("02d9fd94-48dc-40e5-b2fa-fa6b66eaf2ca"),
monitor_name: "foo-monitor".to_string(),
},
MonitorAlertConfigData {
alert_config_id: gen_uuid("41ebffb4-a188-48e9-8ec1-61380085cde3"),
monitor_id: gen_uuid("70810d10-1d86-4bde-b29d-b1f490528675"),
monitor_name: "bar-monitor".to_string(),
},
];
let alert_config_data = AlertConfigData {
Expand Down Expand Up @@ -191,10 +203,16 @@ mod tests {
})
);
assert_eq!(
alert_config.monitor_ids,
alert_config.monitors,
vec![
gen_uuid("02d9fd94-48dc-40e5-b2fa-fa6b66eaf2ca"),
gen_uuid("70810d10-1d86-4bde-b29d-b1f490528675")
AppliedMonitor {
monitor_id: gen_uuid("02d9fd94-48dc-40e5-b2fa-fa6b66eaf2ca"),
name: "foo-monitor".to_string()
},
AppliedMonitor {
monitor_id: gen_uuid("70810d10-1d86-4bde-b29d-b1f490528675"),
name: "bar-monitor".to_string()
}
]
);
}
Expand Down Expand Up @@ -258,9 +276,15 @@ mod tests {
channel: "test-channel".to_owned(),
token: "test-token".to_owned(),
}),
monitor_ids: vec![
gen_uuid("02d9fd94-48dc-40e5-b2fa-fa6b66eaf2ca"),
gen_uuid("70810d10-1d86-4bde-b29d-b1f490528675"),
monitors: vec![
AppliedMonitor {
monitor_id: gen_uuid("02d9fd94-48dc-40e5-b2fa-fa6b66eaf2ca"),
name: "foo-monitor".to_string(),
},
AppliedMonitor {
monitor_id: gen_uuid("70810d10-1d86-4bde-b29d-b1f490528675"),
name: "bar-monitor".to_string(),
},
],
};

Expand Down
6 changes: 3 additions & 3 deletions api/src/infrastructure/seeding/seeds.sql
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ VALUES

-- Monitor alert configs.
INSERT INTO monitor_alert_config
(alert_config_id, monitor_id)
(alert_config_id, monitor_id, monitor_name)
VALUES
('f1b1b1b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b', 'c1bf0515-df39-448b-aa95-686360a33b36'),
('f1b1b1b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b', 'f0b291fe-bd41-4787-bc2d-1329903f7a6a');
('f1b1b1b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b', 'c1bf0515-df39-448b-aa95-686360a33b36', 'db-backup.py'),
('f1b1b1b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b', 'f0b291fe-bd41-4787-bc2d-1329903f7a6a', 'generate-orders.sh');
Loading

0 comments on commit 833a06e

Please sign in to comment.