Skip to content

Commit 5bfcab8

Browse files
authored
Merge pull request cli#124 from github/pr-list-assignee
Support `-a, --assignee` in `pr list`
2 parents ad12087 + f87680e commit 5bfcab8

File tree

12 files changed

+213
-147
lines changed

12 files changed

+213
-147
lines changed

api/queries_pr.go

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -409,21 +409,39 @@ func CreatePullRequest(client *Client, ghRepo Repo, params map[string]interface{
409409
}
410410

411411
func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]PullRequest, error) {
412+
type prBlock struct {
413+
Edges []struct {
414+
Node PullRequest
415+
}
416+
PageInfo struct {
417+
HasNextPage bool
418+
EndCursor string
419+
}
420+
}
412421
type response struct {
413422
Repository struct {
414-
PullRequests struct {
415-
Edges []struct {
416-
Node PullRequest
417-
}
418-
PageInfo struct {
419-
HasNextPage bool
420-
EndCursor string
421-
}
422-
}
423+
PullRequests prBlock
423424
}
425+
Search prBlock
424426
}
425427

426-
query := `
428+
fragment := `
429+
fragment pr on PullRequest {
430+
number
431+
title
432+
state
433+
url
434+
headRefName
435+
headRepositoryOwner {
436+
login
437+
}
438+
isCrossRepository
439+
}
440+
`
441+
442+
// If assignee wasn't specified, use `Repository.pullRequest` for ability to
443+
// query by multiple labels
444+
query := fragment + `
427445
query(
428446
$owner: String!,
429447
$repo: String!,
@@ -444,15 +462,7 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
444462
) {
445463
edges {
446464
node {
447-
number
448-
title
449-
state
450-
url
451-
headRefName
452-
headRepositoryOwner {
453-
login
454-
}
455-
isCrossRepository
465+
...pr
456466
}
457467
}
458468
pageInfo {
@@ -461,13 +471,65 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
461471
}
462472
}
463473
}
464-
}`
474+
}`
465475

