1- use anyhow:: { anyhow, Result } ;
1+ use anyhow:: Result ;
2+ use chrono:: NaiveDateTime ;
3+ use itertools:: Itertools ;
24use reqwest:: header:: { AUTHORIZATION , USER_AGENT } ;
35use serde:: de:: { DeserializeOwned , Deserializer } ;
46use serde:: Deserialize ;
5- use std:: collections:: { BTreeMap , BTreeSet } ;
7+ use std:: collections:: BTreeSet ;
68use std:: fmt:: Write ;
7- use itertools:: Itertools ;
89
910#[ derive( Default ) ]
1011pub 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
0 commit comments