Browse Source

(Try to) implement the `prioritize` command

LeSeulArtichaut 5 năm trước cách đây
mục cha
commit
87ae5f25c7
5 tập tin đã thay đổi với 126 bổ sung0 xóa
  1. 8 0
      parser/src/command.rs
  2. 15 0
      parser/src/command/prioritize.rs
  3. 7 0
      src/config.rs
  4. 1 0
      src/handlers.rs
  5. 95 0
      src/handlers/prioritize.rs

+ 8 - 0
parser/src/command.rs

@@ -6,6 +6,7 @@ pub mod assign;
 pub mod nominate;
 pub mod ping;
 pub mod relabel;
+pub mod prioritize;
 
 pub fn find_commmand_start(input: &str, bot: &str) -> Option<usize> {
     input.find(&format!("@{}", bot))
@@ -17,6 +18,7 @@ pub enum Command<'a> {
     Assign(Result<assign::AssignCommand, Error<'a>>),
     Ping(Result<ping::PingCommand, Error<'a>>),
     Nominate(Result<nominate::NominateCommand, Error<'a>>),
+    Prioritize(Result<prioritize::PrioritizeCommand, Error<'a>>),
     None,
 }
 
@@ -95,6 +97,11 @@ impl<'a> Input<'a> {
             Command::Nominate,
             &original_tokenizer,
         ));
+        success.extend(parse_single_command(
+            prioritize::PrioritizeCommand::parse,
+            Command::Prioritize,
+            &original_tokenizer,
+        ));
 
         if success.len() > 1 {
             panic!(
@@ -133,6 +140,7 @@ impl<'a> Command<'a> {
             Command::Assign(r) => r.is_ok(),
             Command::Ping(r) => r.is_ok(),
             Command::Nominate(r) => r.is_ok(),
+            Command::Prioritize(r) => r.is_ok(),
             Command::None => true,
         }
     }

+ 15 - 0
parser/src/command/prioritize.rs

@@ -0,0 +1,15 @@
+#[derive(PartialEq, Eq, Debug)]
+pub struct PrioritizeCommand;
+
+use crate::error::Error;
+use crate::token::{Token, Tokenizer};
+
+impl PrioritizeCommand {
+    pub fn parse<'a>(input: &mut Tokenizer<'a>) -> Result<Option<Self>, Error<'a>> {
+        if let Some(Token::Word("prioritize")) = input.peek_token()? {
+            Ok(Some(Self))
+        } else {
+            Ok(None)
+        }
+    }
+}

+ 7 - 0
src/config.rs

@@ -19,6 +19,7 @@ pub(crate) struct Config {
     pub(crate) assign: Option<AssignConfig>,
     pub(crate) ping: Option<PingConfig>,
     pub(crate) nominate: Option<NominateConfig>,
+    pub(crate) prioritize: Option<PrioritizeConfig>,
 }
 
 #[derive(PartialEq, Eq, Debug, serde::Deserialize)]
@@ -72,6 +73,11 @@ pub(crate) struct RelabelConfig {
     pub(crate) allow_unauthenticated: Vec<String>,
 }
 
+#[derive(PartialEq, Eq, Debug, serde::Deserialize)]
+pub(crate) struct PrioritizeConfig {
+    pub(crate) zulip_stream: u64,
+}
+
 pub(crate) async fn get(gh: &GithubClient, repo: &str) -> Result<Arc<Config>, ConfigurationError> {
     if let Some(config) = get_cached_config(repo) {
         log::trace!("returning config for {} from cache", repo);
@@ -204,6 +210,7 @@ mod tests {
                 nominate: Some(NominateConfig {
                     teams: nominate_teams
                 }),
+                prioritize: None,
             }
         );
     }

+ 1 - 0
src/handlers.rs

@@ -71,6 +71,7 @@ handlers! {
     relabel = relabel::RelabelHandler,
     ping = ping::PingHandler,
     nominate = nominate::NominateHandler,
+    prioritize = prioritize::PrioritizeHandler,
     //tracking_issue = tracking_issue::TrackingIssueHandler,
 }
 

+ 95 - 0
src/handlers/prioritize.rs

@@ -0,0 +1,95 @@
+use crate::{
+    config::PrioritizeConfig,
+    github::{self, Event},
+    handlers::{Context, Handler},
+    interactions::ErrorComment,
+};
+use futures::future::{BoxFuture, FutureExt};
+use parser::command::prioritize::PrioritizeCommand;
+use parser::command::{Command, Input};
+
+
+pub(super) struct PrioritizeHandler;
+
+impl Handler for PrioritizeHandler {
+    type Input = PrioritizeCommand;
+    type Config = PrioritizeConfig;
+
+    fn parse_input(&self, ctx: &Context, event: &Event) -> Result<Option<Self::Input>, String> {
+        let body = if let Some(b) = event.comment_body() {
+            b
+        } else {
+            // not interested in other events
+            return Ok(None);
+        };
+
+        if let Event::Issue(e) = event {
+            if e.action != github::IssuesAction::Opened {
+                log::debug!("skipping event, issue was {:?}", e.action);
+                // 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::Prioritize(Ok(cmd)) => Ok(Some(cmd)),
+            _ => Ok(None),
+        }
+    }
+
+    fn handle_input<'a>(
+        &self,
+        ctx: &'a Context,
+        config: &'a Self::Config,
+        event: &'a Event,
+        _cmd: Self::Input,
+    ) -> BoxFuture<'a, anyhow::Result<()>> {
+        handle_input(ctx, config, event).boxed()
+    }
+}
+
+async fn handle_input(ctx: &Context, config: &PrioritizeConfig, event: &Event) -> anyhow::Result<()> {
+    let is_team_member = if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github).await {
+        false
+    } else {
+        true
+    };
+
+    let issue = event.issue().unwrap();
+    if !is_team_member {
+        let cmnt = ErrorComment::new(&issue, "Only Rust team members can prioritize issues.");
+        cmnt.post(&ctx.github).await?;
+        return Ok(());
+    }
+
+    if issue.labels().iter().any(|l| l.name == "I-prioritize") {
+        let cmnt = ErrorComment::new(&issue, "This issue is already prioritized!");
+        cmnt.post(&ctx.github).await?;
+        return Ok(());
+    }
+
+    let mut labels = issue.labels().to_owned();
+    labels.push(github::Label {
+        name: "I-prioritize".to_string(),
+    });
+    let github_req = issue.set_labels(&ctx.github, labels);
+
+    let mut zulip_topic = format!("I-pri #{} {}", issue.number, issue.title);
+    zulip_topic.truncate(60); // Zulip limitation
+    let client = reqwest::Client::new(); // TODO: have a Zulip Client akin to GithubClient
+    let zulip_req = client.post("https://rust-lang.zulipchat.com/api/v1/messages")
+        .form(&[
+            ("type", "stream"),
+            ("to", config.zulip_stream.to_string().as_str()),
+            ("topic", &zulip_topic),
+            ("content", "@*WG-prioritization*"),
+        ])
+        .send();
+    
+    let (gh_res, zulip_res) = futures::join!(github_req, zulip_req);
+    gh_res?;
+    zulip_res?;
+    Ok(())
+}