github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/list/http.go (about)

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