Skip to content

Commit 51b0228

Browse files
committed
Switch from scraping rfcbot.rs to using the API endpoint
1 parent 4c00ca9 commit 51b0228

File tree

4 files changed

+112
-96
lines changed

4 files changed

+112
-96
lines changed

tools/agenda-generator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ edition = "2018"
66

77
[dependencies]
88
anyhow = "1.0"
9-
chrono = "0.4"
9+
chrono = { version = "0.4", features = ["serde"] }
1010
reqwest = { version = "0.10", features = ["blocking", "json"] }
1111
serde = { version = "1.0", features = ["derive"] }
1212
serde_json = "1.0"

tools/agenda-generator/src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use structopt::StructOpt;
21
use structopt::clap::arg_enum;
2+
use structopt::StructOpt;
33

44
#[derive(Debug, StructOpt)]
55
pub struct Args {

tools/agenda-generator/src/generator.rs

Lines changed: 109 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use anyhow::{anyhow, Result};
1+
use anyhow::Result;
2+
use chrono::NaiveDateTime;
3+
use itertools::Itertools;
24
use reqwest::header::{AUTHORIZATION, USER_AGENT};
35
use serde::de::{DeserializeOwned, Deserializer};
46
use serde::Deserialize;
5-
use std::collections::{BTreeMap, BTreeSet};
7+
use std::collections::BTreeSet;
68
use std::fmt::Write;
7-
use itertools::Itertools;
89

910
#[derive(Default)]
1011
pub struct Generator {
@@ -33,7 +34,7 @@ impl Generator {
3334
chrono::Utc::now().format("%Y-%m-%d")
3435
)?;
3536

36-
self.fcps()?;
37+
self.fcps(String::from("T-libs-api"))?;
3738

3839
IssueQuery::new("Nominated")
3940
.labels(&["T-libs-api", "I-nominated"])
@@ -86,6 +87,8 @@ impl Generator {
8687
chrono::Utc::now().format("%Y-%m-%d")
8788
)?;
8889

90+
self.fcps(String::from("T-libs"))?;
91+
8992
IssueQuery::new("Critical")
9093
.labels(&["T-libs", "P-critical"])
9194
.labels(&["T-libs-api", "P-critical"])
@@ -144,99 +147,116 @@ impl Generator {
144147
Ok(self.agenda)
145148
}
146149

147-
fn fcps(&mut self) -> Result<()> {
148-
let p = reqwest::blocking::get("https://rfcbot.rs")?.text()?;
149-
let mut p = p.lines();
150-
p.find(|s| s.trim_end() == "<h4><code>T-libs-api</code></h4>")
151-
.ok_or_else(|| anyhow!("Missing T-libs-api section"))?;
152-
153-
let mut fcps = BTreeMap::<&str, Vec<Fcp>>::new();
154-
let mut reviewer_count: BTreeMap<&str, usize> = [
155-
"Amanieu",
156-
"BurntSushi",
157-
"dtolnay",
158-
"joshtriplett",
159-
"m-ou-se",
160-
"sfackler",
161-
"yaahc",
162-
]
163-
.iter()
164-
.map(|&r| (r, 0))
165-
.collect();
166-
167-
loop {
168-
let line = p.next().unwrap();
169-
if line.starts_with("<h4>") || line.starts_with("</html>") {
170-
break;
171-
}
172-
if line.trim() == "<li>" {
173-
let disposition = p.next().unwrap().trim().strip_suffix(':').unwrap();
174-
let url = p
175-
.next()
176-
.unwrap()
177-
.trim()
178-
.strip_prefix("<b><a href=\"")
179-
.unwrap()
180-
.strip_suffix('"')
181-
.unwrap();
182-
assert_eq!(p.next().unwrap().trim(), "target=\"_blank\">");
183-
let title_and_number = p.next().unwrap().trim().strip_suffix(")</a></b>").unwrap();
184-
let (title, number) = title_and_number.rsplit_once(" (").unwrap();
185-
let (repo, number) = number.split_once('#').unwrap();
186-
let mut reviewers = Vec::new();
187-
let mut concerns = false;
188-
loop {
189-
let line = p.next().unwrap().trim();
190-
if line == "</li>" {
191-
break;
192-
}
193-
if line == "pending concerns" {
194-
concerns = true;
195-
} else if let Some(line) = line.strip_prefix("<a href=\"/fcp/") {
196-
let reviewer = line.split_once('"').unwrap().0;
197-
if let Some(n) = reviewer_count.get_mut(reviewer) {
198-
reviewers.push(reviewer);
199-
*n += 1;
200-
}
201-
}
202-
}
203-
fcps.entry(repo).or_default().push(Fcp {
204-
title,
205-
repo,
206-
number,
207-
disposition,
208-
url,
209-
reviewers,
210-
concerns,
211-
});
212-
}
150+
fn fcps(&mut self, label: String) -> Result<()> {
151+
#[derive(Deserialize, Debug)]
152+
pub struct FcpWithInfo {
153+
pub fcp: FcpProposal,
154+
pub reviews: Vec<(GitHubUser, bool)>,
155+
pub issue: Issue,
156+
pub status_comment: IssueComment,
157+
}
158+
159+
#[derive(Debug, Deserialize)]
160+
pub struct FcpProposal {
161+
pub id: i32,
162+
pub fk_issue: i32,
163+
pub fk_initiator: i32,
164+
pub fk_initiating_comment: i32,
165+
pub disposition: String,
166+
pub fk_bot_tracking_comment: i32,
167+
pub fcp_start: Option<NaiveDateTime>,
168+
pub fcp_closed: bool,
169+
}
170+
171+
#[derive(Deserialize, Debug)]
172+
pub struct GitHubUser {
173+
pub id: i32,
174+
pub login: String,
175+
}
176+
177+
#[derive(Deserialize, Debug)]
178+
pub struct Issue {
179+
pub id: i32,
180+
pub number: i32,
181+
pub fk_milestone: Option<i32>,
182+
pub fk_user: i32,
183+
pub fk_assignee: Option<i32>,
184+
pub open: bool,
185+
pub is_pull_request: bool,
186+
pub title: String,
187+
pub body: String,
188+
pub locked: bool,
189+
pub closed_at: Option<NaiveDateTime>,
190+
pub created_at: NaiveDateTime,
191+
pub updated_at: NaiveDateTime,
192+
pub labels: Vec<String>,
193+
pub repository: String,
194+
}
195+
196+
#[derive(Deserialize, Debug)]
197+
pub struct IssueComment {
198+
pub id: i32,
199+
pub fk_issue: i32,
200+
pub fk_user: i32,
201+
pub body: String,
202+
pub created_at: NaiveDateTime,
203+
pub updated_at: NaiveDateTime,
204+
pub repository: String,
213205
}
214206

207+
let mut fcps: Vec<FcpWithInfo> =
208+
reqwest::blocking::get("https://rfcbot.rs/api/all")?.json()?;
209+
fcps.retain(|fcp| fcp.issue.labels.contains(&label));
210+
211+
let reviewer_count = fcps
212+
.iter()
213+
.flat_map(|fcp| fcp.reviews.iter())
214+
.filter(|review| !review.1)
215+
.map(|review| &review.0.login)
216+
.counts();
217+
218+
let repos = fcps
219+
.iter()
220+
.map(|fcp| fcp.issue.repository.as_str())
221+
.collect::<BTreeSet<_>>();
222+
223+
215224
writeln!(self.agenda, "### FCPs")?;
216225
writeln!(self.agenda,)?;
217-
writeln!(
218-
self.agenda,
219-
"{} open T-libs-api FCPs:",
220-
fcps.values().map(|v| v.len()).sum::<usize>()
221-
)?;
222-
for (repo, fcps) in fcps.iter() {
226+
writeln!(self.agenda, "{} open T-libs-api FCPs:", fcps.len())?;
227+
228+
for repo in repos {
229+
let fcps = fcps
230+
.iter()
231+
.filter(|fcp| fcp.issue.repository == repo)
232+
.collect::<Vec<_>>();
233+
223234
writeln!(self.agenda, "<details><summary><a href=\"https://github.com/{}/issues?q=is%3Aopen+label%3AT-libs-api+label%3Aproposed-final-comment-period\">{} <code>{}</code> FCPs</a></summary>\n", repo, fcps.len(), repo)?;
235+
224236
for fcp in fcps {
237+
let url = format!(
238+
"https://github.com/{}/issues/{}#issuecomment-{}",
239+
fcp.issue.repository, fcp.issue.number, fcp.status_comment.id
240+
);
225241
write!(
226242
self.agenda,
227243
" - [[{} {}]({})] *{}*",
228-
fcp.disposition,
229-
fcp.number,
230-
fcp.url,
231-
escape(fcp.title)
244+
fcp.fcp.disposition,
245+
fcp.issue.number,
246+
url,
247+
escape(&fcp.issue.title)
232248
)?;
233-
writeln!(self.agenda, " - ({} checkboxes left)", fcp.reviewers.len())?;
234-
if fcp.concerns {
235-
writeln!(self.agenda, " Blocked on an open concern.")?;
236-
}
249+
let needed = fcp.reviews.iter().filter(|review| !review.1).count();
250+
writeln!(self.agenda, " - ({} checkboxes left)", needed)?;
251+
252+
// TODO I think i need to update the RFCBOT api endpoint to export this info
253+
// if fcp.concerns {
254+
// writeln!(self.agenda, " Blocked on an open concern.")?;
255+
// }
237256
}
238257
writeln!(self.agenda, "</details>")?;
239258
}
259+
240260
writeln!(self.agenda, "<p></p>\n")?;
241261

242262
for (i, (&reviewer, &num)) in reviewer_count.iter().enumerate() {
@@ -257,12 +277,7 @@ impl Generator {
257277

258278
fn write_issues(&mut self, issues: &[Issue]) -> Result<()> {
259279
for issue in issues.iter().rev() {
260-
write!(
261-
self.agenda,
262-
" - [[{}]({})]",
263-
issue.number,
264-
issue.html_url,
265-
)?;
280+
write!(self.agenda, " - [[{}]({})]", issue.number, issue.html_url,)?;
266281
for label in issue.labels.iter().filter(|s| s.starts_with("P-")) {
267282
write!(self.agenda, " `{}`", label)?;
268283
}
@@ -332,7 +347,10 @@ impl IssueQuery {
332347
continue;
333348
}
334349

335-
let url_labels = labels.iter().map(|label| format!("label:{}", label)).join("+");
350+
let url_labels = labels
351+
.iter()
352+
.map(|label| format!("label:{}", label))
353+
.join("+");
336354
writeln!(
337355
generator.agenda,
338356
"- [{} `{repo}` `{labels}` items](https://github.com/{repo}/issues?q=is:open+{url_labels})",
@@ -397,8 +415,6 @@ fn github_api<T: DeserializeOwned>(endpoint: &str) -> Result<T> {
397415
client = client.header(AUTHORIZATION, format!("token {}", token));
398416
}
399417
let response = client.send()?;
400-
// dbg!(response.text());
401-
// panic!();
402418
Ok(response.json()?)
403419
}
404420

tools/agenda-generator/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use anyhow::Result;
2-
use generator::Generator;
32
use cli::AgendaKind;
3+
use generator::Generator;
44

55
mod cli;
66
mod generator;

0 commit comments

Comments
 (0)