OXYGENHUB

Coverage / crates/hub/src/web/chain.rs 96.4%

54 of 56 lines run, 13 of 28 functions · run #502 on main · run missed

1
//! The chain of work between issues and proposals (hub spec §8): the
2
//! issues a proposal closes (`closes #N` in its description or its
3
//! changes), and the proposals that close an issue.
4
5
use crate::db::{self, issues as idb, proposals as pdb};
6
use crate::render::refs;
7
use crate::Shared;
8
9
/// An issue or proposal as a link in the chain.
10
#[derive(Clone, Debug)]
11
pub struct Link {
12
    /// `issue` or `proposal`.
13
    pub kind: &'static str,
14
    pub number: i64,
15
    pub title: String,
16
    /// `open`, `closed`, `draft`, `landed`, …
17
    pub state: String,
18
    pub url: String,
19
}
20
21
/// The issues of this repository that `texts` close.
2275×
pub async fn issues_closed_by(app: &Shared, row: &db::repos::RepoRow, texts: &[&str]) -> Vec<Link> {
23149×
    let mut numbers: Vec<i64> = texts.iter().flat_map(|t| refs::closes(t)).collect();
2475×
    numbers.sort();
2575×
    numbers.dedup();
2675×
    let mut out = Vec::new();
2775×
    for n in numbers {
2810×
        if let Ok(Some(i)) = idb::get(&app.db, row.id, n).await {
2910×
            out.push(Link { kind: "issue", number: n, title: i.title, state: i.state, url: format!("/{}/issue/{n}", row.slug()) });
3010×
        }
31
    }
3275×
    out
3375×
}
34
35
/// The proposals of this repository that close issue `n`: in their
36
/// description, or in the message of one of their changes.
3730×
pub async fn proposals_closing(app: &Shared, row: &db::repos::RepoRow, n: i64) -> Vec<Link> {
3830×
    let all = pdb::list(&app.db, row.id, &["draft", "open", "landed", "closed"]).await.unwrap_or_default();
39
    // The changes' messages, read once from the repository.
4030×
    let hashes: Vec<Vec<String>> = all.iter().map(|p| p.projected_changes()).collect();
4130×
    let messages: Vec<Vec<String>> = match crate::repos::RepoName::new(&row.owner, &row.name) {
4230×
        Some(name) => {
4330×
            let wanted = hashes.clone();
4430×
            app.repos
4530×
                .read(&name, move |r, _| {
4630×
                    Ok(wanted
4730×
                        .iter()
4830×
                        .map(|hs| {
498×
                            hs.iter()
508×
                                .filter_map(|h| oxy_core::hash::ChangeHash::from_hex(h))
518×
                                .filter_map(|h| r.store().get_change_meta(&h).ok().map(|c| c.message))
528×
                                .collect()
538×
                        })
5430×
                        .collect())
5530×
                })
5630×
                .await
5730×
                .unwrap_or_else(|_| vec![vec![]; hashes.len()])
58
        }
590×
        None => vec![vec![]; hashes.len()],
60
    };
6130×
    all.into_iter()
6230×
        .zip(messages)
6330×
        .filter(|(p, msgs)| {
648×
            refs::closes(&p.body).contains(&n) || refs::closes(&p.title).contains(&n) || msgs.iter().any(|m| refs::closes(m).contains(&n))
658×
        })
6630×
        .map(|(p, _)| Link {
67
            kind: "proposal",
682×
            number: p.number,
692×
            url: format!("/{}/proposal/{}", row.slug(), p.number),
702×
            title: p.title,
712×
            state: p.state,
722×
        })
7330×
        .collect()
7430×
}