github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/list/http.go (about)

     1  package list
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  
     7  	"github.com/cli/cli/api"
     8  	"github.com/cli/cli/internal/ghrepo"
     9  	prShared "github.com/cli/cli/pkg/cmd/pr/shared"
    10  	"github.com/cli/cli/pkg/githubsearch"
    11  )
    12  
    13  func listPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
    14  	if filters.Author != "" || filters.Assignee != "" || filters.Search != "" || len(filters.Labels) > 0 {
    15  		return searchPullRequests(httpClient, repo, filters, limit)
    16  	}
    17  
    18  	type response struct {
    19  		Repository struct {
    20  			PullRequests struct {
    21  				Nodes    []api.PullRequest
    22  				PageInfo struct {
    23  					HasNextPage bool
    24  					EndCursor   string
    25  				}
    26  				TotalCount int
    27  			}
    28  		}
    29  	}
    30  
    31  	fragment := fmt.Sprintf("fragment pr on PullRequest{%s}", api.PullRequestGraphQL(filters.Fields))
    32  	query := fragment + `
    33  		query PullRequestList(
    34  			$owner: String!,
    35  			$repo: String!,
    36  			$limit: Int!,
    37  			$endCursor: String,
    38  			$baseBranch: String,
    39  			$state: [PullRequestState!] = OPEN
    40  		) {
    41  			repository(owner: $owner, name: $repo) {
    42  				pullRequests(
    43  					states: $state,
    44  					baseRefName: $baseBranch,
    45  					first: $limit,
    46  					after: $endCursor,
    47  					orderBy: {field: CREATED_AT, direction: DESC}
    48  				) {
    49  					totalCount
    50  					nodes {
    51  						...pr
    52  					}
    53  					pageInfo {
    54  						hasNextPage
    55  						endCursor
    56  					}
    57  				}
    58  			}
    59  		}`
    60  
    61  	pageLimit := min(limit, 100)
    62  	variables := map[string]interface{}{
    63  		"owner": repo.RepoOwner(),
    64  		"repo":  repo.RepoName(),
    65  	}
    66  
    67  	switch filters.State {
    68  	case "open":
    69  		variables["state"] = []string{"OPEN"}
    70  	case "closed":
    71  		variables["state"] = []string{"CLOSED", "MERGED"}
    72  	case "merged":
    73  		variables["state"] = []string{"MERGED"}
    74  	case "all":
    75  		variables["state"] = []string{"OPEN", "CLOSED", "MERGED"}
    76  	default:
    77  		return nil, fmt.Errorf("invalid state: %s", filters.State)
    78  	}
    79  
    80  	if filters.BaseBranch != "" {
    81  		variables["baseBranch"] = filters.BaseBranch
    82  	}
    83  
    84  	res := api.PullRequestAndTotalCount{}
    85  	var check = make(map[int]struct{})
    86  	client := api.NewClientFromHTTP(httpClient)
    87  
    88  loop:
    89  	for {
    90  		variables["limit"] = pageLimit
    91  		var data response
    92  		err := client.GraphQL(repo.RepoHost(), query, variables, &data)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		prData := data.Repository.PullRequests
    97  		res.TotalCount = prData.TotalCount
    98  
    99  		for _, pr := range prData.Nodes {
   100  			if _, exists := check[pr.Number]; exists && pr.Number > 0 {
   101  				continue
   102  			}
   103  			check[pr.Number] = struct{}{}
   104  
   105  			res.PullRequests = append(res.PullRequests, pr)
   106  			if len(res.PullRequests) == limit {
   107  				break loop
   108  			}
   109  		}
   110  
   111  		if prData.PageInfo.HasNextPage {
   112  			variables["endCursor"] = prData.PageInfo.EndCursor
   113  			pageLimit = min(pageLimit, limit-len(res.PullRequests))
   114  		} else {
   115  			break
   116  		}
   117  	}
   118  
   119  	return &res, nil
   120  }
   121  
   122  func searchPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
   123  	type response struct {
   124  		Search struct {
   125  			Nodes    []api.PullRequest
   126  			PageInfo struct {
   127  				HasNextPage bool
   128  				EndCursor   string
   129  			}
   130  			IssueCount int
   131  		}
   132  	}
   133  
   134  	fragment := fmt.Sprintf("fragment pr on PullRequest{%s}", api.PullRequestGraphQL(filters.Fields))
   135  	query := fragment + `
   136  		query PullRequestSearch(
   137  			$q: String!,
   138  			$limit: Int!,
   139  			$endCursor: String,
   140  		) {
   141  			search(query: $q, type: ISSUE, first: $limit, after: $endCursor) {
   142  				issueCount
   143  				nodes {
   144  					...pr
   145  				}
   146  				pageInfo {
   147  					hasNextPage
   148  					endCursor
   149  				}
   150  			}
   151  		}`
   152  
   153  	q := githubsearch.NewQuery()
   154  	q.SetType(githubsearch.PullRequest)
   155  	q.InRepository(ghrepo.FullName(repo))
   156  	q.AddQuery(filters.Search)
   157  
   158  	switch filters.State {
   159  	case "open":
   160  		q.SetState(githubsearch.Open)
   161  	case "closed":
   162  		q.SetState(githubsearch.Closed)
   163  	case "merged":
   164  		q.SetState(githubsearch.Merged)
   165  	}
   166  
   167  	if filters.Author != "" {
   168  		q.AuthoredBy(filters.Author)
   169  	}
   170  	if filters.Assignee != "" {
   171  		q.AssignedTo(filters.Assignee)
   172  	}
   173  	for _, label := range filters.Labels {
   174  		q.AddLabel(label)
   175  	}
   176  	if filters.BaseBranch != "" {
   177  		q.SetBaseBranch(filters.BaseBranch)
   178  	}
   179  
   180  	pageLimit := min(limit, 100)
   181  	variables := map[string]interface{}{
   182  		"q": q.String(),
   183  	}
   184  
   185  	res := api.PullRequestAndTotalCount{}
   186  	var check = make(map[int]struct{})
   187  	client := api.NewClientFromHTTP(httpClient)
   188  
   189  loop:
   190  	for {
   191  		variables["limit"] = pageLimit
   192  		var data response
   193  		err := client.GraphQL(repo.RepoHost(), query, variables, &data)
   194  		if err != nil {
   195  			return nil, err
   196  		}
   197  		prData := data.Search
   198  		res.TotalCount = prData.IssueCount
   199  
   200  		for _, pr := range prData.Nodes {
   201  			if _, exists := check[pr.Number]; exists && pr.Number > 0 {
   202  				continue
   203  			}
   204  			check[pr.Number] = struct{}{}
   205  
   206  			res.PullRequests = append(res.PullRequests, pr)
   207  			if len(res.PullRequests) == limit {
   208  				break loop
   209  			}
   210  		}
   211  
   212  		if prData.PageInfo.HasNextPage {
   213  			variables["endCursor"] = prData.PageInfo.EndCursor
   214  			pageLimit = min(pageLimit, limit-len(res.PullRequests))
   215  		} else {
   216  			break
   217  		}
   218  	}
   219  
   220  	return &res, nil
   221  }
   222  
   223  func min(a, b int) int {
   224  	if a < b {
   225  		return a
   226  	}
   227  	return b
   228  }