Skip to content

Commit

Permalink
Triagebot learns how to comment on GitHub
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
apiraino committed Apr 24, 2023
1 parent add83c3 commit 51013c9
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 8 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ rand = "0.8.5"
ignore = "0.4.18"
postgres-types = { version = "0.2.4", features = ["derive"] }
cron = { version = "0.12.0" }
urlencoding = "2.1.2"

[dependencies.serde]
version = "1"
Expand Down
151 changes: 143 additions & 8 deletions src/zulip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct Request {

#[derive(Debug, serde::Deserialize)]
struct Message {
id: u64,
sender_id: u64,
#[allow(unused)]
recipient_id: u64,
Expand All @@ -45,7 +46,8 @@ struct ResponseOwned {
content: String,
}

pub const BOT_EMAIL: &str = "[email protected]";
const BOT_EMAIL: &str = "[email protected]";
const ZULIP_HOST: &str = "https://rust-lang.zulipchat.com";

pub async fn to_github_id(client: &GithubClient, zulip_id: usize) -> anyhow::Result<Option<i64>> {
let map = crate::team_data::zulip_map(client).await?;
Expand Down Expand Up @@ -188,6 +190,15 @@ fn handle_command<'a>(
})
.unwrap(),
},
// @triagebot prio #12345 P-high
Some("prio") => return match add_comment_to_issue(&ctx, message_data, words, CommentType::AssignIssuePriority).await {
Ok(r) => r,
Err(e) => serde_json::to_string(&Response {
content: &format!("Failed to await at this time: {:?}", e),
})
.unwrap(),
},

_ => {}
}
}
Expand All @@ -203,6 +214,130 @@ fn handle_command<'a>(
})
}

#[derive(PartialEq)]
enum CommentType {
AssignIssuePriority,
}

// https://docs.zulip.com/api/outgoing-webhooks#outgoing-webhook-format
#[derive(serde::Deserialize, Debug)]
struct ZulipReply {
messages: Vec<ZulipMessage>,
}

#[derive(serde::Deserialize, Debug)]
struct ZulipMessage {
subject: String, // ex.: "[weekly] 2023-04-13"
stream_id: u32,
display_recipient: String, // ex. "t-compiler/major changes"
}

async fn get_zulip_msg(ctx: &Context, msg_id: Option<u64>) -> anyhow::Result<ZulipReply> {
let bot_api_token = env::var("ZULIP_API_TOKEN").expect("ZULIP_API_TOKEN");
let zulip_user = env::var("ZULIP_USER").expect("ZULIP_USER");

let mut url = format!("{}/api/v1/messages?apply_markdown=false", ZULIP_HOST);

// TODO: Either pick a specific message of a Zulip topic or the first one
if msg_id.is_some() {
url = format!(
"{}&num_before=0&num_after=0&anchor={}",
url,
msg_id.unwrap()
)
} else {
url = format!("{}&num_before=1&num_after=1&anchor=oldest", url)
}

let zulip_resp = ctx
.github
.raw()
.get(url)
.basic_auth(zulip_user, Some(&bot_api_token))
.send()
.await?;

let zulip_msg_data = zulip_resp.json::<ZulipReply>().await?;
log::debug!("Zulip reply {:?}", zulip_msg_data);
Ok(zulip_msg_data)
}

