Skip to content

Commit bff7ad6

Browse files
authored
Merge pull request #1490 from rust-lang/github-hide-comments
Hide old `rust-timer` comments when a summary is posted
2 parents 98e218f + 8966336 commit bff7ad6

File tree

4 files changed

+199
-18
lines changed

4 files changed

+199
-18
lines changed

site/src/github.rs

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ use serde::Deserialize;
88

99
type BoxedError = Box<dyn std::error::Error + Send + Sync>;
1010

11+
pub const RUST_REPO_GITHUB_API_URL: &str = "https://api.github.com/repos/rust-lang/rust";
12+
pub const RUST_REPO_GITHUB_GRAPH_URL: &str = "https://api.github.com/graphql";
13+
14+
/// Comments that are temporary and do not add any value once there has been a new development
15+
/// (a rustc build or a perf. run was finished) are marked with this comment.
16+
///
17+
/// They are removed once a perf. run comparison summary is posted on a PR.
18+
pub const COMMENT_MARK_TEMPORARY: &str = "<!-- rust-timer: temporary -->";
19+
1120
pub use comparison_summary::post_finished;
1221

1322
/// Enqueues try build artifacts and posts a message about them on the original rollup PR
@@ -234,6 +243,7 @@ pub async fn enqueue_shas(
234243
));
235244
}
236245
}
246+
msg.push_str(&format!("\n{COMMENT_MARK_TEMPORARY}"));
237247
main_client.post_comment(pr_number, msg).await;
238248
Ok(())
239249
}

site/src/github/client.rs

+147
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anyhow::Context;
22
use chrono::{DateTime, Utc};
33
use http::header::USER_AGENT;
4+
use serde::de::DeserializeOwned;
45

56
use crate::{api::github::Issue, load::SiteCtxt};
67

@@ -226,6 +227,124 @@ impl Client {
226227
}
227228
}
228229

230+
pub async fn get_comments(&self, pull_request: u32) -> anyhow::Result<Vec<ResponseComment>> {
231+
const QUERY: &str = "query($owner: String!, $repo: String!, $pr: Int!, $cursor: String) {
232+
repository(owner: $owner, name: $repo) {
233+
pullRequest(number: $pr) {
234+
comments(first: 100, after: $cursor) {
235+
nodes {
236+
id
237+
body
238+
isMinimized
239+
viewerDidAuthor
240+
}
241+
pageInfo {
242+
endCursor
243+
}
244+
}
245+
}
246+
}
247+
}";
248+
249+
#[derive(Debug, serde::Deserialize)]
250+
struct Response {
251+
repository: ResponseRepo,
252+
}
253+
#[derive(Debug, serde::Deserialize)]
254+
#[serde(rename_all = "camelCase")]
255+
struct ResponseRepo {
256+
pull_request: ResponsePR,
257+
}
258+
#[derive(Debug, serde::Deserialize)]
259+
struct ResponsePR {
260+
comments: ResponseComments,
261+
}
262+
#[derive(Debug, serde::Deserialize)]
263+
#[serde(rename_all = "camelCase")]
264+
struct ResponseComments {
265+
nodes: Vec<ResponseComment>,
266+
page_info: GraphPageInfo,
267+
}
268+
269+
let owner = "rust-lang";
270+
let repo = "rust";
271+
272+
let mut comments = Vec::new();
273+
let mut cursor = None;
274+
loop {
275+
let mut resp: Response = self
276+
.graphql(
277+
QUERY,
278+
serde_json::json!({
279+
"owner": owner,
280+
"repo": repo,
281+
"pr": pull_request,
282+
"cursor": cursor,
283+
}),
284+
)
285+
.await?;
286+
cursor = resp.repository.pull_request.comments.page_info.end_cursor;
287+
comments.append(&mut resp.repository.pull_request.comments.nodes);
288+
289+
if cursor.is_none() {
290+
break;
291+
}
292+
}
293+
Ok(comments)
294+
}
295+
296+
pub async fn hide_comment(&self, comment_id: &str, reason: &str) -> anyhow::Result<()> {
297+
#[derive(serde::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::<Option<MinimizeData>, _>(
307+
MINIMIZE,
308+
serde_json::json!({
309+
"node_id": comment_id,
310+
"reason": reason,
311+
}),
312+
)
313+
.await?;
314+
Ok(())
315+
}
316+
317+
async fn graphql<T: DeserializeOwned, V: serde::Serialize>(
318+
&self,
319+
query: &str,
320+
variables: V,
321+
) -> anyhow::Result<T> {
322+
#[derive(serde::Serialize)]
323+
struct GraphPayload<'a, V> {
324+
query: &'a str,
325+
variables: V,
326+
}
327+
328+
let response: GraphResponse<T> = self
329+
.inner
330+
.post(&self.repository_url)
331+
.json(&GraphPayload { query, variables })
332+
.send()
333+
.await?
334+
.error_for_status()?
335+
.json()
336+
.await?;
337+
338+
if response.errors.is_empty() {
339+
Ok(response.data)
340+
} else {
341+
Err(anyhow::anyhow!(
342+
"GraphQL query failed: {}",
343+
response.errors[0].message
344+
))
345+
}
346+
}
347+
229348
async fn send(
230349
&self,
231350
request: reqwest::RequestBuilder,
@@ -238,6 +357,34 @@ impl Client {
238357
}
239358
}
240359

