1
- use anyhow:: { anyhow, Result } ;
1
+ use anyhow:: Result ;
2
+ use chrono:: NaiveDateTime ;
3
+ use itertools:: Itertools ;
2
4
use reqwest:: header:: { AUTHORIZATION , USER_AGENT } ;
3
5
use serde:: de:: { DeserializeOwned , Deserializer } ;
4
6
use serde:: Deserialize ;
5
- use std:: collections:: { BTreeMap , BTreeSet } ;
7
+ use std:: collections:: BTreeSet ;
6
8
use std:: fmt:: Write ;
7
- use itertools:: Itertools ;
8
9
9
10
#[ derive( Default ) ]
10
11
pub struct Generator {
@@ -33,7 +34,7 @@ impl Generator {
33
34
chrono:: Utc :: now( ) . format( "%Y-%m-%d" )
34
35
) ?;
35
36
36
- self . fcps ( ) ?;
37
+ self . fcps ( String :: from ( "T-libs-api" ) ) ?;
37
38
38
39
IssueQuery :: new ( "Nominated" )
39
40
. labels ( & [ "T-libs-api" , "I-nominated" ] )
@@ -86,6 +87,8 @@ impl Generator {
86
87
chrono:: Utc :: now( ) . format( "%Y-%m-%d" )
87
88
) ?;
88
89
90
+ self . fcps ( String :: from ( "T-libs" ) ) ?;
91
+
89
92
IssueQuery :: new ( "Critical" )
90
93
. labels ( & [ "T-libs" , "P-critical" ] )
91
94
. labels ( & [ "T-libs-api" , "P-critical" ] )
@@ -144,99 +147,116 @@ impl Generator {
144
147
Ok ( self . agenda )
145
148
}
146
149
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 ,
213
205
}
214
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
+
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
+
215
224
writeln ! ( self . agenda, "### FCPs" ) ?;
216
225
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
+
223
234
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
+
224
236
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
+ ) ;
225
241
write ! (
226
242
self . agenda,
227
243
" - [[{} {}]({})] *{}*" ,
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)
232
248
) ?;
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
+ // }
237
256
}
238
257
writeln ! ( self . agenda, "</details>" ) ?;
239
258
}
259
+
240
260
writeln ! ( self . agenda, "<p></p>\n " ) ?;
241
261
242
262
for ( i, ( & reviewer, & num) ) in reviewer_count. iter ( ) . enumerate ( ) {
@@ -257,12 +277,7 @@ impl Generator {
257
277
258
278
fn write_issues ( & mut self , issues : & [ Issue ] ) -> Result < ( ) > {
259
279
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, ) ?;
266
281
for label in issue. labels . iter ( ) . filter ( |s| s. starts_with ( "P-" ) ) {
267
282
write ! ( self . agenda, " `{}`" , label) ?;
268
283
}
@@ -332,7 +347,10 @@ impl IssueQuery {
332
347
continue ;
333
348
}
334
349
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 ( "+" ) ;
336
354
writeln ! (
337
355
generator. agenda,
338
356
"- [{} `{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> {
397
415
client = client. header ( AUTHORIZATION , format ! ( "token {}" , token) ) ;
398
416
}
399
417
let response = client. send ( ) ?;
400
- // dbg!(response.text());
401
- // panic!();
402
418
Ok ( response. json ( ) ?)
403
419
}
404
420
0 commit comments