auth_framework/analytics/
reports.rs1use super::{AnalyticsError, ReportType, TimeRange};
10use crate::storage::AuthStorage;
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ReportConfig {
17 pub format: ReportFormat,
19
20 pub include_charts: bool,
22
23 pub template: Option<String>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29pub enum ReportFormat {
30 Json,
31 Html,
32 Pdf,
33 Csv,
34}
35
36impl Default for ReportConfig {
37 fn default() -> Self {
38 Self {
39 format: ReportFormat::Json,
40 include_charts: true,
41 template: None,
42 }
43 }
44}
45
46pub struct ReportGenerator {
48 _config: ReportConfig,
49 storage: Arc<dyn AuthStorage>,
50}
51
52impl ReportGenerator {
53 pub fn new(config: ReportConfig, storage: Arc<dyn AuthStorage>) -> Self {
55 Self {
56 _config: config,
57 storage,
58 }
59 }
60
61 pub async fn generate_report(
63 &self,
64 report_type: ReportType,
65 time_range: TimeRange,
66 ) -> Result<String, AnalyticsError> {
67 let keys = self
68 .storage
69 .list_kv_keys("analytics_event_")
70 .await
71 .unwrap_or_default();
72
73 let mut total_events: u64 = 0;
74 let mut success_count: u64 = 0;
75 let mut failure_count: u64 = 0;
76 let mut event_types: std::collections::HashMap<String, u64> =
77 std::collections::HashMap::new();
78 let mut total_duration_ms: f64 = 0.0;
79 let mut duration_count: u64 = 0;
80
81 for key in keys {
82 if let Ok(Some(data)) = self.storage.get_kv(&key).await {
83 if let Ok(event) = serde_json::from_slice::<crate::analytics::AnalyticsEvent>(&data)
84 {
85 if event.timestamp < time_range.start || event.timestamp > time_range.end {
87 continue;
88 }
89 total_events += 1;
90 match event.result {
91 crate::analytics::EventResult::Success => success_count += 1,
92 _ => failure_count += 1,
93 }
94 *event_types
95 .entry(format!("{:?}", event.event_type))
96 .or_insert(0) += 1;
97 if let Some(d) = event.duration_ms {
98 total_duration_ms += d as f64;
99 duration_count += 1;
100 }
101 }
102 }
103 }
104
105 let avg_duration_ms = if duration_count > 0 {
106 total_duration_ms / duration_count as f64
107 } else {
108 0.0
109 };
110 let success_rate = if total_events > 0 {
111 (success_count as f64 / total_events as f64) * 100.0
112 } else {
113 0.0
114 };
115
116 let report = serde_json::json!({
117 "report_type": format!("{:?}", report_type),
118 "time_range": {
119 "start": time_range.start.to_rfc3339(),
120 "end": time_range.end.to_rfc3339(),
121 },
122 "generated_at": chrono::Utc::now().to_rfc3339(),
123 "summary": {
124 "total_events": total_events,
125 "success_count": success_count,
126 "failure_count": failure_count,
127 "success_rate_percent": (success_rate * 100.0).round() / 100.0,
128 "avg_duration_ms": (avg_duration_ms * 100.0).round() / 100.0,
129 },
130 "event_type_breakdown": event_types,
131 });
132
133 serde_json::to_string_pretty(&report).map_err(|e| AnalyticsError::SerializationError(e))
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn test_report_config_default() {
143 let config = ReportConfig::default();
144 assert!(config.include_charts);
145 assert!(config.template.is_none());
146 assert!(matches!(config.format, ReportFormat::Json));
147 }
148
149 #[test]
150 fn test_report_generator_creation() {
151 let config = ReportConfig::default();
152 let _gen = ReportGenerator::new(
153 config,
154 std::sync::Arc::new(crate::storage::MemoryStorage::new()),
155 );
156 }
157
158 #[tokio::test]
159 async fn test_generate_report_returns_content() {
160 let generator = ReportGenerator::new(
161 ReportConfig::default(),
162 std::sync::Arc::new(crate::storage::MemoryStorage::new()),
163 );
164 let range = TimeRange::last_days(7);
165 let report = generator
166 .generate_report(ReportType::Daily, range)
167 .await
168 .unwrap();
169 assert!(!report.is_empty());
170 }
171}