nominate.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. //! Purpose: Allow team members to nominate issues or PRs.
  2. use crate::{
  3. config::NominateConfig,
  4. github::{self, Event},
  5. handlers::{Context, Handler},
  6. interactions::ErrorComment,
  7. };
  8. use futures::future::{BoxFuture, FutureExt};
  9. use parser::command::nominate::{NominateCommand, Style};
  10. use parser::command::{Command, Input};
  11. pub(super) struct NominateHandler;
  12. impl Handler for NominateHandler {
  13. type Input = NominateCommand;
  14. type Config = NominateConfig;
  15. fn parse_input(
  16. &self,
  17. ctx: &Context,
  18. event: &Event,
  19. _: Option<&Self::Config>,
  20. ) -> Result<Option<Self::Input>, String> {
  21. let body = if let Some(b) = event.comment_body() {
  22. b
  23. } else {
  24. // not interested in other events
  25. return Ok(None);
  26. };
  27. if let Event::Issue(e) = event {
  28. if !matches!(e.action, github::IssuesAction::Opened | github::IssuesAction::Edited) {
  29. // skip events other than opening or editing the issue to avoid retriggering commands in the
  30. // issue body
  31. return Ok(None);
  32. }
  33. }
  34. let mut input = Input::new(&body, &ctx.username);
  35. let command = input.parse_command();
  36. if let Some(previous) = event.comment_from() {
  37. let mut prev_input = Input::new(&previous, &ctx.username);
  38. let prev_command = prev_input.parse_command();
  39. if command == prev_command {
  40. return Ok(None);
  41. }
  42. }
  43. match command {
  44. Command::Nominate(Ok(command)) => Ok(Some(command)),
  45. Command::Nominate(Err(err)) => {
  46. return Err(format!(
  47. "Parsing nominate command in [comment]({}) failed: {}",
  48. event.html_url().expect("has html url"),
  49. err
  50. ));
  51. }
  52. _ => Ok(None),
  53. }
  54. }
  55. fn handle_input<'a>(
  56. &self,
  57. ctx: &'a Context,
  58. config: &'a Self::Config,
  59. event: &'a Event,
  60. input: Self::Input,
  61. ) -> BoxFuture<'a, anyhow::Result<()>> {
  62. handle_input(ctx, config, event, input).boxed()
  63. }
  64. }
  65. async fn handle_input(
  66. ctx: &Context,
  67. config: &NominateConfig,
  68. event: &Event,
  69. cmd: NominateCommand,
  70. ) -> anyhow::Result<()> {
  71. let is_team_member = if let Err(_) | Ok(false) = event.user().is_team_member(&ctx.github).await
  72. {
  73. false
  74. } else {
  75. true
  76. };
  77. if !is_team_member {
  78. let cmnt = ErrorComment::new(
  79. &event.issue().unwrap(),
  80. format!(
  81. "Nominating and approving issues and pull requests is restricted to members of\
  82. the Rust teams."
  83. ),
  84. );
  85. cmnt.post(&ctx.github).await?;
  86. return Ok(());
  87. }
  88. let mut issue_labels = event.issue().unwrap().labels().to_owned();
  89. if cmd.style == Style::BetaApprove {
  90. if !issue_labels.iter().any(|l| l.name == "beta-nominated") {
  91. let cmnt = ErrorComment::new(
  92. &event.issue().unwrap(),
  93. format!(
  94. "This pull request is not beta-nominated, so it cannot be approved yet.\
  95. Perhaps try to beta-nominate it by using `@{} beta-nominate <team>`?",
  96. ctx.username,
  97. ),
  98. );
  99. cmnt.post(&ctx.github).await?;
  100. return Ok(());
  101. }
  102. // Add the beta-accepted label, but don't attempt to remove beta-nominated or the team
  103. // label.
  104. if !issue_labels.iter().any(|l| l.name == "beta-accepted") {
  105. issue_labels.push(github::Label {
  106. name: "beta-accepted".into(),
  107. });
  108. }
  109. } else {
  110. if !config.teams.contains_key(&cmd.team) {
  111. let cmnt = ErrorComment::new(
  112. &event.issue().unwrap(),
  113. format!(
  114. "This team (`{}`) cannot be nominated for via this command;\
  115. it may need to be added to `triagebot.toml` on the master branch.",
  116. cmd.team,
  117. ),
  118. );
  119. cmnt.post(&ctx.github).await?;
  120. return Ok(());
  121. }
  122. let label = config.teams[&cmd.team].clone();
  123. if !issue_labels.iter().any(|l| l.name == label) {
  124. issue_labels.push(github::Label { name: label });
  125. }
  126. let style_label = match cmd.style {
  127. Style::Decision => "I-nominated",
  128. Style::Beta => "beta-nominated",
  129. Style::BetaApprove => unreachable!(),
  130. };
  131. if !issue_labels.iter().any(|l| l.name == style_label) {
  132. issue_labels.push(github::Label {
  133. name: style_label.into(),
  134. });
  135. }
  136. }
  137. if &issue_labels[..] != event.issue().unwrap().labels() {
  138. event
  139. .issue()
  140. .unwrap()
  141. .set_labels(&ctx.github, issue_labels)
  142. .await?;
  143. }
  144. Ok(())
  145. }