//! The chain of work between issues and proposals (hub spec §8): the
//! issues a proposal closes (`closes #N` in its description or its
//! changes), and the proposals that close an issue.
use crate::db::{self, issues as idb, proposals as pdb};
use crate::render::refs;
use crate::Shared;
/// An issue or proposal as a link in the chain.
#[derive(Clone, Debug)]
pub struct Link {
/// `issue` or `proposal`.
pub kind: &'static str,
pub number: i64,
pub title: String,
/// `open`, `closed`, `draft`, `landed`, …
pub state: String,
pub url: String,
}
/// The issues of this repository that `texts` close.
pub async fn issues_closed_by(app: &Shared, row: &db::repos::RepoRow, texts: &[&str]) -> Vec {
let mut numbers: Vec = texts.iter().flat_map(|t| refs::closes(t)).collect();
numbers.sort();
numbers.dedup();
let mut out = Vec::new();
for n in numbers {
if let Ok(Some(i)) = idb::get(&app.db, row.id, n).await {
out.push(Link { kind: "issue", number: n, title: i.title, state: i.state, url: format!("/{}/issue/{n}", row.slug()) });
}
}
out
}
/// The proposals of this repository that close issue `n`: in their
/// description, or in the message of one of their changes.
pub async fn proposals_closing(app: &Shared, row: &db::repos::RepoRow, n: i64) -> Vec {
let all = pdb::list(&app.db, row.id, &["draft", "open", "landed", "closed"]).await.unwrap_or_default();
// The changes' messages, read once from the repository.
let hashes: Vec> = all.iter().map(|p| p.projected_changes()).collect();
let messages: Vec> = match crate::repos::RepoName::new(&row.owner, &row.name) {
Some(name) => {
let wanted = hashes.clone();
app.repos
.read(&name, move |r, _| {
Ok(wanted
.iter()
.map(|hs| {
hs.iter()
.filter_map(|h| oxy_core::hash::ChangeHash::from_hex(h))
.filter_map(|h| r.store().get_change(&h).ok().map(|c| c.message))
.collect()
})
.collect())
})
.await
.unwrap_or_else(|_| vec![vec![]; hashes.len()])
}
None => vec![vec![]; hashes.len()],
};
all.into_iter()
.zip(messages)
.filter(|(p, msgs)| {
refs::closes(&p.body).contains(&n) || refs::closes(&p.title).contains(&n) || msgs.iter().any(|m| refs::closes(m).contains(&n))
})
.map(|(p, _)| Link {
kind: "proposal",
number: p.number,
url: format!("/{}/proposal/{}", row.slug(), p.number),
title: p.title,
state: p.state,
})
.collect()
}