Skip to content

Commit

Permalink
Merge pull request #616 from LeSeulArtichaut/split-prioritize
Browse files Browse the repository at this point in the history
Split the `prioritize` feature in smaller parts
  • Loading branch information
Mark-Simulacrum committed Jun 12, 2020
2 parents aaca50b + c639c16 commit e151d7d
Show file tree
Hide file tree
Showing 5 changed files with 259 additions and 129 deletions.
44 changes: 42 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ pub(crate) struct Config {
pub(crate) prioritize: Option<PrioritizeConfig>,
pub(crate) major_change: Option<MajorChangeConfig>,
pub(crate) glacier: Option<GlacierConfig>,
pub(crate) autolabel: Option<AutolabelConfig>,
pub(crate) notify_zulip: Option<NotifyZulipConfig>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
Expand Down Expand Up @@ -79,11 +81,47 @@ pub(crate) struct RelabelConfig {
#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct PrioritizeConfig {
pub(crate) label: String,
#[serde(default)]
pub(crate) prioritize_on: Vec<String>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct AutolabelConfig {
#[serde(flatten)]
pub(crate) labels: HashMap<String, AutolabelLabelConfig>,
}

impl AutolabelConfig {
pub(crate) fn get_by_trigger(&self, trigger: &str) -> Vec<(&str, &AutolabelLabelConfig)> {
let mut results = Vec::new();
for (label, cfg) in self.labels.iter() {
if cfg.trigger_labels.iter().any(|l| l == trigger) {
results.push((label.as_str(), cfg));
}
}
results
}
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct AutolabelLabelConfig {
pub(crate) trigger_labels: Vec<String>,
#[serde(default)]
pub(crate) exclude_labels: Vec<String>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
pub(crate) struct NotifyZulipConfig {
#[serde(flatten)]
pub(crate) labels: HashMap<String, NotifyZulipLabelConfig>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
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>,
#[serde(default)]
pub(crate) required_labels: Vec<String>,
}

#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
Expand Down Expand Up @@ -231,6 +269,8 @@ mod tests {
prioritize: None,
major_change: None,
glacier: None,
autolabel: None,
notify_zulip: None,
}
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ handlers! {
major_change = major_change::MajorChangeHandler,
//tracking_issue = tracking_issue::TrackingIssueHandler,
glacier = glacier::GlacierHandler,
autolabel = autolabel::AutolabelHandler,
notify_zulip = notify_zulip::NotifyZulipHandler,
}

pub struct Context {
Expand Down
94 changes: 94 additions & 0 deletions src/handlers/autolabel.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use crate::{
config::AutolabelConfig,
github::{self, Event, Label},
handlers::{Context, Handler},
};
use futures::future::{BoxFuture, FutureExt};
pub(super) struct AutolabelInput {
labels: Vec<Label>
}

pub(super) struct AutolabelHandler;

impl Handler for AutolabelHandler {
type Input = AutolabelInput;
type Config = AutolabelConfig;

fn parse_input(
&self,
_ctx: &Context,
event: &Event,
config: Option<&Self::Config>,
) -> Result<Option<Self::Input>, String> {
if let Event::Issue(e) = event {
if e.action == github::IssuesAction::Labeled {
if let Some(config) = config {
let mut autolabels = Vec::new();
let applied_label = &e.label.as_ref().expect("label").name;

'outer: for (label, config) in config.get_by_trigger(applied_label) {
let exclude_patterns: Vec<glob::Pattern> = config
.exclude_labels
.iter()
.filter_map(|label| {
match glob::Pattern::new(label) {
Ok(exclude_glob) => {
Some(exclude_glob)
}
Err(error) => {
log::error!("Invalid glob pattern: {}", error);
None
}
}
})
.collect();

for label in event.issue().unwrap().labels() {
for pat in &exclude_patterns {
if pat.matches(&label.name) {
// If we hit an excluded label, ignore this autolabel and check the next
continue 'outer;
}
}
}

// If we reach here, no excluded labels were found, so we should apply the autolabel.
autolabels.push(Label { name: label.to_owned() });
}
if !autolabels.is_empty() {
return Ok(Some(AutolabelInput { labels: autolabels }));
}
}
}
}
Ok(None)
}

fn handle_input<'a>(
&self,
ctx: &'a Context,
config: &'a Self::Config,
event: &'a Event,
input: Self::Input,
) -> BoxFuture<'a, anyhow::Result<()>> {
handle_input(ctx, config, event, input).boxed()
}
}

async fn handle_input(
ctx: &Context,
_config: &AutolabelConfig,
event: &Event,
input: AutolabelInput,
) -> anyhow::Result<()> {
let issue = event.issue().unwrap();
let mut labels = issue.labels().to_owned();
for label in input.labels {
// Don't add the label if it's already there
if !labels.contains(&label) {
labels.push(label);
}
}
issue.set_labels(&ctx.github, labels).await?;
Ok(())
}
111 changes: 111 additions & 0 deletions src/handlers/notify_zulip.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use crate::{
config::NotifyZulipConfig,
github::{self, Event},
handlers::{Context, Handler},
};
use futures::future::{BoxFuture, FutureExt};

pub(super) struct NotifyZulipInput {
notification_type: NotificationType,
}

pub(super) enum NotificationType {
Labeled,
Unlabeled,
}

pub(super) struct NotifyZulipHandler;

impl Handler for NotifyZulipHandler {
type Input = NotifyZulipInput;
type Config = NotifyZulipConfig;

fn parse_input(
&self,
_ctx: &Context,
event: &Event,
config: Option<&Self::Config>,
) -> Result<Option<Self::Input>, String> {
if let Event::Issue(e) = event {
if let github::IssuesAction::Labeled | github::IssuesAction::Unlabeled = e.action {
let applied_label = &e.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;
}
};
if !e.issue.labels().iter().any(|l| pattern.matches(&l.name)) {
// Issue misses a required label, ignore this event
return Ok(None);
}
}

if e.action == github::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,
}));
}
}
}
}
Ok(None)
}

fn handle_input<'b>(
&self,
ctx: &'b Context,
config: &'b Self::Config,
event: &'b Event,
input: Self::Input,
) -> BoxFuture<'b, anyhow::Result<()>> {
handle_input(ctx, config, event, input).boxed()
}
}

async fn handle_input<'a>(
ctx: &Context,
config: &NotifyZulipConfig,
event: &Event,
input: NotifyZulipInput,
) -> anyhow::Result<()> {
let event = match event {
Event::Issue(e) => e,
_ => unreachable!()
};
let config = config.labels.get(&event.label.as_ref().unwrap().name).unwrap();

let mut topic = config.topic.clone();
topic = topic.replace("{number}", &event.issue.number.to_string());
topic = topic.replace("{title}", &event.issue.title);
topic.truncate(60); // Zulip limitation

let mut msg = match input.notification_type {
NotificationType::Labeled => {
config.message_on_add.as_ref().unwrap().clone()
}
NotificationType::Unlabeled => {
config.message_on_add.as_ref().unwrap().clone()
}
};

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

let zulip_req = crate::zulip::MessageApiRequest {
type_: "stream",
to: &config.zulip_stream.to_string(),
topic: Some(&topic),
content: &msg,
};
zulip_req.send(&ctx.github.raw()).await?;

Ok(())
}
Loading

0 comments on commit e151d7d

Please sign in to comment.