mockforge_analytics/
export.rs

1//! Data export functionality
2
3use crate::database::AnalyticsDatabase;
4use crate::error::Result;
5use crate::models::*;
6use std::io::Write;
7
8impl AnalyticsDatabase {
9    /// Export metrics to CSV format
10    pub async fn export_to_csv<W: Write>(
11        &self,
12        writer: &mut W,
13        filter: &AnalyticsFilter,
14    ) -> Result<usize> {
15        // Write CSV header
16        writeln!(
17            writer,
18            "timestamp,protocol,method,endpoint,status_code,request_count,error_count,avg_latency_ms,p95_latency_ms,bytes_sent,bytes_received"
19        )?;
20
21        let aggregates = self.get_minute_aggregates(filter).await?;
22
23        for agg in &aggregates {
24            let avg_latency = if agg.request_count > 0 {
25                agg.latency_sum / agg.request_count as f64
26            } else {
27                0.0
28            };
29
30            writeln!(
31                writer,
32                "{},{},{},{},{},{},{},{:.2},{:.2},{},{}",
33                agg.timestamp,
34                agg.protocol,
35                agg.method.as_deref().unwrap_or(""),
36                agg.endpoint.as_deref().unwrap_or(""),
37                agg.status_code.unwrap_or(0),
38                agg.request_count,
39                agg.error_count,
40                avg_latency,
41                agg.latency_p95.unwrap_or(0.0),
42                agg.bytes_sent,
43                agg.bytes_received
44            )?;
45        }
46
47        Ok(aggregates.len())
48    }
49
50    /// Export metrics to JSON format
51    pub async fn export_to_json(&self, filter: &AnalyticsFilter) -> Result<String> {
52        let aggregates = self.get_minute_aggregates(filter).await?;
53        let json = serde_json::to_string_pretty(&aggregates)?;
54        Ok(json)
55    }
56
57    /// Export endpoint stats to CSV
58    pub async fn export_endpoints_to_csv<W: Write>(
59        &self,
60        writer: &mut W,
61        workspace_id: Option<&str>,
62        limit: i64,
63    ) -> Result<usize> {
64        writeln!(
65            writer,
66            "endpoint,protocol,method,total_requests,total_errors,error_rate,avg_latency_ms,p95_latency_ms,bytes_sent,bytes_received"
67        )?;
68
69        let endpoints = self.get_top_endpoints(limit, workspace_id).await?;
70
71        for ep in &endpoints {
72            let error_rate = if ep.total_requests > 0 {
73                (ep.total_errors as f64 / ep.total_requests as f64) * 100.0
74            } else {
75                0.0
76            };
77
78            writeln!(
79                writer,
80                "{},{},{},{},{},{:.2},{:.2},{:.2},{},{}",
81                ep.endpoint,
82                ep.protocol,
83                ep.method.as_deref().unwrap_or(""),
84                ep.total_requests,
85                ep.total_errors,
86                error_rate,
87                ep.avg_latency_ms.unwrap_or(0.0),
88                ep.p95_latency_ms.unwrap_or(0.0),
89                ep.total_bytes_sent,
90                ep.total_bytes_received
91            )?;
92        }
93
94        Ok(endpoints.len())
95    }
96
97    /// Export error events to CSV
98    pub async fn export_errors_to_csv<W: Write>(
99        &self,
100        writer: &mut W,
101        filter: &AnalyticsFilter,
102        limit: i64,
103    ) -> Result<usize> {
104        writeln!(
105            writer,
106            "timestamp,protocol,method,endpoint,status_code,error_type,error_category,error_message,client_ip,trace_id"
107        )?;
108
109        let errors = self.get_recent_errors(limit, filter).await?;
110
111        for err in &errors {
112            writeln!(
113                writer,
114                "{},{},{},{},{},{},{},{},{},{}",
115                err.timestamp,
116                err.protocol,
117                err.method.as_deref().unwrap_or(""),
118                err.endpoint.as_deref().unwrap_or(""),
119                err.status_code.unwrap_or(0),
120                err.error_type.as_deref().unwrap_or(""),
121                err.error_category.as_deref().unwrap_or(""),
122                err.error_message.as_deref().unwrap_or(""),
123                err.client_ip.as_deref().unwrap_or(""),
124                err.trace_id.as_deref().unwrap_or("")
125            )?;
126        }
127
128        Ok(errors.len())
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135    use std::path::Path;
136
137    #[tokio::test]
138    async fn test_export_to_csv() {
139        let db = AnalyticsDatabase::new(Path::new(":memory:")).await.unwrap();
140        db.run_migrations().await.unwrap();
141
142        let mut buffer = Vec::new();
143        let filter = AnalyticsFilter::default();
144
145        let count = db.export_to_csv(&mut buffer, &filter).await.unwrap();
146        assert_eq!(count, 0); // No data yet
147
148        let csv = String::from_utf8(buffer).unwrap();
149        assert!(csv.contains("timestamp,protocol"));
150    }
151}