瀏覽代碼

Run commands on just-opened issue bodies

Mark Rousskov 5 年之前
父節點
當前提交
7d9f2c26d5
共有 5 個文件被更改,包括 126 次插入56 次删除
  1. 54 0
      src/github.rs
  2. 32 28
      src/handlers/assign.rs
  3. 21 18
      src/handlers/relabel.rs
  4. 3 10
      src/handlers/tracking_issue.rs
  5. 16 0
      src/main.rs

+ 54 - 0
src/github.rs

@@ -55,6 +55,7 @@ pub struct Issue {
     pub number: u64,
     pub body: String,
     title: String,
+    html_url: String,
     user: User,
     labels: Vec<Label>,
     assignees: Vec<User>,
@@ -280,6 +281,34 @@ pub struct IssueCommentEvent {
     pub repository: Repository,
 }
 
+#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum IssuesAction {
+    Opened,
+    Edited,
+    Deleted,
+    Transferred,
+    Pinned,
+    Unpinned,
+    Closed,
+    Reopened,
+    Assigned,
+    Unassigned,
+    Labeled,
+    Unlabeled,
+    Locked,
+    Unlocked,
+    Milestoned,
+    Demilestoned,
+}
+
+#[derive(Debug, serde::Deserialize)]
+pub struct IssuesEvent {
+    pub action: IssuesAction,
+    pub issue: Issue,
+    pub repository: Repository,
+}
+
 #[derive(Debug, serde::Deserialize)]
 pub struct Repository {
     pub full_name: String,
@@ -288,18 +317,43 @@ pub struct Repository {
 #[derive(Debug)]
 pub enum Event {
     IssueComment(IssueCommentEvent),
+    Issue(IssuesEvent),
 }
 
 impl Event {
     pub fn repo_name(&self) -> &str {
         match self {
             Event::IssueComment(event) => &event.repository.full_name,
+            Event::Issue(event) => &event.repository.full_name,
         }
     }
 
     pub fn issue(&self) -> Option<&Issue> {
         match self {
             Event::IssueComment(event) => Some(&event.issue),
+            Event::Issue(event) => Some(&event.issue),
+        }
+    }
+
+    /// This will both extract from IssueComment events but also Issue events
+    pub fn comment_body(&self) -> Option<&str> {
+        match self {
+            Event::Issue(e) => Some(&e.issue.body),
+            Event::IssueComment(e) => Some(&e.comment.body),
+        }
+    }
+
+    pub fn html_url(&self) -> Option<&str> {
+        match self {
+            Event::Issue(e) => Some(&e.issue.html_url),
+            Event::IssueComment(e) => Some(&e.comment.html_url),
+        }
+    }
+
+    pub fn user(&self) -> &User {
+        match self {
+            Event::Issue(e) => &e.issue.user,
+            Event::IssueComment(e) => &e.comment.user,
         }
     }
 }

+ 32 - 28
src/handlers/assign.rs

@@ -33,21 +33,28 @@ impl Handler for AssignmentHandler {
     type Config = AssignConfig;
 
     fn parse_input(&self, ctx: &Context, event: &Event) -> Result<Option<Self::Input>, Error> {
-        #[allow(irrefutable_let_patterns)]
-        let event = if let Event::IssueComment(e) = event {
-            e
+        let body = if let Some(b) = event.comment_body() {
+            b
         } else {
             // not interested in other events
             return Ok(None);
         };
 
-        let mut input = Input::new(&event.comment.body, &ctx.username);
+        if let Event::Issue(e) = event {
+            if e.action != github::IssuesAction::Opened {
+                // skip events other than opening the issue to avoid retriggering commands in the
+                // issue body
+                return Ok(None);
+            }
+        }
+
+        let mut input = Input::new(&body, &ctx.username);
         match input.parse_command() {
             Command::Assign(Ok(command)) => Ok(Some(command)),
             Command::Assign(Err(err)) => {
                 failure::bail!(
                     "Parsing assign command in [comment]({}) failed: {}",
-                    event.comment.html_url,
+                    event.html_url().expect("has html url"),
                     err
                 );
             }
@@ -62,27 +69,18 @@ impl Handler for AssignmentHandler {
         event: &Event,
         cmd: AssignCommand,
     ) -> Result<(), Error> {
-        #[allow(irrefutable_let_patterns)]
-        let event = if let Event::IssueComment(e) = event {
-            e
+        let is_team_member = if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github) {
+            false
         } else {
-            // not interested in other events
-            return Ok(());
+            true
         };
 
-        let is_team_member =
-            if let Err(_) | Ok(false) = event.comment.user.is_team_member(&ctx.github) {
-                false
-            } else {
-                true
-            };
-
-        let e = EditIssueBody::new(&event.issue, "ASSIGN");
+        let e = EditIssueBody::new(&event.issue().unwrap(), "ASSIGN");
 
         let to_assign = match cmd {
-            AssignCommand::Own => event.comment.user.login.clone(),
+            AssignCommand::Own => event.user().login.clone(),
             AssignCommand::User { username } => {
-                if !is_team_member && username != event.comment.user.login {
+                if !is_team_member && username != event.user().login {
                     failure::bail!("Only Rust team members can assign other users");
                 }
                 username.clone()
@@ -92,18 +90,22 @@ impl Handler for AssignmentHandler {
                     user: Some(current),
                 }) = e.current_data()
                 {
-                    if current == event.comment.user.login || is_team_member {
-                        event.issue.remove_assignees(&ctx.github, Selection::All)?;
+                    if current == event.user().login || is_team_member {
+                        event
+                            .issue()
+                            .unwrap()
+                            .remove_assignees(&ctx.github, Selection::All)?;
                         e.apply(&ctx.github, String::new(), AssignData { user: None })?;
                         return Ok(());
                     } else {
                         failure::bail!("Cannot release another user's assignment");
                     }
                 } else {
-                    let current = &event.comment.user;
-                    if event.issue.contain_assignee(current) {
+                    let current = &event.user();
+                    if event.issue().unwrap().contain_assignee(current) {
                         event
-                            .issue
+                            .issue()
+                            .unwrap()
                             .remove_assignees(&ctx.github, Selection::One(&current))?;
                         e.apply(&ctx.github, String::new(), AssignData { user: None })?;
                         return Ok(());
@@ -119,18 +121,20 @@ impl Handler for AssignmentHandler {
 
         e.apply(&ctx.github, String::new(), &data)?;
 
-        match event.issue.set_assignee(&ctx.github, &to_assign) {
+        match event.issue().unwrap().set_assignee(&ctx.github, &to_assign) {
             Ok(()) => return Ok(()), // we are done
             Err(github::AssignmentError::InvalidAssignee) => {
                 event
-                    .issue
+                    .issue()
+                    .unwrap()
                     .set_assignee(&ctx.github, &ctx.username)
                     .context("self-assignment failed")?;
                 e.apply(
                     &ctx.github,
                     format!(
                         "This issue has been assigned to @{} via [this comment]({}).",
-                        to_assign, event.comment.html_url
+                        to_assign,
+                        event.html_url().unwrap()
                     ),
                     &data,
                 )?;

+ 21 - 18
src/handlers/relabel.rs

@@ -15,7 +15,7 @@ use crate::{
     interactions::ErrorComment,
 };
 use failure::Error;
-use parser::command::relabel::{RelabelCommand, LabelDelta};
+use parser::command::relabel::{LabelDelta, RelabelCommand};
 use parser::command::{Command, Input};
 
 pub(super) struct RelabelHandler;
@@ -25,21 +25,29 @@ impl Handler for RelabelHandler {
     type Config = RelabelConfig;
 
     fn parse_input(&self, ctx: &Context, event: &Event) -> Result<Option<Self::Input>, Error> {
-        #[allow(irrefutable_let_patterns)]
-        let event = if let Event::IssueComment(e) = event {
-            e
+        let body = if let Some(b) = event.comment_body() {
+            b
         } else {
             // not interested in other events
             return Ok(None);
         };
 
-        let mut input = Input::new(&event.comment.body, &ctx.username);
+        if let Event::Issue(e) = event {
+            if e.action != github::IssuesAction::Opened {
+                // skip events other than opening the issue to avoid retriggering commands in the
+                // issue body
+                return Ok(None);
+            }
+        }
+
+        let mut input = Input::new(&body, &ctx.username);
         match input.parse_command() {
             Command::Relabel(Ok(command)) => Ok(Some(command)),
             Command::Relabel(Err(err)) => {
                 failure::bail!(
                     "Parsing label command in [comment]({}) failed: {}",
-                    event.comment.html_url, err
+                    event.html_url().expect("has html url"),
+                    err
                 );
             }
             _ => Ok(None),
@@ -53,20 +61,12 @@ impl Handler for RelabelHandler {
         event: &Event,
         input: RelabelCommand,
     ) -> Result<(), Error> {
-        #[allow(irrefutable_let_patterns)]
-        let event = if let Event::IssueComment(e) = event {
-            e
-        } else {
-            // not interested in other events
-            return Ok(());
-        };
-
-        let mut issue_labels = event.issue.labels().to_owned();
+        let mut issue_labels = event.issue().unwrap().labels().to_owned();
         let mut changed = false;
         for delta in &input.0 {
             let name = delta.label().as_str();
-            if let Err(msg) = check_filter(name, config, &event.comment.user, &ctx.github) {
-                ErrorComment::new(&event.issue, msg.to_string()).post(&ctx.github)?;
+            if let Err(msg) = check_filter(name, config, &event.user(), &ctx.github) {
+                ErrorComment::new(&event.issue().unwrap(), msg.to_string()).post(&ctx.github)?;
                 return Ok(());
             }
             match delta {
@@ -88,7 +88,10 @@ impl Handler for RelabelHandler {
         }
 
         if changed {
-            event.issue.set_labels(&ctx.github, issue_labels)?;
+            event
+                .issue()
+                .unwrap()
+                .set_labels(&ctx.github, issue_labels)?;
         }
 
         Ok(())

+ 3 - 10
src/handlers/tracking_issue.rs

@@ -1,3 +1,4 @@
+#![cfg(empty)]
 use crate::{
     github::GithubClient,
     registry::{Event, Handler},
@@ -84,16 +85,8 @@ impl TrackingIssueHandler {
 
 impl Handler for TrackingIssueHandler {
     fn handle_event(&self, event: &Event) -> Result<(), Error> {
-        #[allow(irrefutable_let_patterns)]
-        let event = if let Event::IssueComment(e) = event {
-            e
-        } else {
-            // not interested in other events
-            return Ok(());
-        };
-
-        self.handle_create(&event)?;
-        self.handle_link(&event)?;
+        //self.handle_create(&event)?;
+        //self.handle_link(&event)?;
         Ok(())
     }
 }

+ 16 - 0
src/main.rs

@@ -23,6 +23,7 @@ use payload::SignedPayload;
 
 enum EventName {
     IssueComment,
+    Issue,
     Other,
 }
 
@@ -36,6 +37,7 @@ impl<'a, 'r> request::FromRequest<'a, 'r> for EventName {
         };
         let ev = match ev {
             "issue_comment" => EventName::IssueComment,
+            "issues" => EventName::Issue,
             _ => EventName::Other,
         };
         Outcome::Success(ev)
@@ -83,6 +85,20 @@ fn webhook(
                 return Err(err.into());
             }
         }
+        EventName::Issue => {
+            let payload = payload
+                .deserialize::<github::IssuesEvent>()
+                .context("IssueCommentEvent failed to deserialize")
+                .map_err(Error::from)?;
+
+            let event = github::Event::Issue(payload);
+            if let Err(err) = handlers::handle(&ctx, &event) {
+                if let Some(issue) = event.issue() {
+                    ErrorComment::new(issue, err.to_string()).post(&ctx.github)?;
+                }
+                return Err(err.into());
+            }
+        }
         // Other events need not be handled
         EventName::Other => {}
     }