a73x

src/status.rs

Ref:   Size: 4.6 KiB

use std::fmt;

use git2::Repository;

use crate::error::Error;
use crate::event::ReviewVerdict;
use crate::state::{self, IssueStatus, PatchStatus};

/// A single recently-updated item (issue or patch) for the status summary.
#[derive(Debug, Clone)]
pub struct RecentItem {
    pub kind: &'static str,
    pub id: String,
    pub title: String,
    pub status: String,
    pub created_at: String,
}

/// Aggregated project status computed from all collab refs.
#[derive(Debug, Clone)]
pub struct ProjectStatus {
    pub issues_open: usize,
    pub issues_closed: usize,
    pub patches_open: usize,
    pub patches_closed: usize,
    pub patches_merged: usize,
    /// Number of open patches whose latest review verdict is Approve.
    pub patches_approved: usize,
    /// Number of open patches whose latest review verdict is RequestChanges.
    pub patches_changes_requested: usize,
    /// Most recently created items (up to 10), sorted newest first.
    pub recent_items: Vec<RecentItem>,
}

/// Compute an aggregate status for the project.
pub fn compute(repo: &Repository) -> Result<ProjectStatus, Error> {
    let issues = state::list_issues(repo)?;
    let patches = state::list_patches(repo)?;

    let issues_open = issues
        .iter()
        .filter(|i| i.status == IssueStatus::Open)
        .count();
    let issues_closed = issues
        .iter()
        .filter(|i| i.status == IssueStatus::Closed)
        .count();

    let patches_open = patches
        .iter()
        .filter(|p| p.status == PatchStatus::Open)
        .count();
    let patches_closed = patches
        .iter()
        .filter(|p| p.status == PatchStatus::Closed)
        .count();
    let patches_merged = patches
        .iter()
        .filter(|p| p.status == PatchStatus::Merged)
        .count();

    // Review summary: for each open patch, look at the latest review verdict.
    let mut patches_approved = 0usize;
    let mut patches_changes_requested = 0usize;
    for p in patches.iter().filter(|p| p.status == PatchStatus::Open) {
        if let Some(last_review) = p.reviews.last() {
            match last_review.verdict {
                ReviewVerdict::Approve => patches_approved += 1,
                ReviewVerdict::RequestChanges => patches_changes_requested += 1,
                ReviewVerdict::Comment => {}
                ReviewVerdict::Reject => {}
            }
        }
    }

    // Collect recent items from both issues and patches, sort by created_at descending, cap at 10.
    let mut recent_items: Vec<RecentItem> = Vec::new();
    for issue in &issues {
        recent_items.push(RecentItem {
            kind: "issue",
            id: issue.id[..8.min(issue.id.len())].to_string(),
            title: issue.title.clone(),
            status: issue.status.to_string(),
            created_at: issue.created_at.clone(),
        });
    }
    for patch in &patches {
        recent_items.push(RecentItem {
            kind: "patch",
            id: patch.id[..8.min(patch.id.len())].to_string(),
            title: patch.title.clone(),
            status: patch.status.to_string(),
            created_at: patch.created_at.clone(),
        });
    }
    recent_items.sort_by(|a, b| b.created_at.cmp(&a.created_at));
    recent_items.truncate(10);

    Ok(ProjectStatus {
        issues_open,
        issues_closed,
        patches_open,
        patches_closed,
        patches_merged,
        patches_approved,
        patches_changes_requested,
        recent_items,
    })
}

impl fmt::Display for ProjectStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        writeln!(f, "Issues:  {} open, {} closed", self.issues_open, self.issues_closed)?;
        writeln!(
            f,
            "Patches: {} open, {} merged, {} closed",
            self.patches_open, self.patches_merged, self.patches_closed
        )?;

        if self.patches_open > 0 {
            writeln!(
                f,
                "  Review: {} approved, {} changes requested, {} pending",
                self.patches_approved,
                self.patches_changes_requested,
                self.patches_open - self.patches_approved - self.patches_changes_requested
            )?;
        }

        if !self.recent_items.is_empty() {
            writeln!(f)?;
            writeln!(f, "Recently updated:")?;
            for item in &self.recent_items {
                writeln!(
                    f,
                    "  [{kind}] {id} {title} ({status})",
                    kind = item.kind,
                    id = item.id,
                    title = item.title,
                    status = item.status,
                )?;
            }
        }

        Ok(())
    }
}