assign.rs 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. //! Permit assignment of any user to issues, without requiring "write" access to the repository.
  2. //!
  3. //! We need to fake-assign ourselves and add a 'claimed by' section to the top-level comment.
  4. //!
  5. //! Such assigned issues should also be placed in a queue to ensure that the user remains
  6. //! active; the assigned user will be asked for a status report every 2 weeks (XXX: timing).
  7. //!
  8. //! If we're intending to ask for a status report but no comments from the assigned user have
  9. //! been given for the past 2 weeks, the bot will de-assign the user. They can once more claim
  10. //! the issue if necessary.
  11. //!
  12. //! Assign users with `@rustbot assign @gh-user` or `@rustbot claim` (self-claim).
  13. use crate::{
  14. config::AssignConfig,
  15. github::{self, Event},
  16. handlers::{Context, Handler},
  17. interactions::EditIssueBody,
  18. };
  19. use failure::{Error, ResultExt};
  20. use parser::command::assign::AssignCommand;
  21. use parser::command::{Command, Input};
  22. pub(super) struct AssignmentHandler;
  23. #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
  24. struct AssignData {
  25. user: Option<String>,
  26. }
  27. impl Handler for AssignmentHandler {
  28. type Input = AssignCommand;
  29. type Config = AssignConfig;
  30. fn parse_input(&self, ctx: &Context, event: &Event) -> Result<Option<Self::Input>, Error> {
  31. #[allow(irrefutable_let_patterns)]
  32. let event = if let Event::IssueComment(e) = event {
  33. e
  34. } else {
  35. // not interested in other events
  36. return Ok(None);
  37. };
  38. let mut input = Input::new(&event.comment.body, &ctx.username);
  39. match input.parse_command() {
  40. Command::Assign(Ok(command)) => Ok(Some(command)),
  41. Command::Assign(Err(err)) => {
  42. failure::bail!(
  43. "Parsing assign command in [comment]({}) failed: {}",
  44. event.comment.html_url,
  45. err
  46. );
  47. }
  48. _ => Ok(None),
  49. }
  50. }
  51. fn handle_input(
  52. &self,
  53. ctx: &Context,
  54. _config: &AssignConfig,
  55. event: &Event,
  56. cmd: AssignCommand,
  57. ) -> Result<(), Error> {
  58. #[allow(irrefutable_let_patterns)]
  59. let event = if let Event::IssueComment(e) = event {
  60. e
  61. } else {
  62. // not interested in other events
  63. return Ok(());
  64. };
  65. let e = EditIssueBody::new(&event.issue, "ASSIGN");
  66. let to_assign = match cmd {
  67. AssignCommand::Own => event.comment.user.login.clone(),
  68. AssignCommand::User { username } => {
  69. if let Err(_) | Ok(false) = event.comment.user.is_team_member(&ctx.github) {
  70. if username != event.comment.user.login {
  71. failure::bail!("Only Rust team members can assign other users");
  72. }
  73. }
  74. username.clone()
  75. }
  76. AssignCommand::Release => {
  77. let current = e.current_data();
  78. if current
  79. == Some(AssignData {
  80. user: Some(event.comment.user.login.clone()),
  81. })
  82. {
  83. event.issue.remove_assignees(&ctx.github)?;
  84. e.apply(&ctx.github, String::new(), AssignData { user: None })?;
  85. return Ok(());
  86. } else if current.map(|d| d.user.is_some()).unwrap_or(false) {
  87. failure::bail!("Cannot release another user's assignment");
  88. } else {
  89. failure::bail!("Cannot release unassigned issue");
  90. }
  91. }
  92. };
  93. let data = AssignData {
  94. user: Some(to_assign.clone()),
  95. };
  96. e.apply(&ctx.github, String::new(), &data)?;
  97. match event.issue.set_assignee(&ctx.github, &to_assign) {
  98. Ok(()) => return Ok(()), // we are done
  99. Err(github::AssignmentError::InvalidAssignee) => {
  100. event
  101. .issue
  102. .set_assignee(&ctx.github, &ctx.username)
  103. .context("self-assignment failed")?;
  104. e.apply(
  105. &ctx.github,
  106. format!(
  107. "This issue has been assigned to @{} via [this comment]({}).",
  108. to_assign, event.comment.html_url
  109. ),
  110. &data,
  111. )?;
  112. }
  113. Err(e) => return Err(e.into()),
  114. }
  115. Ok(())
  116. }
  117. }