Skip to content

Commit 3d393ae

Browse files
committed
github: add method to hide own comments in a PR
1 parent 918c381 commit 3d393ae

File tree

1 file changed

+148
-4
lines changed

1 file changed

+148
-4
lines changed

src/github.rs

Lines changed: 148 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use super::Result;
22
use crate::ci::Outcome;
33
use hyper::header;
44
use reqwest;
5+
use serde::{de::DeserializeOwned, Serialize};
56
use std::env;
67
use std::str;
78
use std::time::Duration;
@@ -83,10 +84,6 @@ pub struct CommitParent {
8384
pub sha: String,
8485
}
8586

86-
pub struct Client {
87-
internal: reqwest::Client,
88-
}
89-
9087
#[derive(Serialize)]
9188
struct Comment<'a> {
9289
body: &'a str,
@@ -125,6 +122,29 @@ pub struct Repository {
125122
pub full_name: String,
126123
}
127124

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+
128148
impl Client {
129149
pub fn new() -> Result<Client> {
130150
let token = env::var("GITHUB_TOKEN")
@@ -192,9 +212,133 @@ impl Client {
192212
Ok(())
193213
}
194214

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+
195316
pub fn internal(&self) -> &reqwest::Client {
196317
&self.internal
197318
}
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+
}
198342
}
199343

200344
pub fn verify_webhook_signature(secret: &[u8], signature: Option<&str>, body: &[u8]) -> Result<()> {

0 commit comments

Comments
 (0)