Skip to content

Allow assigning to users on vacation #1920

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 41 additions & 13 deletions src/handlers/assign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER: &str =

const ON_VACATION_WARNING: &str = "{username} is on vacation.

Please choose another assignee.";
They may take a while to respond.";

const NON_DEFAULT_BRANCH: &str =
"Pull requests are usually filed against the {default} branch for this repo, \
Expand Down Expand Up @@ -342,7 +342,8 @@ async fn determine_assignee(
return Ok((Some(name.to_string()), true));
}
// User included `r?` in the opening PR body.
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &[name]).await {
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, ctx, &[name]).await
{
Ok(assignee) => return Ok((Some(assignee), true)),
Err(e) => {
event
Expand All @@ -356,8 +357,15 @@ async fn determine_assignee(
// Errors fall-through to try fallback group.
match find_reviewers_from_diff(config, diff) {
Ok(candidates) if !candidates.is_empty() => {
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &candidates)
.await
match find_reviewer_from_names(
&db_client,
&teams,
config,
&event.issue,
ctx,
&candidates,
)
.await
{
Ok(assignee) => return Ok((Some(assignee), false)),
Err(FindReviewerError::TeamNotFound(team)) => log::warn!(
Expand Down Expand Up @@ -396,7 +404,9 @@ async fn determine_assignee(
}

if let Some(fallback) = config.adhoc_groups.get("fallback") {
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, fallback).await {
match find_reviewer_from_names(&db_client, &teams, config, &event.issue, ctx, fallback)
.await
{
Ok(assignee) => return Ok((Some(assignee), false)),
Err(e) => {
log::trace!(
Expand Down Expand Up @@ -507,6 +517,10 @@ pub(super) async fn handle_command(
// posts contain commands to instruct the user, not things that the bot
// should respond to.
if event.user().login == ctx.username.as_str() {
tracing::debug!(
"Received command from {}, which is the bot itself, ignoring",
ctx.username.as_str()
);
return Ok(());
}

Expand Down Expand Up @@ -605,6 +619,7 @@ pub(super) async fn handle_command(
&teams,
config,
issue,
ctx,
&[team_name.to_string()],
)
.await
Expand Down Expand Up @@ -809,6 +824,7 @@ async fn find_reviewer_from_names(
teams: &Teams,
config: &AssignConfig,
issue: &Issue,
ctx: &Context,
names: &[String],
) -> Result<String, FindReviewerError> {
let candidates = candidate_reviewers_from_names(teams, config, issue, names)?;
Expand Down Expand Up @@ -843,6 +859,24 @@ async fn find_reviewer_from_names(
return Ok("ghost".to_string());
}

let pick = candidates
.into_iter()
.choose(&mut rand::thread_rng())
.expect("candidate_reviewers_from_names should return at least one entry")
.to_string();

if config.is_on_vacation(&pick) {
let result = issue
.post_comment(
&ctx.github,
&ON_VACATION_WARNING.replace("{username}", &pick),
)
.await;
if let Err(err) = result {
tracing::error!(?err, "Failed to post vacation warning");
}
}

// filter out team members without capacity
// let filtered_candidates = filter_by_capacity(db, &candidates)
// .await
Expand All @@ -864,11 +898,7 @@ async fn find_reviewer_from_names(
// );

// Return unfiltered list of candidates
Ok(candidates
.into_iter()
.choose(&mut rand::thread_rng())
.expect("candidate_reviewers_from_names should return at least one entry")
.to_string())
Ok(pick)
}

/// Returns a list of candidate usernames (from relevant teams) to choose as a reviewer.
Expand Down Expand Up @@ -978,9 +1008,7 @@ fn candidate_reviewers_from_names<'a>(
}

// Assume it is a user.
if filter(&group_or_user) {
candidates.insert(group_or_user);
}
candidates.insert(group_or_user);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, but this tests more things than just vacation, right? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it also detects author assignment and re-assignment, which I think are fine to allow for direct assignments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, fair enough.

}
if candidates.is_empty() {
let initial = names.iter().cloned().collect();
Expand Down
10 changes: 3 additions & 7 deletions src/handlers/assign/tests/tests_candidates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,18 +273,14 @@ fn vacation() {
let config = toml::toml!(users_on_vacation = ["jyn514"]);
let issue = generic_issue("octocat", "rust-lang/rust");

// Test that `r? user` falls through to assigning from the team.
// See `determine_assignee` - ideally we would test that function directly instead of indirectly through `find_reviewer_from_names`.
let err_names = vec!["jyn514".into()];
// Test that the user on vacation can still be assigned manually.
test_from_names(
Some(teams.clone()),
config.clone(),
issue.clone(),
&["jyn514"],
Err(FindReviewerError::AllReviewersFiltered {
initial: err_names.clone(),
filtered: err_names,
}),
Ok(&["jyn514"]),

);

// Test that `r? bootstrap` doesn't assign from users on vacation.
Expand Down
Loading