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

     1  package list
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/ungtb10d/cli/v2/api"
     7  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
     8  	prShared "github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared"
     9  )
    10  
    11  func listIssues(client *api.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
    12  	var states []string
    13  	switch filters.State {
    14  	case "open", "":
    15  		states = []string{"OPEN"}
    16  	case "closed":
    17  		states = []string{"CLOSED"}
    18  	case "all":
    19  		states = []string{"OPEN", "CLOSED"}
    20  	default:
    21  		return nil, fmt.Errorf("invalid state: %s", filters.State)
    22  	}
    23  
    24  	fragments := fmt.Sprintf("fragment issue on Issue {%s}", api.IssueGraphQL(filters.Fields))
    25  	query := fragments + `
    26  	query IssueList($owner: String!, $repo: String!, $limit: Int, $endCursor: String, $states: [IssueState!] = OPEN, $assignee: String, $author: String, $mention: String) {
    27  		repository(owner: $owner, name: $repo) {
    28  			hasIssuesEnabled
    29  			issues(first: $limit, after: $endCursor, orderBy: {field: CREATED_AT, direction: DESC}, states: $states, filterBy: {assignee: $assignee, createdBy: $author, mentioned: $mention}) {
    30  				totalCount
    31  				nodes {
    32  					...issue
    33  				}
    34  				pageInfo {
    35  					hasNextPage
    36  					endCursor
    37  				}
    38  			}
    39  		}
    40  	}
    41  	`
    42  
    43  	variables := map[string]interface{}{
    44  		"owner":  repo.RepoOwner(),
    45  		"repo":   repo.RepoName(),
    46  		"states": states,
    47  	}
    48  	if filters.Assignee != "" {
    49  		variables["assignee"] = filters.Assignee
    50  	}
    51  	if filters.Author != "" {
    52  		variables["author"] = filters.Author
    53  	}
    54  	if filters.Mention != "" {
    55  		variables["mention"] = filters.Mention
    56  	}
    57  
    58  	if filters.Milestone != "" {
    59  		// The "milestone" filter in the GraphQL connection doesn't work as documented and accepts neither a
    60  		// milestone number nor a title. It does accept a numeric database ID, but we cannot obtain one
    61  		// using the GraphQL API.
    62  		return nil, fmt.Errorf("cannot filter by milestone using the `Repository.issues` GraphQL connection")
    63  	}
    64  
    65  	type responseData struct {
    66  		Repository struct {
    67  			Issues struct {
    68  				TotalCount int
    69  				Nodes      []api.Issue
    70  				PageInfo   struct {
    71  					HasNextPage bool
    72  					EndCursor   string
    73  				}
    74  			}
    75  			HasIssuesEnabled bool
    76  		}
    77  	}
    78  
    79  	var issues []api.Issue
    80  	var totalCount int
    81  	pageLimit := min(limit, 100)
    82  
    83  loop:
    84  	for {
    85  		var response responseData
    86  		variables["limit"] = pageLimit
    87  		err := client.GraphQL(repo.RepoHost(), query, variables, &response)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  		if !response.Repository.HasIssuesEnabled {
    92  			return nil, fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(repo))
    93  		}
    94  		totalCount = response.Repository.Issues.TotalCount
    95  
    96  		for _, issue := range response.Repository.Issues.Nodes {
    97  			issues = append(issues, issue)
    98  			if len(issues) == limit {
    99  				break loop
   100  			}
   101  		}
   102  
   103  		if response.Repository.Issues.PageInfo.HasNextPage {
   104  			variables["endCursor"] = response.Repository.Issues.PageInfo.EndCursor
   105  			pageLimit = min(pageLimit, limit-len(issues))
   106  		} else {
   107  			break
   108  		}
   109  	}
   110  
   111  	res := api.IssuesAndTotalCount{Issues: issues, TotalCount: totalCount}
   112  	return &res, nil
   113  }
   114  
   115  func searchIssues(client *api.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
   116  	fragments := fmt.Sprintf("fragment issue on Issue {%s}", api.IssueGraphQL(filters.Fields))
   117  	query := fragments +
   118  		`query IssueSearch($repo: String!, $owner: String!, $type: SearchType!, $limit: Int, $after: String, $query: String!) {
   119  			repository(name: $repo, owner: $owner) {
   120  				hasIssuesEnabled
   121  			}
   122  			search(type: $type, last: $limit, after: $after, query: $query) {
   123  				issueCount
   124  				nodes { ...issue }
   125  				pageInfo {
   126  					hasNextPage
   127  					endCursor
   128  				}
   129  			}
   130  		}`
   131  
   132  	type response struct {
   133  		Repository struct {
   134  			HasIssuesEnabled bool
   135  		}
   136  		Search struct {
   137  			IssueCount int
   138  			Nodes      []api.Issue
   139  			PageInfo   struct {
   140  				HasNextPage bool
   141  				EndCursor   string
   142  			}
   143  		}
   144  	}
   145  
   146  	filters.Repo = ghrepo.FullName(repo)
   147  	filters.Entity = "issue"
   148  	q := prShared.SearchQueryBuild(filters)
   149  
   150  	perPage := min(limit, 100)
   151  
   152  	variables := map[string]interface{}{
   153  		"owner": repo.RepoOwner(),
   154  		"repo":  repo.RepoName(),
   155  		"type":  "ISSUE",
   156  		"limit": perPage,
   157  		"query": q,
   158  	}
   159  
   160  	ic := api.IssuesAndTotalCount{SearchCapped: limit > 1000}
   161  
   162  loop:
   163  	for {
   164  		var resp response
   165  		err := client.GraphQL(repo.RepoHost(), query, variables, &resp)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  
   170  		if !resp.Repository.HasIssuesEnabled {
   171  			return nil, fmt.Errorf("the '%s' repository has disabled issues", ghrepo.FullName(repo))
   172  		}
   173  
   174  		ic.TotalCount = resp.Search.IssueCount
   175  
   176  		for _, issue := range resp.Search.Nodes {
   177  			ic.Issues = append(ic.Issues, issue)
   178  			if len(ic.Issues) == limit {
   179  				break loop
   180  			}
   181  		}
   182  
   183  		if !resp.Search.PageInfo.HasNextPage {
   184  			break
   185  		}
   186  		variables["after"] = resp.Search.PageInfo.EndCursor
   187  		variables["perPage"] = min(perPage, limit-len(ic.Issues))
   188  	}
   189  
   190  	return &ic, nil
   191  }
   192  
   193  func min(a, b int) int {
   194  	if a < b {
   195  		return a
   196  	}
   197  	return b
   198  }