Bladeren bron

Permit releasing assignment

Only the currently assigned user or a Rust team member can relinquish
assignment.
Mark Rousskov 6 jaren geleden
bovenliggende
commit
0ee4ed2419
3 gewijzigde bestanden met toevoegingen van 73 en 25 verwijderingen
  1. 16 10
      parser/src/command/assign.rs
  2. 27 11
      src/github.rs
  3. 30 4
      src/handlers/assign.rs

+ 16 - 10
parser/src/command/assign.rs

@@ -5,7 +5,7 @@
 //! The grammar is as follows:
 //!
 //! ```text
-//! Command: `@bot claim` or `@bot assign @user`.
+//! Command: `@bot claim`, `@bot release-assignment`, or `@bot assign @user`.
 //! ```
 
 use crate::error::Error;
@@ -15,6 +15,7 @@ use std::fmt;
 #[derive(PartialEq, Eq, Debug)]
 pub enum AssignCommand {
     Own,
+    Release,
     User { username: String },
 }
 
@@ -62,6 +63,15 @@ impl AssignCommand {
             } else {
                 return Err(toks.error(ParseError::NoUser));
             }
+        } else if let Some(Token::Word("release-assignment")) = toks.peek_token()? {
+            toks.next_token()?;
+            if let Some(Token::Dot) | Some(Token::EndOfLine) = toks.peek_token()? {
+                toks.next_token()?;
+                *input = toks;
+                return Ok(Some(AssignCommand::Release));
+            } else {
+                return Err(toks.error(ParseError::ExpectedEnd));
+            }
         } else {
             return Ok(None);
         }
@@ -76,25 +86,21 @@ fn parse<'a>(input: &'a str) -> Result<Option<AssignCommand>, Error<'a>> {
 
 #[test]
 fn test_1() {
-    assert_eq!(
-        parse("claim."),
-        Ok(Some(AssignCommand::Own)),
-    );
+    assert_eq!(parse("claim."), Ok(Some(AssignCommand::Own)),);
 }
 
 #[test]
 fn test_2() {
-    assert_eq!(
-        parse("claim"),
-        Ok(Some(AssignCommand::Own)),
-    );
+    assert_eq!(parse("claim"), Ok(Some(AssignCommand::Own)),);
 }
 
 #[test]
 fn test_3() {
     assert_eq!(
         parse("assign @user"),
-        Ok(Some(AssignCommand::User { username: "user".to_owned() })),
+        Ok(Some(AssignCommand::User {
+            username: "user".to_owned()
+        })),
     );
 }
 

+ 27 - 11
src/github.rs

@@ -180,6 +180,31 @@ impl Issue {
         &self.labels
     }
 
+    pub fn remove_assignees(&self, client: &GithubClient) -> Result<(), AssignmentError> {
+        let url = format!(
+            "{repo_url}/issues/{number}/assignees",
+            repo_url = self.repository_url,
+            number = self.number
+        );
+
+        #[derive(serde::Serialize)]
+        struct AssigneeReq<'a> {
+            assignees: &'a [&'a str],
+        }
+        client
+            .delete(&url)
+            .json(&AssigneeReq {
+                assignees: &self
+                    .assignees
+                    .iter()
+                    .map(|u| u.login.as_str())
+                    .collect::<Vec<_>>()[..],
+            })
+            .send_req()
+            .map_err(AssignmentError::Http)?;
+        Ok(())
+    }
+
     pub fn set_assignee(&self, client: &GithubClient, user: &str) -> Result<(), AssignmentError> {
         let url = format!(
             "{repo_url}/issues/{number}/assignees",
@@ -204,21 +229,12 @@ impl Issue {
             Err(e) => return Err(AssignmentError::Http(e)),
         }
 
+        self.remove_assignees(client)?;
+
         #[derive(serde::Serialize)]
         struct AssigneeReq<'a> {
             assignees: &'a [&'a str],
         }
-        client
-            .delete(&url)
-            .json(&AssigneeReq {
-                assignees: &self
-                    .assignees
-                    .iter()
-                    .map(|u| u.login.as_str())
-                    .collect::<Vec<_>>()[..],
-            })
-            .send_req()
-            .map_err(AssignmentError::Http)?;
 
         client
             .post(&url)

+ 30 - 4
src/handlers/assign.rs

@@ -23,6 +23,11 @@ use parser::command::{Command, Input};
 
 pub(super) struct AssignmentHandler;
 
+#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+struct AssignData {
+    user: Option<String>,
+}
+
 impl Handler for AssignmentHandler {
     type Input = AssignCommand;
     type Config = AssignConfig;
@@ -65,18 +70,39 @@ impl Handler for AssignmentHandler {
             return Ok(());
         };
 
+        let e = EditIssueBody::new(&event.issue, "ASSIGN");
         let to_assign = match cmd {
             AssignCommand::Own => event.comment.user.login.clone(),
             AssignCommand::User { username } => {
                 if let Err(_) | Ok(false) = event.comment.user.is_team_member(&ctx.github) {
-                    failure::bail!("Only Rust team members can assign other users");
+                    if username != event.comment.user.login {
+                        failure::bail!("Only Rust team members can assign other users");
+                    }
                 }
                 username.clone()
             }
+            AssignCommand::Release => {
+                let current = e.current_data();
+                if current
+                    == Some(AssignData {
+                        user: Some(event.comment.user.login.clone()),
+                    })
+                {
+                    event.issue.remove_assignees(&ctx.github)?;
+                    e.apply(&ctx.github, String::new(), AssignData { user: None })?;
+                    return Ok(());
+                } else if current.map(|d| d.user.is_some()).unwrap_or(false) {
+                    failure::bail!("Cannot release another user's assignment");
+                } else {
+                    failure::bail!("Cannot release unassigned issue");
+                }
+            }
+        };
+        let data = AssignData {
+            user: Some(to_assign.clone()),
         };
 
-        let e = EditIssueBody::new(&event.issue, "ASSIGN");
-        e.apply(&ctx.github, String::new(), ())?;
+        e.apply(&ctx.github, String::new(), &data)?;
 
         match event.issue.set_assignee(&ctx.github, &to_assign) {
             Ok(()) => return Ok(()), // we are done
@@ -91,7 +117,7 @@ impl Handler for AssignmentHandler {
                         "This issue has been assigned to @{} via [this comment]({}).",
                         to_assign, event.comment.html_url
                     ),
-                    (),
+                    &data,
                 )?;
             }
             Err(e) => return Err(e.into()),