360+
#[derive(serde::Deserialize)]
361+
struct GraphResponse<T> {
362+
data: T,
363+
#[serde(default)]
364+
errors: Vec<GraphError>,
365+
}
366+
367+
#[derive(Debug, serde::Deserialize)]
368+
struct GraphError {
369+
message: String,
370+
}
371+
372+
#[derive(Debug, serde::Deserialize)]
373+
#[serde(rename_all = "camelCase")]
374+
struct GraphPageInfo {
375+
end_cursor: Option<String>,
376+
}
377+
378+
#[derive(Debug, serde::Deserialize)]
379+
#[serde(rename_all = "camelCase")]
380+
pub struct ResponseComment {
381+
pub id: String,
382+
pub body: String,
383+
pub is_minimized: bool,
384+
// Is the account that fetches this comment also the original author of the comment?
385+
pub viewer_did_author: bool,
386+
}
387+
241388
#[derive(Debug, serde::Deserialize)]
242389
pub struct CreatePrResponse {
243390
pub number: u32,

site/src/github/comparison_summary.rs

+26-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::load::SiteCtxt;
66

77
use database::{ArtifactId, QueuedCommit};
88

9+
use crate::github::{COMMENT_MARK_TEMPORARY, RUST_REPO_GITHUB_API_URL, RUST_REPO_GITHUB_GRAPH_URL};
910
use std::collections::HashSet;
1011
use std::fmt::Write;
1112

@@ -57,26 +58,45 @@ pub async fn post_finished(ctxt: &SiteCtxt) {
5758
assert_eq!(completed, queued_commit);
5859

5960
let is_master_commit = master_commits.contains(&queued_commit.sha);
60-
post_comparison_comment(ctxt, queued_commit, is_master_commit).await;
61+
if let Err(error) = post_comparison_comment(ctxt, queued_commit, is_master_commit).await
62+
{
63+
log::error!("Cannot post comparison comment: {error:?}");
64+
}
6165
}
6266
}
6367
}
6468

