diff --git a/services/reports/src/email/template/mod.rs b/services/reports/src/email/template/mod.rs --- a/services/reports/src/email/template/mod.rs +++ b/services/reports/src/email/template/mod.rs @@ -1,7 +1,7 @@ -use maud::{html, Markup, Render}; +use maud::{html, Markup, PreEscaped, Render}; use tracing::error; -use crate::{config::SERVICE_PUBLIC_URL, report_types::*}; +use crate::{config::SERVICE_PUBLIC_URL, report_types::*, report_utils}; mod html_layout; @@ -65,6 +65,10 @@ li { "Time: " b { (time) } } li { "Report ID: " b { (report_id) } } } + + @if report.report_type.is_inconsistency() { + (inconsistency_details(report)) + } (further_actions(report_id, report.report_type.is_error())) (display_contents(report, report_id)) } @@ -107,6 +111,39 @@ .unwrap_or("completed") } +fn inconsistency_details(report: &ReportInput) -> Markup { + let (subjects, ids) = match report.report_type { + ReportType::ThreadInconsistency => ( + "Thread IDs", + report_utils::inconsistent_thread_ids(&report.report_content), + ), + ReportType::UserInconsistency => ( + "User IDs", + report_utils::inconsistent_user_ids(&report.report_content), + ), + ReportType::EntryInconsistency => ( + "Entries", + report_utils::inconsistent_entry_ids(&report.report_content), + ), + _ => return html!(), + }; + + let formatted_ids = if ids.is_empty() { + html! { em { "[None found]" } }.into_string() + } else { + ids + .into_iter() + .map(|id| html! { code { (id) } }.into_string()) + .collect::>() + .join(", ") + }; + + html! { + h3 { "Inconsistency details" } + p { (subjects) " that are inconsistent: " (PreEscaped(formatted_ids))} + } +} + fn further_actions( report_id: &ReportID, display_download_link: bool, diff --git a/services/reports/src/main.rs b/services/reports/src/main.rs --- a/services/reports/src/main.rs +++ b/services/reports/src/main.rs @@ -4,6 +4,7 @@ pub mod email; pub mod http; pub mod report_types; +pub mod report_utils; pub mod service; use anyhow::Result; diff --git a/services/reports/src/report_utils.rs b/services/reports/src/report_utils.rs new file mode 100644 --- /dev/null +++ b/services/reports/src/report_utils.rs @@ -0,0 +1,79 @@ +use serde_json::Value; +use std::collections::{HashMap, HashSet}; + +type ReportContent = HashMap; + +/// Returns a set of keys which differ for both objects +pub fn find_inconsistent_object_keys( + first: &serde_json::Map, + second: &serde_json::Map, +) -> HashSet { + let mut non_matching_ids = HashSet::new(); + for (k, v) in first { + if !second.get(k).is_some_and(|it| v == it) { + non_matching_ids.insert(k.to_string()); + } + } + for k in second.keys() { + if !first.contains_key(k) { + non_matching_ids.insert(k.to_string()); + } + } + non_matching_ids +} + +pub fn inconsistent_thread_ids(content: &ReportContent) -> HashSet { + let Some(push_result) = content + .get("pushResult") + .and_then(Value::as_object) else { return HashSet::new(); }; + let Some(before_action) = content + .get("beforeAction") + .and_then(Value::as_object) else { return HashSet::new(); }; + + find_inconsistent_object_keys(push_result, before_action) +} + +pub fn inconsistent_user_ids(content: &ReportContent) -> HashSet { + let Some(before) = content + .get("beforeStateCheck") + .and_then(Value::as_object) else { return HashSet::new(); }; + let Some(after) = content + .get("afterStateCheck") + .and_then(Value::as_object) else { return HashSet::new(); }; + + find_inconsistent_object_keys(before, after) +} + +pub fn inconsistent_entry_ids(_content: &ReportContent) -> HashSet { + HashSet::from(["--Unimplemented--".to_string()]) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_inconsistent_object_keys() { + let obj_a = json!({ + "foo": "bar", + "a": 2, + "b": "x", + "c": false, + }); + let obj_b = json!({ + "foo": "bar", + "a": 2, + "b": "y", + "D": true, + }); + + let expected_keys = + HashSet::from(["b".to_string(), "c".to_string(), "D".to_string()]); + let inconsistent_keys = find_inconsistent_object_keys( + obj_a.as_object().unwrap(), + obj_b.as_object().unwrap(), + ); + assert_eq!(&expected_keys, &inconsistent_keys); + } +}