Skip to main content

ironflow_api/entities/
stats.rs

1//! Statistics DTOs.
2
3use std::collections::HashMap;
4
5use chrono::{DateTime, Utc};
6use rust_decimal::Decimal;
7use serde::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use ironflow_store::entities::{HistoryGranularity, HistoryPeriod, StatsHistoryBucket};
11use ironflow_store::models::RunStatus;
12
13use super::run::parse_label_param;
14
15/// Aggregate statistics response.
16///
17/// Computed from all runs in the store.
18///
19/// # Examples
20///
21/// ```
22/// use ironflow_api::entities::StatsResponse;
23/// ```
24#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
25#[derive(Debug, Serialize)]
26pub struct StatsResponse {
27    /// Total number of runs.
28    pub total_runs: u64,
29    /// Number of completed runs.
30    pub completed_runs: u64,
31    /// Number of failed runs.
32    pub failed_runs: u64,
33    /// Number of cancelled runs.
34    pub cancelled_runs: u64,
35    /// Number of active runs: pending, running, retrying, awaiting approval
36    /// or sleeping.
37    pub active_runs: u64,
38    /// Number of runs awaiting approval. A subset of `active_runs`.
39    pub awaiting_approval_runs: u64,
40    /// Success rate: completed / (completed + failed), as a percentage.
41    pub success_rate_percent: f64,
42    /// Aggregated cost across all runs in USD.
43    pub total_cost_usd: Decimal,
44    /// Aggregated duration across all runs in milliseconds.
45    pub total_duration_ms: u64,
46}
47
48/// Query parameters for `GET /api/v1/stats/history`.
49///
50/// # Examples
51///
52/// ```
53/// use ironflow_api::entities::StatsHistoryQuery;
54/// ```
55#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))]
56#[derive(Debug, Deserialize)]
57pub struct StatsHistoryQuery {
58    /// Filter by workflow name (case-insensitive substring match, same as
59    /// `GET /api/v1/runs`). Omit to aggregate all workflows.
60    pub workflow: Option<String>,
61    /// Time period to query. Defaults to `7d`.
62    pub period: Option<HistoryPeriod>,
63    /// Bucket granularity. Auto-derived from period when omitted.
64    pub granularity: Option<HistoryGranularity>,
65    /// Filter by run status.
66    pub status: Option<RunStatus>,
67    /// Filter by step presence (only applies to completed/cancelled runs).
68    /// Non-terminal runs (pending, running, etc.) are always included.
69    /// When `true`, only count completed/cancelled runs that have steps.
70    /// When `false`, only count completed/cancelled runs without steps.
71    pub has_steps: Option<bool>,
72    /// Filter by labels. Comma-separated `key:value` pairs.
73    pub label: Option<String>,
74    /// Filter by author: the user ID that triggered the run.
75    ///
76    /// Also matches runs triggered by one of that user's API keys.
77    pub created_by: Option<Uuid>,
78}
79
80impl StatsHistoryQuery {
81    /// Parse the comma-separated `label` param into a `HashMap`.
82    ///
83    /// # Examples
84    ///
85    /// ```
86    /// use ironflow_api::entities::StatsHistoryQuery;
87    ///
88    /// let query = StatsHistoryQuery {
89    ///     workflow: None,
90    ///     period: None,
91    ///     granularity: None,
92    ///     status: None,
93    ///     has_steps: None,
94    ///     label: Some("env:prod,team:core".to_string()),
95    ///     created_by: None,
96    /// };
97    /// let labels = query.parse_labels().unwrap_or_default();
98    /// assert_eq!(labels.get("env").map(String::as_str), Some("prod"));
99    /// assert_eq!(labels.len(), 2);
100    /// ```
101    pub fn parse_labels(&self) -> Option<HashMap<String, String>> {
102        parse_label_param(&self.label)
103    }
104}
105
106/// Time-bucketed historical statistics response.
107///
108/// # Examples
109///
110/// ```
111/// use ironflow_api::entities::StatsHistoryResponse;
112/// ```
113#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
114#[derive(Debug, Serialize)]
115pub struct StatsHistoryResponse {
116    /// The period that was queried.
117    pub period: HistoryPeriod,
118    /// The granularity of each bucket.
119    pub granularity: HistoryGranularity,
120    /// Workflow name filter, if applied.
121    pub workflow: Option<String>,
122    /// Every bucket of the period, zero-filled, sorted by time ascending.
123    pub buckets: Vec<StatsHistoryBucketResponse>,
124}
125
126/// One time bucket in the history response.
127///
128/// Runs are assigned to the bucket of their creation time and counted under
129/// their current status. Each status has its own counter, so the counters add
130/// up to the number of runs created in the bucket.
131///
132/// # Examples
133///
134/// ```
135/// use ironflow_api::entities::StatsHistoryBucketResponse;
136/// ```
137#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
138#[derive(Debug, Serialize)]
139pub struct StatsHistoryBucketResponse {
140    /// Start of the time bucket.
141    pub time: DateTime<Utc>,
142    /// Number of runs created in this bucket and currently completed.
143    /// Strict: runs in the `warning` state are counted in `warning`.
144    pub completed: u64,
145    /// Number of runs created in this bucket and currently in `warning`.
146    pub warning: u64,
147    /// Number of runs created in this bucket and currently failed.
148    pub failed: u64,
149    /// Number of runs created in this bucket and currently cancelled.
150    pub cancelled: u64,
151    /// Number of runs created in this bucket and currently pending.
152    pub pending: u64,
153    /// Number of runs created in this bucket and currently running.
154    pub running: u64,
155    /// Number of runs created in this bucket and currently retrying.
156    pub retrying: u64,
157    /// Number of runs created in this bucket and currently awaiting approval.
158    pub awaiting_approval: u64,
159    /// Number of runs created in this bucket and currently sleeping.
160    pub sleeping: u64,
161    /// Success rate: (completed + warning) / (completed + warning + failed),
162    /// as a percentage. `null` when the bucket has no completed, warning or
163    /// failed run.
164    pub success_rate_percent: Option<f64>,
165    /// Average duration in milliseconds.
166    pub avg_duration_ms: u64,
167    /// 95th percentile duration in milliseconds.
168    pub p95_duration_ms: u64,
169    /// Total cost in USD.
170    pub total_cost_usd: Decimal,
171}
172
173impl From<StatsHistoryBucket> for StatsHistoryBucketResponse {
174    fn from(b: StatsHistoryBucket) -> Self {
175        let success_rate_percent = b.success_rate_percent();
176        StatsHistoryBucketResponse {
177            time: b.time,
178            completed: b.completed,
179            warning: b.warning,
180            failed: b.failed,
181            cancelled: b.cancelled,
182            pending: b.pending,
183            running: b.running,
184            retrying: b.retrying,
185            awaiting_approval: b.awaiting_approval,
186            sleeping: b.sleeping,
187            success_rate_percent,
188            avg_duration_ms: b.avg_duration_ms,
189            p95_duration_ms: b.p95_duration_ms,
190            total_cost_usd: b.total_cost_usd,
191        }
192    }
193}