Skip to content

Commit 5fcda2b

Browse files
committed
Merge remote-tracking branch 'origin/master' into pr-list
2 parents e6d55ff + cdd4951 commit 5fcda2b

File tree

13 files changed

+513
-73
lines changed

13 files changed

+513
-73
lines changed

api/queries.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api
22

33
import (
44
"fmt"
5+
"time"
56
)
67

78
type PullRequestsPayload struct {
@@ -23,6 +24,108 @@ type Repo interface {
2324
RepoOwner() string
2425
}
2526

27+
type IssuesPayload struct {
28+
Assigned []Issue
29+
Mentioned []Issue
30+
Recent []Issue
31+
}
32+
33+
type Issue struct {
34+
Number int
35+
Title string
36+
}
37+
38+
func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload, error) {
39+
type issues struct {
40+
Issues struct {
41+
Edges []struct {
42+
Node Issue
43+
}
44+
}
45+
}
46+
47+
type response struct {
48+
Assigned issues
49+
Mentioned issues
50+
Recent issues
51+
}
52+
53+
query := `
54+
fragment issue on Issue {
55+
number
56+
title
57+
}
58+
query($owner: String!, $repo: String!, $since: DateTime!, $viewer: String!, $per_page: Int = 10) {
59+
assigned: repository(owner: $owner, name: $repo) {
60+
issues(filterBy: {assignee: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) {
61+
edges {
62+
node {
63+
...issue
64+
}
65+
}
66+
}
67+
}
68+
mentioned: repository(owner: $owner, name: $repo) {
69+
issues(filterBy: {mentioned: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) {
70+
edges {
71+
node {
72+
...issue
73+
}
74+
}
75+
}
76+
}
77+
recent: repository(owner: $owner, name: $repo) {
78+
issues(filterBy: {since: $since}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) {
79+
edges {
80+
node {
81+
...issue
82+
}
83+
}
84+
}
85+
}
86+
}
87+
`
88+
89+
owner := ghRepo.RepoOwner()
90+
repo := ghRepo.RepoName()
91+
since := time.Now().UTC().Add(time.Hour * -24).Format("2006-01-02T15:04:05-0700")
92+
variables := map[string]interface{}{
93+
"owner": owner,
94+
"repo": repo,
95+
"viewer": currentUsername,
96+
"since": since,
97+
}
98+
99+
var resp response
100+
err := client.GraphQL(query, variables, &resp)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
var assigned []Issue
106+
for _, edge := range resp.Assigned.Issues.Edges {
107+
assigned = append(assigned, edge.Node)
108+
}
109+
110+
var mentioned []Issue
111+
for _, edge := range resp.Mentioned.Issues.Edges {
112+
mentioned = append(mentioned, edge.Node)
113+
}
114+
115+
var recent []Issue
116+
for _, edge := range resp.Recent.Issues.Edges {
117+
recent = append(recent, edge.Node)
118+
}
119+
120+
payload := IssuesPayload{
121+
assigned,
122+
mentioned,
123+
recent,
124+
}
125+
126+
return &payload, nil
127+
}
128+
26129
func PullRequests(client *Client, ghRepo Repo, currentBranch, currentUsername string) (*PullRequestsPayload, error) {
27130
type edges struct {
28131
Edges []struct {

command/completion.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func init() {
10+
RootCmd.AddCommand(completionCmd)
11+
completionCmd.Flags().StringP("shell", "s", "bash", "The type of shell")
12+
}
13+
14+
var completionCmd = &cobra.Command{
15+
Use: "completion",
16+
Hidden: true,
17+
Short: "Generates completion scripts",
18+
Long: `To enable completion in your shell, run:
19+
20+
eval "$(gh completion)"
21+
22+
You can add that to your '~/.bash_profile' to enable completion whenever you
23+
start a new shell.
24+
25+
When installing with Homebrew, see https://docs.brew.sh/Shell-Completion
26+
`,
27+
RunE: func(cmd *cobra.Command, args []string) error {
28+
shellType, err := cmd.Flags().GetString("shell")
29+
if err != nil {
30+
return err
31+
}
32+
33+
switch shellType {
34+
case "bash":
35+
RootCmd.GenBashCompletion(cmd.OutOrStdout())
36+
case "zsh":
37+
RootCmd.GenZshCompletion(cmd.OutOrStdout())
38+
default:
39+
return fmt.Errorf("unsupported shell type: %s", shellType)
40+
}
41+
return nil
42+
},
43+
}

command/completion_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package command
2+
3+
import (
4+
"bytes"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func TestCompletion_bash(t *testing.T) {
10+
out := bytes.Buffer{}
11+
completionCmd.SetOut(&out)
12+
13+
RootCmd.SetArgs([]string{"completion"})
14+
_, err := RootCmd.ExecuteC()
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
19+
outStr := out.String()
20+
if !strings.Contains(outStr, "complete -o default -F __start_gh gh") {
21+
t.Errorf("problem in bash completion:\n%s", outStr)
22+
}
23+
}
24+
25+
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()
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
35+
outStr := out.String()
36+
if !strings.Contains(outStr, "#compdef _gh gh") {
37+
t.Errorf("problem in zsh completion:\n%s", outStr)
38+
}
39+
}
40+
41+
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()
47+
if err == nil || err.Error() != "unsupported shell type: fish" {
48+
t.Fatal(err)
49+
}
50+
}

command/issue.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package command
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
7+
"github.com/github/gh-cli/api"
8+
"github.com/github/gh-cli/utils"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func init() {
13+
var issueCmd = &cobra.Command{
14+
Use: "issue",
15+
Short: "Work with GitHub issues",
16+
Long: `This command allows you to work with issues.`,
17+
Args: cobra.MinimumNArgs(1),
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
return fmt.Errorf("%+v is not a valid issue command", args)
20+
},
21+
}
22+
23+
issueCmd.AddCommand(
24+
&cobra.Command{
25+
Use: "status",
26+
Short: "Display issue status",
27+
RunE: issueList,
28+
},
29+
&cobra.Command{
30+
Use: "view [issue-number]",
31+
Args: cobra.MinimumNArgs(1),
32+
Short: "Open a issue in the browser",
33+
RunE: issueView,
34+
},
35+
)
36+
37+
RootCmd.AddCommand(issueCmd)
38+
}
39+
40+
func issueList(cmd *cobra.Command, args []string) error {
41+
ctx := contextForCommand(cmd)
42+
apiClient, err := apiClientForContext(ctx)
43+
if err != nil {
44+
return err
45+
}
46+
47+
baseRepo, err := ctx.BaseRepo()
48+
if err != nil {
49+
return err
50+
}
51+
52+
currentUser, err := ctx.AuthLogin()
53+
if err != nil {
54+
return err
55+
}
56+
57+
issuePayload, err := api.Issues(apiClient, baseRepo, currentUser)
58+
if err != nil {
59+
return err
60+
}
61+
62+
printHeader("Issues assigned to you")
63+
if issuePayload.Assigned != nil {
64+
printIssues(issuePayload.Assigned...)
65+
} else {
66+
message := fmt.Sprintf(" There are no issues assgined to you")
67+
printMessage(message)
68+
}
69+
fmt.Println()
70+
71+
printHeader("Issues mentioning you")
72+
if len(issuePayload.Mentioned) > 0 {
73+
printIssues(issuePayload.Mentioned...)
74+
} else {
75+
printMessage(" There are no issues mentioning you")
76+
}
77+
fmt.Println()
78+
79+
printHeader("Recent issues")
80+
if len(issuePayload.Recent) > 0 {
81+
printIssues(issuePayload.Recent...)
82+
} else {
83+
printMessage(" There are no recent issues")
84+
}
85+
fmt.Println()
86+
87+
return nil
88+
}
89+
90+
func issueView(cmd *cobra.Command, args []string) error {
91+
ctx := contextForCommand(cmd)
92+
93+
baseRepo, err := ctx.BaseRepo()
94+
if err != nil {
95+
return err
96+
}
97+
98+
var openURL string
99+
if number, err := strconv.Atoi(args[0]); err == nil {
100+
// TODO: move URL generation into GitHubRepository
101+
openURL = fmt.Sprintf("https://github.com/%s/%s/issues/%d", baseRepo.RepoOwner(), baseRepo.RepoName(), number)
102+
} else {
103+
return fmt.Errorf("invalid issue number: '%s'", args[0])
104+
}
105+
106+
fmt.Printf("Opening %s in your browser.\n", openURL)
107+
return utils.OpenInBrowser(openURL)
108+
}
109+
110+
func printIssues(issues ...api.Issue) {
111+
for _, issue := range issues {
112+
fmt.Printf(" #%d %s\n", issue.Number, truncate(70, issue.Title))
113+
}
114+
}

command/issue_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package command
2+
3+
import (
4+
"os"
5+
"regexp"
6+
"testing"
7+
8+
"github.com/github/gh-cli/test"
9+
)
10+
11+
func TestIssueStatus(t *testing.T) {
12+
initBlankContext("OWNER/REPO", "master")
13+
http := initFakeHTTP()
14+
15+
jsonFile, _ := os.Open("../test/fixtures/issueStatus.json")
16+
defer jsonFile.Close()
17+
http.StubResponse(200, jsonFile)
18+
19+
output, err := test.RunCommand(RootCmd, "issue status")
20+
if err != nil {
21+
t.Errorf("error running command `issue status`: %v", err)
22+
}
23+
24+
expectedIssues := []*regexp.Regexp{
25+
regexp.MustCompile(`#8.*carrots`),
26+
regexp.MustCompile(`#9.*squash`),
27+
regexp.MustCompile(`#10.*broccoli`),
28+
regexp.MustCompile(`#11.*swiss chard`),
29+
}
30+
31+
for _, r := range expectedIssues {
32+
if !r.MatchString(output) {
33+
t.Errorf("output did not match regexp /%s/", r)
34+
}
35+
}
36+
}
37+
38+
func TestIssueView(t *testing.T) {
39+
initBlankContext("OWNER/REPO", "master")
40+
http := initFakeHTTP()
41+
42+
jsonFile, _ := os.Open("../test/fixtures/issueView.json")
43+
defer jsonFile.Close()
44+
http.StubResponse(200, jsonFile)
45+
46+
teardown, callCount := mockOpenInBrowser()
47+
defer teardown()
48+
49+
output, err := test.RunCommand(RootCmd, "issue view 8")
50+
if err != nil {
51+
t.Errorf("error running command `issue view`: %v", err)
52+
}
53+
54+
if output == "" {
55+
t.Errorf("command output expected got an empty string")
56+
}
57+
58+
if *callCount != 1 {
59+
t.Errorf("OpenInBrowser should be called 1 time but was called %d time(s)", *callCount)
60+
}
61+
}

0 commit comments

Comments
 (0)