466476
prs := []PullRequest{}
467477
pageLimit := min(limit, 100)
468478
variables := map[string]interface{}{}
469-
for name, val := range vars {
470-
variables[name] = val
479+
480+
// If assignee was specified, use the `search` API rather than
481+
// `Repository.pullRequests`, but this mode doesn't support multiple labels
482+
if assignee, ok := vars["assignee"].(string); ok {
483+
query = fragment + `
484+
query(
485+
$q: String!,
486+
$limit: Int!,
487+
$endCursor: String,
488+
) {
489+
search(query: $q, type: ISSUE, first: $limit, after: $endCursor) {
490+
edges {
491+
node {
492+
...pr
493+
}
494+
}
495+
pageInfo {
496+
hasNextPage
497+
endCursor
498+
}
499+
}
500+
}`
501+
owner := vars["owner"].(string)
502+
repo := vars["repo"].(string)
503+
search := []string{
504+
fmt.Sprintf("repo:%s/%s", owner, repo),
505+
fmt.Sprintf("assignee:%s", assignee),
506+
"is:pr",
507+
"sort:created-desc",
508+
}
509+
if states, ok := vars["state"].([]string); ok && len(states) == 1 {
510+
switch states[0] {
511+
case "OPEN":
512+
search = append(search, "state:open")
513+
case "CLOSED":
514+
search = append(search, "state:closed")
515+
case "MERGED":
516+
search = append(search, "is:merged")
517+
}
518+
}
519+
if labels, ok := vars["labels"].([]string); ok && len(labels) > 0 {
520+
if len(labels) > 1 {
521+
return nil, fmt.Errorf("multiple labels with --assignee are not supported")
522+
}
523+
search = append(search, fmt.Sprintf(`label:"%s"`, labels[0]))
524+
}
525+
if baseBranch, ok := vars["baseBranch"].(string); ok {
526+
search = append(search, fmt.Sprintf(`base:"%s"`, baseBranch))
527+
}
528+
variables["q"] = strings.Join(search, " ")
529+
} else {
530+
for name, val := range vars {
531+
variables[name] = val
532+
}
471533
}
472534

473535
for {
@@ -478,6 +540,9 @@ func PullRequestList(client *Client, vars map[string]interface{}, limit int) ([]
478540
return nil, err
479541
}
480542
prData := data.Repository.PullRequests
543+
if _, ok := variables["q"]; ok {
544+
prData = data.Search
545+
}
481546

482547
for _, edge := range prData.Edges {
483548
prs = append(prs, edge.Node)

command/completion_test.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,34 @@
11
package command
22

33
import (
4-
"bytes"
54
"strings"
65
"testing"
76
)
87

98
func TestCompletion_bash(t *testing.T) {
10-
out := bytes.Buffer{}
11-
completionCmd.SetOut(&out)
12-
13-
RootCmd.SetArgs([]string{"completion"})
14-
_, err := RootCmd.ExecuteC()
9+
outStr, err := RunCommand(completionCmd, `completion`)
1510
if err != nil {
1611
t.Fatal(err)
1712
}
1813

19-
outStr := out.String()
2014
if !strings.Contains(outStr, "complete -o default -F __start_gh gh") {
2115
t.Errorf("problem in bash completion:\n%s", outStr)
2216
}
2317
}
2418

2519
func TestCompletion_zsh(t *testing.T) {
26-
out := bytes.Buffer{}
27-
completionCmd.SetOut(&out)
28-
29-
RootCmd.SetArgs([]string{"completion", "-s", "zsh"})
30-
_, err := RootCmd.ExecuteC()
20+
outStr, err := RunCommand(completionCmd, `completion -s zsh`)
3121
if err != nil {
3222
t.Fatal(err)
3323
}
3424

35-
outStr := out.String()
3625
if !strings.Contains(outStr, "#compdef _gh gh") {
3726
t.Errorf("problem in zsh completion:\n%s", outStr)
3827
}
3928
}
4029

4130
func TestCompletion_unsupported(t *testing.T) {
42-
out := bytes.Buffer{}
43-
completionCmd.SetOut(&out)
44-
45-
RootCmd.SetArgs([]string{"completion", "-s", "fish"})
46-
_, err := RootCmd.ExecuteC()
31+
_, err := RunCommand(completionCmd, `completion -s fish`)
4732
if err == nil || err.Error() != "unsupported shell type: fish" {
4833
t.Fatal(err)
4934
}

command/issue.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,21 @@ import (
1515

1616
func init() {
1717
RootCmd.AddCommand(issueCmd)
18-
issueCmd.AddCommand(issueCreateCmd)
1918
issueCmd.AddCommand(issueStatusCmd)
2019
issueCmd.AddCommand(issueViewCmd)
20+
21+
issueCmd.AddCommand(issueCreateCmd)
2122
issueCreateCmd.Flags().StringP("title", "t", "",
2223
"Supply a title. Will prompt for one otherwise.")
2324
issueCreateCmd.Flags().StringP("body", "b", "",
2425
"Supply a body. Will prompt for one otherwise.")
2526
issueCreateCmd.Flags().BoolP("web", "w", false, "Open the web browser to create an issue")
2627

27-
issueListCmd := &cobra.Command{
28-
Use: "list",
29-
Short: "List and filter issues in this repository",
30-
RunE: issueList,
31-
}
28+
issueCmd.AddCommand(issueListCmd)
3229
issueListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee")
3330
issueListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label")
3431
issueListCmd.Flags().StringP("state", "s", "", "Filter by state (open|closed|all)")
3532
issueListCmd.Flags().IntP("limit", "L", 30, "Maximum number of issues to fetch")
36-
issueCmd.AddCommand((issueListCmd))
3733
}
3834

3935
var issueCmd = &cobra.Command{
@@ -46,6 +42,11 @@ var issueCreateCmd = &cobra.Command{
4642
Short: "Create a new issue",
4743
RunE: issueCreate,
4844
}
45+
var issueListCmd = &cobra.Command{
46+
Use: "list",
47+
Short: "List and filter issues in this repository",
48+
RunE: issueList,
49+
}
4950
var issueStatusCmd = &cobra.Command{
5051
Use: "status",
5152
Short: "Show status of relevant issues",
@@ -191,7 +192,7 @@ func issueView(cmd *cobra.Command, args []string) error {
191192
return fmt.Errorf("invalid issue number: '%s'", args[0])
192193
}
193194

194-
fmt.Printf("Opening %s in your browser.\n", openURL)
195+
cmd.Printf("Opening %s in your browser.\n", openURL)
195196
return utils.OpenInBrowser(openURL)
196197
}
197198

command/issue_test.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"regexp"
1010
"testing"
1111

12-
"github.com/github/gh-cli/test"
1312
"github.com/github/gh-cli/utils"
1413
)
1514

@@ -21,7 +20,7 @@ func TestIssueStatus(t *testing.T) {
2120
defer jsonFile.Close()
2221
http.StubResponse(200, jsonFile)
2322

24-
output, err := test.RunCommand(RootCmd, "issue status")
23+
output, err := RunCommand(issueStatusCmd, "issue status")
2524
if err != nil {
2625
t.Errorf("error running command `issue status`: %v", err)
2726
}
@@ -49,7 +48,7 @@ func TestIssueList(t *testing.T) {
4948
defer jsonFile.Close()
5049
http.StubResponse(200, jsonFile)
5150

52-
output, err := test.RunCommand(RootCmd, "issue list")
51+
output, err := RunCommand(issueListCmd, "issue list")
5352
if err != nil {
5453
t.Errorf("error running command `issue list`: %v", err)
5554
}
@@ -73,7 +72,7 @@ func TestIssueList_withFlags(t *testing.T) {
7372

7473
http.StubResponse(200, bytes.NewBufferString(`{"data": {}}`)) // Since we are testing that the flags are passed, we don't care about the response
7574

76-
_, err := test.RunCommand(RootCmd, "issue list -a probablyCher -l web,bug -s open")
75+
_, err := RunCommand(issueListCmd, "issue list -a probablyCher -l web,bug -s open")
7776
if err != nil {
7877
t.Errorf("error running command `issue list`: %v", err)
7978
}
@@ -108,7 +107,7 @@ func TestIssueView(t *testing.T) {
108107
})
109108
defer restoreCmd()
110109

111-
output, err := test.RunCommand(RootCmd, "issue view 8")
110+
output, err := RunCommand(issueViewCmd, "issue view 8")
112111
if err != nil {
113112
t.Errorf("error running command `issue view`: %v", err)
114113
}
@@ -141,11 +140,7 @@ func TestIssueCreate(t *testing.T) {
141140
} } } }
142141
`))
143142

144-
out := bytes.Buffer{}
145-
issueCreateCmd.SetOut(&out)
146-
147-
RootCmd.SetArgs([]string{"issue", "create", "-t", "hello", "-b", "cash rules everything around me"})
148-
_, err := RootCmd.ExecuteC()
143+
output, err := RunCommand(issueCreateCmd, `issue create -t hello -b "cash rules everything around me"`)
149144
if err != nil {
150145
t.Errorf("error running command `issue create`: %v", err)
151146
}
@@ -166,5 +161,5 @@ func TestIssueCreate(t *testing.T) {
166161
eq(t, reqBody.Variables.Input.Title, "hello")
167162
eq(t, reqBody.Variables.Input.Body, "cash rules everything around me")
168163

169-
eq(t, out.String(), "https://github.com/OWNER/REPO/issues/12\n")
164+
eq(t, output, "https://github.com/OWNER/REPO/issues/12\n")
170165
}

command/pr.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ func init() {
2727
prListCmd.Flags().IntP("limit", "L", 30, "Maximum number of items to fetch")
2828
prListCmd.Flags().StringP("state", "s", "open", "Filter by state")
2929
prListCmd.Flags().StringP("base", "B", "", "Filter by base branch")
30-
prListCmd.Flags().StringArrayP("label", "l", nil, "Filter by label")
30+
prListCmd.Flags().StringSliceP("label", "l", nil, "Filter by label")
31+
prListCmd.Flags().StringP("assignee", "a", "", "Filter by assignee")
3132
}
3233

3334
var prCmd = &cobra.Command{
@@ -136,7 +137,11 @@ func prList(cmd *cobra.Command, args []string) error {
136137
if err != nil {
137138
return err
138139
}
139-
labels, err := cmd.Flags().GetStringArray("label")
140+
labels, err := cmd.Flags().GetStringSlice("label")
141+
if err != nil {
142+
return err
143+
}
144+
assignee, err := cmd.Flags().GetString("assignee")
140145
if err != nil {
141146
return err
142147
}
@@ -166,6 +171,9 @@ func prList(cmd *cobra.Command, args []string) error {
166171
if baseBranch != "" {
167172
params["baseBranch"] = baseBranch
168173
}
174+
if assignee != "" {
175+
params["assignee"] = assignee
176+
}
169177

170178
prs, err := api.PullRequestList(apiClient, params, limit)
171179
if err != nil {
@@ -241,7 +249,7 @@ func prView(cmd *cobra.Command, args []string) error {
241249
}
242250
}
243251

244-
fmt.Printf("Opening %s in your browser.\n", openURL)
252+
cmd.Printf("Opening %s in your browser.\n", openURL)
245253
return utils.OpenInBrowser(openURL)
246254
}
247255

0 commit comments

Comments
 (0)