cli/pkg/cmd/pr/status/http.go
Kynan Ware 7477bdb690 refactor(pr status): remove ChecksStatus slow path
All supported GHES versions (3.16 through 3.20) support the
checkRunCountsByState and statusContextCountsByState fields on
StatusCheckRollupContextConnection. The slow path that iterated
individual CheckContext nodes in ChecksStatus() is dead code.

This commit:

- Removes the slow path from ChecksStatus(), keeping only the
  aggregated counts-by-state path
- Removes parseCheckStatusFromCheckConclusionState (no callers remain)
- Removes CheckRunAndStatusContextCounts from PullRequestFeatures
  and its introspection detection
- Consolidates the two feature detection introspection queries into
  one (PullRequest + WorkflowRun fits within the platform limit of
  two __type expressions)
- Removes the errgroup dependency from feature detection
- Always uses statusCheckRollupWithCountByState in pr status queries
- Updates pr view fixtures to include counts-by-state fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-11 15:21:17 -06:00

217 lines
5.6 KiB
Go

package status
import (
"fmt"
"net/http"
"strings"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/set"
ghauth "github.com/cli/go-gh/v2/pkg/auth"
)
type requestOptions struct {
CurrentPR int
HeadRef string
Username string
Fields []string
ConflictStatus bool
}
type pullRequestsPayload struct {
ViewerCreated api.PullRequestAndTotalCount
ReviewRequested api.PullRequestAndTotalCount
CurrentPR *api.PullRequest
DefaultBranch string
}
func pullRequestStatus(httpClient *http.Client, repo ghrepo.Interface, options requestOptions) (*pullRequestsPayload, error) {
apiClient := api.NewClientFromHTTP(httpClient)
type edges struct {
TotalCount int
Edges []struct {
Node api.PullRequest
}
}
type response struct {
Repository struct {
DefaultBranchRef struct {
Name string
}
PullRequests edges
PullRequest *api.PullRequest
}
ViewerCreated edges
ReviewRequested edges
}
var fragments string
if len(options.Fields) > 0 {
fields := set.NewStringSet()
fields.AddValues(options.Fields)
// these are always necessary to find the PR for the current branch
fields.AddValues([]string{"isCrossRepository", "headRepositoryOwner", "headRefName"})
gr := api.PullRequestGraphQL(fields.ToSlice())
fragments = fmt.Sprintf("fragment pr on PullRequest{%s}fragment prWithReviews on PullRequest{...pr}", gr)
} else {
var err error
fragments, err = pullRequestFragment(options.ConflictStatus)
if err != nil {
return nil, err
}
}
queryPrefix := `
query PullRequestStatus($owner: String!, $repo: String!, $headRefName: String!, $viewerQuery: String!, $reviewerQuery: String!, $per_page: Int = 10) {
repository(owner: $owner, name: $repo) {
defaultBranchRef {
name
}
pullRequests(headRefName: $headRefName, first: $per_page, orderBy: { field: CREATED_AT, direction: DESC }) {
totalCount
edges {
node {
...prWithReviews
}
}
}
}
`
if options.CurrentPR > 0 {
queryPrefix = `
query PullRequestStatus($owner: String!, $repo: String!, $number: Int!, $viewerQuery: String!, $reviewerQuery: String!, $per_page: Int = 10) {
repository(owner: $owner, name: $repo) {
defaultBranchRef {
name
}
pullRequest(number: $number) {
...prWithReviews
baseRef {
branchProtectionRule {
requiredApprovingReviewCount
}
}
}
}
`
}
query := fragments + queryPrefix + `
viewerCreated: search(query: $viewerQuery, type: ISSUE, first: $per_page) {
totalCount: issueCount
edges {
node {
...prWithReviews
}
}
}
reviewRequested: search(query: $reviewerQuery, type: ISSUE, first: $per_page) {
totalCount: issueCount
edges {
node {
...pr
}
}
}
}
`
currentUsername, err := getCurrentUsername(options.Username, repo.RepoHost(), apiClient)
if err != nil {
return nil, err
}
viewerQuery := fmt.Sprintf("repo:%s state:open is:pr author:%s", ghrepo.FullName(repo), currentUsername)
reviewerQuery := fmt.Sprintf("repo:%s state:open review-requested:%s", ghrepo.FullName(repo), currentUsername)
currentPRHeadRef := options.HeadRef
branchWithoutOwner := currentPRHeadRef
if idx := strings.Index(currentPRHeadRef, ":"); idx >= 0 {
branchWithoutOwner = currentPRHeadRef[idx+1:]
}
variables := map[string]interface{}{
"viewerQuery": viewerQuery,
"reviewerQuery": reviewerQuery,
"owner": repo.RepoOwner(),
"repo": repo.RepoName(),
"headRefName": branchWithoutOwner,
"number": options.CurrentPR,
}
var resp response
if err := apiClient.GraphQL(repo.RepoHost(), query, variables, &resp); err != nil {
return nil, err
}
var viewerCreated []api.PullRequest
for _, edge := range resp.ViewerCreated.Edges {
viewerCreated = append(viewerCreated, edge.Node)
}
var reviewRequested []api.PullRequest
for _, edge := range resp.ReviewRequested.Edges {
reviewRequested = append(reviewRequested, edge.Node)
}
var currentPR = resp.Repository.PullRequest
if currentPR == nil {
for _, edge := range resp.Repository.PullRequests.Edges {
if edge.Node.HeadLabel() == currentPRHeadRef {
currentPR = &edge.Node
break // Take the most recent PR for the current branch
}
}
}
payload := pullRequestsPayload{
ViewerCreated: api.PullRequestAndTotalCount{
PullRequests: viewerCreated,
TotalCount: resp.ViewerCreated.TotalCount,
},
ReviewRequested: api.PullRequestAndTotalCount{
PullRequests: reviewRequested,
TotalCount: resp.ReviewRequested.TotalCount,
},
CurrentPR: currentPR,
DefaultBranch: resp.Repository.DefaultBranchRef.Name,
}
return &payload, nil
}
func getCurrentUsername(username string, hostname string, apiClient *api.Client) (string, error) {
if username == "@me" && ghauth.IsEnterprise(hostname) {
var err error
username, err = api.CurrentLoginName(apiClient, hostname)
if err != nil {
return "", err
}
}
return username, nil
}
func pullRequestFragment(conflictStatus bool) (string, error) {
fields := []string{
"number", "title", "state", "url", "isDraft", "isCrossRepository",
"headRefName", "headRepositoryOwner", "mergeStateStatus",
"requiresStrictStatusChecks", "autoMergeRequest",
}
if conflictStatus {
fields = append(fields, "mergeable")
}
fields = append(fields, "statusCheckRollupWithCountByState")
reviewFields := []string{"reviewDecision", "latestReviews"}
fragments := fmt.Sprintf(`
fragment pr on PullRequest {%s}
fragment prWithReviews on PullRequest {...pr,%s}
`, api.PullRequestGraphQL(fields), api.PullRequestGraphQL(reviewFields))
return fragments, nil
}