diff --git a/Cargo.lock b/Cargo.lock index a10682b85..751c938eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -219,6 +219,7 @@ dependencies = [ "anyhow", "chrono", "clap", + "map-macro", "octocrab", "tokio", "url", diff --git a/tools/automator/Cargo.toml b/tools/automator/Cargo.toml index 5ab1d8ed4..577c45c21 100644 --- a/tools/automator/Cargo.toml +++ b/tools/automator/Cargo.toml @@ -7,6 +7,7 @@ publish = false [dependencies] anyhow = "1.0.62" chrono = "0.4.22" +map-macro = "0.2.4" octocrab = "0.17.0" url = "2.2.2" diff --git a/tools/automator/src/announcement.rs b/tools/automator/src/announcement.rs index 90031d5bb..d0805ad7c 100644 --- a/tools/automator/src/announcement.rs +++ b/tools/automator/src/announcement.rs @@ -1,13 +1,14 @@ -use std::{fmt::Write, path::PathBuf}; +use std::{collections::HashSet, fmt::Write, path::PathBuf}; use anyhow::Context; use chrono::{Date, Datelike, Utc}; +use map_macro::set; use tokio::{ fs::{self, File}, io::AsyncWriteExt, }; -use crate::pull_requests::PullRequest; +use crate::pull_requests::{Author, PullRequest}; pub async fn create_release_announcement( last_release_date: Date, @@ -50,12 +51,43 @@ async fn generate_announcement( pull_requests: impl IntoIterator, file: &mut File, ) -> anyhow::Result<()> { + let mut pull_request_list = String::new(); let mut pull_request_links = String::new(); - - for PullRequest { number, html_url } in pull_requests { - let link = format!("[#{number}]: {html_url}\n"); - + let mut author_links = String::new(); + + let author_blacklist = set! { + "hannobraun", + "dependabot[bot]" + }; + let mut authors = HashSet::new(); + + for pull_request in pull_requests { + let PullRequest { + number, + title, + url, + author, + } = pull_request; + + let author = if authors.contains(&author.name) + || author_blacklist.contains(author.name.as_str()) + { + None + } else { + authors.insert(author.name.clone()); + Some(author) + }; + + let item = format!("- {title} ([#{number}])\n"); + pull_request_list.push_str(&item); + + let link = format!("[#{number}]: {url}\n"); pull_request_links.push_str(&link); + + if let Some(Author { name, profile }) = author { + let author_link = format!("[@{name}]: {profile}\n"); + author_links.push_str(&author_link); + } } let mut buf = String::new(); @@ -102,6 +134,12 @@ Improvements that are relevant to developers working on Fornjot itself. **TASK: Add internal improvements.** +### Unsorted pull requests + +**TASK: Sort into the categories above; update/merge as appropriate.** + +{pull_request_list} + ### Issue of the Week **TASK: Write.** @@ -112,7 +150,8 @@ Improvements that are relevant to developers working on Fornjot itself. **TASK: Write.** -{pull_request_links}\ +{pull_request_links} +{author_links}\ " )?; diff --git a/tools/automator/src/pull_requests.rs b/tools/automator/src/pull_requests.rs index 4fa04e787..5d8c3a08d 100644 --- a/tools/automator/src/pull_requests.rs +++ b/tools/automator/src/pull_requests.rs @@ -2,12 +2,17 @@ use std::collections::BTreeMap; use anyhow::anyhow; use chrono::{Date, Utc}; -use octocrab::params::{pulls::Sort, Direction, State}; +use octocrab::{ + models::pulls::PullRequest as OctoPullRequest, + params::{pulls::Sort, Direction, State}, +}; use url::Url; pub struct PullRequest { pub number: u64, - pub html_url: Url, + pub title: String, + pub url: Url, + pub author: Author, } impl PullRequest { @@ -18,14 +23,19 @@ impl PullRequest { let mut page = 1u32; 'outer: loop { + const MAX_RESULTS_PER_PAGE: u8 = 100; + println!("Fetching page {}...", page); let pull_request_page = octocrab::instance() .pulls("hannobraun", "Fornjot") .list() .state(State::Closed) + // It would be *much* better to sort by the date the pull + // requests were merged, since "updated" could result in false + // positives. GitHub doesn't support that though. .sort(Sort::Updated) .direction(Direction::Descending) - .per_page(100) // this is the maximum number of results per page + .per_page(MAX_RESULTS_PER_PAGE) .page(page) .send() .await?; @@ -45,12 +55,22 @@ impl PullRequest { if let Some(merged_at) = pull_request.merged_at { if merged_at.date() >= last_release_date { let number = pull_request.number; - let html_url = - pull_request.html_url.ok_or_else(|| { + let title = + pull_request.title.clone().ok_or_else(|| { + anyhow!("Pull request is missing title") + })?; + let url = + pull_request.html_url.clone().ok_or_else(|| { anyhow!("Pull request is missing URL") })?; + let author = Author::from_pull_request(&pull_request)?; - let pull_request = Self { number, html_url }; + let pull_request = Self { + number, + title, + url, + author, + }; pull_requests.insert(pull_request.number, pull_request); } @@ -67,3 +87,24 @@ impl PullRequest { Ok(pull_requests) } } + +pub struct Author { + pub name: String, + pub profile: Url, +} + +impl Author { + pub fn from_pull_request( + pull_request: &OctoPullRequest, + ) -> anyhow::Result { + let user = pull_request + .user + .clone() + .ok_or_else(|| anyhow!("Pull request is missing author"))?; + + let name = user.login; + let profile = user.html_url; + + Ok(Self { name, profile }) + } +}