Skip to content

Commit

Permalink
Merge pull request #1791 from fmease/notify-zulip-multiple-messages
Browse files Browse the repository at this point in the history
Allow a single notify-zulip notification to send multiple Zulip messages
  • Loading branch information
Mark-Simulacrum authored Apr 16, 2024
2 parents 1cf4ceb + c4ac8c3 commit f1c2168
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 30 deletions.
53 changes: 49 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,26 @@ pub(crate) struct NotifyZulipConfig {
pub(crate) struct NotifyZulipLabelConfig {
pub(crate) zulip_stream: u64,
pub(crate) topic: String,
pub(crate) message_on_add: Option<String>,
pub(crate) message_on_remove: Option<String>,
pub(crate) message_on_close: Option<String>,
pub(crate) message_on_reopen: Option<String>,
#[serde(rename = "message_on_add", default, deserialize_with = "string_or_seq")]
pub(crate) messages_on_add: Vec<String>,
#[serde(
rename = "message_on_remove",
default,
deserialize_with = "string_or_seq"
)]
pub(crate) messages_on_remove: Vec<String>,
#[serde(
rename = "message_on_close",
default,
deserialize_with = "string_or_seq"
)]
pub(crate) messages_on_close: Vec<String>,
#[serde(
rename = "message_on_reopen",
default,
deserialize_with = "string_or_seq"
)]
pub(crate) messages_on_reopen: Vec<String>,
#[serde(default)]
pub(crate) required_labels: Vec<String>,
}
Expand Down Expand Up @@ -384,6 +400,35 @@ impl fmt::Display for ConfigurationError {
}
}

fn string_or_seq<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct Visitor;

impl<'de> serde::de::Visitor<'de> for Visitor {
type Value = Vec<String>;

fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("string or sequence of strings")
}

fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
Ok(Vec::new())
}

fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
Ok(vec![value.to_owned()])
}

fn visit_seq<A: serde::de::SeqAccess<'de>>(self, seq: A) -> Result<Self::Value, A::Error> {
serde::Deserialize::deserialize(serde::de::value::SeqAccessDeserializer::new(seq))
}
}

deserializer.deserialize_any(Visitor)
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
57 changes: 32 additions & 25 deletions src/handlers/notify_zulip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,13 @@ fn parse_label_change_input(
}

match event.action {
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() => {
IssuesAction::Labeled { .. } if !config.messages_on_add.is_empty() => {
Some(NotifyZulipInput {
notification_type: NotificationType::Labeled,
label,
})
}
IssuesAction::Unlabeled { .. } if !config.messages_on_remove.is_empty() => {
Some(NotifyZulipInput {
notification_type: NotificationType::Unlabeled,
label,
Expand Down Expand Up @@ -96,13 +98,13 @@ fn parse_close_reopen_input(
}

match event.action {
IssuesAction::Closed if config.message_on_close.is_some() => {
IssuesAction::Closed if !config.messages_on_close.is_empty() => {
Some(NotifyZulipInput {
notification_type: NotificationType::Closed,
label,
})
}
IssuesAction::Reopened if config.message_on_reopen.is_some() => {
IssuesAction::Reopened if !config.messages_on_reopen.is_empty() => {
Some(NotifyZulipInput {
notification_type: NotificationType::Reopened,
label,
Expand Down Expand Up @@ -140,34 +142,39 @@ pub(super) async fn handle_input<'a>(
for input in inputs {
let config = &config.labels[&input.label.name];

let mut topic = config.topic.clone();
topic = topic.replace("{number}", &event.issue.number.to_string());
topic = topic.replace("{title}", &event.issue.title);
let topic = &config.topic;
let topic = topic.replace("{number}", &event.issue.number.to_string());
let mut topic = topic.replace("{title}", &event.issue.title);
// Truncate to 60 chars (a Zulip limitation)
let mut chars = topic.char_indices().skip(59);
if let (Some((len, _)), Some(_)) = (chars.next(), chars.next()) {
topic.truncate(len);
topic.push('…');
}

let mut msg = match input.notification_type {
NotificationType::Labeled => config.message_on_add.as_ref().unwrap().clone(),
NotificationType::Unlabeled => config.message_on_remove.as_ref().unwrap().clone(),
NotificationType::Closed => config.message_on_close.as_ref().unwrap().clone(),
NotificationType::Reopened => config.message_on_reopen.as_ref().unwrap().clone(),
let msgs = match input.notification_type {
NotificationType::Labeled => &config.messages_on_add,
NotificationType::Unlabeled => &config.messages_on_remove,
NotificationType::Closed => &config.messages_on_close,
NotificationType::Reopened => &config.messages_on_reopen,
};

msg = msg.replace("{number}", &event.issue.number.to_string());
msg = msg.replace("{title}", &event.issue.title);

let zulip_req = crate::zulip::MessageApiRequest {
recipient: crate::zulip::Recipient::Stream {
id: config.zulip_stream,
topic: &topic,
},
content: &msg,
let recipient = crate::zulip::Recipient::Stream {
id: config.zulip_stream,
topic: &topic,
};
zulip_req.send(&ctx.github.raw()).await?;

for msg in msgs {
let msg = msg.replace("{number}", &event.issue.number.to_string());
let msg = msg.replace("{title}", &event.issue.title);

crate::zulip::MessageApiRequest {
recipient,
content: &msg,
}
.send(&ctx.github.raw())
.await?;
}
}

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion src/zulip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ pub struct Member {
pub user_id: u64,
}

#[derive(serde::Serialize)]
#[derive(Copy, Clone, serde::Serialize)]
#[serde(tag = "type")]
#[serde(rename_all = "snake_case")]
pub enum Recipient<'a> {
Expand Down

0 comments on commit f1c2168

Please sign in to comment.