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(())
}
}