@@ -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,29 @@ pub struct Repository {
125
122
pub full_name : String ,
126
123
}
127
124
125
+ #[ derive( Deserialize ) ]
126
+ struct GraphResponse < T > {
127
+ data : T ,
128
+ #[ serde( default ) ]
129
+ errors : Vec < GraphError > ,
130
+ }
131
+
132
+ #[ derive( Debug , Deserialize ) ]
133
+ struct GraphError {
134
+ message : String ,
135
+ path : serde_json:: Value ,
136
+ }
137
+
138
+ #[ derive( Debug , Deserialize ) ]
139
+ #[ serde( rename_all = "camelCase" ) ]
140
+ struct GraphPageInfo {
141
+ end_cursor : Option < String > ,
142
+ }
143
+
144
+ pub struct Client {
145
+ internal : reqwest:: Client ,
146
+ }
147
+
128
148
impl Client {
129
149
pub fn new ( ) -> Result < Client > {
130
150
let token = env:: var ( "GITHUB_TOKEN" )
@@ -192,9 +212,133 @@ impl Client {
192
212
Ok ( ( ) )
193
213
}
194
214
215
+ pub fn hide_own_comments ( & self , repo : & str , pull_request_id : u32 ) -> Result < ( ) > {
216
+ const QUERY : & str =
217
+ "query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
218
+ repository(owner: $owner, name: $repo) {
219
+ pullRequest(number: $pr) {
220
+ comments(first: 100, after: $cursor) {
221
+ nodes {
222
+ id
223
+ isMinimized
224
+ viewerDidAuthor
225
+ }
226
+ pageInfo {
227
+ endCursor
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }" ;
233
+
234
+ #[ derive( Debug , Deserialize ) ]
235
+ struct Response {
236
+ repository : ResponseRepo ,
237
+ }
238
+ #[ derive( Debug , Deserialize ) ]
239
+ #[ serde( rename_all = "camelCase" ) ]
240
+ struct ResponseRepo {
241
+ pull_request : ResponsePR ,
242
+ }
243
+ #[ derive( Debug , Deserialize ) ]
244
+ struct ResponsePR {
245
+ comments : ResponseComments ,
246
+ }
247
+ #[ derive( Debug , Deserialize ) ]
248
+ #[ serde( rename_all = "camelCase" ) ]
249
+ struct ResponseComments {
250
+ nodes : Vec < ResponseComment > ,
251
+ page_info : GraphPageInfo ,
252
+ }
253
+ #[ derive( Debug , Deserialize ) ]
254
+ #[ serde( rename_all = "camelCase" ) ]
255
+ struct ResponseComment {
256
+ id : String ,
257
+ is_minimized : bool ,
258
+ viewer_did_author : bool ,
259
+ }
260
+
261
+ let ( owner, repo) = if let Some ( mid) = repo. find ( '/' ) {
262
+ let split = repo. split_at ( mid) ;
263
+ ( split. 0 , split. 1 . trim_start_matches ( '/' ) )
264
+ } else {
265
+ bail ! ( "invalid repository name: {}" , repo) ;
266
+ } ;
267
+
268
+ let mut comments = Vec :: new ( ) ;
269
+ let mut cursor = None ;
270
+ loop {
271
+ let mut resp: Response = self . graphql (
272
+ QUERY ,
273
+ serde_json:: json!( {
274
+ "owner" : owner,
275
+ "repo" : repo,
276
+ "pr" : pull_request_id,
277
+ "cursor" : cursor,
278
+ } ) ,
279
+ ) ?;
280
+ cursor = resp. repository . pull_request . comments . page_info . end_cursor ;
281
+ comments. append ( & mut resp. repository . pull_request . comments . nodes ) ;
282
+
283
+ if cursor. is_none ( ) {
284
+ break ;
285
+ }
286
+ }
287
+
288
+ for comment in & comments {
289
+ if comment. viewer_did_author && !comment. is_minimized {
290
+ self . hide_comment ( & comment. id , "OUTDATED" ) ?;
291
+ }
292
+ }
293
+ Ok ( ( ) )
294
+ }
295
+
296
+ fn hide_comment ( & self , node_id : & str , reason : & str ) -> Result < ( ) > {
297
+ #[ derive( Deserialize ) ]
298
+ struct MinimizeData { }
299
+
300
+ const MINIMIZE : & str = "mutation($node_id: ID!, $reason: ReportedContentClassifiers!) {
301
+ minimizeComment(input: {subjectId: $node_id, classifier: $reason}) {
302
+ __typename
303
+ }
304
+ }" ;
305
+
306
+ self . graphql :: < MinimizeData , _ > (
307
+ MINIMIZE ,
308
+ serde_json:: json!( {
309
+ "node_id" : node_id,
310
+ "reason" : reason,
311
+ } ) ,
312
+ ) ?;
313
+ Ok ( ( ) )
314
+ }
315
+
195
316
pub fn internal ( & self ) -> & reqwest:: Client {
196
317
& self . internal
197
318
}
319
+
320
+ fn graphql < T : DeserializeOwned , V : Serialize > ( & self , query : & str , variables : V ) -> Result < T > {
321
+ #[ derive( Serialize ) ]
322
+ struct GraphPayload < ' a , V > {
323
+ query : & ' a str ,
324
+ variables : V ,
325
+ }
326
+
327
+ let response: GraphResponse < T > = self
328
+ . internal
329
+ . post ( & format ! ( "{}/graphql" , API_BASE ) )
330
+ . json ( & GraphPayload { query, variables } )
331
+ . send ( ) ?
332
+ . error_for_status ( ) ?
333
+ . json ( ) ?;
334
+
335
+ if response. errors . is_empty ( ) {
336
+ Ok ( response. data )
337
+ } else {
338
+ dbg ! ( & response. errors) ;
339
+ bail ! ( "GraphQL query failed: {}" , response. errors[ 0 ] . message) ;
340
+ }
341
+ }
198
342
}
199
343
200
344
pub fn verify_webhook_signature ( secret : & [ u8 ] , signature : Option < & str > , body : & [ u8 ] ) -> Result < ( ) > {
0 commit comments