use std::collections::HashMap; use std::sync::Arc; use anyhow::Result; use chrono::{DateTime, Utc}; use fabro_types::{Run, RunId, RunStatus}; use crate::server_client::Client; #[derive(Debug, Clone)] pub(crate) struct ServerRunInfo { run: Run, } impl ServerRunInfo { pub(crate) fn from_run(run: Run) -> Self { Self { run } } pub(crate) fn run_id(&self) -> RunId { self.run.id } pub(crate) fn parent_id(&self) -> Option { self.run.parent_id } pub(crate) fn workflow_name(&self) -> Option<&str> { self.run.workflow.name.as_deref() } pub(crate) fn workflow_graph_name(&self) -> Option<&str> { self.run.workflow.graph_name.as_deref() } pub(crate) fn workflow_slug(&self) -> Option<&str> { self.run.workflow.slug.as_deref() } pub(crate) fn workflow_display_name(&self) -> String { self.run.workflow.display_name().unwrap_or("-").to_string() } pub(crate) fn workflow_matches(&self, pattern: &str) -> bool { [ self.workflow_name(), self.workflow_graph_name(), self.workflow_slug(), ] .into_iter() .flatten() .any(|value| value.contains(pattern)) } pub(crate) fn status(&self) -> RunStatus { self.run.lifecycle.status } pub(crate) fn start_time(&self) -> String { self.start_time_dt() .map(|time| time.to_rfc3339()) .unwrap_or_default() } pub(crate) fn start_time_dt(&self) -> Option> { self.run .timestamps .started_at .or(Some(self.run.id.created_at())) } pub(crate) fn labels(&self) -> &HashMap { &self.run.labels } pub(crate) fn wall_time_ms(&self) -> Option { self.run.timing.as_ref().map(|t| t.wall_time_ms) } pub(crate) fn total_usd_micros(&self) -> Option { self.run.usage.cost.map(|cost| cost.usd_micros) } pub(crate) fn source_directory(&self) -> Option<&str> { self.run.source_directory.as_deref() } pub(crate) fn repo_origin_url(&self) -> Option<&str> { self.run .repository .as_ref() .and_then(|repository| repository.origin_url.as_deref()) } pub(crate) fn goal(&self) -> String { self.run.goal.clone() } } pub(crate) struct ServerRunLookup { runs: Vec, } impl ServerRunLookup { pub(crate) async fn from_client(client: Arc) -> Result { let runs = client.list_store_runs().await?; Ok(Self::from_runs(runs)) } pub(crate) async fn from_client_by_parent( client: Arc, parent_id: RunId, ) -> Result { let runs = client.list_store_runs_by_parent(parent_id).await?; Ok(Self::from_runs(runs)) } fn from_runs(runs: Vec) -> Self { let mut runs = runs .into_iter() .map(ServerRunInfo::from_run) .collect::>(); runs.sort_by(|a, b| { b.start_time_dt() .cmp(&a.start_time_dt()) .then_with(|| b.run_id().cmp(&a.run_id())) }); Self { runs } } pub(crate) fn runs(&self) -> &[ServerRunInfo] { &self.runs } } pub(crate) fn filter_server_runs( runs: &[ServerRunInfo], before: Option<&str>, workflow: Option<&str>, labels: &[(String, String)], running_only: bool, ) -> Vec { runs.iter() .filter(|run| !running_only || run.status().is_active()) .filter(|run| { before.is_none_or(|before| { let start_time = run.start_time(); start_time.is_empty() || start_time.as_str() < before }) }) .filter(|run| workflow.is_none_or(|pattern| run.workflow_matches(pattern))) .filter(|run| { labels.iter().all(|(key, value)| { run.labels() .get(key) .is_some_and(|current| current == value) }) }) .cloned() .collect() }