123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- use crate::{
- config::MajorChangeConfig,
- github::{self, Event},
- handlers::{Context, Handler},
- interactions::ErrorComment,
- };
- use anyhow::Context as _;
- use futures::future::{BoxFuture, FutureExt};
- use parser::command::second::SecondCommand;
- use parser::command::{Command, Input};
- pub(super) enum Invocation {
- Second,
- NewProposal,
- }
- pub(super) struct MajorChangeHandler;
- impl Handler for MajorChangeHandler {
- type Input = Invocation;
- type Config = MajorChangeConfig;
- fn parse_input(
- &self,
- ctx: &Context,
- event: &Event,
- _: Option<&Self::Config>,
- ) -> Result<Option<Self::Input>, String> {
- let body = if let Some(b) = event.comment_body() {
- b
- } else {
- // not interested in other events
- return Ok(None);
- };
- match event {
- Event::Issue(e) => {
- if e.action != github::IssuesAction::Opened
- && e.action != github::IssuesAction::Reopened
- {
- return Ok(None);
- }
- }
- Event::IssueComment(e) => {
- if e.action != github::IssueCommentAction::Created {
- return Ok(None);
- }
- }
- }
- if let Event::Issue(e) = event {
- if e.issue.labels().iter().any(|l| l.name == "major-change") {
- return Ok(Some(Invocation::NewProposal));
- }
- }
- let mut input = Input::new(&body, &ctx.username);
- match input.parse_command() {
- Command::Second(Ok(SecondCommand)) => Ok(Some(Invocation::Second)),
- _ => 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, cmd).boxed()
- }
- }
- async fn handle_input(
- ctx: &Context,
- config: &MajorChangeConfig,
- event: &Event,
- cmd: Invocation,
- ) -> anyhow::Result<()> {
- let issue = event.issue().unwrap();
- let (zulip_msg, label_to_add) = match cmd {
- Invocation::Second => {
- if !issue.labels().iter().any(|l| l.name == "major-change") {
- let cmnt = ErrorComment::new(
- &issue,
- "This is not a major change (it lacks the `major-change` label).",
- );
- cmnt.post(&ctx.github).await?;
- return Ok(());
- }
- if !issue.labels().iter().any(|l| l.name == "major-change") {
- let cmnt = ErrorComment::new(
- &issue,
- "This is not a major change (it lacks the `major-change` label).",
- );
- cmnt.post(&ctx.github).await?;
- return Ok(());
- }
- let is_team_member =
- if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github).await {
- false
- } else {
- true
- };
- if !is_team_member {
- let cmnt = ErrorComment::new(&issue, "Only team members can second issues.");
- cmnt.post(&ctx.github).await?;
- return Ok(());
- }
- (format!(
- "@*{}*: Proposal [#{}]({}) has been seconded, and will be approved in 10 days if no objections are raised.",
- config.zulip_ping,
- issue.number,
- event.html_url().unwrap()
- ), config.second_label.clone())
- }
- Invocation::NewProposal => {
- if !issue.labels().iter().any(|l| l.name == "major-change") {
- let cmnt = ErrorComment::new(
- &issue,
- "This is not a major change (it lacks the `major-change` label).",
- );
- cmnt.post(&ctx.github).await?;
- return Ok(());
- }
- (
- format!(
- "A new proposal has been announced: [#{}]({}). It will be
- announced at the next meeting to try and draw attention to it,
- but usually MCPs are not discussed during triage meetings. If
- you think this would benefit from discussion amongst the
- team, consider proposing a design meeting.",
- issue.number,
- event.html_url().unwrap()
- ),
- config.meeting_label.clone(),
- )
- }
- };
- let mut labels = issue.labels().to_owned();
- labels.push(github::Label { name: label_to_add });
- let github_req = issue.set_labels(&ctx.github, labels);
- let mut zulip_topic = format!(" {}", issue.zulip_topic_reference());
- // We prepend the issue title, truncating such that the overall length does
- // not exceed 60 characters (a Zulip limitation).
- zulip_topic.insert_str(
- 0,
- &issue.title[..std::cmp::min(issue.title.len(), 60 - zulip_topic.len())],
- );
- let zulip_stream = config.zulip_stream.to_string();
- let zulip_req = crate::zulip::MessageApiRequest {
- type_: "stream",
- to: &zulip_stream,
- topic: Some(&zulip_topic),
- content: &zulip_msg,
- };
- let topic_url = zulip_req.url();
- let comment = format!(
- "This issue is not meant to be used for technical discussion. \
- There is a Zulip [stream] for that. Use this issue to leave \
- procedural comments, such as volunteering to review, indicating that you \
- second the proposal (or third, etc), or raising a concern that you would \
- like to be addressed. \
- \n\n[stream]: {}",
- topic_url
- );
- issue
- .post_comment(&ctx.github, &comment)
- .await
- .context("post major change comment")?;
- let zulip_req = zulip_req.send(&ctx.github.raw());
- let (gh_res, zulip_res) = futures::join!(github_req, zulip_req);
- gh_res?;
- zulip_res?;
- Ok(())
- }
|