-
Notifications
You must be signed in to change notification settings - Fork 113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dismiss stale reviews per rule #463
Changes from all commits
e79bd9e
5618099
7d7e691
984a8d9
e129af2
7e42996
723a861
1434ffb
b854004
18e6fed
ab39ae7
a3760e4
5d87aea
2e73996
35e41b5
770bacc
42b8acd
bbb50c6
4b84279
8ffc2c0
a3d3768
d86a367
697a273
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -198,6 +198,7 @@ const ( | |
) | ||
|
||
type Review struct { | ||
ID string | ||
CreatedAt time.Time | ||
LastEditedAt time.Time | ||
Author string | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ import ( | |
"github.com/palantir/policy-bot/pull" | ||
"github.com/pkg/errors" | ||
"github.com/rs/zerolog" | ||
"github.com/shurcooL/githubv4" | ||
) | ||
|
||
const ( | ||
|
@@ -186,7 +187,17 @@ func (b *Base) Evaluate(ctx context.Context, installationID int64, trigger commo | |
return err | ||
} | ||
|
||
return b.RequestReviewsForResult(ctx, prctx, client, trigger, result) | ||
err = b.RequestReviewsForResult(ctx, prctx, client, trigger, result) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = b.dismissStaleReviewsForResult(ctx, prctx, v4client, result) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (b *Base) ValidateFetchedConfig(ctx context.Context, prctx pull.Context, client *github.Client, fc FetchedConfig, trigger common.Trigger) (common.Evaluator, error) { | ||
|
@@ -391,6 +402,110 @@ func selectionToReviewersRequest(s reviewer.Selection) github.ReviewersRequest { | |
return req | ||
} | ||
|
||
func (b *Base) findResultsWithAllowedCandidates(result *common.Result) []*common.Result { | ||
var results []*common.Result | ||
for _, c := range result.Children { | ||
results = append(results, b.findResultsWithAllowedCandidates(c)...) | ||
} | ||
|
||
if len(result.Children) == 0 && len(result.AllowedCandidates) > 0 && result.Error == nil { | ||
results = append(results, result) | ||
} | ||
|
||
return results | ||
} | ||
|
||
func (b *Base) reviewIsAllowed(review *pull.Review, allowedCandidates []*common.Candidate) bool { | ||
for _, candidate := range allowedCandidates { | ||
if review.ID == candidate.ReviewID { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// We already know that these are discarded review candidates for one of three reasons | ||
// so first we check for edited and then we check to see if its a review thats at least | ||
// 5 seconds old and we know that it was invalidated by a new commit, and then finally | ||
// if it was missing a github review comment pattern that was required. | ||
// | ||
// This is brittle and may need refactoring in future versions because it assumes the bot | ||
// will take less than 5 seconds to respond, but thought that having a dismissal reason | ||
// was valuable. | ||
func (b *Base) reasonForDismissedReview(review *pull.Review) string { | ||
if !review.LastEditedAt.IsZero() { | ||
return "was edited" | ||
} | ||
|
||
if review.CreatedAt.Before(time.Now().Add(-5 * time.Second)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess the assumption here is that if the review was submitted or edited (and doesn't match a pattern), the bot is responding to that event within 5 seconds, whereas it's unlikely that someone will submit a review and then a new commit will be pushed within 5 seconds? I'm always hesitant about fixed time ranges like this, but as a heuristic for generating informational text, it's probably fine. An alternative might be to also pass the push time of the most recent commit in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yea it feels brittle to me but works.. I think it's hard to grok because it requires so much knowledge about the filtering.. we already know that these are discarded review candidates for one of three reasons so first we check for edited and then we check to see if its a review thats at least 5 seconds old and we know that it was invalidated by a new commit, and then the finally is that it didn't include a github review comment pattern that was also required. but yes this assumes the bot is responding within those 5 seconds, which seemed fine in my brief testing, but also still small enough to be unlikely that someone would push a new commit in that time frame. but yes still feels brittle, so i was hoping you would have a better solution. i'll try this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adding some comments on the function or next to each condition about what it's checking for and why this works might help decipher the logic for readers in the future too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i tried all the approaches i could think of including your Result suggestion and nothing really felt any better, so just added a comment explaining the function as much as i could. |
||
return "was invalidated by another commit" | ||
} | ||
|
||
return "didn't include a valid comment pattern" | ||
} | ||
|
||
func (b *Base) dismissStaleReviewsForResult(ctx context.Context, prctx pull.Context, v4client *githubv4.Client, result common.Result) error { | ||
logger := zerolog.Ctx(ctx) | ||
|
||
var allowedCandidates []*common.Candidate | ||
|
||
results := b.findResultsWithAllowedCandidates(&result) | ||
for _, res := range results { | ||
for _, candidate := range res.AllowedCandidates { | ||
if candidate.Type != common.ReviewCandidate { | ||
continue | ||
} | ||
allowedCandidates = append(allowedCandidates, candidate) | ||
} | ||
} | ||
|
||
reviews, err := prctx.Reviews() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, r := range reviews { | ||
if r.State != pull.ReviewApproved { | ||
continue | ||
} | ||
|
||
if b.reviewIsAllowed(r, allowedCandidates) { | ||
continue | ||
} | ||
|
||
reason := b.reasonForDismissedReview(r) | ||
message := fmt.Sprintf("Dismissed because the approval %s", reason) | ||
logger.Info().Msgf("Dismissing stale review %s because it %s", r.ID, reason) | ||
err := b.dismissPullRequestReview(ctx, v4client, r.ID, message) | ||
if err != nil { | ||
logger.Err(err).Msg("Failed to dismiss stale review") | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (b *Base) dismissPullRequestReview(ctx context.Context, v4client *githubv4.Client, reviewID string, message string) error { | ||
var m struct { | ||
DismissPullRequestReview struct { | ||
ClientMutationID *githubv4.String | ||
} `graphql:"dismissPullRequestReview(input: $input)"` | ||
} | ||
|
||
input := githubv4.DismissPullRequestReviewInput{ | ||
PullRequestReviewID: githubv4.String(reviewID), | ||
Message: githubv4.String(message), | ||
} | ||
|
||
if err := v4client.Mutate(ctx, &m, input, nil); err != nil { | ||
return errors.Wrapf(err, "failed to dismiss pull request review %s", reviewID) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func setStringFromEnv(key, prefix string, value *string) bool { | ||
if v, ok := os.LookupEnv(prefix + key); ok { | ||
*value = v | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i left this bit because i still thought it was a good idea for refactoring the tests, but let me know if you want me to revert this too.