Skip to content

Commit e914e5d

Browse files
authored
Merge pull request #1977 from Kobzol/assign-vacation-review-preferences
Take rotation mode into account when performing assignments
2 parents 075d776 + 72bf664 commit e914e5d

File tree

2 files changed

+83
-21
lines changed

2 files changed

+83
-21
lines changed

src/handlers/assign.rs

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
//! `assign.owners` config, it will auto-select an assignee based on the files
2121
//! the PR modifies.
2222
23-
use crate::db::review_prefs::get_review_prefs_batch;
23+
use crate::db::review_prefs::{get_review_prefs_batch, RotationMode};
2424
use crate::github::UserId;
2525
use crate::handlers::pr_tracking::ReviewerWorkqueue;
2626
use crate::{
@@ -75,9 +75,9 @@ Use `r?` to explicitly pick a reviewer";
7575
const RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER: &str =
7676
"@{author}: no appropriate reviewer found, use `r?` to override";
7777

78-
fn on_vacation_warning(username: &str) -> String {
78+
fn reviewer_off_rotation_message(username: &str) -> String {
7979
format!(
80-
r"{username} is on vacation.
80+
r"`{username}` is not available for reviewing at the moment.
8181
8282
Please choose another assignee."
8383
)
@@ -347,7 +347,7 @@ async fn determine_assignee(
347347
e @ FindReviewerError::NoReviewer { .. }
348348
| e @ FindReviewerError::ReviewerIsPrAuthor { .. }
349349
| e @ FindReviewerError::ReviewerAlreadyAssigned { .. }
350-
| e @ FindReviewerError::ReviewerOnVacation { .. }
350+
| e @ FindReviewerError::ReviewerOffRotation { .. }
351351
| e @ FindReviewerError::DatabaseError(_)
352352
| e @ FindReviewerError::ReviewerAtMaxCapacity { .. },
353353
) => log::trace!(
@@ -672,9 +672,10 @@ enum FindReviewerError {
672672
/// This could happen if there is a cyclical group or other misconfiguration.
673673
/// `initial` is the initial list of candidate names.
674674
NoReviewer { initial: Vec<String> },
675-
/// Requested reviewer is on vacation
676-
/// (i.e. username is in [users_on_vacation] in the triagebot.toml)
677-
ReviewerOnVacation { username: String },
675+
/// Requested reviewer is off the review rotation (e.g. on a vacation).
676+
/// Either the username is in [users_on_vacation] in `triagebot.toml` or the user has
677+
/// configured [RotationMode::OffRotation] in their reviewer preferences.
678+
ReviewerOffRotation { username: String },
678679
/// Requested reviewer is PR author
679680
ReviewerIsPrAuthor { username: String },
680681
/// Requested reviewer is already assigned to that PR
@@ -708,8 +709,8 @@ impl fmt::Display for FindReviewerError {
708709
initial.join(",")
709710
)
710711
}
711-
FindReviewerError::ReviewerOnVacation { username } => {
712-
write!(f, "{}", on_vacation_warning(username))
712+
FindReviewerError::ReviewerOffRotation { username } => {
713+
write!(f, "{}", reviewer_off_rotation_message(username))
713714
}
714715
FindReviewerError::ReviewerIsPrAuthor { username } => {
715716
write!(
@@ -955,7 +956,7 @@ async fn candidate_reviewers_from_names<'a>(
955956
username: candidate.clone(),
956957
})
957958
} else if is_on_vacation {
958-
Some(FindReviewerError::ReviewerOnVacation {
959+
Some(FindReviewerError::ReviewerOffRotation {
959960
username: candidate.clone(),
960961
})
961962
} else if is_already_assigned {
@@ -997,20 +998,22 @@ async fn candidate_reviewers_from_names<'a>(
997998
let username = username?;
998999

9991000
// If no review prefs were found, we assume the default unlimited
1000-
// review capacity.
1001+
// review capacity and being on rotation.
10011002
let Some(review_prefs) = review_prefs.get(username.as_str()) else {
10021003
return Ok(username);
10031004
};
1004-
let Some(capacity) = review_prefs.max_assigned_prs else {
1005-
return Ok(username);
1006-
};
1007-
let assigned_prs = workqueue.assigned_pr_count(review_prefs.user_id as UserId);
1008-
// Can we assign one more PR?
1009-
if (assigned_prs as i32) < capacity {
1010-
Ok(username)
1011-
} else {
1012-
Err(FindReviewerError::ReviewerAtMaxCapacity { username })
1005+
if let Some(capacity) = review_prefs.max_assigned_prs {
1006+
let assigned_prs = workqueue.assigned_pr_count(review_prefs.user_id as UserId);
1007+
// Is the reviewer at max capacity?
1008+
if (assigned_prs as i32) >= capacity {
1009+
return Err(FindReviewerError::ReviewerAtMaxCapacity { username });
1010+
}
1011+
}
1012+
if review_prefs.rotation_mode == RotationMode::OffRotation {
1013+
return Err(FindReviewerError::ReviewerOffRotation { username });
10131014
}
1015+
1016+
return Ok(username);
10141017
})
10151018
.collect();
10161019
}
@@ -1021,6 +1024,13 @@ async fn candidate_reviewers_from_names<'a>(
10211024
.filter_map(|res| res.as_deref().ok())
10221025
.collect();
10231026

1027+
log::debug!(
1028+
"Candidate reviewer results for review request `{}` on `{}`: {:?}",
1029+
names.join(", "),
1030+
issue.global_id(),
1031+
candidates
1032+
);
1033+
10241034
if valid_candidates.is_empty() {
10251035
// If we requested a single user for a review, we return a concrete error message
10261036
// describing why they couldn't be assigned.

src/handlers/assign/tests/tests_candidates.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,58 @@ async fn unlimited_capacity() {
243243
.await;
244244
}
245245

246+
#[tokio::test]
247+
async fn ignore_user_off_rotation_direct() {
248+
run_db_test(|ctx| async move {
249+
let user = user("martin", 1);
250+
review_prefs_test(ctx)
251+
.set_review_prefs(&user, None, RotationMode::OffRotation)
252+
.await
253+
.check(
254+
&["martin"],
255+
Err(FindReviewerError::ReviewerOffRotation {
256+
username: "martin".to_string(),
257+
}),
258+
)
259+
.await
260+
})
261+
.await;
262+
}
263+
264+
#[tokio::test]
265+
async fn ignore_user_off_rotation_through_team() {
266+
run_db_test(|ctx| async move {
267+
let teams = toml::toml!(compiler = ["martin", "diana"]);
268+
let user = user("martin", 1);
269+
review_prefs_test(ctx)
270+
.teams(&teams)
271+
.set_review_prefs(&user, None, RotationMode::OffRotation)
272+
.await
273+
.check(&["compiler"], Ok(&["diana"]))
274+
.await
275+
})
276+
.await;
277+
}
278+
279+
#[tokio::test]
280+
async fn review_prefs_prefer_capacity_before_rotation() {
281+
run_db_test(|ctx| async move {
282+
let user = user("martin", 1);
283+
review_prefs_test(ctx)
284+
.set_review_prefs(&user, Some(1), RotationMode::OffRotation)
285+
.await
286+
.assign_prs(user.id, 2)
287+
.check(
288+
&["martin"],
289+
Err(FindReviewerError::ReviewerAtMaxCapacity {
290+
username: "martin".to_string(),
291+
}),
292+
)
293+
.await
294+
})
295+
.await;
296+
}
297+
246298
#[tokio::test]
247299
async fn multiple_reviewers() {
248300
run_db_test(|ctx| async move {
@@ -462,7 +514,7 @@ async fn vacation() {
462514
.teams(&teams)
463515
.check(
464516
&["jyn514"],
465-
Err(FindReviewerError::ReviewerOnVacation {
517+
Err(FindReviewerError::ReviewerOffRotation {
466518
username: "jyn514".to_string(),
467519
}),
468520
)

0 commit comments

Comments
 (0)