Accept issue URL in issue view <issue>

Also validates that the issue passed either by number or by URL exists.
This commit is contained in:
Mislav Marohnić 2019-12-03 21:41:22 +01:00
parent 29de133ccf
commit b223176b37
3 changed files with 126 additions and 13 deletions

View file

@ -202,3 +202,35 @@ func IssueList(client *Client, ghRepo Repo, state string, labels []string, assig
return resp.Repository.Issues.Nodes, nil
}
func IssueByNumber(client *Client, ghRepo Repo, number int) (*Issue, error) {
type response struct {
Repository struct {
Issue Issue
}
}
query := `
query($owner: String!, $repo: String!, $issue_number: Int!) {
repository(owner: $owner, name: $repo) {
issue(number: $issue_number) {
number
url
}
}
}`
variables := map[string]interface{}{
"owner": ghRepo.RepoOwner(),
"repo": ghRepo.RepoName(),
"issue_number": number,
}
var resp response
err := client.GraphQL(query, variables, &resp)
if err != nil {
return nil, err
}
return &resp.Repository.Issue, nil
}

View file

@ -4,10 +4,12 @@ import (
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"github.com/github/gh-cli/api"
"github.com/github/gh-cli/context"
"github.com/github/gh-cli/utils"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -183,19 +185,36 @@ func issueView(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
var openURL string
if number, err := strconv.Atoi(args[0]); err == nil {
// TODO: move URL generation into GitHubRepository
openURL = fmt.Sprintf("https://github.com/%s/%s/issues/%d", baseRepo.RepoOwner(), baseRepo.RepoName(), number)
} else {
return fmt.Errorf("invalid issue number: '%s'", args[0])
apiClient, err := apiClientForContext(ctx)
if err != nil {
return err
}
issue, err := issueFromArg(apiClient, baseRepo, args[0])
if err != nil {
return err
}
openURL := issue.URL
cmd.Printf("Opening %s in your browser.\n", openURL)
return utils.OpenInBrowser(openURL)
}
var issueURLRE = regexp.MustCompile(`^https://github\.com/([^/]+)/([^/]+)/issues/(\d+)`)
func issueFromArg(apiClient *api.Client, baseRepo context.GitHubRepository, arg string) (*api.Issue, error) {
if issueNumber, err := strconv.Atoi(arg); err == nil {
return api.IssueByNumber(apiClient, baseRepo, issueNumber)
}
if m := issueURLRE.FindStringSubmatch(arg); m != nil {
issueNumber, _ := strconv.Atoi(m[3])
return api.IssueByNumber(apiClient, baseRepo, issueNumber)
}
return nil, fmt.Errorf("invalid issue format: %q", arg)
}
func issueCreate(cmd *cobra.Command, args []string) error {
ctx := contextForCommand(cmd)

View file

@ -96,9 +96,12 @@ func TestIssueView(t *testing.T) {
initBlankContext("OWNER/REPO", "master")
http := initFakeHTTP()
jsonFile, _ := os.Open("../test/fixtures/issueView.json")
defer jsonFile.Close()
http.StubResponse(200, jsonFile)
http.StubResponse(200, bytes.NewBufferString(`
{ "data": { "repository": { "issue": {
"number": 123,
"url": "https://github.com/OWNER/REPO/issues/123"
} } } }
`))
var seenCmd *exec.Cmd
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
@ -107,7 +110,7 @@ func TestIssueView(t *testing.T) {
})
defer restoreCmd()
output, err := RunCommand(issueViewCmd, "issue view 8")
output, err := RunCommand(issueViewCmd, "issue view 123")
if err != nil {
t.Errorf("error running command `issue view`: %v", err)
}
@ -120,9 +123,68 @@ func TestIssueView(t *testing.T) {
t.Fatal("expected a command to run")
}
url := seenCmd.Args[len(seenCmd.Args)-1]
if url != "https://github.com/OWNER/REPO/issues/8" {
t.Errorf("got: %q", url)
eq(t, url, "https://github.com/OWNER/REPO/issues/123")
}
func TestIssueView_notFound(t *testing.T) {
initBlankContext("OWNER/REPO", "master")
http := initFakeHTTP()
http.StubResponse(200, bytes.NewBufferString(`
{ "errors": [
{ "message": "Could not resolve to an Issue with the number of 9999." }
] }
`))
var seenCmd *exec.Cmd
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
seenCmd = cmd
return &outputStub{}
})
defer restoreCmd()
_, err := RunCommand(issueViewCmd, "issue view 9999")
if err == nil || err.Error() != "graphql error: 'Could not resolve to an Issue with the number of 9999.'" {
t.Errorf("error running command `issue view`: %v", err)
}
if seenCmd != nil {
t.Fatal("did not expect any command to run")
}
}
func TestIssueView_urlArg(t *testing.T) {
initBlankContext("OWNER/REPO", "master")
http := initFakeHTTP()
http.StubResponse(200, bytes.NewBufferString(`
{ "data": { "repository": { "issue": {
"number": 123,
"url": "https://github.com/OWNER/REPO/issues/123"
} } } }
`))
var seenCmd *exec.Cmd
restoreCmd := utils.SetPrepareCmd(func(cmd *exec.Cmd) utils.Runnable {
seenCmd = cmd
return &outputStub{}
})
defer restoreCmd()
output, err := RunCommand(issueViewCmd, "issue view https://github.com/OWNER/REPO/issues/123")
if err != nil {
t.Errorf("error running command `issue view`: %v", err)
}
if output == "" {
t.Errorf("command output expected got an empty string")
}
if seenCmd == nil {
t.Fatal("expected a command to run")
}
url := seenCmd.Args[len(seenCmd.Args)-1]
eq(t, url, "https://github.com/OWNER/REPO/issues/123")
}
func TestIssueCreate(t *testing.T) {