-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from cw-kajiwara/support-pull-request-review
Support pull request review (only approval!)
- Loading branch information
Showing
5 changed files
with
260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"os" | ||
|
||
"github.com/k-kinzal/pr/pkg/pr" | ||
"github.com/spf13/cobra" | ||
"golang.org/x/xerrors" | ||
) | ||
|
||
func ReviewRun(cmd *cobra.Command, args []string) error { | ||
pulls, err := pr.Review(owner, repo, reviewOption) | ||
if err != nil { | ||
if _, ok := err.(*pr.NoMatchError); ok { | ||
fmt.Fprintln(os.Stderr, err.Error()) | ||
fmt.Fprintln(os.Stdout, "[]") | ||
if exitCode { | ||
os.Exit(127) | ||
} | ||
return nil | ||
} | ||
return err | ||
} | ||
|
||
out, err := json.Marshal(pulls) | ||
if err != nil { | ||
return xerrors.Errorf("review: %s", err) | ||
} | ||
fmt.Fprintln(os.Stdout, string(out)) | ||
|
||
return nil | ||
} | ||
|
||
var ( | ||
reviewOption *pr.ReviewOption | ||
reviewCmd = &cobra.Command{ | ||
Use: "review owner/repo", | ||
Short: "Add review to PRs that match rules", | ||
RunE: ReviewRun, | ||
SilenceErrors: true, | ||
SilenceUsage: true, | ||
} | ||
) | ||
|
||
func setReviewFrags(cmd *cobra.Command) *pr.ReviewOption { | ||
opt := &pr.ReviewOption{} | ||
cmd.Flags().StringVar(&opt.Action, "action", "approve", "review action to take. currently, approve is only supported") | ||
return opt | ||
} | ||
|
||
func init() { | ||
reviewOption = setReviewFrags(reviewCmd) | ||
reviewOption.ListOption = setListFrags(reviewCmd) | ||
rootCmd.AddCommand(reviewCmd) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package api | ||
|
||
import ( | ||
"context" | ||
"github.com/google/go-github/v28/github" | ||
"golang.org/x/sync/errgroup" | ||
) | ||
|
||
type ReviewOption struct { | ||
Action string | ||
} | ||
|
||
func (c *Client) AddApproval(ctx context.Context, pulls []*PullRequest, opt *ReviewOption) ([]*PullRequest, error) { | ||
eg, ctx := errgroup.WithContext(ctx) | ||
|
||
for _, pull := range pulls { | ||
eg.Go(func(pull *PullRequest) func() error { | ||
return func() error { | ||
pullRequestReviewRequest := &github.PullRequestReviewRequest{Event: github.String(opt.Action)} | ||
pullRequestReview, _, err := c.github.PullRequests.CreateReview(ctx, pull.Owner, pull.Repo, int(pull.Number), pullRequestReviewRequest) | ||
if err != nil { | ||
return err | ||
} | ||
pull.Reviews = append(pull.Reviews, newPullRequestReview(pullRequestReview)) | ||
return nil | ||
} | ||
}(pull)) | ||
} | ||
if err := eg.Wait(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return pulls, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package api_test | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"github.com/google/go-github/v28/github" | ||
"math" | ||
"net/http" | ||
"testing" | ||
"time" | ||
|
||
"github.com/jarcoal/httpmock" | ||
"github.com/k-kinzal/pr/pkg/api" | ||
"github.com/k-kinzal/pr/test/gen" | ||
) | ||
|
||
func TestClient_AddApproval(t *testing.T) { | ||
gen.Reset() | ||
httpmock.Activate() | ||
defer httpmock.DeactivateAndReset() | ||
|
||
httpmock.RegisterResponder("POST", "=~^https://api.github.com/repos/octocat/Hello-World/pulls/\\d+/reviews", func(request *http.Request) (response *http.Response, e error) { | ||
var req struct { | ||
Action *string `json:"event"` | ||
} | ||
if err := json.NewDecoder(request.Body).Decode(&req); err != nil { | ||
return nil, err | ||
} | ||
|
||
pullRequestReview, err := gen.PullRequestReview() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pullRequestReview.State = github.String("APPROVED") | ||
|
||
resp, err := httpmock.NewJsonResponse(200, pullRequestReview) | ||
if err != nil { | ||
return nil, err | ||
} | ||
resp.Header.Add("X-RateLimit-Limit", "5000") | ||
resp.Header.Add("X-RateLimit-Remaining", "4999") | ||
resp.Header.Add("X-RateLimit-Reset", fmt.Sprint(time.Now().Unix())) | ||
resp.Request = request | ||
|
||
return resp, nil | ||
}) | ||
|
||
pulls := []*api.PullRequest{ | ||
{ | ||
Id: 1, | ||
Number: 1, | ||
State: "open", | ||
Head: &api.PullRequestBranch{ | ||
Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e", | ||
}, | ||
Owner: "octocat", | ||
Repo: "Hello-World", | ||
}, | ||
{ | ||
Id: 2, | ||
Number: 2, | ||
State: "open", | ||
Head: &api.PullRequestBranch{ | ||
Sha: "6dcb09b5b57875f334f61aebed695e2e4193db5e", | ||
}, | ||
Owner: "octocat", | ||
Repo: "Hello-World", | ||
}, | ||
} | ||
|
||
ctx := context.Background() | ||
client := api.NewClient(ctx, &api.Options{ | ||
Token: "xxxx", | ||
RateLimit: math.MaxInt32, | ||
}) | ||
opt := &api.ReviewOption{ | ||
Action: "approve", | ||
} | ||
|
||
pulls, err := client.AddApproval(ctx, pulls, opt) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
for i, pull := range pulls { | ||
for j, review := range pull.Reviews { | ||
if review.State != "APPROVED" { | ||
t.Fatalf("pull[`%d`].reviews[`%d`]): expect APPROVED, but actual `%s`", i, j, review.State) | ||
} | ||
} | ||
} | ||
|
||
info := httpmock.GetCallCountInfo() | ||
if info["POST =~^https://api.github.com/repos/octocat/Hello-World/pulls/\\d+/reviews"] != 2 { | ||
t.Fatalf("expect `2`, but actual `%d`: %#v", info["POST =~^https://api.github.com/repos/octocat/Hello-World/pulls/\\d+/reviews"], info) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package pr | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/k-kinzal/pr/pkg/api" | ||
"strings" | ||
) | ||
|
||
const ( | ||
ReviewActionAddApproval = "APPROVE" | ||
) | ||
|
||
type ReviewOption struct { | ||
Action string | ||
*ListOption | ||
} | ||
|
||
func Review(owner string, repo string, opt *ReviewOption) ([]*api.PullRequest, error) { | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
|
||
clientOption := &api.Options{ | ||
Token: token, | ||
RateLimit: opt.Rate, | ||
} | ||
client := api.NewClient(ctx, clientOption) | ||
|
||
pullOption := api.PullsOption{ | ||
EnableComments: opt.EnableComments, | ||
EnableReviews: opt.EnableReviews, | ||
EnableCommits: opt.EnableCommits, | ||
EnableStatuses: opt.EnableStatuses, | ||
EnableChecks: opt.EnableChecks, | ||
Rules: api.NewPullRequestRules(opt.Rules, opt.Limit), | ||
} | ||
pulls, err := client.GetPulls(ctx, owner, repo, pullOption) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(pulls) == 0 { | ||
return nil, &NoMatchError{pullOption.Rules} | ||
} | ||
|
||
// GitHub Pull Request Reviews require `event` parameter to be uppercase | ||
// https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request | ||
action := strings.ToUpper(opt.Action) | ||
|
||
switch action { | ||
case ReviewActionAddApproval: | ||
reviewOption := &api.ReviewOption{ | ||
Action: action, | ||
} | ||
return client.AddApproval(ctx, pulls, reviewOption) | ||
} | ||
return nil, fmt.Errorf("currently, `approve` is only supported action, but %s was passed", opt.Action) | ||
} |