Skip to main content

auth_framework/analytics/
reports.rs

1//! RBAC Analytics Reports
2//!
3//! This module provides comprehensive reporting capabilities
4//! for RBAC analytics data.
5//!
6//! > **Status:** Report generation is available today using the analytics
7//! > events and snapshots currently persisted by the framework.
8
9use super::{AnalyticsError, ReportType, TimeRange};
10use crate::storage::AuthStorage;
11use serde::{Deserialize, Serialize};
12use std::sync::Arc;
13
14/// Report generator configuration
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct ReportConfig {
17    /// Output format
18    pub format: ReportFormat,
19
20    /// Include charts in reports
21    pub include_charts: bool,
22
23    /// Report template
24    pub template: Option<String>,
25}
26
27/// Report output formats
28#[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
46/// Report generator
47pub struct ReportGenerator {
48    _config: ReportConfig,
49    storage: Arc<dyn AuthStorage>,
50}
51
52impl ReportGenerator {
53    /// Create new report generator
54    pub fn new(config: ReportConfig, storage: Arc<dyn AuthStorage>) -> Self {
55        Self {
56            _config: config,
57            storage,
58        }
59    }
60
61    /// Generate report
62    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                    // Filter by time range
86                    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}