Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

165 cold chain notification configuration should show the real sensors #169

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/coldchain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ chrono = { version = "0.4", features = ["serde"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["macros"] }
log = "0.4"

[features]
telegram-tests = ["service/telegram-tests"]
Expand Down
147 changes: 147 additions & 0 deletions backend/coldchain/src/alerts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use chrono::NaiveDateTime;
jmbrunskill marked this conversation as resolved.
Show resolved Hide resolved
use serde::Serialize;
use service::{
notification::{
self,
enqueue::{create_notification_events, NotificationContext, NotificationTarget},
},
service_provider::ServiceContext,
};

/*

Temperature Alerts will look something like this...
-----------------------
High temperature alert!

Facility: Store A
Location: Fridge 1
Sensor: E5:4G:D4:6D:A4

Date: 17 Jul 2023
Time: 17:04

Temperature: 10Β° C
-----------------------
*/

#[derive(Clone, Debug, Serialize)]
pub struct TemperatureAlert {
pub store_id: String,
pub store_name: String,
pub location_id: String,
pub location_name: String,
pub sensor_id: String,
pub sensor_name: String,
pub datetime: NaiveDateTime,
pub temperature: f64,
}

// Later this function probably won't exist, but serves as a reminder/POC...
pub async fn send_high_temperature_alert_telegram(
ctx: &ServiceContext,
alert: TemperatureAlert,
recipients: Vec<NotificationTarget>,
) -> Result<(), notification::NotificationServiceError> {
let notification = NotificationContext {
title_template_name: Some("coldchain/telegram/temperature_title.html".to_string()),
body_template_name: "coldchain/telegram/temperature.html".to_string(),
recipients,
template_data: serde_json::to_value(alert).map_err(|e| {
notification::NotificationServiceError::GenericError(format!(
"Error serializing template data: {}",
e
))
})?,
};

// TODO : Get the config ID for this notification

create_notification_events(ctx, None, notification)
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use repository::{
mock::MockDataInserts, test_db::setup_all, NotificationEventRowRepository, NotificationType,
};
use service::test_utils::email_test::send_test_emails;
use service::test_utils::get_test_settings;
use service::test_utils::telegram_test::get_default_telegram_chat_id;
use service::test_utils::telegram_test::send_test_notifications;

use std::str::FromStr;

use service::service_provider::ServiceContext;
use service::service_provider::ServiceProvider;

use super::*;

#[tokio::test]
async fn test_send_high_temperature_alert() {
let (_, _, connection_manager, _) =
setup_all("test_enqueue_telegram_and_email", MockDataInserts::none()).await;

let connection = connection_manager.connection().unwrap();
let service_provider = Arc::new(ServiceProvider::new(
connection_manager,
get_test_settings(""),
));
let context = ServiceContext::as_server_admin(service_provider).unwrap();

let example_alert = TemperatureAlert {
store_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b1".to_string(),
store_name: "Store A".to_string(),
location_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b2".to_string(),
location_name: "Fridge 1".to_string(),
sensor_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b3".to_string(),
sensor_name: "E5:4G:D4:6D:A4".to_string(),
datetime: NaiveDateTime::from_str("2023-07-17T17:04:00").unwrap(),
temperature: 10.12345,
};

let recipient1 = NotificationTarget {
name: "test".to_string(),
to_address: get_default_telegram_chat_id(),
notification_type: NotificationType::Telegram,
};
let recipient2 = NotificationTarget {
name: "test-email".to_string(),
to_address: "[email protected]".to_string(),
notification_type: NotificationType::Email,
};

let result = send_high_temperature_alert_telegram(
&context,
example_alert.clone(),
vec![recipient1, recipient2],
)
.await;

assert!(result.is_ok());

// Check we have a notification event
let notification_event_row_repository = NotificationEventRowRepository::new(&connection);
let notification_event_rows = notification_event_row_repository.un_sent().unwrap();

assert_eq!(notification_event_rows.len(), 2);
assert_eq!(
notification_event_rows[0].to_address,
get_default_telegram_chat_id()
);
assert!(notification_event_rows[0]
.message
.contains(&example_alert.store_name));

// Check email recipient
assert_eq!(notification_event_rows[1].to_address, "[email protected]");
assert!(notification_event_rows[1]
.message
.contains(&example_alert.store_name));

send_test_notifications(&context).await;
send_test_emails(&context);
}
}
147 changes: 30 additions & 117 deletions backend/coldchain/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,148 +1,61 @@
use chrono::NaiveDateTime;
use serde::Serialize;
use service::{
notification::{
self,
enqueue::{create_notification_events, NotificationContext, NotificationTarget},
},
plugin::{PluginError, PluginTrait},
service_provider::ServiceContext,
};

/*
pub mod alerts;

Temperature Alerts will look something like this...
-----------------------
High temperature alert!
pub struct ColdChainPlugin {}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making cold chain a plugin isn't really required for this PR, as it's currently using an SQL query in the frontend to retrieve data. I think it should somehow query the plugin instead, but leaving that for a separate PR & Issue.
Figured it doesn't hurt to keep this change in this PR.


Facility: Store A
Location: Fridge 1
Sensor: E5:4G:D4:6D:A4

Date: 17 Jul 2023
Time: 17:04

Temperature: 10Β° C
-----------------------
*/

#[derive(Clone, Debug, Serialize)]
pub struct TemperatureAlert {
pub store_id: String,
pub store_name: String,
pub location_id: String,
pub location_name: String,
pub sensor_id: String,
pub sensor_name: String,
pub datetime: NaiveDateTime,
pub temperature: f64,
}

// Later this function probably won't exist, but serves as a reminder/POC...
pub async fn send_high_temperature_alert_telegram(
ctx: &ServiceContext,
alert: TemperatureAlert,
recipients: Vec<NotificationTarget>,
) -> Result<(), notification::NotificationServiceError> {
let notification = NotificationContext {
title_template_name: Some("coldchain/telegram/temperature_title.html".to_string()),
body_template_name: "coldchain/telegram/temperature.html".to_string(),
recipients,
template_data: serde_json::to_value(alert).map_err(|e| {
notification::NotificationServiceError::GenericError(format!(
"Error serializing template data: {}",
e
))
})?,
};
impl PluginTrait for ColdChainPlugin {
fn new() -> Self
where
Self: Sized,
{
ColdChainPlugin {}
}

// TODO : Get the config ID for this notification
fn name(&self) -> String {
"ColdChain".to_string()
}

create_notification_events(ctx, None, notification)
fn tick(&self, _ctx: &ServiceContext) -> Result<(), PluginError> {
log::debug!("Running ColdChainPlugin");
log::error!("COLD CHAIN NOT YET IMPLEMENTED");
Ok(())
}
}

#[cfg(test)]
mod tests {
use std::sync::Arc;

use repository::{
mock::MockDataInserts, test_db::setup_all, NotificationEventRowRepository, NotificationType,
};
use service::test_utils::email_test::send_test_emails;
use repository::{mock::MockDataInserts, test_db::setup_all};
use service::test_utils::get_test_settings;
use service::test_utils::telegram_test::get_default_telegram_chat_id;
use service::test_utils::telegram_test::send_test_notifications;

use crate::notification::enqueue::NotificationTarget;
use std::str::FromStr;

use service::service_provider::ServiceContext;
use service::service_provider::ServiceProvider;

use super::*;

#[tokio::test]
async fn test_send_high_temperature_alert() {
async fn cold_chain_plugin_has_a_name() {
let plugin = ColdChainPlugin::new();
assert_eq!(plugin.name(), "ColdChain");
}

#[tokio::test]
async fn cold_chain_plugin_can_tick() {
let (_, _, connection_manager, _) =
setup_all("test_enqueue_telegram_and_email", MockDataInserts::none()).await;
setup_all("cold_chain_plugin_can_start", MockDataInserts::none()).await;

let connection = connection_manager.connection().unwrap();
let service_provider = Arc::new(ServiceProvider::new(
connection_manager,
get_test_settings(""),
));
let context = ServiceContext::as_server_admin(service_provider).unwrap();

let example_alert = TemperatureAlert {
store_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b1".to_string(),
store_name: "Store A".to_string(),
location_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b2".to_string(),
location_name: "Fridge 1".to_string(),
sensor_id: "6a3399dd-10a9-40b7-853e-3ac0634ce6b3".to_string(),
sensor_name: "E5:4G:D4:6D:A4".to_string(),
datetime: NaiveDateTime::from_str("2023-07-17T17:04:00").unwrap(),
temperature: 10.12345,
};

let recipient1 = NotificationTarget {
name: "test".to_string(),
to_address: get_default_telegram_chat_id(),
notification_type: NotificationType::Telegram,
};
let recipient2 = NotificationTarget {
name: "test-email".to_string(),
to_address: "[email protected]".to_string(),
notification_type: NotificationType::Email,
};

let result = send_high_temperature_alert_telegram(
&context,
example_alert.clone(),
vec![recipient1, recipient2],
)
.await;
let ctx = ServiceContext::as_server_admin(service_provider).unwrap();

let plugin = ColdChainPlugin::new();
let result = plugin.tick(&ctx);
assert!(result.is_ok());

// Check we have a notification event
let notification_event_row_repository = NotificationEventRowRepository::new(&connection);
let notification_event_rows = notification_event_row_repository.un_sent().unwrap();

assert_eq!(notification_event_rows.len(), 2);
assert_eq!(
notification_event_rows[0].to_address,
get_default_telegram_chat_id()
);
assert!(notification_event_rows[0]
.message
.contains(&example_alert.store_name));

// Check email recipient
assert_eq!(notification_event_rows[1].to_address, "[email protected]");
assert!(notification_event_rows[1]
.message
.contains(&example_alert.store_name));

send_test_notifications(&context).await;
send_test_emails(&context);
}
}
1 change: 1 addition & 0 deletions backend/server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ datasource = {path = "../datasource"}
repository = { path = "../repository" }
service = { path = "../service" }
scheduled = { path = "../scheduled" }
coldchain = { path = "../coldchain" }
telegram = { path = "../telegram" }
util = { path = "../util" }

Expand Down
7 changes: 5 additions & 2 deletions backend/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use middleware::{add_authentication_context, limit_content_length};
use repository::{get_storage_connection_manager, run_db_migrations, StorageConnectionManager};

use actix_web::{web::Data, App, HttpServer};
use coldchain::ColdChainPlugin;
use scheduled::ScheduledNotificationPlugin;
use std::{
ops::DerefMut,
Expand Down Expand Up @@ -89,8 +90,10 @@ async fn run_server(
};

// Setup plugins
let plugins: Vec<Box<dyn service::plugin::PluginTrait>> =
vec![Box::new(ScheduledNotificationPlugin::new())];
let plugins: Vec<Box<dyn service::plugin::PluginTrait>> = vec![
Box::new(ScheduledNotificationPlugin::new()),
Box::new(ColdChainPlugin::new()),
];

let scheduled_task_handle = actix_web::rt::spawn(async move {
scheduled_task_runner(scheduled_task_context, plugins).await;
Expand Down
2 changes: 2 additions & 0 deletions frontend/packages/common/src/intl/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
"heading.quote-withdraw-title": "Specify reason",
"heading.select-items": "Select Items",
"heading.select-locations": "Select Locations",
"heading.select-sensors": "Select Sensors",
"heading.selected-sensors": "Selected Sensors",
"heading.settings-display": "Display settings",
"heading.username": "Username",
"label.actions": "Actions",
Expand Down
3 changes: 2 additions & 1 deletion frontend/packages/common/src/intl/locales/en/system.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,6 @@
"helper-text.sql-query": "Your sql query can contain parameters, which are created using double curly braces within the query. For example: SELECT * FROM my_table WHERE id = {{param1}} AND name = {{param2}}",
"label.test-sql-query": "Test SQL Query",
"tooltip.manage-recipient-list": "Manage Recipient List",
"message.no-parameters": "No parameters found"
"message.no-parameters": "No parameters found",
"message.no-sensors-selected": "No sensors selected"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: do you need a distinction between common.json and system.json?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's pretty messy right now probably should create a coldchain one at least...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
Loading