feat: populate RCA and postmortem docs with real timeline data
Add format_event_type() and calculate_duration() helpers to convert raw timeline events into human-readable tables and metrics. RCA now includes an Incident Timeline section and Incident Metrics (event count, duration, time-to-root-cause). Postmortem replaces placeholder timeline rows with real events, calculates impact duration, and auto-populates What Went Well from evidence. 10 new Rust tests covering timeline rendering, duration calculation, and event type formatting.
This commit is contained in:
parent
107fee8853
commit
79a623dbb2
@ -778,7 +778,9 @@ mod tests {
|
|||||||
fn test_timeline_events_indexes() {
|
fn test_timeline_events_indexes() {
|
||||||
let conn = setup_test_db();
|
let conn = setup_test_db();
|
||||||
let mut stmt = conn
|
let mut stmt = conn
|
||||||
.prepare("SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='timeline_events'")
|
.prepare(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='timeline_events'",
|
||||||
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let indexes: Vec<String> = stmt
|
let indexes: Vec<String> = stmt
|
||||||
.query_map([], |row| row.get(0))
|
.query_map([], |row| row.get(0))
|
||||||
|
|||||||
@ -130,14 +130,21 @@ pub struct TimelineEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TimelineEvent {
|
impl TimelineEvent {
|
||||||
pub fn new(issue_id: String, event_type: String, description: String, metadata: String) -> Self {
|
pub fn new(
|
||||||
|
issue_id: String,
|
||||||
|
event_type: String,
|
||||||
|
description: String,
|
||||||
|
metadata: String,
|
||||||
|
) -> Self {
|
||||||
TimelineEvent {
|
TimelineEvent {
|
||||||
id: Uuid::now_v7().to_string(),
|
id: Uuid::now_v7().to_string(),
|
||||||
issue_id,
|
issue_id,
|
||||||
event_type,
|
event_type,
|
||||||
description,
|
description,
|
||||||
metadata,
|
metadata,
|
||||||
created_at: chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC").to_string(),
|
created_at: chrono::Utc::now()
|
||||||
|
.format("%Y-%m-%d %H:%M:%S UTC")
|
||||||
|
.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::db::models::IssueDetail;
|
use crate::db::models::IssueDetail;
|
||||||
|
use crate::docs::rca::{calculate_duration, format_event_type};
|
||||||
|
|
||||||
pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
||||||
let issue = &detail.issue;
|
let issue = &detail.issue;
|
||||||
@ -51,7 +52,16 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
|||||||
|
|
||||||
// Impact
|
// Impact
|
||||||
md.push_str("## Impact\n\n");
|
md.push_str("## Impact\n\n");
|
||||||
|
if detail.timeline_events.len() >= 2 {
|
||||||
|
let first = &detail.timeline_events[0].created_at;
|
||||||
|
let last = &detail.timeline_events[detail.timeline_events.len() - 1].created_at;
|
||||||
|
md.push_str(&format!(
|
||||||
|
"- **Duration:** {}\n",
|
||||||
|
calculate_duration(first, last)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
md.push_str("- **Duration:** _[How long did the incident last?]_\n");
|
md.push_str("- **Duration:** _[How long did the incident last?]_\n");
|
||||||
|
}
|
||||||
md.push_str("- **Users Affected:** _[Number/percentage of affected users]_\n");
|
md.push_str("- **Users Affected:** _[Number/percentage of affected users]_\n");
|
||||||
md.push_str("- **Revenue Impact:** _[Financial impact, if applicable]_\n");
|
md.push_str("- **Revenue Impact:** _[Financial impact, if applicable]_\n");
|
||||||
md.push_str("- **SLA Impact:** _[Were any SLAs breached?]_\n\n");
|
md.push_str("- **SLA Impact:** _[Were any SLAs breached?]_\n\n");
|
||||||
@ -67,7 +77,19 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
|||||||
if let Some(ref resolved) = issue.resolved_at {
|
if let Some(ref resolved) = issue.resolved_at {
|
||||||
md.push_str(&format!("| {resolved} | Issue resolved |\n"));
|
md.push_str(&format!("| {resolved} | Issue resolved |\n"));
|
||||||
}
|
}
|
||||||
md.push_str("| _HH:MM_ | _[Add additional timeline events]_ |\n\n");
|
if detail.timeline_events.is_empty() {
|
||||||
|
md.push_str("| _HH:MM_ | _[Add additional timeline events]_ |\n");
|
||||||
|
} else {
|
||||||
|
for event in &detail.timeline_events {
|
||||||
|
md.push_str(&format!(
|
||||||
|
"| {} | {} - {} |\n",
|
||||||
|
event.created_at,
|
||||||
|
format_event_type(&event.event_type),
|
||||||
|
event.description
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
md.push('\n');
|
||||||
|
|
||||||
// Root Cause Analysis
|
// Root Cause Analysis
|
||||||
md.push_str("## Root Cause Analysis\n\n");
|
md.push_str("## Root Cause Analysis\n\n");
|
||||||
@ -114,6 +136,19 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
|||||||
|
|
||||||
// What Went Well
|
// What Went Well
|
||||||
md.push_str("## What Went Well\n\n");
|
md.push_str("## What Went Well\n\n");
|
||||||
|
if !detail.resolution_steps.is_empty() {
|
||||||
|
md.push_str(&format!(
|
||||||
|
"- Systematic 5-whys analysis conducted ({} steps completed)\n",
|
||||||
|
detail.resolution_steps.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if detail
|
||||||
|
.timeline_events
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.event_type == "root_cause_identified")
|
||||||
|
{
|
||||||
|
md.push_str("- Root cause was identified during triage\n");
|
||||||
|
}
|
||||||
md.push_str("- _[e.g., Quick detection through existing alerts]_\n");
|
md.push_str("- _[e.g., Quick detection through existing alerts]_\n");
|
||||||
md.push_str("- _[e.g., Effective cross-team collaboration]_\n");
|
md.push_str("- _[e.g., Effective cross-team collaboration]_\n");
|
||||||
md.push_str("- _[e.g., Smooth communication with stakeholders]_\n\n");
|
md.push_str("- _[e.g., Smooth communication with stakeholders]_\n\n");
|
||||||
@ -158,7 +193,7 @@ pub fn generate_postmortem_markdown(detail: &IssueDetail) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::models::{Issue, IssueDetail, ResolutionStep};
|
use crate::db::models::{Issue, IssueDetail, ResolutionStep, TimelineEvent};
|
||||||
|
|
||||||
fn make_test_detail() -> IssueDetail {
|
fn make_test_detail() -> IssueDetail {
|
||||||
IssueDetail {
|
IssueDetail {
|
||||||
@ -247,4 +282,76 @@ mod tests {
|
|||||||
assert!(md.contains("| Priority | Action | Owner | Due Date | Status |"));
|
assert!(md.contains("| Priority | Action | Owner | Due Date | Status |"));
|
||||||
assert!(md.contains("| P0 |"));
|
assert!(md.contains("| P0 |"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_postmortem_timeline_with_real_events() {
|
||||||
|
let mut detail = make_test_detail();
|
||||||
|
detail.timeline_events = vec![
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-1".to_string(),
|
||||||
|
issue_id: "pm-456".to_string(),
|
||||||
|
event_type: "triage_started".to_string(),
|
||||||
|
description: "Triage initiated".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-02-10 08:05:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-2".to_string(),
|
||||||
|
issue_id: "pm-456".to_string(),
|
||||||
|
event_type: "root_cause_identified".to_string(),
|
||||||
|
description: "Certificate expiry confirmed".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-02-10 10:30:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let md = generate_postmortem_markdown(&detail);
|
||||||
|
assert!(md.contains("## Timeline"));
|
||||||
|
assert!(md.contains("| 2025-02-10 08:05:00 UTC | Triage Started - Triage initiated |"));
|
||||||
|
assert!(md.contains(
|
||||||
|
"| 2025-02-10 10:30:00 UTC | Root Cause Identified - Certificate expiry confirmed |"
|
||||||
|
));
|
||||||
|
assert!(!md.contains("_[Add additional timeline events]_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_postmortem_impact_with_duration() {
|
||||||
|
let mut detail = make_test_detail();
|
||||||
|
detail.timeline_events = vec![
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-1".to_string(),
|
||||||
|
issue_id: "pm-456".to_string(),
|
||||||
|
event_type: "triage_started".to_string(),
|
||||||
|
description: "Triage initiated".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-02-10 08:00:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-2".to_string(),
|
||||||
|
issue_id: "pm-456".to_string(),
|
||||||
|
event_type: "root_cause_identified".to_string(),
|
||||||
|
description: "Found it".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-02-10 10:30:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let md = generate_postmortem_markdown(&detail);
|
||||||
|
assert!(md.contains("**Duration:** 2h 30m"));
|
||||||
|
assert!(!md.contains("_[How long did the incident last?]_"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_postmortem_what_went_well_with_steps() {
|
||||||
|
let mut detail = make_test_detail();
|
||||||
|
detail.timeline_events = vec![TimelineEvent {
|
||||||
|
id: "te-1".to_string(),
|
||||||
|
issue_id: "pm-456".to_string(),
|
||||||
|
event_type: "root_cause_identified".to_string(),
|
||||||
|
description: "Root cause found".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-02-10 10:00:00 UTC".to_string(),
|
||||||
|
}];
|
||||||
|
let md = generate_postmortem_markdown(&detail);
|
||||||
|
assert!(md.contains("Systematic 5-whys analysis conducted (1 steps completed)"));
|
||||||
|
assert!(md.contains("Root cause was identified during triage"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,48 @@
|
|||||||
use crate::db::models::IssueDetail;
|
use crate::db::models::IssueDetail;
|
||||||
|
|
||||||
|
pub fn format_event_type(event_type: &str) -> &str {
|
||||||
|
match event_type {
|
||||||
|
"triage_started" => "Triage Started",
|
||||||
|
"log_uploaded" => "Log File Uploaded",
|
||||||
|
"why_level_advanced" => "Why Level Advanced",
|
||||||
|
"root_cause_identified" => "Root Cause Identified",
|
||||||
|
"rca_generated" => "RCA Document Generated",
|
||||||
|
"postmortem_generated" => "Post-Mortem Generated",
|
||||||
|
"document_exported" => "Document Exported",
|
||||||
|
other => other,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_duration(start: &str, end: &str) -> String {
|
||||||
|
let fmt = "%Y-%m-%d %H:%M:%S UTC";
|
||||||
|
let start_dt = match chrono::NaiveDateTime::parse_from_str(start, fmt) {
|
||||||
|
Ok(dt) => dt,
|
||||||
|
Err(_) => return "N/A".to_string(),
|
||||||
|
};
|
||||||
|
let end_dt = match chrono::NaiveDateTime::parse_from_str(end, fmt) {
|
||||||
|
Ok(dt) => dt,
|
||||||
|
Err(_) => return "N/A".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let duration = end_dt.signed_duration_since(start_dt);
|
||||||
|
let total_minutes = duration.num_minutes();
|
||||||
|
if total_minutes < 0 {
|
||||||
|
return "N/A".to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
let days = total_minutes / (24 * 60);
|
||||||
|
let hours = (total_minutes % (24 * 60)) / 60;
|
||||||
|
let minutes = total_minutes % 60;
|
||||||
|
|
||||||
|
if days > 0 {
|
||||||
|
format!("{days}d {hours}h")
|
||||||
|
} else if hours > 0 {
|
||||||
|
format!("{hours}h {minutes}m")
|
||||||
|
} else {
|
||||||
|
format!("{minutes}m")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn generate_rca_markdown(detail: &IssueDetail) -> String {
|
pub fn generate_rca_markdown(detail: &IssueDetail) -> String {
|
||||||
let issue = &detail.issue;
|
let issue = &detail.issue;
|
||||||
|
|
||||||
@ -57,6 +100,52 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String {
|
|||||||
md.push_str("\n\n");
|
md.push_str("\n\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Incident Timeline
|
||||||
|
md.push_str("## Incident Timeline\n\n");
|
||||||
|
if detail.timeline_events.is_empty() {
|
||||||
|
md.push_str("_No timeline events recorded._\n\n");
|
||||||
|
} else {
|
||||||
|
md.push_str("| Time (UTC) | Event | Description |\n");
|
||||||
|
md.push_str("|------------|-------|-------------|\n");
|
||||||
|
for event in &detail.timeline_events {
|
||||||
|
md.push_str(&format!(
|
||||||
|
"| {} | {} | {} |\n",
|
||||||
|
event.created_at,
|
||||||
|
format_event_type(&event.event_type),
|
||||||
|
event.description
|
||||||
|
));
|
||||||
|
}
|
||||||
|
md.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Incident Metrics
|
||||||
|
md.push_str("## Incident Metrics\n\n");
|
||||||
|
md.push_str(&format!(
|
||||||
|
"- **Total Events:** {}\n",
|
||||||
|
detail.timeline_events.len()
|
||||||
|
));
|
||||||
|
if detail.timeline_events.len() >= 2 {
|
||||||
|
let first = &detail.timeline_events[0].created_at;
|
||||||
|
let last = &detail.timeline_events[detail.timeline_events.len() - 1].created_at;
|
||||||
|
md.push_str(&format!(
|
||||||
|
"- **Incident Duration:** {}\n",
|
||||||
|
calculate_duration(first, last)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
md.push_str("- **Incident Duration:** N/A\n");
|
||||||
|
}
|
||||||
|
let root_cause_event = detail
|
||||||
|
.timeline_events
|
||||||
|
.iter()
|
||||||
|
.find(|e| e.event_type == "root_cause_identified");
|
||||||
|
if let (Some(first), Some(rc)) = (detail.timeline_events.first(), root_cause_event) {
|
||||||
|
md.push_str(&format!(
|
||||||
|
"- **Time to Root Cause:** {}\n",
|
||||||
|
calculate_duration(&first.created_at, &rc.created_at)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
md.push('\n');
|
||||||
|
|
||||||
// 5 Whys Analysis
|
// 5 Whys Analysis
|
||||||
md.push_str("## 5 Whys Analysis\n\n");
|
md.push_str("## 5 Whys Analysis\n\n");
|
||||||
if detail.resolution_steps.is_empty() {
|
if detail.resolution_steps.is_empty() {
|
||||||
@ -143,7 +232,7 @@ pub fn generate_rca_markdown(detail: &IssueDetail) -> String {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::db::models::{Issue, IssueDetail, LogFile, ResolutionStep};
|
use crate::db::models::{Issue, IssueDetail, LogFile, ResolutionStep, TimelineEvent};
|
||||||
|
|
||||||
fn make_test_detail() -> IssueDetail {
|
fn make_test_detail() -> IssueDetail {
|
||||||
IssueDetail {
|
IssueDetail {
|
||||||
@ -248,4 +337,135 @@ mod tests {
|
|||||||
let md = generate_rca_markdown(&detail);
|
let md = generate_rca_markdown(&detail);
|
||||||
assert!(md.contains("Unassigned"));
|
assert!(md.contains("Unassigned"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rca_timeline_section_with_events() {
|
||||||
|
let mut detail = make_test_detail();
|
||||||
|
detail.timeline_events = vec![
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-1".to_string(),
|
||||||
|
issue_id: "test-123".to_string(),
|
||||||
|
event_type: "triage_started".to_string(),
|
||||||
|
description: "Triage initiated by oncall".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-01-15 10:00:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-2".to_string(),
|
||||||
|
issue_id: "test-123".to_string(),
|
||||||
|
event_type: "log_uploaded".to_string(),
|
||||||
|
description: "app.log uploaded".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-01-15 10:30:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-3".to_string(),
|
||||||
|
issue_id: "test-123".to_string(),
|
||||||
|
event_type: "root_cause_identified".to_string(),
|
||||||
|
description: "Connection pool leak found".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-01-15 12:15:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let md = generate_rca_markdown(&detail);
|
||||||
|
assert!(md.contains("## Incident Timeline"));
|
||||||
|
assert!(md.contains("| Time (UTC) | Event | Description |"));
|
||||||
|
assert!(md
|
||||||
|
.contains("| 2025-01-15 10:00:00 UTC | Triage Started | Triage initiated by oncall |"));
|
||||||
|
assert!(md.contains("| 2025-01-15 10:30:00 UTC | Log File Uploaded | app.log uploaded |"));
|
||||||
|
assert!(md.contains(
|
||||||
|
"| 2025-01-15 12:15:00 UTC | Root Cause Identified | Connection pool leak found |"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rca_timeline_section_empty() {
|
||||||
|
let detail = make_test_detail();
|
||||||
|
let md = generate_rca_markdown(&detail);
|
||||||
|
assert!(md.contains("## Incident Timeline"));
|
||||||
|
assert!(md.contains("_No timeline events recorded._"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rca_metrics_section() {
|
||||||
|
let mut detail = make_test_detail();
|
||||||
|
detail.timeline_events = vec![
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-1".to_string(),
|
||||||
|
issue_id: "test-123".to_string(),
|
||||||
|
event_type: "triage_started".to_string(),
|
||||||
|
description: "Triage started".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-01-15 10:00:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
TimelineEvent {
|
||||||
|
id: "te-2".to_string(),
|
||||||
|
issue_id: "test-123".to_string(),
|
||||||
|
event_type: "root_cause_identified".to_string(),
|
||||||
|
description: "Root cause found".to_string(),
|
||||||
|
metadata: "{}".to_string(),
|
||||||
|
created_at: "2025-01-15 12:15:00 UTC".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let md = generate_rca_markdown(&detail);
|
||||||
|
assert!(md.contains("## Incident Metrics"));
|
||||||
|
assert!(md.contains("**Total Events:** 2"));
|
||||||
|
assert!(md.contains("**Incident Duration:** 2h 15m"));
|
||||||
|
assert!(md.contains("**Time to Root Cause:** 2h 15m"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_duration_hours_minutes() {
|
||||||
|
assert_eq!(
|
||||||
|
calculate_duration("2025-01-15 10:00:00 UTC", "2025-01-15 12:15:00 UTC"),
|
||||||
|
"2h 15m"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_duration_days() {
|
||||||
|
assert_eq!(
|
||||||
|
calculate_duration("2025-01-15 10:00:00 UTC", "2025-01-18 11:00:00 UTC"),
|
||||||
|
"3d 1h"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_duration_minutes_only() {
|
||||||
|
assert_eq!(
|
||||||
|
calculate_duration("2025-01-15 10:00:00 UTC", "2025-01-15 10:45:00 UTC"),
|
||||||
|
"45m"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_calculate_duration_invalid() {
|
||||||
|
assert_eq!(calculate_duration("bad-date", "also-bad"), "N/A");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_event_type_known() {
|
||||||
|
assert_eq!(format_event_type("triage_started"), "Triage Started");
|
||||||
|
assert_eq!(format_event_type("log_uploaded"), "Log File Uploaded");
|
||||||
|
assert_eq!(
|
||||||
|
format_event_type("why_level_advanced"),
|
||||||
|
"Why Level Advanced"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format_event_type("root_cause_identified"),
|
||||||
|
"Root Cause Identified"
|
||||||
|
);
|
||||||
|
assert_eq!(format_event_type("rca_generated"), "RCA Document Generated");
|
||||||
|
assert_eq!(
|
||||||
|
format_event_type("postmortem_generated"),
|
||||||
|
"Post-Mortem Generated"
|
||||||
|
);
|
||||||
|
assert_eq!(format_event_type("document_exported"), "Document Exported");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_format_event_type_unknown() {
|
||||||
|
assert_eq!(format_event_type("custom_event"), "custom_event");
|
||||||
|
assert_eq!(format_event_type(""), "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user