Skip to content

Commit fedb383

Browse files
authored
Merge pull request #1895 from apiraino/track-reasons-filtered-out-candidates
Better tracking when candidates for PR assignment are filtered out
2 parents 0b5ed11 + 82c549a commit fedb383

File tree

2 files changed

+104
-22
lines changed

2 files changed

+104
-22
lines changed

src/handlers/assign.rs

Lines changed: 101 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ Use `r?` to explicitly pick a reviewer";
7070
const RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER: &str =
7171
"@{author}: no appropriate reviewer found, use `r?` to override";
7272

73-
const ON_VACATION_WARNING: &str = "{username} is on vacation. Please do not assign them to PRs.";
73+
const ON_VACATION_WARNING: &str = "{username} is on vacation.
74+
75+
Please choose another assignee.";
7476

7577
const NON_DEFAULT_BRANCH: &str =
7678
"Pull requests are usually filed against the {default} branch for this repo, \
@@ -105,9 +107,14 @@ Please contact us on [#t-infra](https://rust-lang.zulipchat.com/#narrow/stream/2
105107
106108
cc: @jackh726 @apiraino";
107109

108-
fn on_vacation_msg(user: &str) -> String {
109-
ON_VACATION_WARNING.replace("{username}", user)
110-
}
110+
const REVIEWER_IS_PR_AUTHOR: &str = "Pull request author cannot be assigned as reviewer.
111+
112+
Please choose another assignee.";
113+
114+
const REVIEWER_ALREADY_ASSIGNED: &str =
115+
"Requested reviewer is already assigned to this pull request.
116+
117+
Please choose another assignee.";
111118

