OXYGENHUB
# e8e7158bbc73 oxygenhub / crates / hub / src / web / chain.rs
2.8 KiB · Rust · last changed Pin oxygen with the Linux-scale work; read change metadata alone 1 month ago source blame history raw
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.
22
pub async fn issues_closed_by(app: &Shared, row: &db::repos::RepoRow, texts: &[&str]) -> Vec<Link> {
23
    let mut numbers: Vec<i64> = texts.iter().flat_map(|t| refs::closes(t)).collect();
24
    numbers.sort();
25
    numbers.dedup();
26
    let mut out = Vec::new();
27
    for n in numbers {
28
        if let Ok(Some(i)) = idb::get(&app.db, row.id, n).await {
29
            out.push(Link { kind: "issue", number: n, title: i.title, state: i.state, url: format!("/{}/issue/{n}", row.slug()) });
30
        }
31
    }
32
    out
33
}
34
35
/// The proposals of this repository that close issue `n`: in their
36
/// description, or in the message of one of their changes.
37
pub async fn proposals_closing(app: &Shared, row: &db::repos::RepoRow, n: i64) -> Vec<Link> {
38
    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.
40
    let hashes: Vec<Vec<String>> = all.iter().map(|p| p.projected_changes()).collect();
41
    let messages: Vec<Vec<String>> = match crate::repos::RepoName::new(&row.owner, &row.name) {
42
        Some(name) => {
43
            let wanted = hashes.clone();
44
            app.repos
45
                .read(&name, move |r, _| {
46
                    Ok(wanted
47
                        .iter()
48
                        .map(|hs| {
49
                            hs.iter()
50
                                .filter_map(|h| oxy_core::hash::ChangeHash::from_hex(h))
51
                                .filter_map(|h| r.store().get_change_meta(&h).ok().map(|c| c.message))
52
                                .collect()
53
                        })
54
                        .collect())
55
                })
56
                .await
57
                .unwrap_or_else(|_| vec![vec![]; hashes.len()])
58
        }
59
        None => vec![vec![]; hashes.len()],
60
    };
61
    all.into_iter()
62
        .zip(messages)
63
        .filter(|(p, msgs)| {
64
            refs::closes(&p.body).contains(&n) || refs::closes(&p.title).contains(&n) || msgs.iter().any(|m| refs::closes(m).contains(&n))
65
        })
66
        .map(|(p, _)| Link {
67
            kind: "proposal",
68
            number: p.number,
69
            url: format!("/{}/proposal/{}", row.slug(), p.number),
70
            title: p.title,
71
            state: p.state,
72
        })
73
        .collect()
74
}