mockforge_ui/handlers/
coverage_metrics.rs1use axum::{extract::Query, http::StatusCode, Json};
7use mockforge_analytics::{
8 AnalyticsDatabase, DriftPercentageMetrics, EndpointCoverage, PersonaCIHit,
9 RealityLevelStaleness, ScenarioUsageMetrics,
10};
11use serde::Deserialize;
12use std::sync::Arc;
13use tracing::{debug, error};
14
15use crate::models::ApiResponse;
16
17#[derive(Clone)]
19pub struct CoverageMetricsState {
20 pub db: Arc<tokio::sync::OnceCell<AnalyticsDatabase>>,
21}
22
23impl CoverageMetricsState {
24 pub fn new(db: AnalyticsDatabase) -> Self {
25 let cell = tokio::sync::OnceCell::new();
26 let _ = cell.set(db);
27 Self { db: Arc::new(cell) }
28 }
29
30 async fn get_db(&self) -> Result<&AnalyticsDatabase, StatusCode> {
31 self.db.get().ok_or_else(|| {
32 error!("Analytics database not initialized");
33 StatusCode::SERVICE_UNAVAILABLE
34 })
35 }
36}
37
38#[derive(Debug, Deserialize)]
40pub struct CoverageQuery {
41 pub workspace_id: Option<String>,
43 pub org_id: Option<String>,
45 pub limit: Option<i64>,
47 pub min_coverage: Option<f64>,
49 pub max_staleness_days: Option<i32>,
51}
52
53pub async fn get_scenario_usage(
57 axum::extract::Extension(state): axum::extract::Extension<CoverageMetricsState>,
58 Query(params): Query<CoverageQuery>,
59) -> Result<Json<ApiResponse<Vec<ScenarioUsageMetrics>>>, StatusCode> {
60 debug!("Getting scenario usage metrics");
61
62 let db = state.get_db().await?;
63 match db
64 .get_scenario_usage(params.workspace_id.as_deref(), params.org_id.as_deref(), params.limit)
65 .await
66 {
67 Ok(metrics) => Ok(Json(ApiResponse::success(metrics))),
68 Err(e) => {
69 error!("Failed to get scenario usage metrics: {}", e);
70 Err(StatusCode::INTERNAL_SERVER_ERROR)
71 }
72 }
73}
74
75pub async fn get_persona_ci_hits(
79 axum::extract::Extension(state): axum::extract::Extension<CoverageMetricsState>,
80 Query(params): Query<CoverageQuery>,
81) -> Result<Json<ApiResponse<Vec<PersonaCIHit>>>, StatusCode> {
82 debug!("Getting persona CI hits");
83
84 let db = state.get_db().await?;
85 match db
86 .get_persona_ci_hits(params.workspace_id.as_deref(), params.org_id.as_deref(), params.limit)
87 .await
88 {
89 Ok(hits) => Ok(Json(ApiResponse::success(hits))),
90 Err(e) => {
91 error!("Failed to get persona CI hits: {}", e);
92 Err(StatusCode::INTERNAL_SERVER_ERROR)
93 }
94 }
95}
96
97pub async fn get_endpoint_coverage(
101 axum::extract::Extension(state): axum::extract::Extension<CoverageMetricsState>,
102 Query(params): Query<CoverageQuery>,
103) -> Result<Json<ApiResponse<Vec<EndpointCoverage>>>, StatusCode> {
104 debug!("Getting endpoint coverage");
105
106 let db = state.get_db().await?;
107 match db
108 .get_endpoint_coverage(
109 params.workspace_id.as_deref(),
110 params.org_id.as_deref(),
111 params.min_coverage,
112 )
113 .await
114 {
115 Ok(coverage) => Ok(Json(ApiResponse::success(coverage))),
116 Err(e) => {
117 error!("Failed to get endpoint coverage: {}", e);
118 Err(StatusCode::INTERNAL_SERVER_ERROR)
119 }
120 }
121}
122
123pub async fn get_reality_level_staleness(
127 axum::extract::Extension(state): axum::extract::Extension<CoverageMetricsState>,
128 Query(params): Query<CoverageQuery>,
129) -> Result<Json<ApiResponse<Vec<RealityLevelStaleness>>>, StatusCode> {
130 debug!("Getting reality level staleness");
131
132 let db = state.get_db().await?;
133 match db
134 .get_reality_level_staleness(
135 params.workspace_id.as_deref(),
136 params.org_id.as_deref(),
137 params.max_staleness_days,
138 )
139 .await
140 {
141 Ok(staleness) => Ok(Json(ApiResponse::success(staleness))),
142 Err(e) => {
143 error!("Failed to get reality level staleness: {}", e);
144 Err(StatusCode::INTERNAL_SERVER_ERROR)
145 }
146 }
147}
148
149pub async fn get_drift_percentage(
153 axum::extract::Extension(state): axum::extract::Extension<CoverageMetricsState>,
154 Query(params): Query<CoverageQuery>,
155) -> Result<Json<ApiResponse<Vec<DriftPercentageMetrics>>>, StatusCode> {
156 debug!("Getting drift percentage metrics");
157
158 let db = state.get_db().await?;
159 match db
160 .get_drift_percentage(
161 params.workspace_id.as_deref(),
162 params.org_id.as_deref(),
163 params.limit,
164 )
165 .await
166 {
167 Ok(metrics) => Ok(Json(ApiResponse::success(metrics))),
168 Err(e) => {
169 error!("Failed to get drift percentage metrics: {}", e);
170 Err(StatusCode::INTERNAL_SERVER_ERROR)
171 }
172 }
173}
174
175pub fn coverage_metrics_router() -> axum::Router {
178 use axum::routing::get;
179
180 axum::Router::new()
181 .route("/scenarios/usage", get(get_scenario_usage))
182 .route("/personas/ci-hits", get(get_persona_ci_hits))
183 .route("/endpoints/coverage", get(get_endpoint_coverage))
184 .route("/reality-levels/staleness", get(get_reality_level_staleness))
185 .route("/drift/percentage", get(get_drift_percentage))
186}