OXYGENHUB

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

54 of 56 lines run, 13 of 28 functions · run #465 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.
2272×
pub async fn issues_closed_by(app: &Shared, row: &db::repos::RepoRow, texts: &[&str]) -> Vec<Link> {
23144×
    let mut numbers: Vec<i64> = texts.iter().flat_map(|t| refs::closes(t)).collect();
2472×
    numbers.sort();
2572×
    numbers.dedup();
2672×
    let mut out = Vec::new();
2772×
    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
    }
3272×
    out
3372×
}
34
35
/// The proposals of this repository that close issue `n`: in their
36
/// description, or in the message of one of their changes.
3727×
pub async fn proposals_closing(app: &Shared, row: &db::repos::RepoRow, n: i64) -> Vec<Link> {
3827×
    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.
4027×
    let hashes: Vec<Vec<String>> = all.iter().map(|p| p.projected_changes()).collect();
4127×
    let messages: Vec<Vec<String>> = match crate::repos::RepoName::new(&row.owner, &row.name) {
4227×
        Some(name) => {
4327×
            let wanted = hashes.clone();
4427×
            app.repos
4527×
                .read(&name, move |r, _| {
4627×
                    Ok(wanted
4727×
                        .iter()
4827×
                        .map(|hs| {
497×
                            hs.iter()
507×
                                .filter_map(|h| oxy_core::hash::ChangeHash::from_hex(h))
517×
                                .filter_map(|h| r.store().get_change_meta(&h).ok().map(|c| c.message))
527×
                                .collect()
537×
                        })
5427×
                        .collect())
5527×
                })
5627×
                .await
5727×
                .unwrap_or_else(|_| vec![vec![]; hashes.len()])
58
        }
590×
        None => vec![vec![]; hashes.len()],
60
    };
6127×
    all.into_iter()
6227×
        .zip(messages)
6327×
        .filter(|(p, msgs)| {
647×
            refs::closes(&p.body).contains(&n) || refs::closes(&p.title).contains(&n) || msgs.iter().any(|m| refs::closes(m).contains(&n))
657×
        })
6627×
        .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×
        })
7327×
        .collect()
7427×
}