6569
/// Posts a comment to GitHub summarizing the comparison of the queued commit with its parent
6670
///
6771
/// `is_master_commit` is used to differentiate messages for try runs and post-merge runs.
68-
async fn post_comparison_comment(ctxt: &SiteCtxt, commit: QueuedCommit, is_master_commit: bool) {
69-
let client = super::client::Client::from_ctxt(
70-
ctxt,
71-
"https://api.github.com/repos/rust-lang/rust".to_owned(),
72-
);
72+
async fn post_comparison_comment(
73+
ctxt: &SiteCtxt,
74+
commit: QueuedCommit,
75+
is_master_commit: bool,
76+
) -> anyhow::Result<()> {
77+
let client = super::client::Client::from_ctxt(ctxt, RUST_REPO_GITHUB_API_URL.to_owned());
7378
let pr = commit.pr;
7479
let body = match summarize_run(ctxt, commit, is_master_commit).await {
7580
Ok(message) => message,
7681
Err(error) => error,
7782
};
7883

7984
client.post_comment(pr, body).await;
85+
86+
let graph_client =
87+
super::client::Client::from_ctxt(ctxt, RUST_REPO_GITHUB_GRAPH_URL.to_owned());
88+
for comment in graph_client.get_comments(pr).await? {
89+
// If this bot is the author of the comment, the comment is not yet minimized and it is
90+
// a temporary comment, minimize it.
91+
if comment.viewer_did_author
92+
&& !comment.is_minimized
93+
&& comment.body.contains(COMMENT_MARK_TEMPORARY)
94+
{
95+
log::debug!("Hiding comment {}", comment.id);
96+
graph_client.hide_comment(&comment.id, "OUTDATED").await?;
97+
}
98+
}
99+
Ok(())
80100
}
81101

82102
fn make_comparison_url(commit: &QueuedCommit, stat: Metric) -> String {

site/src/request_handlers/github.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use crate::api::{github, ServerResult};
2-
use crate::github::{client, enqueue_shas, parse_homu_comment, rollup_pr_number, unroll_rollup};
2+
use crate::github::{
3+
client, enqueue_shas, parse_homu_comment, rollup_pr_number, unroll_rollup,
4+
COMMENT_MARK_TEMPORARY, RUST_REPO_GITHUB_API_URL,
5+
};
36
use crate::load::SiteCtxt;
47

58
use std::sync::Arc;
@@ -29,10 +32,7 @@ async fn handle_push(ctxt: Arc<SiteCtxt>, push: github::Push) -> ServerResult<gi
2932
&ctxt,
3033
"https://api.github.com/repos/rust-lang-ci/rust".to_owned(),
3134
);
32-
let main_repo_client = client::Client::from_ctxt(
33-
&ctxt,
34-
"https://api.github.com/repos/rust-lang/rust".to_owned(),
35-
);
35+
let main_repo_client = client::Client::from_ctxt(&ctxt, RUST_REPO_GITHUB_API_URL.to_owned());
3636
if push.r#ref != "refs/heads/master" || push.sender.login != "bors" {
3737
return Ok(github::Response);
3838
}
@@ -70,10 +70,7 @@ async fn handle_issue(
7070
issue: github::Issue,
7171
comment: github::Comment,
7272
) -> ServerResult<github::Response> {
73-
let main_client = client::Client::from_ctxt(
74-
&ctxt,
75-
"https://api.github.com/repos/rust-lang/rust".to_owned(),
76-
);
73+
let main_client = client::Client::from_ctxt(&ctxt, RUST_REPO_GITHUB_API_URL.to_owned());
7774
let ci_client = client::Client::from_ctxt(
7875
&ctxt,
7976
"https://api.github.com/repos/rust-lang-ci/rust".to_owned(),
@@ -112,7 +109,10 @@ async fn handle_rust_timer(
112109
main_client
113110
.post_comment(
114111
issue.number,
115-
"Insufficient permissions to issue commands to rust-timer.",
112+
format!(
113+
"Insufficient permissions to issue commands to rust-timer.
114+
{COMMENT_MARK_TEMPORARY}"
115+
),
116116
)
117117
.await;
118118
return Ok(github::Response);
@@ -129,9 +129,13 @@ async fn handle_rust_timer(
129129
main_client
130130
.post_comment(
131131
issue.number,
132-
"Awaiting bors try build completion.
132+
format!(
133+
"Awaiting bors try build completion.
134+
135+
@rustbot label: +S-waiting-on-perf
133136
134-
@rustbot label: +S-waiting-on-perf",
137+
{COMMENT_MARK_TEMPORARY}"
138+
),
135139
)
136140
.await;
137141
return Ok(github::Response);

0 commit comments

Comments
 (0)