diff --git a/api/export_pr.go b/api/export_pr.go index 18bce025b..3c4d14078 100644 --- a/api/export_pr.go +++ b/api/export_pr.go @@ -75,6 +75,8 @@ func (pr *PullRequest) ExportData(fields []string) map[string]interface{} { data[f] = pr.ProjectCards.Nodes case "reviews": data[f] = pr.Reviews.Nodes + case "latestReviews": + data[f] = pr.LatestReviews.Nodes case "files": data[f] = pr.Files.Nodes case "reviewRequests": diff --git a/api/queries_pr.go b/api/queries_pr.go index 300846c23..19337512b 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -64,7 +64,8 @@ type PullRequest struct { BaseRef struct { BranchProtectionRule struct { - RequiresStrictStatusChecks bool + RequiresStrictStatusChecks bool + RequiredApprovingReviewCount int } } @@ -108,6 +109,7 @@ type PullRequest struct { Comments Comments ReactionGroups ReactionGroups Reviews PullRequestReviews + LatestReviews PullRequestReviews ReviewRequests ReviewRequests } @@ -405,6 +407,11 @@ func PullRequestStatus(client *Client, repo ghrepo.Interface, options StatusOpti } pullRequest(number: $number) { ...prWithReviews + baseRef { + branchProtectionRule { + requiredApprovingReviewCount + } + } } } ` @@ -519,7 +526,7 @@ func pullRequestFragment(httpClient *http.Client, hostname string) (string, erro var reviewFields []string if prFeatures.HasReviewDecision { - reviewFields = append(reviewFields, "reviewDecision") + reviewFields = append(reviewFields, "reviewDecision", "latestReviews") } fragments := fmt.Sprintf(` diff --git a/api/query_builder.go b/api/query_builder.go index 88f8bfa4b..977089df6 100644 --- a/api/query_builder.go +++ b/api/query_builder.go @@ -82,6 +82,18 @@ var prReviews = shortenQuery(` } `) +var prLatestReviews = shortenQuery(` + latestReviews(first: 100) { + nodes { + author{login}, + authorAssociation, + submittedAt, + body, + state + } + } +`) + var prFiles = shortenQuery(` files(first: 100) { nodes { @@ -180,6 +192,7 @@ var PullRequestFields = append(IssueFields, "headRepositoryOwner", "isCrossRepository", "isDraft", + "latestReviews", "maintainerCanModify", "mergeable", "mergeCommit", @@ -229,6 +242,8 @@ func PullRequestGraphQL(fields []string) string { q = append(q, prReviewRequests) case "reviews": q = append(q, prReviews) + case "latestReviews": + q = append(q, prLatestReviews) case "files": q = append(q, prFiles) case "commits": diff --git a/pkg/cmd/pr/status/fixtures/prStatusChecks.json b/pkg/cmd/pr/status/fixtures/prStatusChecks.json index dd1605b1d..32ed236a9 100644 --- a/pkg/cmd/pr/status/fixtures/prStatusChecks.json +++ b/pkg/cmd/pr/status/fixtures/prStatusChecks.json @@ -44,6 +44,33 @@ "url": "https://github.com/cli/cli/pull/7", "headRefName": "banananana", "reviewDecision": "APPROVED", + "baseRef": { + "branchProtectionRule": { + "requiredApprovingReviewCount": 0 + } + }, + "latestReviews": { + "nodes": [ + { + "author": { + "login": "bob" + }, + "state": "APPROVED" + }, + { + "author": { + "login": "stella" + }, + "state": "CHANGES_REQUESTED" + }, + { + "author": { + "login": "alice" + }, + "state": "APPROVED" + } + ] + }, "statusCheckRollup": { "nodes": [ { diff --git a/pkg/cmd/pr/status/status.go b/pkg/cmd/pr/status/status.go index a28e88b00..3e7e84453 100644 --- a/pkg/cmd/pr/status/status.go +++ b/pkg/cmd/pr/status/status.go @@ -199,6 +199,16 @@ func prSelectorForCurrentBranch(baseRepo ghrepo.Interface, prHeadRef string, rem return } +func totalApprovals(pr *api.PullRequest) int { + approvals := 0 + for _, review := range pr.LatestReviews.Nodes { + if review.State == "APPROVED" { + approvals++ + } + } + return approvals +} + func printPrs(io *iostreams.IOStreams, totalCount int, prs ...api.PullRequest) { w := io.Out cs := io.ColorScheme() @@ -246,7 +256,13 @@ func printPrs(io *iostreams.IOStreams, totalCount int, prs ...api.PullRequest) { } else if reviews.ReviewRequired { fmt.Fprint(w, cs.Yellow("- Review required")) } else if reviews.Approved { - fmt.Fprint(w, cs.Green("✓ Approved")) + numRequiredApprovals := pr.BaseRef.BranchProtectionRule.RequiredApprovingReviewCount + gotApprovals := totalApprovals(&pr) + s := fmt.Sprintf("%d", gotApprovals) + if numRequiredApprovals > 0 { + s = fmt.Sprintf("%d/%d", gotApprovals, numRequiredApprovals) + } + fmt.Fprint(w, cs.Green(fmt.Sprintf("✓ %s Approved", s))) } if pr.BaseRef.BranchProtectionRule.RequiresStrictStatusChecks { diff --git a/pkg/cmd/pr/status/status_test.go b/pkg/cmd/pr/status/status_test.go index 58256c6b2..bf341c728 100644 --- a/pkg/cmd/pr/status/status_test.go +++ b/pkg/cmd/pr/status/status_test.go @@ -112,7 +112,7 @@ func TestPRStatus_reviewsAndChecks(t *testing.T) { expected := []string{ "✓ Checks passing + Changes requested", - "- Checks pending ✓ Approved", + "- Checks pending ✓ 2 Approved", "× 1/3 checks failing - Review required", }