mirror of
https://git.vectorsigma.ru/public/atlantis.git
synced 2026-07-29 00:38:24 +00:00
feat: dismiss approvals when planning (#2696)
* feat: dismiss approvals when planning * feat: add pagination and move query in separate method * tests: add test for dismissing * refactor: fix linting issue * implement change requests * Update cmd/server.go Co-authored-by: PePe Amengual <jose.amengual@gmail.com> Co-authored-by: nitrocode <7775707+nitrocode@users.noreply.github.com> Co-authored-by: PePe Amengual <jose.amengual@gmail.com>
This commit is contained in:
committed by
GitHub
parent
c00cfa407d
commit
01a9a5f5a7
@@ -35,6 +35,11 @@ import (
|
||||
// by GitHub.
|
||||
const maxCommentLength = 65536
|
||||
|
||||
var (
|
||||
clientMutationID = githubv4.NewString("atlantis")
|
||||
pullRequestDismissalMessage = *githubv4.NewString("Dismissing reviews because of plan changes")
|
||||
)
|
||||
|
||||
// GithubClient is used to perform GitHub actions.
|
||||
type GithubClient struct {
|
||||
user string
|
||||
@@ -59,6 +64,19 @@ type GithubAppTemporarySecrets struct {
|
||||
URL string
|
||||
}
|
||||
|
||||
type GithubReview struct {
|
||||
ID githubv4.ID
|
||||
SubmittedAt githubv4.DateTime
|
||||
Author struct {
|
||||
Login githubv4.String
|
||||
}
|
||||
}
|
||||
|
||||
type GithubPRReviewSummary struct {
|
||||
ReviewDecision githubv4.String
|
||||
Reviews []GithubReview
|
||||
}
|
||||
|
||||
// NewGithubClient returns a valid GitHub client.
|
||||
func NewGithubClient(hostname string, credentials GithubCredentials, config GithubConfig, logger logging.SimpleLogging) (*GithubClient, error) {
|
||||
transport, err := credentials.Client()
|
||||
@@ -241,6 +259,58 @@ func (g *GithubClient) HidePrevCommandComments(repo models.Repo, pullNum int, co
|
||||
return nil
|
||||
}
|
||||
|
||||
// getPRReviews Retrieves PR reviews for a pull request on a specific repository.
|
||||
// The reviews are being retrieved using pages with the size of 10 reviews.
|
||||
func (g *GithubClient) getPRReviews(repo models.Repo, pull models.PullRequest) (GithubPRReviewSummary, error) {
|
||||
var query struct {
|
||||
Repository struct {
|
||||
PullRequest struct {
|
||||
ReviewDecision githubv4.String
|
||||
Reviews struct {
|
||||
Nodes []GithubReview
|
||||
// contains pagination information
|
||||
PageInfo struct {
|
||||
EndCursor githubv4.String
|
||||
HasNextPage githubv4.Boolean
|
||||
}
|
||||
} `graphql:"reviews(first: $entries, after: $reviewCursor, states: $reviewState)"`
|
||||
} `graphql:"pullRequest(number: $number)"`
|
||||
} `graphql:"repository(owner: $owner, name: $name)"`
|
||||
}
|
||||
|
||||
variables := map[string]interface{}{
|
||||
"owner": githubv4.String(repo.Owner),
|
||||
"name": githubv4.String(repo.Name),
|
||||
"number": githubv4.Int(pull.Num),
|
||||
"entries": githubv4.Int(10),
|
||||
"reviewState": []githubv4.PullRequestReviewState{githubv4.PullRequestReviewStateApproved},
|
||||
"reviewCursor": (*githubv4.String)(nil), // initialize the reviewCursor with null
|
||||
}
|
||||
|
||||
var allReviews []GithubReview
|
||||
for {
|
||||
err := g.v4Client.Query(g.ctx, &query, variables)
|
||||
if err != nil {
|
||||
return GithubPRReviewSummary{
|
||||
query.Repository.PullRequest.ReviewDecision,
|
||||
allReviews,
|
||||
}, errors.Wrap(err, "getting reviewDecision")
|
||||
}
|
||||
|
||||
allReviews = append(allReviews, query.Repository.PullRequest.Reviews.Nodes...)
|
||||
// if we don't have a NextPage pointer, we have requested all pages
|
||||
if !query.Repository.PullRequest.Reviews.PageInfo.HasNextPage {
|
||||
break
|
||||
}
|
||||
// set the end cursor, so the next batch of reviews is going to be requested and not the same again
|
||||
variables["reviewCursor"] = githubv4.NewString(query.Repository.PullRequest.Reviews.PageInfo.EndCursor)
|
||||
}
|
||||
return GithubPRReviewSummary{
|
||||
query.Repository.PullRequest.ReviewDecision,
|
||||
allReviews,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PullIsApproved returns true if the pull request was approved.
|
||||
func (g *GithubClient) PullIsApproved(repo models.Repo, pull models.PullRequest) (approvalStatus models.ApprovalStatus, err error) {
|
||||
nextPage := 0
|
||||
@@ -273,6 +343,39 @@ func (g *GithubClient) PullIsApproved(repo models.Repo, pull models.PullRequest)
|
||||
return approvalStatus, nil
|
||||
}
|
||||
|
||||
// DiscardReviews dismisses all reviews on a pull request
|
||||
func (g *GithubClient) DiscardReviews(repo models.Repo, pull models.PullRequest) error {
|
||||
reviewStatus, err := g.getPRReviews(repo, pull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// https://docs.github.com/en/graphql/reference/input-objects#dismisspullrequestreviewinput
|
||||
var mutation struct {
|
||||
DismissPullRequestReview struct {
|
||||
PullRequestReview struct {
|
||||
ID githubv4.ID
|
||||
}
|
||||
} `graphql:"dismissPullRequestReview(input: $input)"`
|
||||
}
|
||||
|
||||
// dismiss every review one by one.
|
||||
// currently there is no way to dismiss them in one mutation.
|
||||
for _, review := range reviewStatus.Reviews {
|
||||
input := githubv4.DismissPullRequestReviewInput{
|
||||
PullRequestReviewID: review.ID,
|
||||
Message: pullRequestDismissalMessage,
|
||||
ClientMutationID: clientMutationID,
|
||||
}
|
||||
mutationResult := &mutation
|
||||
err := g.v4Client.Mutate(g.ctx, mutationResult, input, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "dismissing reviewDecision")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRequiredCheck is a helper function to determine if a check is required or not
|
||||
func isRequiredCheck(check string, required []string) bool {
|
||||
//in go1.18 can prob replace this with slices.Contains
|
||||
|
||||
Reference in New Issue
Block a user