@@ -2,6 +2,7 @@ use super::Result;
2
2
use crate :: ci:: Outcome ;
3
3
use hyper:: header;
4
4
use reqwest;
5
+ use serde:: { de:: DeserializeOwned , Serialize } ;
5
6
use std:: env;
6
7
use std:: str;
7
8
use std:: time:: Duration ;
@@ -83,10 +84,6 @@ pub struct CommitParent {
83
84
pub sha : String ,
84
85
}
85
86
86
- pub struct Client {
87
- internal : reqwest:: Client ,
88
- }
89
-
90
87
#[ derive( Serialize ) ]
91
88
struct Comment < ' a > {
92
89
body : & ' a str ,
@@ -125,6 +122,55 @@ pub struct Repository {
125
122
pub full_name : String ,
126
123
}
127
124
125
+ #[ derive( Deserialize ) ]
126
+ #[ serde( rename_all = "snake_case" ) ]
127
+ pub enum PullRequestAction {
128
+ Opened ,
129
+ Edited ,
130
+ Closed ,
131
+ Assigned ,
132
+ Unassigned ,
133
+ ReviewRequested ,
134
+ ReviewRequestRemoved ,
135
+ ReadyForReview ,
136
+ Labeled ,
137
+ Unlabeled ,
138
+ Synchronize ,
139
+ Locked ,
140
+ Unlocked ,
141
+ Reopened ,
142
+ }
143
+
144
+ #[ derive( Deserialize ) ]
145
+ pub struct PullRequestEvent {
146
+ pub action : PullRequestAction ,
147
+ pub number : u32 ,
148
+ pub repository : Repository ,
149
+ }
150
+
151
+ #[ derive( Deserialize ) ]
152
+ struct GraphResponse < T > {
153
+ data : T ,
154
+ #[ serde( default ) ]
155
+ errors : Vec < GraphError > ,
156
+ }
157
+
158
+ #[ derive( Debug , Deserialize ) ]
159
+ struct GraphError {
160
+ message : String ,
161
+ path : serde_json:: Value ,
162
+ }
163
+
164
+ #[ derive( Debug , Deserialize ) ]
165
+ #[ serde( rename_all = "camelCase" ) ]
166
+ struct GraphPageInfo {
167
+ end_cursor : Option < String > ,
168
+ }
169
+
170
+ pub struct Client {
171
+ internal : reqwest:: Client ,
172
+ }
173
+
128
174
impl Client {
129
175
pub fn new ( ) -> Result < Client > {
130
176
let token = env:: var ( "GITHUB_TOKEN" )
@@ -192,9 +238,132 @@ impl Client {
192
238
Ok ( ( ) )
193
239
}
194
240
241
+ pub fn hide_own_comments ( & self , repo : & str , pull_request_id : u32 ) -> Result < ( ) > {
242
+ const QUERY : & str = "query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
243
+ repository(owner: $owner, name: $repo) {
244
+ pullRequest(number: $pr) {
245
+ comments(first: 100, after: $cursor) {
246
+ nodes {
247
+ id
248
+ isMinimized
249
+ viewerDidAuthor
250
+ }
251
+ pageInfo {
252
+ endCursor
253
+ }
254
+ }
255
+ }
256
+ }
257
+ }" ;
258
+
259
+ #[ derive( Debug , Deserialize ) ]
260
+ struct Response {
261
+ repository : ResponseRepo ,
262
+ }
263
+ #[ derive( Debug , Deserialize ) ]
264
+ #[ serde( rename_all = "camelCase" ) ]
265
+ struct ResponseRepo {
266
+ pull_request : ResponsePR ,
267
+ }
268
+ #[ derive( Debug , Deserialize ) ]
269
+ struct ResponsePR {
270
+ comments : ResponseComments ,
271
+ }
272
+ #[ derive( Debug , Deserialize ) ]
273
+ #[ serde( rename_all = "camelCase" ) ]
274
+ struct ResponseComments {
275
+ nodes : Vec < ResponseComment > ,
276
+ page_info : GraphPageInfo ,
277
+ }
278
+ #[ derive( Debug , Deserialize ) ]
279
+ #[ serde( rename_all = "camelCase" ) ]
280
+ struct ResponseComment {
281
+ id : String ,
282
+ is_minimized : bool ,
283
+ viewer_did_author : bool ,
284
+ }
285
+
286
+ let ( owner, repo) = if let Some ( mid) = repo. find ( '/' ) {
287
+ let split = repo. split_at ( mid) ;
288
+ ( split. 0 , split. 1 . trim_start_matches ( '/' ) )
289
+ } else {
290
+ bail ! ( "invalid repository name: {}" , repo) ;
291
+ } ;
292
+
293
+ let mut comments = Vec :: new ( ) ;
294
+ let mut cursor = None ;
295
+ loop {
296
+ let mut resp: Response = self . graphql (
297
+ QUERY ,
298
+ serde_json:: json!( {
299
+ "owner" : owner,
300
+ "repo" : repo,
301
+ "pr" : pull_request_id,
302
+ "cursor" : cursor,
303
+ } ) ,
304
+ ) ?;
305
+ cursor = resp. repository . pull_request . comments . page_info . end_cursor ;
306
+ comments. append ( & mut resp. repository . pull_request . comments . nodes ) ;
307
+
308
+ if cursor. is_none ( ) {
309
+ break ;
310
+ }
311
+ }
312
+
313
+ for comment in & comments {
314
+ if comment. viewer_did_author && !comment. is_minimized {
315
+ self . hide_comment ( & comment. id , "OUTDATED" ) ?;
316
+ }
317
+ }
318
+ Ok ( ( ) )
319
+ }
320
+
321
+ fn hide_comment ( & self , node_id : & str , reason : & str ) -> Result < ( ) > {
322
+ #[ derive( Deserialize ) ]
323
+ struct MinimizeData { }
324
+
325
+ const MINIMIZE : & str = "mutation($node_id: ID!, $reason: ReportedContentClassifiers!) {
326
+ minimizeComment(input: {subjectId: $node_id, classifier: $reason}) {
327
+ __typename
328
+ }
329
+ }" ;
330
+
331
+ self . graphql :: < MinimizeData , _ > (
332
+ MINIMIZE ,
333
+ serde_json:: json!( {
334
+ "node_id" : node_id,
335
+ "reason" : reason,
336
+ } ) ,
337
+ ) ?;
338
+ Ok ( ( ) )
339
+ }
340
+
195
341
pub fn internal ( & self ) -> & reqwest:: Client {
196
342
& self . internal
197
343
}
344
+
345
+ fn graphql < T : DeserializeOwned , V : Serialize > ( & self , query : & str , variables : V ) -> Result < T > {
346
+ #[ derive( Serialize ) ]
347
+ struct GraphPayload < ' a , V > {
348
+ query : & ' a str ,
349
+ variables : V ,
350
+ }
351
+
352
+ let response: GraphResponse < T > = self
353
+ . internal
354
+ . post ( & format ! ( "{}/graphql" , API_BASE ) )
355
+ . json ( & GraphPayload { query, variables } )
356
+ . send ( ) ?
357
+ . error_for_status ( ) ?
358
+ . json ( ) ?;
359
+
360
+ if response. errors . is_empty ( ) {
361
+ Ok ( response. data )
362
+ } else {
363
+ dbg ! ( & response. errors) ;
364
+ bail ! ( "GraphQL query failed: {}" , response. errors[ 0 ] . message) ;
365
+ }
366
+ }
198
367
}
199
368
200
369
pub fn verify_webhook_signature ( secret : & [ u8 ] , signature : Option < & str > , body : & [ u8 ] ) -> Result < ( ) > {
0 commit comments