hive-forge: surface issues an issue/pr blocks in list/issue/pr

This commit is contained in:
damocles 2026-08-19 15:03:15 +02:00
commit a3f14f5126
4 changed files with 88 additions and 18 deletions

View file

@ -32,7 +32,7 @@ use forgejo_api::structs::{
use serde_json::Value;
use crate::client::Client;
use crate::verbs::{dependency_summaries, labels, milestone, print_json};
use crate::verbs::{blocking_summaries, dependency_summaries, labels, milestone, print_json};
/// What kind of items to return. Maps onto Forgejo's `type` query
/// parameter: `issues` / `pulls`, or no filter at all for `both`
@ -178,12 +178,18 @@ pub fn run(client: &Client, args: Args) -> Result<()> {
return print_json(&items);
}
for item in items.as_array().into_iter().flatten() {
let progress = item
.get("number")
.and_then(Value::as_u64)
let number = item.get("number").and_then(Value::as_u64);
let progress = number
.map(|n| dependency_summaries(client, owner, name, n))
.transpose()?;
print_row(item, progress.as_deref().and_then(dep_progress));
let blocks = number
.map(|n| blocking_summaries(client, owner, name, n))
.transpose()?;
print_row(
item,
progress.as_deref().and_then(dep_progress),
blocks.as_deref().map_or(0, blocking_open_count),
);
}
if let Some(msg) = trailer(
headers.x_total_count.and_then(|t| u64::try_from(t).ok()),
@ -243,11 +249,14 @@ fn trailer(total: Option<u64>, count: u64, page: u64, limit: u64) -> Option<Stri
}
/// Render one issue/PR as a single `#NNN [author] title` line, plus a
/// `(N/M deps done)` suffix when `progress` is `Some`.
/// Defensive: missing fields drop to placeholders so a partial
/// response from a future API change still produces readable output
/// instead of panicking on `unwrap`.
fn print_row(item: &Value, progress: Option<(usize, usize)>) {
/// `(N/M deps done)` suffix when `progress` is `Some` and a `(blocks N)`
/// suffix when `blocking_open` is nonzero — the actionability signal the
/// operator asked for: an issue blocking open work is worth picking over
/// one with no unresolved followers, at a glance in `list`'s own output
/// rather than a per-issue `show`. Defensive: missing fields drop to
/// placeholders so a partial response from a future API change still
/// produces readable output instead of panicking on `unwrap`.
fn print_row(item: &Value, progress: Option<(usize, usize)>, blocking_open: usize) {
let number = item.get("number").and_then(Value::as_u64).unwrap_or(0);
let title = item.get("title").and_then(Value::as_str).unwrap_or("");
let author = item
@ -261,12 +270,23 @@ fn print_row(item: &Value, progress: Option<(usize, usize)>) {
// (which would render every issue as a PR).
let is_pr = item.get("pull_request").is_some_and(|v| !v.is_null());
let kind = if is_pr { "PR" } else { " " };
match progress {
Some((done, total)) => {
println!("#{number:>4} {kind} [{author}] {title} ({done}/{total} deps done)");
}
None => println!("#{number:>4} {kind} [{author}] {title}"),
}
let deps_suffix = progress.map(|(done, total)| format!(" ({done}/{total} deps done)"));
let blocks_suffix = (blocking_open > 0).then(|| format!(" (blocks {blocking_open})"));
println!(
"#{number:>4} {kind} [{author}] {title}{}{}",
deps_suffix.unwrap_or_default(),
blocks_suffix.unwrap_or_default()
);
}
/// Count of *open* issues `blocking` this row's issue blocks — closed
/// followers don't count toward "actionable" (see [`print_row`]'s doc
/// comment); they're already done regardless of this one's own state.
fn blocking_open_count(blocking: &[Value]) -> usize {
blocking
.iter()
.filter(|b| b.get("state").and_then(Value::as_str) == Some("open"))
.count()
}
/// Dependency completion progress from a `dependency_summaries` list —