-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Notify Zulip when issue is closed or reopened #1078
Conversation
This adds optional `message_on_close` and `message_on_reopen` fields to `triagebot.toml` to notify the Zulip topic when the issue is closed or reopened. Inspired by [this comment] by Ryan Levick. [this comment]: https://rust-lang.zulipchat.com/#narrow/stream/245100-t-compiler.2Fwg-prioritization.2Falerts/topic/.2380375.20ICE.3A.20compiler.2Frustc_middle.2Fsrc.2Fty.2Fsubst.2Ers.3A568.3A17.3A.20c.E2.80.A6/near/221205328
@camelid what's the status of this? |
I need to fix the bug you found in #1078 (comment) :) I'll try to get to this sometime soon. |
I've returned to try working on this again, and I realized what I got stuck on:
I'm not familiar with this code, so I'm not sure what the right solution is. It This is my attempted change in case you want to play around with it to try to diff --git a/src/handlers/notify_zulip.rs b/src/handlers/notify_zulip.rs
index 92f7b1f..12aa7ca 100644
--- a/src/handlers/notify_zulip.rs
+++ b/src/handlers/notify_zulip.rs
@@ -19,55 +19,63 @@ pub(super) fn parse_input(
_ctx: &Context,
event: &IssuesEvent,
config: Option<&NotifyZulipConfig>,
-) -> Result<Option<NotifyZulipInput>, String> {
- let applied_label = &event.label.as_ref().expect("label").name;
+) -> Result<Vec<NotifyZulipInput>, String> {
+ let config = match config {
+ Some(config) => config,
+ None => return Ok(vec![]),
+ };
+ //let applied_label = &event.label.as_ref().expect("label").name;
- if let Some(config) = config.and_then(|c| c.labels.get(applied_label)) {
- for label in &config.required_labels {
- let pattern = match glob::Pattern::new(label) {
- Ok(pattern) => pattern,
- Err(err) => {
- log::error!("Invalid glob pattern: {}", err);
- continue;
+ Ok(event
+ .issue
+ .labels
+ .iter()
+ .filter_map(|label| config.labels.get(&label.name))
+ .flat_map(|config| {
+ for label in &config.required_labels {
+ let pattern = match glob::Pattern::new(label) {
+ Ok(pattern) => pattern,
+ Err(err) => {
+ log::error!("Invalid glob pattern: {}", err);
+ continue;
+ }
+ };
+ if !event
+ .issue
+ .labels()
+ .iter()
+ .any(|l| pattern.matches(&l.name))
+ {
+ // Issue misses a required label, ignore this event
+ return None;
}
- };
- if !event
- .issue
- .labels()
- .iter()
- .any(|l| pattern.matches(&l.name))
- {
- // Issue misses a required label, ignore this event
- return Ok(None);
}
- }
- match event.action {
- IssuesAction::Labeled if config.message_on_add.is_some() => {
- Ok(Some(NotifyZulipInput {
- notification_type: NotificationType::Labeled,
- }))
- }
- IssuesAction::Unlabeled if config.message_on_remove.is_some() => {
- Ok(Some(NotifyZulipInput {
- notification_type: NotificationType::Unlabeled,
- }))
- }
- IssuesAction::Closed if config.message_on_close.is_some() => {
- Ok(Some(NotifyZulipInput {
- notification_type: NotificationType::Closed,
- }))
- }
- IssuesAction::Reopened if config.message_on_reopen.is_some() => {
- Ok(Some(NotifyZulipInput {
- notification_type: NotificationType::Reopened,
- }))
+ match event.action {
+ IssuesAction::Labeled if config.message_on_add.is_some() => {
+ Some(NotifyZulipInput {
+ notification_type: NotificationType::Labeled,
+ })
+ }
+ IssuesAction::Unlabeled if config.message_on_remove.is_some() => {
+ Some(NotifyZulipInput {
+ notification_type: NotificationType::Unlabeled,
+ })
+ }
+ IssuesAction::Closed if config.message_on_close.is_some() => {
+ Some(NotifyZulipInput {
+ notification_type: NotificationType::Closed,
+ })
+ }
+ IssuesAction::Reopened if config.message_on_reopen.is_some() => {
+ Some(NotifyZulipInput {
+ notification_type: NotificationType::Reopened,
+ })
+ }
+ _ => None,
}
- _ => Ok(None),
- }
- } else {
- Ok(None)
- }
+ })
+ .collect())
}
pub(super) async fn handle_input<'a>( |
If you have ideas, let me know! I'm kind of stuck. |
Does changing |
Based on your text, I assume you mean
To solve this I might have to make every handler return a |
Did you change the signature of |
Why didn't I think of that? 🤦 Thanks :) |
Unfortunately I had to do a bunch of refactoring and there don't seem to be any tests. Though as an extra benefit, the code is hopefully clearer :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to make this refactoring because it turns out there's a fundamental difference between label/unlabel events and close/reopen events:
In the case of label changes, you either notify based on the label's triagebot.toml config, or you don't notify because there's no action specified in the config.
On the other hand, for close/reopen events, you have to notify for every label on the issue that has a message_on_close
/message_on_reopen
action in its config. E.g., if we decided to add an I-nominated
Zulip notification, then an issue with I-nominated
and I-prioritize
would need to notify both the I-nominated
stream and the I-prioritize
stream upon close or reopen.
So, I split out two functions: one for label changes, and one for close/reopen. This seemed like the best way to separate their logic since they have unique logic. I also split out a helper function to check if the issue has all of the required_labels
so that it isn't duplicated between the label change and close/reopen functions.
{ | ||
// Issue misses a required label, ignore this event | ||
return Ok(None); | ||
) -> Result<Option<Vec<NotifyZulipInput>>, String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately there's now two ways to represent a lack of notifications: None
and Some(vec![])
. However, I don't think there's a way to get rid of that without changing a lot of other code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only difference between None
and Some(vec![])
in this case is that Some(vec![])
will spawn an additional Future
that does nothing. I think we can always improve/cleanup later.
IssuesAction::Labeled if config.message_on_add.is_some() => Some(NotifyZulipInput { | ||
notification_type: NotificationType::Labeled, | ||
label, | ||
}), | ||
IssuesAction::Unlabeled if config.message_on_remove.is_some() => Some(NotifyZulipInput { | ||
notification_type: NotificationType::Unlabeled, | ||
label, | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This previously looked like:
if event.action == IssuesAction::Labeled && config.message_on_add.is_some() {
return Ok(Some(NotifyZulipInput {
notification_type: NotificationType::Labeled,
}));
} else if config.message_on_remove.is_some() {
return Ok(Some(NotifyZulipInput {
notification_type: NotificationType::Unlabeled,
}));
}
(notice the catch-all else if
)
The new version should hopefully be clearer.
pub(super) async fn handle_input<'a>( | ||
ctx: &Context, | ||
config: &NotifyZulipConfig, | ||
event: &IssuesEvent, | ||
input: NotifyZulipInput, | ||
inputs: Vec<NotifyZulipInput>, | ||
) -> anyhow::Result<()> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend hiding whitespace changes when reviewing this function's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a relatively quick review this looks good to me, left two potential nits.
@Mark-Simulacrum will you have time to review this?
.cloned() | ||
.filter_map(|label| { | ||
global_config | ||
.labels | ||
.get(&label.name) | ||
.map(|config| (label, config)) | ||
}) | ||
.flat_map(|(label, config)| { | ||
if !has_all_required_labels(&event.issue, config) { | ||
// Issue misses a required label, ignore this event | ||
return None; | ||
} | ||
|
||
match event.action { | ||
IssuesAction::Closed if config.message_on_close.is_some() => { | ||
Some(NotifyZulipInput { | ||
notification_type: NotificationType::Closed, | ||
label, | ||
}) | ||
} | ||
IssuesAction::Reopened if config.message_on_reopen.is_some() => { | ||
Some(NotifyZulipInput { | ||
notification_type: NotificationType::Reopened, | ||
label, | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: I think we could only clone the labels we're actually interested in?
.cloned() | |
.filter_map(|label| { | |
global_config | |
.labels | |
.get(&label.name) | |
.map(|config| (label, config)) | |
}) | |
.flat_map(|(label, config)| { | |
if !has_all_required_labels(&event.issue, config) { | |
// Issue misses a required label, ignore this event | |
return None; | |
} | |
match event.action { | |
IssuesAction::Closed if config.message_on_close.is_some() => { | |
Some(NotifyZulipInput { | |
notification_type: NotificationType::Closed, | |
label, | |
}) | |
} | |
IssuesAction::Reopened if config.message_on_reopen.is_some() => { | |
Some(NotifyZulipInput { | |
notification_type: NotificationType::Reopened, | |
label, | |
}) | |
} | |
.filter_map(|label| { | |
global_config | |
.labels | |
.get(&label.name) | |
.map(|config| (label, config)) | |
}) | |
.flat_map(|(label, config)| { | |
if !has_all_required_labels(&event.issue, config) { | |
// Issue misses a required label, ignore this event | |
return None; | |
} | |
match event.action { | |
IssuesAction::Closed if config.message_on_close.is_some() => { | |
Some(NotifyZulipInput { | |
notification_type: NotificationType::Closed, | |
label: label.clone(), | |
}) | |
} | |
IssuesAction::Reopened if config.message_on_reopen.is_some() => { | |
Some(NotifyZulipInput { | |
notification_type: NotificationType::Reopened, | |
label: label.clone(), | |
}) | |
} |
|
I am happy to review, but it'll likely be this weekend - not sure if I'll have time before then. It's in my todo list :) |
Co-authored-by: Léo Lanteri Thauvin <leseulartichaut@gmail.com>
@Mark-Simulacrum could you take a look at this sometime soon? Thanks! By the way, this might help with reviewing: #1078 (comment) |
Was wondering, what's the status of this? is this still needed? should we merge it?. |
Yeah, it just needs a review :) |
Thanks! |
…storino Notify when an `I-prioritize` issue is closed or reopened Companion PR to rust-lang/triagebot#1078, blocked on that PR. r? `@spastorino` cc `@rust-lang/wg-prioritization`
…storino Notify when an `I-prioritize` issue is closed or reopened Companion PR to rust-lang/triagebot#1078, blocked on that PR. r? ``@spastorino`` cc ``@rust-lang/wg-prioritization``
…storino Notify when an `I-prioritize` issue is closed or reopened Companion PR to rust-lang/triagebot#1078, blocked on that PR. r? ```@spastorino``` cc ```@rust-lang/wg-prioritization```
…storino Notify when an `I-prioritize` issue is closed or reopened Companion PR to rust-lang/triagebot#1078, blocked on that PR. r? ``@spastorino`` cc ``@rust-lang/wg-prioritization``
This adds optional
message_on_close
andmessage_on_reopen
fields totriagebot.toml
to notify the Zulip topic when the issue is closed orreopened.
Inspired by this comment by Ryan Levick.
cc @rylev