Skip to content

Commit 1d487b4

Browse files
feat: user summary
1 parent 1b5506d commit 1d487b4

File tree

12 files changed

+502
-354
lines changed

12 files changed

+502
-354
lines changed

crates/api/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ async fn main() -> anyhow::Result<()> {
276276
let bi_metrics_service: Arc<dyn services::bi_metrics::BiMetricsService> =
277277
Arc::new(services::bi_metrics::BiMetricsServiceImpl::new(
278278
bi_metrics_repo as Arc<dyn services::bi_metrics::BiMetricsRepository>,
279+
system_configs_service.clone()
280+
as Arc<dyn services::system_configs::ports::SystemConfigsService>,
279281
));
280282

281283
// Load rate limit config from system configs

crates/api/src/models.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ pub struct AdminUserListResponse {
484484

485485
impl AdminUserResponse {
486486
pub fn from_stats(
487-
u: services::user::ports::AdminUserWithStats,
487+
u: services::bi_metrics::UserWithStats,
488488
subscription_plan_name: Option<String>,
489489
) -> Self {
490490
Self {
@@ -505,8 +505,8 @@ impl AdminUserResponse {
505505
}
506506
}
507507

508-
impl From<services::user::ports::AdminUserWithStats> for AdminUserResponse {
509-
fn from(u: services::user::ports::AdminUserWithStats) -> Self {
508+
impl From<services::bi_metrics::UserWithStats> for AdminUserResponse {
509+
fn from(u: services::bi_metrics::UserWithStats) -> Self {
510510
Self::from_stats(u, None)
511511
}
512512
}

crates/api/src/openapi.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ use utoipa::OpenApi;
9595
crate::routes::admin::admin_delete_instance,
9696
crate::routes::admin::admin_sync_agent_status,
9797
crate::routes::admin::bi_list_users,
98+
crate::routes::admin::bi_users_summary,
9899
crate::routes::admin::bi_list_deployments,
99100
crate::routes::admin::bi_deployment_summary,
100101
crate::routes::admin::bi_status_history,
@@ -205,6 +206,9 @@ use utoipa::OpenApi;
205206
crate::routes::admin::BiUsageResponse,
206207
crate::routes::admin::BiTopConsumersResponse,
207208
crate::routes::admin::BiStatusHistoryResponse,
209+
services::bi_metrics::UserSummary,
210+
services::bi_metrics::UserSummaryPlanCount,
211+
services::bi_metrics::UserSummaryAgentCountBucket,
208212
services::bi_metrics::DeploymentRecord,
209213
services::bi_metrics::DeploymentStatusCount,
210214
services::bi_metrics::DeploymentSummary,

crates/api/src/routes/admin.rs

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use services::analytics::{ActivityLogEntry, AnalyticsSummary, TopActiveUsersResp
1818
use services::bi_metrics::{
1919
DeploymentFilter, DeploymentRecord, DeploymentSummary, StatusChangeRecord, TopConsumer,
2020
TopConsumerFilter, TopConsumerGroupBy, UsageAggregation, UsageFilter, UsageGroupBy,
21-
UsageRankBy as BiUsageRankBy,
21+
UsageRankBy as BiUsageRankBy, UserSummary,
2222
};
2323

2424
/// Maximum rows for BI usage aggregation queries.
@@ -298,7 +298,7 @@ async fn list_users_bi_impl(
298298
})
299299
.unwrap_or((None, false, None));
300300

301-
let filter = services::user::ports::AdminListUsersFilter {
301+
let filter = services::bi_metrics::ListUsersFilter {
302302
subscription_status,
303303
subscription_plan_price_ids,
304304
subscription_plan_none,
@@ -312,27 +312,27 @@ async fn list_users_bi_impl(
312312
}),
313313
};
314314

315-
let sort = services::user::ports::AdminListUsersSort {
315+
let sort = services::bi_metrics::ListUsersSort {
316316
sort_by: match params.sort_by.as_str() {
317-
"created_at" => services::user::ports::AdminUsersSortBy::CreatedAt,
318-
"total_spent_nano" => services::user::ports::AdminUsersSortBy::TotalSpentNano,
319-
"agent_spent_nano" => services::user::ports::AdminUsersSortBy::AgentSpentNano,
320-
"agent_token_usage" => services::user::ports::AdminUsersSortBy::AgentTokenUsage,
321-
"last_activity_at" => services::user::ports::AdminUsersSortBy::LastActivityAt,
322-
"agent_count" => services::user::ports::AdminUsersSortBy::AgentCount,
323-
"email" => services::user::ports::AdminUsersSortBy::Email,
324-
"name" => services::user::ports::AdminUsersSortBy::Name,
325-
_ => services::user::ports::AdminUsersSortBy::CreatedAt,
317+
"created_at" => services::bi_metrics::UsersSortBy::CreatedAt,
318+
"total_spent_nano" => services::bi_metrics::UsersSortBy::TotalSpentNano,
319+
"agent_spent_nano" => services::bi_metrics::UsersSortBy::AgentSpentNano,
320+
"agent_token_usage" => services::bi_metrics::UsersSortBy::AgentTokenUsage,
321+
"last_activity_at" => services::bi_metrics::UsersSortBy::LastActivityAt,
322+
"agent_count" => services::bi_metrics::UsersSortBy::AgentCount,
323+
"email" => services::bi_metrics::UsersSortBy::Email,
324+
"name" => services::bi_metrics::UsersSortBy::Name,
325+
_ => services::bi_metrics::UsersSortBy::CreatedAt,
326326
},
327327
sort_order: if params.sort_order == "asc" {
328-
services::user::ports::AdminUsersSortOrder::Asc
328+
services::bi_metrics::UsersSortOrder::Asc
329329
} else {
330-
services::user::ports::AdminUsersSortOrder::Desc
330+
services::bi_metrics::UsersSortOrder::Desc
331331
},
332332
};
333333

334334
let (users, total) = app_state
335-
.user_service
335+
.bi_metrics_service
336336
.list_users_with_stats(params.limit, params.offset, &filter, &sort)
337337
.await
338338
.map_err(|e| {
@@ -2102,6 +2102,36 @@ pub async fn bi_list_users(
21022102
list_users_bi_impl(&app_state, params).await
21032103
}
21042104

2105+
/// User distribution by subscription plan and by deployed agent count (BI). Requires admin authentication.
2106+
#[utoipa::path(
2107+
get,
2108+
path = "/v1/admin/bi/users/summary",
2109+
tag = "Admin",
2110+
responses(
2111+
(status = 200, description = "User distribution summary", body = UserSummary),
2112+
(status = 401, description = "Unauthorized", body = crate::error::ApiErrorResponse),
2113+
(status = 403, description = "Forbidden - Admin access required", body = crate::error::ApiErrorResponse),
2114+
(status = 500, description = "Internal server error", body = crate::error::ApiErrorResponse)
2115+
),
2116+
security(
2117+
("session_token" = [])
2118+
)
2119+
)]
2120+
pub async fn bi_users_summary(
2121+
State(app_state): State<AppState>,
2122+
) -> Result<Json<UserSummary>, ApiError> {
2123+
tracing::info!("BI: Getting user summary");
2124+
let summary = app_state
2125+
.bi_metrics_service
2126+
.get_user_summary()
2127+
.await
2128+
.map_err(|e| {
2129+
tracing::error!("Failed to get user summary: {}", e);
2130+
ApiError::internal_server_error("Failed to get user distribution summary")
2131+
})?;
2132+
Ok(Json(summary))
2133+
}
2134+
21052135
/// List deployments with optional filters (BI). Requires admin authentication.
21062136
#[utoipa::path(
21072137
get,
@@ -2389,6 +2419,7 @@ pub fn create_admin_router() -> Router<AppState> {
23892419
.nest(
23902420
"/bi",
23912421
Router::new()
2422+
.route("/users/summary", get(bi_users_summary))
23922423
.route("/users", get(bi_list_users))
23932424
.route("/deployments", get(bi_list_deployments))
23942425
.route("/deployments/summary", get(bi_deployment_summary))

crates/api/tests/common.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,8 @@ pub async fn create_test_server_and_db(
242242
let bi_metrics_service: Arc<dyn services::bi_metrics::BiMetricsService> =
243243
Arc::new(services::bi_metrics::BiMetricsServiceImpl::new(
244244
bi_metrics_repo as Arc<dyn services::bi_metrics::BiMetricsRepository>,
245+
system_configs_service.clone()
246+
as Arc<dyn services::system_configs::ports::SystemConfigsService>,
245247
));
246248

247249
// Create application state

0 commit comments

Comments
 (0)