Skip to content

Commit e50aef6

Browse files
committed
Triagebot learns how to comment on GitHub
In this first version triagebot learns how to post a comment on GitHub to assign priority to an issue marked as regression. The code should allow for any kind of comment to be created.
1 parent add83c3 commit e50aef6

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

Cargo.lock

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ rand = "0.8.5"
4444
ignore = "0.4.18"
4545
postgres-types = { version = "0.2.4", features = ["derive"] }
4646
cron = { version = "0.12.0" }
47+
urlencoding = "2.1.2"
4748

4849
[dependencies.serde]
4950
version = "1"

src/zulip.rs

+109
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ pub struct Request {
2222

2323
#[derive(Debug, serde::Deserialize)]
2424
struct Message {
25+
id: u64,
2526
sender_id: u64,
2627
#[allow(unused)]
2728
recipient_id: u64,
@@ -188,6 +189,15 @@ fn handle_command<'a>(
188189
})
189190
.unwrap(),
190191
},
192+
// @triagebot prio #12345 P-high
193+
Some("prio") => return match add_comment_to_issue(&ctx, message_data, words, CommentType::AssignIssuePriority).await {
194+
Ok(r) => r,
195+
Err(e) => serde_json::to_string(&Response {
196+
content: &format!("Failed to await at this time: {:?}", e),
197+
})
198+
.unwrap(),
199+
},
200+
191201
_ => {}
192202
}
193203
}
@@ -203,6 +213,105 @@ fn handle_command<'a>(
203213
})
204214
}
205215

216+
#[derive(PartialEq)]
217+
enum CommentType {
218+
AssignIssuePriority,
219+
}
220+
221+
// https://docs.zulip.com/api/outgoing-webhooks#outgoing-webhook-format
222+
#[derive(serde::Deserialize, Debug)]
223+
struct ZulipReply {
224+
message: ZulipMessage,
225+
}
226+
227+
#[derive(serde::Deserialize, Debug)]
228+
struct ZulipMessage {
229+
subject: String, // ex.: "[weekly] 2023-04-13"
230+
}
231+
232+
async fn get_zulip_msg(ctx: &Context, id: u64) -> anyhow::Result<ZulipReply> {
233+
let bot_api_token = env::var("ZULIP_API_TOKEN").expect("ZULIP_API_TOKEN");
234+
let zulip_resp = ctx
235+
.github
236+
.raw()
237+
.get(format!(
238+
"https://rust-lang.zulipchat.com/api/v1/messages/{}",
239+
id
240+
))
241+
.basic_auth(BOT_EMAIL, Some(&bot_api_token))
242+
.send()
243+
.await?;
244+
245+
let zulip_msg_data = zulip_resp.json::<ZulipReply>().await.expect("TODO");
246+
log::debug!("Zulip reply {:?}", zulip_msg_data);
247+
Ok(zulip_msg_data)
248+
}
249+
250+
// Add a comment to a Github issue/pr issuing a @rustbot command
251+
async fn add_comment_to_issue(
252+
ctx: &Context,
253+
message: &Message,
254+
mut words: impl Iterator<Item = &str> + std::fmt::Debug,
255+
ty: CommentType,
256+
) -> anyhow::Result<String> {
257+
// retrieve the original Zulip message to build the complete URL
258+
let zulip_msg = get_zulip_msg(ctx, message.id).await?;
259+
260+
// comment example:
261+
// WG-prioritization assigning priority ([Zulip discussion](#)).
262+
// @rustbot label -I-prioritize +P-XXX
263+
let mut issue_id = 0;
264+
let mut comment = String::new();
265+
if ty == CommentType::AssignIssuePriority {
266+
let zulip_stream = "245100-t-compiler/wg-prioritization/alerts";
267+
let mut zulip_msg_link = format!(
268+
"https://rust-lang.zulipchat.com/#narrow/stream/{}/topic/{}/near/{}",
269+
zulip_stream, zulip_msg.message.subject, message.id
270+
);
271+
// Encode url and replace "%" with "."
272+
// (apparently Zulip does that to public URLs)
273+
urlencoding::encode(&zulip_msg_link);
274+
zulip_msg_link = zulip_msg_link.replace("%", ".");
275+
276+
issue_id = words
277+
.next()
278+
.unwrap()
279+
.replace("#", "")
280+
.parse::<u64>()
281+
.unwrap();
282+
let p_label = words.next().unwrap();
283+
284+
comment = format!(
285+
"WG-prioritization assigning priority ([Zulip discussion]({}))
286+
\n\n@rustbot label -I-prioritize +{}",
287+
zulip_msg_link, p_label
288+
);
289+
}
290+
// else ... handle other comment type
291+
292+
let github_resp = ctx
293+
.octocrab
294+
.issues("owner", "repo")
295+
.create_comment(issue_id.clone(), comment.clone())
296+
.await;
297+
298+
let _reply = match github_resp {
299+
Ok(data) => data,
300+
Err(e) => {
301+
return Ok(serde_json::to_string(&Response {
302+
content: &format!("Failed to post comment on Github: {:?}.", e),
303+
})
304+
.unwrap());
305+
}
306+
};
307+
log::debug!("Created comment on issue #{}: {:?}", issue_id, comment);
308+
309+
Ok(serde_json::to_string(&ResponseNotRequired {
310+
response_not_required: true,
311+
})
312+
.unwrap())
313+
}
314+
206315
// This does two things:
207316
// * execute the command for the other user
208317
// * tell the user executed for that a command was run as them by the user

0 commit comments

Comments
 (0)