Login
4 branches 0 tags
Ben (Desktop/Arch) CI d108cf3 17 days ago 230 Commits
rubhub / src / models / ci.rs
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;

use super::common::format_relative_time;

/// Status of a CI job
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum CiJobStatus {
    #[default]
    Pending,
    Running,
    Success,
    Failed,
}

impl CiJobStatus {
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Running => "running",
            Self::Success => "success",
            Self::Failed => "failed",
        }
    }
}

/// A CI job record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiJob {
    pub id: Uuid,
    pub owner: String,
    pub project: String,
    pub workflow_name: String,
    pub branch: String,
    pub commit_hash: String,
    pub status: CiJobStatus,
    #[serde(with = "time::serde::rfc3339")]
    pub created_at: OffsetDateTime,
    #[serde(default, with = "time::serde::rfc3339::option")]
    pub started_at: Option<OffsetDateTime>,
    #[serde(default, with = "time::serde::rfc3339::option")]
    pub finished_at: Option<OffsetDateTime>,
    pub exit_code: Option<i32>,
}

impl CiJob {
    /// Create a new pending job
    pub fn new(
        owner: String,
        project: String,
        workflow_name: String,
        branch: String,
        commit_hash: String,
    ) -> Self {
        Self {
            id: Uuid::now_v7(),
            owner,
            project,
            workflow_name,
            branch,
            commit_hash,
            status: CiJobStatus::Pending,
            created_at: OffsetDateTime::now_utc(),
            started_at: None,
            finished_at: None,
            exit_code: None,
        }
    }

    /// Get the directory path for this job's data
    pub fn job_dir(&self, ci_root: &std::path::Path) -> PathBuf {
        ci_root
            .join(&self.owner)
            .join(&self.project)
            .join(self.id.to_string())
    }

    /// Get the path to the job metadata file
    pub fn job_file(&self, ci_root: &std::path::Path) -> PathBuf {
        self.job_dir(ci_root).join("job.json")
    }

    /// Get the path to the stdout log file
    pub fn stdout_file(&self, ci_root: &std::path::Path) -> PathBuf {
        self.job_dir(ci_root).join("stdout.log")
    }

    /// Get the path to the stderr log file
    pub fn stderr_file(&self, ci_root: &std::path::Path) -> PathBuf {
        self.job_dir(ci_root).join("stderr.log")
    }

    /// Format the created_at date for display
    pub fn relative_time(&self) -> String {
        let now = OffsetDateTime::now_utc();
        let diff = now - self.created_at;
        format_relative_time(diff.whole_seconds())
    }

    /// Get a short version of the commit hash
    pub fn short_hash(&self) -> &str {
        if self.commit_hash.len() > 8 {
            &self.commit_hash[..8]
        } else {
            &self.commit_hash
        }
    }

    /// URI for viewing this job
    pub fn uri(&self) -> String {
        format!("/~{}/{}/ci/{}", self.owner, self.project, self.id)
    }
}

/// Workflow configuration parsed from .rubhub/workflows/*.yaml
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CiWorkflow {
    pub name: String,
    #[serde(default)]
    pub packages: Vec<String>,
    pub script: String,
    #[serde(default)]
    pub branches: Vec<String>,
}

impl CiWorkflow {
    /// Check if this workflow should run on the given branch
    pub fn should_run_on_branch(&self, branch: &str) -> bool {
        // Empty branches list means run on all branches
        self.branches.is_empty() || self.branches.iter().any(|b| b == branch)
    }
}

/// Request to run a CI job (sent through the channel)
#[derive(Debug, Clone)]
pub struct CiJobRequest {
    pub owner: String,
    pub project: String,
    pub branch: String,
    pub commit_hash: String,
    pub workflow_name: String,
    pub workflow: CiWorkflow,
}