1
1
//! This module updates the PR workqueue of the Rust project contributors
2
+ //! Runs after a PR has been assigned or unassigned
2
3
//!
3
4
//! Purpose:
4
5
//!
5
- //! - Adds the PR to the workqueue of one team member (when the PR has been assigned)
6
- //! - Removes the PR from the workqueue of one team member (when the PR is unassigned or closed)
6
+ //! - Adds the PR to the workqueue of one team member (after the PR has been assigned)
7
+ //! - Removes the PR from the workqueue of one team member (after the PR has been unassigned or closed)
7
8
8
9
use crate :: {
9
10
config:: ReviewPrefsConfig ,
10
11
db:: notifications:: record_username,
11
12
github:: { IssuesAction , IssuesEvent } ,
12
13
handlers:: Context ,
14
+ ReviewPrefs ,
13
15
} ;
14
16
use anyhow:: Context as _;
15
17
use tokio_postgres:: Client as DbClient ;
16
18
19
+ use super :: assign:: { FindReviewerError , REVIEWER_HAS_NO_CAPACITY , SELF_ASSIGN_HAS_NO_CAPACITY } ;
20
+
17
21
pub ( super ) struct ReviewPrefsInput { }
18
22
19
23
pub ( super ) async fn parse_input (
@@ -49,7 +53,7 @@ pub(super) async fn handle_input<'a>(
49
53
) -> anyhow:: Result < ( ) > {
50
54
let db_client = ctx. db . get ( ) . await ;
51
55
52
- // extract the assignee matching the assignment or unassignment enum variants or return and ignore this handler
56
+ // extract the assignee or ignore this handler and return
53
57
let IssuesEvent {
54
58
action : IssuesAction :: Assigned { assignee } | IssuesAction :: Unassigned { assignee } ,
55
59
..
@@ -66,18 +70,60 @@ pub(super) async fn handle_input<'a>(
66
70
if matches ! ( event. action, IssuesAction :: Unassigned { .. } ) {
67
71
delete_pr_from_workqueue ( & db_client, assignee. id . unwrap ( ) , event. issue . number )
68
72
. await
69
- . context ( "Failed to remove PR from workqueue " ) ?;
73
+ . context ( "Failed to remove PR from work queue " ) ?;
70
74
}
71
75
76
+ // This handler is reached also when assigning a PR using the Github UI
77
+ // (i.e. from the "Assignees" dropdown menu).
78
+ // We need to also check assignee availability here.
72
79
if matches ! ( event. action, IssuesAction :: Assigned { .. } ) {
80
+ let work_queue = has_user_capacity ( & db_client, & assignee. login )
81
+ . await
82
+ . context ( "Failed to retrieve user work queue" ) ;
83
+
84
+ // if user has no capacity, revert the PR assignment (GitHub has already assigned it)
85
+ // and post a comment suggesting what to do
86
+ if let Err ( _) = work_queue {
87
+ event
88
+ . issue
89
+ . remove_assignees ( & ctx. github , crate :: github:: Selection :: One ( & assignee. login ) )
90
+ . await ?;
91
+
92
+ let msg = if assignee. login . to_lowercase ( ) == event. issue . user . login . to_lowercase ( ) {
93
+ SELF_ASSIGN_HAS_NO_CAPACITY . replace ( "{username}" , & assignee. login )
94
+ } else {
95
+ REVIEWER_HAS_NO_CAPACITY . replace ( "{username}" , & assignee. login )
96
+ } ;
97
+ event. issue . post_comment ( & ctx. github , & msg) . await ?;
98
+ }
99
+
73
100
upsert_pr_into_workqueue ( & db_client, assignee. id . unwrap ( ) , event. issue . number )
74
101
. await
75
- . context ( "Failed to add PR to workqueue " ) ?;
102
+ . context ( "Failed to add PR to work queue " ) ?;
76
103
}
77
104
78
105
Ok ( ( ) )
79
106
}
80
107
108
+ pub async fn has_user_capacity (
109
+ db : & crate :: db:: PooledClient ,
110
+ assignee : & str ,
111
+ ) -> anyhow:: Result < ReviewPrefs , FindReviewerError > {
112
+ let q = "
113
+ SELECT username, r.*
114
+ FROM review_prefs r
115
+ JOIN users ON users.user_id = r.user_id
116
+ WHERE username = $1
117
+ AND CARDINALITY(r.assigned_prs) < max_assigned_prs;" ;
118
+ let rec = db. query_one ( q, & [ & assignee] ) . await ;
119
+ if let Err ( _) = rec {
120
+ return Err ( FindReviewerError :: ReviewerHasNoCapacity {
121
+ username : assignee. to_string ( ) ,
122
+ } ) ;
123
+ }
124
+ Ok ( rec. unwrap ( ) . into ( ) )
125
+ }
126
+
81
127
/// Add a PR to the workqueue of a team member.
82
128
/// Ensures no accidental PR duplicates.
83
129
async fn upsert_pr_into_workqueue (
0 commit comments