// Add a comment to a Github issue/pr and issue a @rustbot command
async fn add_comment_to_issue(
ctx: &Context,
message: &Message,
mut words: impl Iterator<Item = &str> + std::fmt::Debug,
ty: CommentType,
) -> anyhow::Result<String> {
// retrieve the original Zulip topic and rebuild the complete URL to it
let zulip_msg = get_zulip_msg(ctx, None).await?;

if zulip_msg.messages.is_empty() {
return Ok(serde_json::to_string(&Response {
content: &format!("Failed creating comment on Github: could not retrieve Zulip topic"),
})
.unwrap());
}

// comment example:
// WG-prioritization assigning priority ([Zulip discussion](#)).
// @rustbot label -I-prioritize +P-XXX
let mut issue_id = 0;
let mut comment = String::new();
if ty == CommentType::AssignIssuePriority {
// ex. "245100-t-compiler/wg-prioritization/alerts";
let zulip_stream = format!(
"{}-{}",
zulip_msg.messages[0].stream_id, zulip_msg.messages[0].display_recipient
);
let zulip_msg_link = format!(
"narrow/stream/{}/topic/{}/near/{}",
zulip_stream, zulip_msg.messages[0].subject, message.id
);
// Don't urlencode, just replace spaces (Zulip custom URL encoding)
let zulip_msg_link = zulip_msg_link.replace(" ", ".20");
let zulip_msg_link = format!("{}/#{}", ZULIP_HOST, zulip_msg_link);
log::debug!("Zulip link: {}", zulip_msg_link);

issue_id = words
.next()
.unwrap()
.replace("#", "")
.parse::<u64>()
.unwrap();
let p_label = words.next().unwrap();

comment = format!(
"WG-prioritization assigning priority ([Zulip discussion]({}))
\n\n@rustbot label -I-prioritize +{}",
zulip_msg_link, p_label
);
}
// else ... handle other comment type

let github_resp = ctx
.octocrab
.issues("rust-lang", "rust")
.create_comment(issue_id.clone(), comment.clone())
.await;

let _reply = match github_resp {
Ok(data) => data,
Err(e) => {
return Ok(serde_json::to_string(&Response {
content: &format!("Failed creating comment on Github: {:?}.", e),
})
.unwrap());
}
};
log::debug!("Created comment on issue #{}: {:?}", issue_id, comment);

Ok(serde_json::to_string(&ResponseNotRequired {
response_not_required: true,
})
.unwrap())
}

// This does two things:
// * execute the command for the other user
// * tell the user executed for that a command was run as them by the user
Expand Down Expand Up @@ -249,7 +384,7 @@ async fn execute_for_other_user(
let members = ctx
.github
.raw()
.get("https://rust-lang.zulipchat.com/api/v1/users")
.get(format!("{}/api/v1/users", ZULIP_HOST))
.basic_auth(BOT_EMAIL, Some(&bot_api_token))
.send()
.await;
Expand Down Expand Up @@ -402,7 +537,7 @@ impl Recipient<'_> {
}

pub fn url(&self) -> String {
format!("https://rust-lang.zulipchat.com/#narrow/{}", self.narrow())
format!("{}/#narrow/{}", ZULIP_HOST, self.narrow())
}
}

Expand Down Expand Up @@ -458,7 +593,7 @@ impl<'a> MessageApiRequest<'a> {
}

Ok(client
.post("https://rust-lang.zulipchat.com/api/v1/messages")
.post(format!("{}/api/v1/messages", ZULIP_HOST))
.basic_auth(BOT_EMAIL, Some(&bot_api_token))
.form(&SerializedApi {
type_: match self.recipient {
Expand Down Expand Up @@ -510,8 +645,8 @@ impl<'a> UpdateMessageApiRequest<'a> {

Ok(client
.patch(&format!(
"https://rust-lang.zulipchat.com/api/v1/messages/{}",
self.message_id
"{}/api/v1/messages/{}",
ZULIP_HOST, self.message_id
))
.basic_auth(BOT_EMAIL, Some(&bot_api_token))
.form(&SerializedApi {
Expand Down Expand Up @@ -723,8 +858,8 @@ impl<'a> AddReaction<'a> {

Ok(client
.post(&format!(
"https://rust-lang.zulipchat.com/api/v1/messages/{}/reactions",
self.message_id
"{}/api/v1/messages/{}/reactions",
ZULIP_HOST, self.message_id
))
.basic_auth(BOT_EMAIL, Some(&bot_api_token))
.form(&self)
Expand Down

0 comments on commit 51013c9

Please sign in to comment.