use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use sqlx::FromRow;
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct WorkflowSummary {
pub pending: Option<i64>,
pub running: Option<i64>,
pub completed: Option<i64>,
pub failed: Option<i64>,
pub paused: Option<i64>,
pub cancelled: Option<i64>,
}
impl WorkflowSummary {
pub fn total(&self) -> i64 {
self.pending.unwrap_or(0)
+ self.running.unwrap_or(0)
+ self.completed.unwrap_or(0)
+ self.failed.unwrap_or(0)
+ self.paused.unwrap_or(0)
+ self.cancelled.unwrap_or(0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct WorkflowRow {
pub id: String,
pub name: String,
pub status: String,
pub on_error: String,
pub output_task_index: Option<i32>,
pub success_policy: Option<JsonValue>,
pub result: Option<String>,
pub error: Option<String>,
pub sent_at: DateTime<Utc>,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub updated_at: DateTime<Utc>,
pub total_tasks: Option<i64>,
pub completed_tasks: Option<i64>,
pub failed_tasks: Option<i64>,
pub running_tasks: Option<i64>,
pub pending_tasks: Option<i64>,
pub enqueued_tasks: Option<i64>,
pub skipped_tasks: Option<i64>,
}
impl WorkflowRow {
fn terminal_tasks(&self) -> i64 {
let total = self.total_tasks.unwrap_or(0);
let pending = self.pending_tasks.unwrap_or(0);
let running = self.running_tasks.unwrap_or(0);
let enqueued = self.enqueued_tasks.unwrap_or(0);
total - pending - running - enqueued
}
pub fn progress_str(&self) -> String {
let terminal = self.terminal_tasks();
let total = self.total_tasks.unwrap_or(0);
format!("{}/{}", terminal, total)
}
pub fn progress_pct(&self) -> f64 {
let total = self.total_tasks.unwrap_or(0);
if total == 0 {
return 0.0;
}
let terminal = self.terminal_tasks();
terminal as f64 / total as f64
}
pub fn success_str(&self) -> String {
let completed = self.completed_tasks.unwrap_or(0);
let total = self.total_tasks.unwrap_or(0);
format!("{}/{}", completed, total)
}
pub fn success_pct(&self) -> f64 {
let total = self.total_tasks.unwrap_or(0);
if total == 0 {
return 0.0;
}
let completed = self.completed_tasks.unwrap_or(0);
completed as f64 / total as f64
}
pub fn duration_str(&self) -> String {
match (self.started_at, self.completed_at) {
(Some(start), Some(end)) => {
let duration = end - start;
let secs = duration.num_seconds();
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else {
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
}
}
(Some(start), None) => {
let duration = Utc::now() - start;
let secs = duration.num_seconds();
if secs < 60 {
format!("{}s", secs)
} else if secs < 3600 {
format!("{}m {}s", secs / 60, secs % 60)
} else {
format!("{}h {}m", secs / 3600, (secs % 3600) / 60)
}
}
_ => "-".to_string(),
}
}
pub fn short_id(&self) -> &str {
if self.id.len() > 8 {
&self.id[..8]
} else {
&self.id
}
}
pub fn to_clipboard_json(&self, tasks: &[WorkflowTaskRow]) -> String {
#[derive(Serialize)]
struct ClipboardWorkflow<'a> {
workflow: &'a WorkflowRow,
tasks: &'a [WorkflowTaskRow],
}
let data = ClipboardWorkflow {
workflow: self,
tasks,
};
serde_json::to_string_pretty(&data).unwrap_or_else(|_| "{}".to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
pub struct WorkflowTaskRow {
pub task_index: i32,
pub node_id: Option<String>,
pub task_name: String,
pub queue_name: String,
pub priority: i32,
pub status: String,
pub dependencies: Option<Vec<i32>>,
pub args_from: Option<JsonValue>,
pub workflow_ctx_from: Option<Vec<String>>,
pub allow_failed_deps: bool,
pub join_type: String,
pub min_success: Option<i32>,
pub task_options: Option<String>,
pub task_id: Option<String>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub result: Option<String>,
pub error: Option<String>,
}