mockforge_intelligence/handlers/drift_budget/
metrics.rs1use super::DriftBudgetStateProvider;
4use crate::handlers::structural_incidents::StructuralIncidents;
5use axum::{
6 extract::{Query, State},
7 http::StatusCode,
8 response::Json,
9};
10use mockforge_foundation::incidents_types::{IncidentQuery, IncidentType};
11use serde::Deserialize;
12use std::collections::HashMap;
13
14#[derive(Debug, Deserialize)]
18pub struct GetMetricsQuery {
19 pub endpoint: Option<String>,
21 pub method: Option<String>,
23 pub workspace_id: Option<String>,
25 pub days: Option<u32>,
27}
28
29pub async fn get_drift_metrics<S: DriftBudgetStateProvider>(
33 State(state): State<S>,
34 Query(params): Query<GetMetricsQuery>,
35) -> Result<Json<serde_json::Value>, StatusCode> {
36 let mut query = IncidentQuery {
38 endpoint: params.endpoint,
39 method: params.method,
40 workspace_id: params.workspace_id,
41 ..IncidentQuery::default()
42 };
43
44 if let Some(days) = params.days {
46 let start_date = chrono::Utc::now()
47 .checked_sub_signed(chrono::Duration::days(days as i64))
48 .map(|dt| dt.timestamp())
49 .unwrap_or(0);
50 query.start_date = Some(start_date);
51 }
52
53 let incidents = state.incident_manager().query_incidents(query).await;
54
55 let total_incidents = incidents.len();
57 let breaking_changes = incidents
58 .iter()
59 .filter(|i| matches!(i.incident_type, IncidentType::BreakingChange))
60 .count();
61 let threshold_exceeded = total_incidents - breaking_changes;
62
63 let by_severity: HashMap<String, usize> =
64 incidents.iter().fold(HashMap::new(), |mut acc, inc| {
65 let key = format!("{:?}", inc.severity).to_lowercase();
66 *acc.entry(key).or_insert(0) += 1;
67 acc
68 });
69
70 Ok(Json(serde_json::json!({
71 "total_incidents": total_incidents,
72 "breaking_changes": breaking_changes,
73 "threshold_exceeded": threshold_exceeded,
74 "by_severity": by_severity,
75 "incidents": incidents.iter().take(100).collect::<Vec<_>>(), })))
77}