Skip to content

Commit 829bbbc

Browse files
authored
fix: github-bot multiple fixes on ReviewBy requirements (#4145)
This PR: - f99df53 Improves the `gnoweb` review rule to make it fit with [this description](#3238 (comment)): > a. If Alexis or Guilhem is the author of a PR related to gnoweb, the other is assigned as a reviewer. b. If someone else is the author of a PR related to gnoweb, both are assigned as reviewers, but only one review from one of them is required. - 7088bff Prevents [an error](#3238 (comment)) when the bot try to request a review from the PR author. - 24b4738 Adds a `RequestAction` mechanism similar to the `LabelAction` to the `ReviewByUser` and `ReviewByTeam` requirements.
1 parent 53d50b7 commit 829bbbc

File tree

3 files changed

+347
-163
lines changed

3 files changed

+347
-163
lines changed

contribs/github-bot/internal/config/config.go

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
4848
Then: r.And(
4949
r.Or(
5050
r.AuthorInTeam(gh, "tech-staff"),
51-
r.ReviewByTeamMembers(gh, "tech-staff").WithDesiredState(utils.ReviewStateApproved),
51+
r.ReviewByTeamMembers(gh, "tech-staff", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
5252
),
5353
r.Or(
5454
r.AuthorInTeam(gh, "devrels"),
55-
r.ReviewByTeamMembers(gh, "devrels").WithDesiredState(utils.ReviewStateApproved),
55+
r.ReviewByTeamMembers(gh, "devrels", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
5656
),
5757
),
5858
},
@@ -63,8 +63,26 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
6363
c.FileChanged(gh, "^gno.land/pkg/gnoweb/"),
6464
),
6565
Then: r.Or(
66-
r.ReviewByUser(gh, "alexiscolin"),
67-
r.ReviewByUser(gh, "gfanton"),
66+
// If alexiscolin or gfanton is the author of the PR, the other must review it.
67+
r.Or(
68+
r.And(
69+
r.Author("alexiscolin"),
70+
r.ReviewByUser(gh, "gfanton", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
71+
),
72+
r.And(
73+
r.Author("gfanton"),
74+
r.ReviewByUser(gh, "alexiscolin", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
75+
),
76+
),
77+
// If neither of them is the author of the PR, at least one of them must review it.
78+
r.And(
79+
r.Not(r.Author("alexiscolin")),
80+
r.Not(r.Author("gfanton")),
81+
r.Or(
82+
r.ReviewByUser(gh, "alexiscolin", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
83+
r.ReviewByUser(gh, "gfanton", r.RequestApply).WithDesiredState(utils.ReviewStateApproved),
84+
),
85+
),
6886
),
6987
},
7088
{
@@ -81,16 +99,29 @@ func Config(gh *client.GitHub) ([]AutomaticCheck, []ManualCheck) {
8199
Then: r.
82100
If(r.Or(
83101
r.ReviewByOrgMembers(gh).WithDesiredState(utils.ReviewStateApproved),
84-
r.ReviewByTeamMembers(gh, "tech-staff"),
102+
r.ReviewByTeamMembers(gh, "tech-staff", r.RequestIgnore),
85103
r.Draft(),
86104
)).
87105
// Either there was a first approval from a member, and we
88-
// assert that the label for triage-pending is removed...
89-
Then(r.Not(r.Label(gh, "review/triage-pending", r.LabelRemove))).
90-
// Or there was not, and we apply the triage pending label.
106+
// assert that the label for triage-pending is removed and we
107+
// request a review from the tech-staff team...
108+
Then(
109+
r.And(
110+
r.Not(r.Label(gh, "review/triage-pending", r.LabelRemove)),
111+
r.ReviewByTeamMembers(gh, "tech-staff", r.RequestApply),
112+
),
113+
).
114+
// Or there was not, and we apply the triage-pending label
115+
// and remove the request for review from the tech-staff team.
91116
// The requirement should always fail, to mark the PR is not
92117
// ready to be merged.
93-
Else(r.And(r.Label(gh, "review/triage-pending", r.LabelApply), r.Never())),
118+
Else(
119+
r.And(
120+
r.Label(gh, "review/triage-pending", r.LabelApply),
121+
r.ReviewByTeamMembers(gh, "tech-staff", r.RequestRemove),
122+
r.Never(), // Always fail the requirement.
123+
),
124+
),
94125
},
95126
}
96127

contribs/github-bot/internal/requirements/reviewer.go

Lines changed: 107 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,18 @@ import (
1111
"github.com/xlab/treeprint"
1212
)
1313

14+
// RequestAction controls what to do about the review request.
15+
type RequestAction byte
16+
17+
const (
18+
// RequestApply will request a review from the user/team if not already requested.
19+
RequestApply = iota
20+
// RequestRemove will remove the review request from the user/team.
21+
RequestRemove
22+
// RequestIgnore always leaves the review request as it is.
23+
RequestIgnore
24+
)
25+
1426
// deduplicateReviews returns a list of reviews with at most 1 review per
1527
// author, where approval/changes requested reviews are preferred over comments
1628
// and later reviews are preferred over earlier ones.
@@ -60,6 +72,7 @@ type ReviewByUserRequirement struct {
6072
gh *client.GitHub
6173
user string
6274
desiredState string
75+
action RequestAction
6376
}
6477

6578
var _ Requirement = &ReviewByUserRequirement{}
@@ -88,8 +101,9 @@ func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details tr
88101
}
89102
r.gh.Logger.Debugf("User %s has not reviewed PR %d yet", r.user, pr.GetNumber())
90103

91-
// If not a dry run, make the user a reviewer if he's not already.
92-
if !r.gh.DryRun {
104+
// If not a dry run, change the review request according to the action.
105+
if !r.gh.DryRun && r.action != RequestIgnore {
106+
// Check if this user is already requested for review.
93107
requested := false
94108
reviewers, err := r.gh.ListPRReviewers(pr.GetNumber())
95109
if err != nil {
@@ -104,20 +118,44 @@ func (r *ReviewByUserRequirement) IsSatisfied(pr *github.PullRequest, details tr
104118
}
105119
}
106120

107-
if requested {
108-
r.gh.Logger.Debugf("Review of user %s already requested on PR %d", r.user, pr.GetNumber())
109-
} else {
110-
r.gh.Logger.Debugf("Requesting review from user %s on PR %d", r.user, pr.GetNumber())
111-
if _, _, err := r.gh.Client.PullRequests.RequestReviewers(
112-
r.gh.Ctx,
113-
r.gh.Owner,
114-
r.gh.Repo,
115-
pr.GetNumber(),
116-
github.ReviewersRequest{
117-
Reviewers: []string{r.user},
118-
},
119-
); err != nil {
120-
r.gh.Logger.Errorf("Unable to request review from user %s on PR %d: %v", r.user, pr.GetNumber(), err)
121+
switch r.action {
122+
case RequestApply:
123+
switch {
124+
case requested:
125+
r.gh.Logger.Debugf("Review of user %s already requested on PR %d", r.user, pr.GetNumber())
126+
case r.user == pr.GetUser().GetLogin():
127+
r.gh.Logger.Debugf("Review of user %s is not requested on PR %d because he's the author", r.user, pr.GetNumber())
128+
default:
129+
r.gh.Logger.Debugf("Requesting review from user %s on PR %d", r.user, pr.GetNumber())
130+
if _, _, err := r.gh.Client.PullRequests.RequestReviewers(
131+
r.gh.Ctx,
132+
r.gh.Owner,
133+
r.gh.Repo,
134+
pr.GetNumber(),
135+
github.ReviewersRequest{
136+
Reviewers: []string{r.user},
137+
},
138+
); err != nil {
139+
r.gh.Logger.Errorf("Unable to request review from user %s on PR %d: %v", r.user, pr.GetNumber(), err)
140+
}
141+
}
142+
case RequestRemove:
143+
switch {
144+
case !requested:
145+
r.gh.Logger.Debugf("Review of user %s already not requested on PR %d", r.user, pr.GetNumber())
146+
default:
147+
r.gh.Logger.Debugf("Removing review request from user %s on PR %d", r.user, pr.GetNumber())
148+
if _, err := r.gh.Client.PullRequests.RemoveReviewers(
149+
r.gh.Ctx,
150+
r.gh.Owner,
151+
r.gh.Repo,
152+
pr.GetNumber(),
153+
github.ReviewersRequest{
154+
Reviewers: []string{r.user},
155+
},
156+
); err != nil {
157+
r.gh.Logger.Errorf("Unable to remove review request from user %s on PR %d: %v", r.user, pr.GetNumber(), err)
158+
}
121159
}
122160
}
123161
}
@@ -138,8 +176,12 @@ func (r *ReviewByUserRequirement) WithDesiredState(s utils.ReviewState) *ReviewB
138176
}
139177

140178
// ReviewByUser asserts that the PR has been reviewed by the given user.
141-
func ReviewByUser(gh *client.GitHub, user string) *ReviewByUserRequirement {
142-
return &ReviewByUserRequirement{gh: gh, user: user}
179+
func ReviewByUser(gh *client.GitHub, user string, action RequestAction) *ReviewByUserRequirement {
180+
return &ReviewByUserRequirement{
181+
gh: gh,
182+
user: user,
183+
action: action,
184+
}
143185
}
144186

145187
// ReviewByTeamMembersRequirement asserts that count members of the given team
@@ -150,6 +192,7 @@ type ReviewByTeamMembersRequirement struct {
150192
team string
151193
count uint
152194
desiredState string
195+
action RequestAction
153196
}
154197

155198
var _ Requirement = &ReviewByTeamMembersRequirement{}
@@ -177,7 +220,8 @@ func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, det
177220

178221
// If not a dry run, request a team review if no member has reviewed yet,
179222
// and the team review has not been requested.
180-
if !r.gh.DryRun {
223+
if !r.gh.DryRun && r.action != RequestIgnore {
224+
// Check if the team or any of its members are already requested for review.
181225
var teamRequested bool
182226
var usersRequested []string
183227

@@ -210,24 +254,45 @@ func (r *ReviewByTeamMembersRequirement) IsSatisfied(pr *github.PullRequest, det
210254
}
211255
}
212256

213-
switch {
214-
case teamRequested:
215-
r.gh.Logger.Debugf("Review of team %s already requested on PR %d", r.team, pr.GetNumber())
216-
case len(usersRequested) > 0:
217-
r.gh.Logger.Debugf("Members %v of team %s already requested on (or reviewed) PR %d",
218-
usersRequested, r.team, pr.GetNumber())
219-
default:
220-
r.gh.Logger.Debugf("Requesting review from team %s on PR %d", r.team, pr.GetNumber())
221-
if _, _, err := r.gh.Client.PullRequests.RequestReviewers(
222-
r.gh.Ctx,
223-
r.gh.Owner,
224-
r.gh.Repo,
225-
pr.GetNumber(),
226-
github.ReviewersRequest{
227-
TeamReviewers: []string{r.team},
228-
},
229-
); err != nil {
230-
r.gh.Logger.Errorf("Unable to request review from team %s on PR %d: %v", r.team, pr.GetNumber(), err)
257+
switch r.action {
258+
case RequestApply:
259+
switch {
260+
case teamRequested:
261+
r.gh.Logger.Debugf("Review of team %s already requested on PR %d", r.team, pr.GetNumber())
262+
case len(usersRequested) > 0:
263+
r.gh.Logger.Debugf("Members %v of team %s already requested on (or reviewed) PR %d",
264+
usersRequested, r.team, pr.GetNumber())
265+
default:
266+
r.gh.Logger.Debugf("Requesting review from team %s on PR %d", r.team, pr.GetNumber())
267+
if _, _, err := r.gh.Client.PullRequests.RequestReviewers(
268+
r.gh.Ctx,
269+
r.gh.Owner,
270+
r.gh.Repo,
271+
pr.GetNumber(),
272+
github.ReviewersRequest{
273+
TeamReviewers: []string{r.team},
274+
},
275+
); err != nil {
276+
r.gh.Logger.Errorf("Unable to request review from team %s on PR %d: %v", r.team, pr.GetNumber(), err)
277+
}
278+
}
279+
case RequestRemove:
280+
switch {
281+
case !teamRequested:
282+
r.gh.Logger.Debugf("Review of team %s already not requested on PR %d", r.team, pr.GetNumber())
283+
default:
284+
r.gh.Logger.Debugf("Removing review request from team %s on PR %d", r.team, pr.GetNumber())
285+
if _, err := r.gh.Client.PullRequests.RemoveReviewers(
286+
r.gh.Ctx,
287+
r.gh.Owner,
288+
r.gh.Repo,
289+
pr.GetNumber(),
290+
github.ReviewersRequest{
291+
TeamReviewers: []string{r.team},
292+
},
293+
); err != nil {
294+
r.gh.Logger.Errorf("Unable to remove review request from team %s on PR %d: %v", r.team, pr.GetNumber(), err)
295+
}
231296
}
232297
}
233298
}
@@ -283,11 +348,12 @@ func (r *ReviewByTeamMembersRequirement) WithDesiredState(s utils.ReviewState) *
283348
//
284349
// The number of required reviews, or the state of the reviews (e.g., to filter
285350
// only for approval reviews) can be modified using WithCount and WithDesiredState.
286-
func ReviewByTeamMembers(gh *client.GitHub, team string) *ReviewByTeamMembersRequirement {
351+
func ReviewByTeamMembers(gh *client.GitHub, team string, action RequestAction) *ReviewByTeamMembersRequirement {
287352
return &ReviewByTeamMembersRequirement{
288-
gh: gh,
289-
team: team,
290-
count: 1,
353+
gh: gh,
354+
team: team,
355+
count: 1,
356+
action: action,
291357
}
292358
}
293359

0 commit comments

Comments
 (0)