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 }