112119
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
113120
struct AssignData {
@@ -354,16 +361,24 @@ async fn determine_assignee(
354361
is there maybe a misconfigured group?",
355362
event.issue.global_id()
356363
),
357-
// TODO: post a comment on the PR if the reviewers were filtered due to being on vacation
358364
Err(
359365
e @ FindReviewerError::NoReviewer { .. }
360366
| e @ FindReviewerError::AllReviewersFiltered { .. }
361367
| e @ FindReviewerError::NoReviewerHasCapacity
362-
| e @ FindReviewerError::ReviewerHasNoCapacity { .. },
368+
| e @ FindReviewerError::ReviewerHasNoCapacity { .. }
369+
| e @ FindReviewerError::ReviewerIsPrAuthor { .. }
370+
| e @ FindReviewerError::ReviewerAlreadyAssigned { .. },
363371
) => log::trace!(
364372
"no reviewer could be determined for PR {}: {e}",
365373
event.issue.global_id()
366374
),
375+
Err(e @ FindReviewerError::ReviewerOnVacation { .. }) => {
376+
// TODO: post a comment on the PR if the reviewer(s) were filtered due to being on vacation
377+
log::trace!(
378+
"no reviewer could be determined for PR {}: {e}",
379+
event.issue.global_id()
380+
)
381+
}
367382
}
368383
}
369384
// If no owners matched the diff, fall-through.
@@ -508,7 +523,10 @@ pub(super) async fn handle_command(
508523
{
509524
// This is a comment, so there must already be a reviewer assigned. No need to assign anyone else.
510525
issue
511-
.post_comment(&ctx.github, &on_vacation_msg(&username))
526+
.post_comment(
527+
&ctx.github,
528+
&ON_VACATION_WARNING.replace("{username}", &username),
529+
)
512530
.await?;
513531
return Ok(());
514532
}
@@ -544,8 +562,9 @@ pub(super) async fn handle_command(
544562
let work_queue = has_user_capacity(&db_client, &name).await;
545563
if work_queue.is_err() {
546564
// NOTE: disabled for now, just log
547-
log::info!(
548-
"DB reported that user {} has no review capacity. Ignoring.",
565+
log::warn!(
566+
"[#{}] PR self-assign failed, DB reported that user {} has no review capacity. Ignoring.",
567+
issue.number,
549568
name
550569
);
551570
// issue
@@ -702,6 +721,13 @@ pub enum FindReviewerError {
702721
/// The requested reviewer has no capacity to accept a pull request
703722
/// assignment at this time
704723
ReviewerHasNoCapacity { username: String },
724+
/// Requested reviewer is on vacation
725+
/// (i.e. username is in [users_on_vacation] in the triagebot.toml)
726+
ReviewerOnVacation { username: String },
727+
/// Requested reviewer is PR author
728+
ReviewerIsPrAuthor { username: String },
729+
/// Requested reviewer is already assigned to that PR
730+
ReviewerAlreadyAssigned { username: String },
705731
}
706732

707733
impl std::error::Error for FindReviewerError {}
@@ -747,6 +773,23 @@ impl fmt::Display for FindReviewerError {
747773
FindReviewerError::NoReviewerHasCapacity => {
748774
write!(f, "{}", NO_REVIEWER_HAS_CAPACITY)
749775
}
776+
FindReviewerError::ReviewerOnVacation { username } => {
777+
write!(f, "{}", ON_VACATION_WARNING.replace("{username}", username))
778+
}
779+
FindReviewerError::ReviewerIsPrAuthor { username } => {
780+
write!(
781+
f,
782+
"{}",
783+
REVIEWER_IS_PR_AUTHOR.replace("{username}", username)
784+
)
785+
}
786+
FindReviewerError::ReviewerAlreadyAssigned { username } => {
787+
write!(
788+
f,
789+
"{}",
790+
REVIEWER_ALREADY_ASSIGNED.replace("{username}", username)
791+
)
792+
}
750793
}
751794
}
752795
}
@@ -785,7 +828,11 @@ async fn find_reviewer_from_names(
785828
// These are all ideas for improving the selection here. However, I'm not
786829
// sure they are really worth the effort.
787830

788-
log::info!("Initial unfiltered list of candidates: {:?}", candidates);
831+
log::info!(
832+
"[#{}] Initial unfiltered list of candidates: {:?}",
833+
issue.number,
834+
candidates
835+
);
789836

790837
// Special case user "ghost", we always skip filtering
791838
if candidates.contains("ghost") {
@@ -799,14 +846,18 @@ async fn find_reviewer_from_names(
799846

800847
if filtered_candidates.is_empty() {
801848
// NOTE: disabled for now, just log
802-
log::info!("Filtered list of PR assignee is empty");
849+
log::info!("[#{}] Filtered list of PR assignee is empty", issue.number);
803850
// return Err(FindReviewerError::AllReviewersFiltered {
804851
// initial: names.to_vec(),
805852
// filtered: names.to_vec(),
806853
// });
807854
}
808855

809-
log::info!("Filtered list of candidates: {:?}", filtered_candidates);
856+
log::info!(
857+
"[#{}] Filtered list of candidates: {:?}",
858+
issue.number,
859+
filtered_candidates
860+
);
810861

811862
// Return unfiltered list of candidates
812863
Ok(candidates
@@ -862,21 +913,45 @@ fn candidate_reviewers_from_names<'a>(
862913
let mut group_expansion: Vec<&str> = names.iter().map(|n| n.as_str()).collect();
863914
// Keep track of which users get filtered out for a better error message.
864915
let mut filtered = Vec::new();
916+
// For debugging purposes, keep track about /why/ candidates were filtered out
917+
let mut filtered_debug: HashMap<String, Option<FindReviewerError>> = HashMap::new();
865918
let repo = issue.repository();
866919
let org_prefix = format!("{}/", repo.organization);
867920
// Don't allow groups or teams to include the current author or assignee.
868921
let mut filter = |name: &&str| -> bool {
869922
let name_lower = name.to_lowercase();
870-
let ok = name_lower != issue.user.login.to_lowercase()
871-
&& !config.is_on_vacation(name)
872-
&& !issue
873-
.assignees
874-
.iter()
875-
.any(|assignee| name_lower == assignee.login.to_lowercase());
876-
if !ok {
923+
let is_pr_author = name_lower == issue.user.login.to_lowercase();
924+
let is_on_vacation = config.is_on_vacation(name);
925+
let is_already_assigned = issue
926+
.assignees
927+
.iter()
928+
.any(|assignee| name_lower == assignee.login.to_lowercase());
929+
930+
// Record the reason why the candidate was filtered out
931+
let reason = {
932+
if is_pr_author {
933+
Some(FindReviewerError::ReviewerIsPrAuthor {
934+
username: name.to_string(),
935+
})
936+
} else if is_on_vacation {
937+
Some(FindReviewerError::ReviewerOnVacation {
938+
username: name.to_string(),
939+
})
940+
} else if is_already_assigned {
941+
Some(FindReviewerError::ReviewerAlreadyAssigned {
942+
username: name.to_string(),
943+
})
944+
} else {
945+
None
946+
}
947+
};
948+
949+
let can_be_assigned = !is_pr_author && !is_on_vacation && !is_already_assigned;
950+
if !can_be_assigned {
877951
filtered.push(name.to_string());
952+
filtered_debug.insert(name.to_string(), reason);
878953
}
879-
ok
954+
can_be_assigned
880955
};
881956

882957
// Loop over groups to recursively expand them.
@@ -934,6 +1009,12 @@ fn candidate_reviewers_from_names<'a>(
9341009
if filtered.is_empty() {
9351010
Err(FindReviewerError::NoReviewer { initial })
9361011
} else {
1012+
log::warn!(
1013+
"[#{}] Initial list of candidates {:?}, filtered-out with reasons: {:?}",
1014+
issue.number,
1015+
initial,
1016+
filtered_debug
1017+
);
9371018
Err(FindReviewerError::AllReviewersFiltered { initial, filtered })
9381019
}
9391020
} else {

src/handlers/pr_tracking.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ pub(super) async fn handle_input<'a>(
8585
// if user has no capacity, revert the PR assignment (GitHub has already assigned it)
8686
// and post a comment suggesting what to do
8787
if let Err(_) = work_queue {
88-
log::info!(
89-
"DB reported that user {} has no review capacity. Ignoring.",
88+
log::warn!(
89+
"[#{}] DB reported that user {} has no review capacity. Ignoring.",
90+
event.issue.number,
9091
&assignee.login
9192
);
9293

0 commit comments

Comments
 (0)