1- use anyhow:: { anyhow, Result } ;
1+ use anyhow:: Result ;
2+ use chrono:: { Duration , 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,123 @@ 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 ,
213194 }
214195
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 ,
205+ }
206+
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+ let waiting_on_author = "S-waiting-on-author" . to_string ( ) ;
211+ fcps. retain ( |fcp| !fcp. issue . labels . contains ( & waiting_on_author) ) ;
212+ let now = chrono:: Utc :: now ( ) . naive_utc ( ) ;
213+ fcps. retain ( |fcp| {
214+ let created = fcp. status_comment . created_at ;
215+ let updated = fcp. status_comment . updated_at ;
216+ ( now - created) > Duration :: weeks ( 4 ) && ( now - updated) > Duration :: days ( 5 )
217+ } ) ;
218+
219+ let reviewer_count = fcps
220+ . iter ( )
221+ . flat_map ( |fcp| fcp. reviews . iter ( ) )
222+ . filter ( |review| !review. 1 )
223+ . map ( |review| & review. 0 . login )
224+ . counts ( ) ;
225+
226+ let repos = fcps
227+ . iter ( )
228+ . map ( |fcp| fcp. issue . repository . as_str ( ) )
229+ . collect :: < BTreeSet < _ > > ( ) ;
230+
215231 writeln ! ( self . agenda, "### FCPs" ) ?;
216232 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 ( ) {
233+ writeln ! ( self . agenda, "{} open T-libs-api FCPs:" , fcps. len( ) ) ?;
234+
235+ for repo in repos {
236+ let fcps = fcps
237+ . iter ( )
238+ . filter ( |fcp| fcp. issue . repository == repo)
239+ . collect :: < Vec < _ > > ( ) ;
240+
223241 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) ?;
242+
224243 for fcp in fcps {
244+ let url = format ! (
245+ "https://github.com/{}/issues/{}#issuecomment-{}" ,
246+ fcp. issue. repository, fcp. issue. number, fcp. status_comment. id
247+ ) ;
225248 write ! (
226249 self . agenda,
227250 " - [[{} {}]({})] *{}*" ,
228- fcp. disposition,
229- fcp. number,
230- fcp . url,
231- escape( fcp. title)
251+ fcp. fcp . disposition,
252+ fcp. issue . number,
253+ url,
254+ escape( & fcp. issue . title)
232255 ) ?;
233- writeln ! ( self . agenda, " - ({} checkboxes left)" , fcp. reviewers. len( ) ) ?;
234- if fcp. concerns {
235- writeln ! ( self . agenda, " Blocked on an open concern." ) ?;
236- }
256+ let needed = fcp. reviews . iter ( ) . filter ( |review| !review. 1 ) . count ( ) ;
257+ writeln ! ( self . agenda, " - ({} checkboxes left)" , needed) ?;
258+
259+ // TODO I think i need to update the RFCBOT api endpoint to export this info
260+ // if fcp.concerns {
261+ // writeln!(self.agenda, " Blocked on an open concern.")?;
262+ // }
237263 }
238264 writeln ! ( self . agenda, "</details>" ) ?;
239265 }
266+
240267 writeln ! ( self . agenda, "<p></p>\n " ) ?;
241268
242269 for ( i, ( & reviewer, & num) ) in reviewer_count. iter ( ) . enumerate ( ) {
@@ -257,12 +284,7 @@ impl Generator {
257284
258285 fn write_issues ( & mut self , issues : & [ Issue ] ) -> Result < ( ) > {
259286 for issue in issues. iter ( ) . rev ( ) {
260- write ! (
261- self . agenda,
262- " - [[{}]({})]" ,
263- issue. number,
264- issue. html_url,
265- ) ?;
287+ write ! ( self . agenda, " - [[{}]({})]" , issue. number, issue. html_url, ) ?;
266288 for label in issue. labels . iter ( ) . filter ( |s| s. starts_with ( "P-" ) ) {
267289 write ! ( self . agenda, " `{}`" , label) ?;
268290 }
@@ -332,7 +354,10 @@ impl IssueQuery {
332354 continue ;
333355 }
334356
335- let url_labels = labels. iter ( ) . map ( |label| format ! ( "label:{}" , label) ) . join ( "+" ) ;
357+ let url_labels = labels
358+ . iter ( )
359+ . map ( |label| format ! ( "label:{}" , label) )
360+ . join ( "+" ) ;
336361 writeln ! (
337362 generator. agenda,
338363 "- [{} `{repo}` `{labels}` items](https://github.com/{repo}/issues?q=is:open+{url_labels})" ,
@@ -397,8 +422,6 @@ fn github_api<T: DeserializeOwned>(endpoint: &str) -> Result<T> {
397422 client = client. header ( AUTHORIZATION , format ! ( "token {}" , token) ) ;
398423 }
399424 let response = client. send ( ) ?;
400- // dbg!(response.text());
401- // panic!();
402425 Ok ( response. json ( ) ?)
403426}
404427
0 commit comments