//! 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_meta(&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() }