Skip to content

Commit cdca4de

Browse files
Merge pull request #72 from form3tech-oss/wp-groups
feat: Add comment on pr when user approved request but is not member …
2 parents c980455 + e9aa863 commit cdca4de

File tree

10 files changed

+441
-22
lines changed

10 files changed

+441
-22
lines changed

internal/api/api_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ func TestWhenPRReviewedByNonTeamMemberAndAuthorsArePartOfTeam(t *testing.T) {
328328
then.
329329
ExpectPendingAnswerReturned().
330330
ExpectStatusPendingReported().
331-
ExpectNoCommentsMade().
331+
ExpectInvalidCommentCharlieIgnoredAsReviewer().
332332
ExpectLabelsUpdated().
333-
ExpectNoReviewRequestsMade()
333+
ExpectedReviewRequestsMadeForFoo()
334334
}
335335

336336
func TestGitHubTeamApproverCleansUpOldIgnoredReviewsComments(t *testing.T) {
@@ -358,6 +358,56 @@ func TestGitHubTeamApproverCleansUpOldIgnoredReviewsComments(t *testing.T) {
358358
ExpectedReviewRequestsMadeForFoo()
359359
}
360360

361+
func TestGitHubTeamApproverCleansUpOldInvalidReviewsComments(t *testing.T) {
362+
given, when, then := stages.ApiTest(t)
363+
364+
given.
365+
EncryptionKeyExists().
366+
GitHubWebHookTokenExists().
367+
FakeGHRunning().
368+
OrganisationWithTeamFoo().
369+
RepoWithNoContributorReviewEnabledAndFooAsApprovingTeam().
370+
PullRequestExists().
371+
InvalidReviewCommentsExist().
372+
CommitsWithAliceAsContributor().
373+
CharlieApprovesPullRequest().
374+
GitHubTeamApproverRunning()
375+
when.
376+
SendingApprovedPRReviewSubmittedEvent()
377+
then.
378+
ExpectPendingAnswerReturned().
379+
ExpectStatusPendingReported().
380+
ExpectPreviousIgnoredReviewCommentsDeleted().
381+
ExpectInvalidCommentCharlieIgnoredAsReviewer().
382+
ExpectLabelsUpdated().
383+
ExpectedReviewRequestsMadeForFoo()
384+
}
385+
386+
func TestGitHubTeamApproverCleansUpOldInvalidAndRetainsIgnoredReviewsComments(t *testing.T) {
387+
given, when, then := stages.ApiTest(t)
388+
389+
given.
390+
EncryptionKeyExists().
391+
GitHubWebHookTokenExists().
392+
FakeGHRunning().
393+
OrganisationWithTeamFoo().
394+
RepoWithNoContributorReviewEnabledAndFooAsApprovingTeam().
395+
PullRequestExists().
396+
InvalidAndIgnoredReviewCommentsExist().
397+
CommitsWithAliceAsContributor().
398+
CharlieApprovesPullRequest().
399+
GitHubTeamApproverRunning()
400+
when.
401+
SendingApprovedPRReviewSubmittedEvent()
402+
then.
403+
ExpectPendingAnswerReturned().
404+
ExpectStatusPendingReported().
405+
ExpectPreviousAliceIgnoredReviewCommentsRetained().
406+
ExpectInvalidCommentCharlieIgnoredAsReviewer().
407+
ExpectLabelsUpdated().
408+
ExpectedReviewRequestsMadeForFoo()
409+
}
410+
361411
func TestGitHubTeamApproverReportsInvalidTeamHandlesInConfiguration(t *testing.T) {
362412
given, when, then := stages.ApiTest(t)
363413

internal/api/approval/approval.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ func (a *Approval) ComputeApprovalStatus(ctx context.Context, pr *PR) (*Result,
113113
}
114114
}
115115

116+
allAllowedMembers := map[string]bool{}
116117
// Check if each required team has approved the pull request.
117118
for _, rule := range rules {
118119
matched, err := a.isRuleMatched(ctx, rule, pr)
@@ -149,11 +150,12 @@ func (a *Approval) ComputeApprovalStatus(ctx context.Context, pr *PR) (*Result,
149150
return nil, err
150151
}
151152

153+
addMembers(allAllowedMembers, members)
154+
152155
allowed, ignored, err := a.allowedAndIgnoreReviewers(ctx, pr, members, rule.IgnoreContributorApproval)
153156
if err != nil {
154157
return nil, err
155158
}
156-
157159
// Check whether the current team has approved the PR.
158160
approvalCount := countApprovalsForTeam(reviews, allowed)
159161
// Need to use full team handle here, as we'll be comparing recorded handles
@@ -164,19 +166,31 @@ func (a *Approval) ComputeApprovalStatus(ctx context.Context, pr *PR) (*Result,
164166
state.addMatchedRule(mr)
165167
}
166168

169+
state.updateInvalidReviewers(allAllowedMembers)
170+
167171
result := state.result(a.log, teams) // state should not be consumed past this point
168172

169173
if result.pendingReviewsWaiting() {
170-
err := a.client.ReportIgnoredReviews(
171-
ctx, pr.OwnerLogin, pr.RepoName, pr.Number, result.IgnoredReviewers())
172-
if err != nil {
174+
if err := a.client.ReportIgnoredReviews(
175+
ctx, pr.OwnerLogin, pr.RepoName, pr.Number, result.IgnoredReviewers()); err != nil {
176+
return nil, err
177+
}
178+
179+
if err := a.client.ReportInvalidReviews(
180+
ctx, pr.OwnerLogin, pr.RepoName, pr.Number, result.InvalidReviewers()); err != nil {
173181
return nil, err
174182
}
175183
}
176184

177185
return result, nil
178186
}
179187

188+
func addMembers(allowed map[string]bool, members []*github.User) {
189+
for _, m := range members {
190+
allowed[m.GetLogin()] = true
191+
}
192+
}
193+
180194
func (a *Approval) isRuleMatched(ctx context.Context, rule configuration.Rule, pr *PR) (bool, error) {
181195
// Check whether the pull request's body matches the aforementioned regex (ignoring case).
182196
prBodyMatch, err := a.isRegexMatched(ctx, pr.OwnerLogin, pr.RepoName, pr.Number, rule.Regex, pr.Body)

internal/api/approval/result.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type Result struct {
1212
finalLabels []string
1313
reviewsToRequest []string
1414
ignoredReviewers []string
15+
invalidReviewers []string
1516
}
1617

1718
func (r *Result) pendingReviewsWaiting() bool {
@@ -26,6 +27,7 @@ func (r *Result) Status() string { return r.status }
2627
func (r *Result) FinalLabels() []string { return r.finalLabels }
2728
func (r *Result) ReviewsToRequest() []string { return r.reviewsToRequest }
2829
func (r *Result) IgnoredReviewers() []string { return r.ignoredReviewers }
30+
func (r *Result) InvalidReviewers() []string { return r.invalidReviewers }
2931

3032
func truncate(v string, n int) string {
3133
suffix := "..."

internal/api/approval/state.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type state struct {
3030
ignoredReviewers []string
3131
// Invalid team handles found in configuration
3232
invalidTeamHandles []string
33+
// Reviewers who have approved PR but are not member of any valid team
34+
invalidReviewers []string
3335
}
3436

3537
func newState() *state {
@@ -131,6 +133,7 @@ func (s *state) result(log *log.Entry, teams []*github.Team) *Result {
131133
result := &Result{
132134
finalLabels: s.labels,
133135
ignoredReviewers: s.ignoredReviewers,
136+
invalidReviewers: s.invalidReviewers,
134137
}
135138

136139
pendingTeamNames := s.pendingTeamNames()
@@ -171,6 +174,17 @@ func (s *state) result(log *log.Entry, teams []*github.Team) *Result {
171174
return result
172175
}
173176

177+
func (s *state) updateInvalidReviewers(allAllowedMembers map[string]bool) {
178+
if len(s.matchedRules) == 0 {
179+
return
180+
}
181+
for member := range s.approvingReviewers {
182+
if _, ok := allAllowedMembers[member]; !ok {
183+
s.invalidReviewers = appendIfMissing(s.invalidReviewers, member)
184+
}
185+
}
186+
}
187+
174188
func computeReviewsToRequest(log *log.Entry, teams []*github.Team, pendingTeams []string) []string {
175189
var reviewsToRequest []string
176190

internal/api/github/client.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const (
3535
envGitHubAppId = "GITHUB_APP_ID"
3636
envGitHubAppInstallationId = "GITHUB_APP_INSTALLATION_ID"
3737
envGitHubAppPrivateKeyPath = "GITHUB_APP_PRIVATE_KEY_PATH"
38+
39+
ignoredReviewersTitle = "Following reviewers have been ignored as they either contributed to or reopened the PR:\n"
40+
invalidReviewersTitle = "Following reviewers have been ignored as they are not a member of any valid team:\n"
3841
)
3942

4043
var (
@@ -364,12 +367,18 @@ func (c *Client) ReportStatus(ctx context.Context, ownerLogin, repoName, statuse
364367
}
365368

366369
func (c *Client) ReportIgnoredReviews(ctx context.Context, owner, repo string, prNumber int, reviewers []string) error {
370+
return c.reportIgnoredReviews(ctx, owner, repo, prNumber, reviewers, ignoredReviewersTitle)
371+
}
372+
373+
func (c *Client) ReportInvalidReviews(ctx context.Context, owner, repo string, prNumber int, reviewers []string) error {
374+
return c.reportIgnoredReviews(ctx, owner, repo, prNumber, reviewers, invalidReviewersTitle)
375+
}
376+
377+
func (c *Client) reportIgnoredReviews(ctx context.Context, owner, repo string, prNumber int, reviewers []string, title string) error {
367378
if len(reviewers) == 0 {
368379
return nil
369380
}
370381

371-
title := "Following reviewers have been ignored as they either contributed to or reopened the PR:\n"
372-
373382
err := c.removeOldBotComments(ctx, owner, repo, prNumber, title)
374383
if err != nil {
375384
return err

internal/api/handler_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,22 @@ func Test_Handle(t *testing.T) {
190190

191191
expectedFinalStatus: approval.StatusEventStatusSuccess,
192192
},
193+
{
194+
name: `PR review Submitted (requires approval from CAB - FOO, David is invalid user thus PR review isn't accepted)`,
195+
196+
eventType: eventTypePullRequestReview,
197+
eventBody: readGitHubExampleFile("pull_request_review_submitted.json"),
198+
eventSignature: "sha256=802a01c378001fbbbea8f59e7d5eab688550bcbd097491abc907d8850cef6e17",
199+
pacts: []pacttesting.Pact{
200+
"pull_request_commits_pr_7",
201+
"pull_request_get_comments_pr_7",
202+
"pull_request_post_comment_pr_7",
203+
"pull_request_review_submitted_david_approved",
204+
"issue_events_pr_7",
205+
},
206+
207+
expectedFinalStatus: approval.StatusEventStatusPending,
208+
},
193209
}
194210

195211
for _, tt := range tests {

0 commit comments

Comments
 (0)