forked from rust-lang/triagebot
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathuser_comments_in_org.rs
More file actions
150 lines (138 loc) · 5.37 KB
/
user_comments_in_org.rs
File metadata and controls
150 lines (138 loc) · 5.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
use anyhow::Context;
use chrono::{DateTime, Utc};
use crate::github::GithubClient;
/// A comment made by a user on an issue or PR.
#[derive(Debug, Clone)]
pub struct UserComment {
pub repo_owner: String,
pub repo_name: String,
pub issue_number: u64,
pub issue_title: String,
pub issue_url: String,
pub comment_url: String,
pub body: String,
pub created_at: Option<DateTime<Utc>>,
}
impl GithubClient {
/// Fetches recent comments made by a user in a GitHub organization.
///
/// Returns up to `limit` comments, sorted by creation date (most recent first).
/// Each comment includes the URL, body snippet, and the issue/PR title it was made on.
pub async fn recent_user_comments_in_org(
&self,
username: &str,
org: &str,
limit: usize,
) -> anyhow::Result<Vec<UserComment>> {
// GitHub's GraphQL API doesn't seem to support filtering comments by author directly.
// We can use two endpoints here - either use the search query and filter by commented and
// organization, or use the user query and access its issueComments connection.
// The user endpoint would be more efficient, in theory. However, if the user makes a lot of
// comments in different organizations, we might load a lot of data before we get to their
// comments in the given organization. So instead we use the search endpoint.
// The endpoint loads issues (and PRs), not comments.
// The `commenter:` filter guarantees each returned issue has at least one comment
// from the user. So we fetch `limit` issues and a small number of recent comments
// per issue, then filter to only the user's comments.
let search_query = format!("commenter:{username} org:{org} sort:updated-desc");
let issues_to_fetch = limit;
let comments_per_issue = 100;
let data = self
.graphql_query(
r#"
query($query: String!, $issueLimit: Int!, $commentLimit: Int!) {
search(query: $query, type: ISSUE, first: $issueLimit) {
nodes {
... on Issue {
number
url
title
repository {
name
owner { login }
}
comments(first: $commentLimit, orderBy: {field: UPDATED_AT, direction: DESC}) {
nodes {
author { login }
body
url
createdAt
}
}
}
... on PullRequest {
number
url
title
repository {
name
owner { login }
}
comments(first: $commentLimit, orderBy: {field: UPDATED_AT, direction: DESC}) {
nodes {
author { login }
body
url
createdAt
}
}
}
}
}
}
"#,
serde_json::json!({
"query": search_query,
"issueLimit": issues_to_fetch,
"commentLimit": comments_per_issue,
}),
)
.await
.context("failed to search for user comments")?;
let mut all_comments: Vec<UserComment> = Vec::new();
if let Some(nodes) = data["data"]["search"]["nodes"].as_array() {
for node in nodes {
let repo_owner = node["repository"]["owner"]["login"]
.as_str()
.unwrap_or("")
.to_string();
let repo_name = node["repository"]["name"]
.as_str()
.unwrap_or("")
.to_string();
let issue_number = node["number"].as_u64().unwrap_or(0);
let issue_title = node["title"].as_str().unwrap_or("Unknown");
let issue_url = node["url"].as_str().unwrap_or("");
if let Some(comments) = node["comments"]["nodes"].as_array() {
for comment in comments {
// Filter to only comments by the target user
let author = comment["author"]["login"].as_str().unwrap_or("");
if !author.eq_ignore_ascii_case(username) {
continue;
}
let body = comment["body"].as_str().unwrap_or("");
let url = comment["url"].as_str().unwrap_or("");
let created_at = comment["createdAt"]
.as_str()
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map(|dt| dt.with_timezone(&chrono::Utc));
all_comments.push(UserComment {
repo_owner: repo_owner.clone(),
repo_name: repo_name.clone(),
issue_number,
issue_title: issue_title.to_string(),
issue_url: issue_url.to_string(),
comment_url: url.to_string(),
body: body.to_string(),
created_at,
});
}
}
}
}
// Sort by creation date (most recent first) and take the limit
all_comments.sort_by(|a, b| b.created_at.cmp(&a.created_at));
all_comments.truncate(limit);
Ok(all_comments)
}
}