From d658a8f407d9ff39ea6bd55990d71d3166aaa644 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Wed, 30 Oct 2019 16:26:33 -0700 Subject: [PATCH 1/7] Add Issue query --- api/queries.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/api/queries.go b/api/queries.go index e1e078138..c0bc53eca 100644 --- a/api/queries.go +++ b/api/queries.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "time" ) type PullRequestsPayload struct { @@ -22,6 +23,108 @@ type Repo interface { RepoOwner() string } +type IssuesPayload struct { + Assigned []Issue + Mentioned []Issue + Recent []Issue +} + +type Issue struct { + Number int + Title string +} + +func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload, error) { + type issues struct { + Issues struct { + Edges []struct { + Node Issue + } + } + } + + type response struct { + Assigned issues + Mentioned issues + Recent issues + } + + query := ` + fragment issue on Issue { + number + title + } + query($owner: String!, $repo: String!, $since: DateTime!, $viewer: String!, $per_page: Int = 10) { + assigned: repository(owner: $owner, name: $repo) { + issues(filterBy: {assignee: $viewer}, first: $per_page) { + edges { + node { + ...issue + } + } + } + } + mentioned: repository(owner: $owner, name: $repo) { + issues(filterBy: {mentioned: $viewer}, first: $per_page) { + edges { + node { + ...issue + } + } + } + } + recent: repository(owner: $owner, name: $repo) { + issues(filterBy: {since: $since, orderBy: {field: CREATED_AT, direction: DESC}}, first: $per_page) { + edges { + node { + ...issue + } + } + } + } + } + ` + + owner := ghRepo.RepoOwner() + repo := ghRepo.RepoName() + since := time.Now().UTC().Add(time.Hour * -24).Format("2006-01-02T15:04:05-0700") + variables := map[string]interface{}{ + "owner": owner, + "repo": repo, + "viewer": currentUsername, + "since": since, + } + + var resp response + err := client.GraphQL(query, variables, &resp) + if err != nil { + return nil, err + } + + var assigned []Issue + for _, edge := range resp.Assigned.Issues.Edges { + assigned = append(assigned, edge.Node) + } + + var mentioned []Issue + for _, edge := range resp.Mentioned.Issues.Edges { + mentioned = append(mentioned, edge.Node) + } + + var recent []Issue + for _, edge := range resp.Recent.Issues.Edges { + recent = append(recent, edge.Node) + } + + payload := IssuesPayload{ + assigned, + mentioned, + recent, + } + + return &payload, nil +} + func PullRequests(client *Client, ghRepo Repo, currentBranch, currentUsername string) (*PullRequestsPayload, error) { type edges struct { Edges []struct { From aea7ae8efd7b3c07c01e41896c26557385477947 Mon Sep 17 00:00:00 2001 From: Corey Johnson Date: Thu, 31 Oct 2019 11:02:27 -0700 Subject: [PATCH 2/7] Add `gh issue list` and `gh issue view ISSUE_NUMBER` --- command/issue.go | 117 +++++++++++++++++++++++++++++++++++ command/issue_test.go | 61 ++++++++++++++++++ command/pr.go | 6 +- command/pr_test.go | 35 ----------- command/testing.go | 39 ++++++++++++ test/fixtures/issueList.json | 47 ++++++++++++++ test/fixtures/issueView.json | 36 +++++++++++ test/helpers.go | 1 + 8 files changed, 303 insertions(+), 39 deletions(-) create mode 100644 command/issue.go create mode 100644 command/issue_test.go create mode 100644 command/testing.go create mode 100644 test/fixtures/issueList.json create mode 100644 test/fixtures/issueView.json diff --git a/command/issue.go b/command/issue.go new file mode 100644 index 000000000..e64d8d806 --- /dev/null +++ b/command/issue.go @@ -0,0 +1,117 @@ +package command + +import ( + "fmt" + "strconv" + + "github.com/github/gh-cli/api" + "github.com/github/gh-cli/utils" + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(issueCmd) + issueCmd.AddCommand( + &cobra.Command{ + Use: "list", + Short: "List issues", + RunE: issueList, + }, + &cobra.Command{ + Use: "view issue-number", + Args: cobra.MinimumNArgs(1), + Short: "Open a issue in the browser", + RunE: issueView, + }, + ) +} + +var issueCmd = &cobra.Command{ + Use: "issue", + Short: "Work with GitHub issues", + Long: `This command allows you to work with issues.`, + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return fmt.Errorf("%+v is not a valid issue command", args) + }, +} + +func issueList(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + ctx := contextForCommand(cmd) + apiClient, err := apiClientForContext(ctx) + if err != nil { + return err + } + + baseRepo, err := ctx.BaseRepo() + if err != nil { + return err + } + + currentUser, err := ctx.AuthLogin() + if err != nil { + return err + } + + issuePayload, err := api.Issues(apiClient, baseRepo, currentUser) + if err != nil { + return err + } + + printHeader("Issues assigned to you") + if issuePayload.Assigned != nil { + printIssues(issuePayload.Assigned...) + } else { + message := fmt.Sprintf(" There are no issues assgined to you") + printMessage(message) + } + fmt.Println() + + printHeader("Issues mentioning you") + if len(issuePayload.Mentioned) > 0 { + printIssues(issuePayload.Mentioned...) + } else { + printMessage(" There are no issues mentioning you") + } + fmt.Println() + + printHeader("Recent issues") + if len(issuePayload.Recent) > 0 { + printIssues(issuePayload.Recent...) + } else { + printMessage(" There are no recent issues") + } + fmt.Println() + + return nil +} + +func issueView(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + + ctx := contextForCommand(cmd) + + baseRepo, err := ctx.BaseRepo() + 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]) + } + + fmt.Printf("Opening %s in your browser.\n", openURL) + return utils.OpenInBrowser(openURL) +} + +func printIssues(issues ...api.Issue) { + for _, issue := range issues { + fmt.Printf(" #%d %s\n", issue.Number, truncateTitle(issue.Title, 70)) + } +} diff --git a/command/issue_test.go b/command/issue_test.go new file mode 100644 index 000000000..69312cac7 --- /dev/null +++ b/command/issue_test.go @@ -0,0 +1,61 @@ +package command + +import ( + "os" + "regexp" + "testing" + + "github.com/github/gh-cli/test" +) + +func TestIssueList(t *testing.T) { + initBlankContext("OWNER/REPO", "master") + http := initFakeHTTP() + + jsonFile, _ := os.Open("../test/fixtures/issueList.json") + defer jsonFile.Close() + http.StubResponse(200, jsonFile) + + output, err := test.RunCommand(RootCmd, "issue list") + if err != nil { + t.Errorf("error running command `issue list`: %v", err) + } + + expectedIssues := []*regexp.Regexp{ + regexp.MustCompile(`#8.*carrots`), + regexp.MustCompile(`#9.*squash`), + regexp.MustCompile(`#10.*broccoli`), + regexp.MustCompile(`#11.*swiss chard`), + } + + for _, r := range expectedIssues { + if !r.MatchString(output) { + t.Errorf("output did not match regexp /%s/", r) + } + } +} + +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) + + teardown, callCount := mockOpenInBrowser() + defer teardown() + + output, err := test.RunCommand(RootCmd, "issue view 8") + if err != nil { + t.Errorf("error running command `issue view`: %v", err) + } + + if output == "" { + t.Errorf("command output expected got an empty string") + } + + if *callCount != 1 { + t.Errorf("OpenInBrowser should be called 1 time but was called %d time(s)", *callCount) + } +} diff --git a/command/pr.go b/command/pr.go index f1dfe8fb0..182522f29 100644 --- a/command/pr.go +++ b/command/pr.go @@ -129,7 +129,7 @@ func prView(cmd *cobra.Command, args []string) error { func printPrs(prs ...api.PullRequest) { for _, pr := range prs { - fmt.Printf(" #%d %s %s\n", pr.Number, truncateTitle(pr.Title), utils.Cyan("["+pr.HeadRefName+"]")) + fmt.Printf(" #%d %s %s\n", pr.Number, truncateTitle(pr.Title, 50), utils.Cyan("["+pr.HeadRefName+"]")) } } @@ -141,9 +141,7 @@ func printMessage(s string) { fmt.Println(utils.Gray(s)) } -func truncateTitle(title string) string { - const maxLength = 50 - +func truncateTitle(title string, maxLength int) string { if len(title) > maxLength { return title[0:maxLength-3] + "..." } diff --git a/command/pr_test.go b/command/pr_test.go index e4a5122d7..5ac21b9bd 100644 --- a/command/pr_test.go +++ b/command/pr_test.go @@ -5,29 +5,9 @@ import ( "regexp" "testing" - "github.com/github/gh-cli/api" - "github.com/github/gh-cli/context" "github.com/github/gh-cli/test" - "github.com/github/gh-cli/utils" ) -func initBlankContext(repo, branch string) { - initContext = func() context.Context { - ctx := context.NewBlank() - ctx.SetBaseRepo(repo) - ctx.SetBranch(branch) - return ctx - } -} - -func initFakeHTTP() *api.FakeHTTP { - http := &api.FakeHTTP{} - apiClientForContext = func(context.Context) (*api.Client, error) { - return api.NewClient(api.ReplaceTripper(http)), nil - } - return http -} - func TestPRList(t *testing.T) { initBlankContext("OWNER/REPO", "master") http := initFakeHTTP() @@ -114,18 +94,3 @@ func TestPRView_NoActiveBranch(t *testing.T) { t.Errorf("OpenInBrowser should be called once but was called %d time(s)", *callCount) } } - -func mockOpenInBrowser() (func(), *int) { - callCount := 0 - originalOpenInBrowser := utils.OpenInBrowser - teardown := func() { - utils.OpenInBrowser = originalOpenInBrowser - } - - utils.OpenInBrowser = func(_ string) error { - callCount++ - return nil - } - - return teardown, &callCount -} diff --git a/command/testing.go b/command/testing.go new file mode 100644 index 000000000..a6ccf4807 --- /dev/null +++ b/command/testing.go @@ -0,0 +1,39 @@ +package command + +import ( + "github.com/github/gh-cli/api" + "github.com/github/gh-cli/context" + "github.com/github/gh-cli/utils" +) + +func initBlankContext(repo, branch string) { + initContext = func() context.Context { + ctx := context.NewBlank() + ctx.SetBaseRepo(repo) + ctx.SetBranch(branch) + return ctx + } +} + +func initFakeHTTP() *api.FakeHTTP { + http := &api.FakeHTTP{} + apiClientForContext = func(context.Context) (*api.Client, error) { + return api.NewClient(api.ReplaceTripper(http)), nil + } + return http +} + +func mockOpenInBrowser() (func(), *int) { + callCount := 0 + originalOpenInBrowser := utils.OpenInBrowser + teardown := func() { + utils.OpenInBrowser = originalOpenInBrowser + } + + utils.OpenInBrowser = func(_ string) error { + callCount++ + return nil + } + + return teardown, &callCount +} diff --git a/test/fixtures/issueList.json b/test/fixtures/issueList.json new file mode 100644 index 000000000..784c0cc8f --- /dev/null +++ b/test/fixtures/issueList.json @@ -0,0 +1,47 @@ +{ + "data": { + "assigned": { + "issues": { + "edges": [ + { + "node": { + "number": 9, + "title": "corey thinks squash tastes bad" + } + }, + { + "node": { + "number": 10, + "title": "broccoli is a superfood" + } + } + ] + } + }, + "mentioned": { + "issues": { + "edges": [ + { + "node": { + "number": 8, + "title": "rabbits eat carrots" + } + }, + { + "node": { + "number": 11, + "title": "swiss chard is neutral" + } + } + ] + } + }, + "recent": { + "issues": { + "edges": [] + } + }, + + "pageInfo": { "hasNextPage": false } + } +} diff --git a/test/fixtures/issueView.json b/test/fixtures/issueView.json new file mode 100644 index 000000000..c9b0cb1b6 --- /dev/null +++ b/test/fixtures/issueView.json @@ -0,0 +1,36 @@ +{ + "data": { + "repository": { + "issues": { + "edges": [ + { + "node": { + "number": 8, + "title": "rabbits eat carrots", + "url": "https://github.com/github/gh-cli/pull/10" + } + }, + { + "node": { + "number": 9, + "title": "corey thinks squash tastes bad" + } + }, + { + "node": { + "number": 10, + "title": "broccoli is a superfood" + } + }, + { + "node": { + "number": 11, + "title": "swiss chard is neutral" + } + } + ] + } + }, + "pageInfo": { "hasNextPage": false } + } +} diff --git a/test/helpers.go b/test/helpers.go index d9276565c..162befa56 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -71,6 +71,7 @@ func RunCommand(root *cobra.Command, s string) (string, error) { root.SetArgs(strings.Split(s, " ")) _, err = root.ExecuteC() }) + if err != nil { return "", err } From 40131abfbab0d673f8941117b3ab620392014212 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 1 Nov 2019 14:33:44 +0100 Subject: [PATCH 3/7] =?UTF-8?q?`issue=20list`=20=E2=86=92=20`issue=20statu?= =?UTF-8?q?s`=20to=20bring=20in=20line=20with=20pr=20prototype?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- command/issue.go | 14 +++++--------- command/issue_test.go | 4 ++-- command/pr.go | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/command/issue.go b/command/issue.go index e64d8d806..0ad1c8d45 100644 --- a/command/issue.go +++ b/command/issue.go @@ -13,14 +13,14 @@ func init() { RootCmd.AddCommand(issueCmd) issueCmd.AddCommand( &cobra.Command{ - Use: "list", - Short: "List issues", + Use: "status", + Short: "Show status of relevant issues", RunE: issueList, }, &cobra.Command{ - Use: "view issue-number", + Use: "view ", Args: cobra.MinimumNArgs(1), - Short: "Open a issue in the browser", + Short: "Open an issue in the browser", RunE: issueView, }, ) @@ -29,11 +29,7 @@ func init() { var issueCmd = &cobra.Command{ Use: "issue", Short: "Work with GitHub issues", - Long: `This command allows you to work with issues.`, - Args: cobra.MinimumNArgs(1), - RunE: func(cmd *cobra.Command, args []string) error { - return fmt.Errorf("%+v is not a valid issue command", args) - }, + Long: `Helps you work with issues.`, } func issueList(cmd *cobra.Command, args []string) error { diff --git a/command/issue_test.go b/command/issue_test.go index 69312cac7..d796c1819 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -16,9 +16,9 @@ func TestIssueList(t *testing.T) { defer jsonFile.Close() http.StubResponse(200, jsonFile) - output, err := test.RunCommand(RootCmd, "issue list") + output, err := test.RunCommand(RootCmd, "issue status") if err != nil { - t.Errorf("error running command `issue list`: %v", err) + t.Errorf("error running command `issue status`: %v", err) } expectedIssues := []*regexp.Regexp{ diff --git a/command/pr.go b/command/pr.go index 182522f29..720405da8 100644 --- a/command/pr.go +++ b/command/pr.go @@ -18,7 +18,7 @@ func init() { RunE: prList, }, &cobra.Command{ - Use: "view [pr-number]", + Use: "view []", Short: "Open a pull request in the browser", RunE: prView, }, From 5d2ecf12cacc5384ffb7894c26628433a92e39c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 1 Nov 2019 14:34:33 +0100 Subject: [PATCH 4/7] Fix issues order --- api/queries.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/queries.go b/api/queries.go index c0bc53eca..161aa9e59 100644 --- a/api/queries.go +++ b/api/queries.go @@ -56,7 +56,7 @@ func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload } query($owner: String!, $repo: String!, $since: DateTime!, $viewer: String!, $per_page: Int = 10) { assigned: repository(owner: $owner, name: $repo) { - issues(filterBy: {assignee: $viewer}, first: $per_page) { + issues(filterBy: {assignee: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { edges { node { ...issue @@ -65,7 +65,7 @@ func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload } } mentioned: repository(owner: $owner, name: $repo) { - issues(filterBy: {mentioned: $viewer}, first: $per_page) { + issues(filterBy: {mentioned: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { edges { node { ...issue @@ -74,7 +74,7 @@ func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload } } recent: repository(owner: $owner, name: $repo) { - issues(filterBy: {since: $since, orderBy: {field: CREATED_AT, direction: DESC}}, first: $per_page) { + issues(filterBy: {since: $since}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { edges { node { ...issue From 401aef283f8e5fd01a064e652187e7d43b88bc46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 1 Nov 2019 15:31:07 +0100 Subject: [PATCH 5/7] Prototype `issue create` --- api/queries.go | 1 + api/queries_issue.go | 70 +++++++++++++++++++++++++++++++++++++ command/issue.go | 83 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 api/queries_issue.go diff --git a/api/queries.go b/api/queries.go index 161aa9e59..1a405a858 100644 --- a/api/queries.go +++ b/api/queries.go @@ -32,6 +32,7 @@ type IssuesPayload struct { type Issue struct { Number int Title string + URL string } func Issues(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload, error) { diff --git a/api/queries_issue.go b/api/queries_issue.go new file mode 100644 index 000000000..c488a18b5 --- /dev/null +++ b/api/queries_issue.go @@ -0,0 +1,70 @@ +package api + +import "fmt" + +func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*Issue, error) { + repoId, err := GitHubRepoId(client, ghRepo) + if err != nil { + return nil, err + } + + query := ` + mutation CreateIssue($input: CreateIssueInput!) { + createIssue(input: $input) { + issue { + url + } + } + }` + + inputParams := map[string]interface{}{ + "repositoryId": repoId, + } + for key, val := range params { + inputParams[key] = val + } + variables := map[string]interface{}{ + "input": inputParams, + } + + result := struct { + CreateIssue struct { + Issue Issue + } + }{} + + err = client.GraphQL(query, variables, &result) + if err != nil { + return nil, err + } + + return &result.CreateIssue.Issue, nil +} + +func GitHubRepoId(client *Client, ghRepo Repo) (string, error) { + owner := ghRepo.RepoOwner() + repo := ghRepo.RepoName() + + query := ` + query FindRepoID($owner:String!, $name:String!) { + repository(owner:$owner, name:$name) { + id + } + }` + variables := map[string]interface{}{ + "owner": owner, + "name": repo, + } + + result := struct { + Repository struct { + Id string + } + }{} + err := client.GraphQL(query, variables, &result) + if err != nil || result.Repository.Id == "" { + return "", fmt.Errorf("failed to determine GH repo ID: %s", err) + } + + return result.Repository.Id, nil +} diff --git a/command/issue.go b/command/issue.go index 0ad1c8d45..3111e28cb 100644 --- a/command/issue.go +++ b/command/issue.go @@ -2,11 +2,15 @@ package command import ( "fmt" + "io/ioutil" + "os" "strconv" + "strings" "github.com/github/gh-cli/api" "github.com/github/gh-cli/utils" "github.com/spf13/cobra" + "golang.org/x/crypto/ssh/terminal" ) func init() { @@ -24,6 +28,9 @@ func init() { RunE: issueView, }, ) + issueCmd.AddCommand(issueCreateCmd) + issueCreateCmd.Flags().StringArrayP("message", "m", nil, "set title and body") + issueCreateCmd.Flags().BoolP("web", "w", false, "open the web browser to create an issue") } var issueCmd = &cobra.Command{ @@ -31,6 +38,11 @@ var issueCmd = &cobra.Command{ Short: "Work with GitHub issues", Long: `Helps you work with issues.`, } +var issueCreateCmd = &cobra.Command{ + Use: "create", + Short: "Create a new issue", + RunE: issueCreate, +} func issueList(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true @@ -106,6 +118,77 @@ func issueView(cmd *cobra.Command, args []string) error { return utils.OpenInBrowser(openURL) } +func issueCreate(cmd *cobra.Command, args []string) error { + ctx := contextForCommand(cmd) + + baseRepo, err := ctx.BaseRepo() + if err != nil { + return err + } + + if isWeb, err := cmd.Flags().GetBool("web"); err == nil && isWeb { + // TODO: move URL generation into GitHubRepository + openURL := fmt.Sprintf("https://github.com/%s/%s/issues/new", baseRepo.RepoOwner(), baseRepo.RepoName()) + // TODO: figure out how to stub this in tests + if stat, err := os.Stat(".github/ISSUE_TEMPLATE"); err == nil && stat.IsDir() { + openURL += "/choose" + } + return utils.OpenInBrowser(openURL) + } + + var title string + var body string + + message, err := cmd.Flags().GetStringArray("message") + if err != nil { + return err + } + + apiClient, err := apiClientForContext(ctx) + if err != nil { + return err + } + + if len(message) > 0 { + title = message[0] + body = strings.Join(message[1:], "\n\n") + } else { + // TODO: open the text editor for issue title & body + input := os.Stdin + if terminal.IsTerminal(int(input.Fd())) { + cmd.Println("Enter the issue title and body; press Enter + Ctrl-D when done:") + } + inputBytes, err := ioutil.ReadAll(input) + if err != nil { + return err + } + + parts := strings.SplitN(string(inputBytes), "\n\n", 2) + if len(parts) > 0 { + title = parts[0] + } + if len(parts) > 1 { + body = parts[1] + } + } + + if title == "" { + return fmt.Errorf("aborting due to empty title") + } + params := map[string]interface{}{ + "title": title, + "body": body, + } + + newIssue, err := api.IssueCreate(apiClient, baseRepo, params) + if err != nil { + return err + } + + fmt.Fprintln(cmd.OutOrStdout(), newIssue.URL) + return nil +} + func printIssues(issues ...api.Issue) { for _, issue := range issues { fmt.Printf(" #%d %s\n", issue.Number, truncateTitle(issue.Title, 70)) From 29b5593e2168e5a24461e6555a1a63d661b2bf33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Wed, 6 Nov 2019 19:12:53 +0100 Subject: [PATCH 6/7] go mod tidy --- go.mod | 1 + go.sum | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 18ae306a8..9c89ff7b1 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,6 @@ require ( github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b github.com/mitchellh/go-homedir v1.1.0 github.com/spf13/cobra v0.0.5 + golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 gopkg.in/yaml.v3 v3.0.0-20191010095647-fc94e3f71652 ) diff --git a/go.sum b/go.sum index a9995f4f2..af7d46f56 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,6 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -37,12 +36,10 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= From 25142d4895b41a31f186e3418759cb1692a671e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mislav=20Marohni=C4=87?= Date: Fri, 8 Nov 2019 15:20:24 +0100 Subject: [PATCH 7/7] Add `issue create` test --- api/queries_issue.go | 34 ++------------------------------ api/queries_repo.go | 31 +++++++++++++++++++++++++++++ command/issue_test.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 api/queries_repo.go diff --git a/api/queries_issue.go b/api/queries_issue.go index c488a18b5..56a235c89 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -1,9 +1,7 @@ package api -import "fmt" - func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*Issue, error) { - repoId, err := GitHubRepoId(client, ghRepo) + repoID, err := GitHubRepoId(client, ghRepo) if err != nil { return nil, err } @@ -18,7 +16,7 @@ func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*I }` inputParams := map[string]interface{}{ - "repositoryId": repoId, + "repositoryId": repoID, } for key, val := range params { inputParams[key] = val @@ -40,31 +38,3 @@ func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*I return &result.CreateIssue.Issue, nil } - -func GitHubRepoId(client *Client, ghRepo Repo) (string, error) { - owner := ghRepo.RepoOwner() - repo := ghRepo.RepoName() - - query := ` - query FindRepoID($owner:String!, $name:String!) { - repository(owner:$owner, name:$name) { - id - } - }` - variables := map[string]interface{}{ - "owner": owner, - "name": repo, - } - - result := struct { - Repository struct { - Id string - } - }{} - err := client.GraphQL(query, variables, &result) - if err != nil || result.Repository.Id == "" { - return "", fmt.Errorf("failed to determine GH repo ID: %s", err) - } - - return result.Repository.Id, nil -} diff --git a/api/queries_repo.go b/api/queries_repo.go new file mode 100644 index 000000000..92010b880 --- /dev/null +++ b/api/queries_repo.go @@ -0,0 +1,31 @@ +package api + +import "fmt" + +func GitHubRepoId(client *Client, ghRepo Repo) (string, error) { + owner := ghRepo.RepoOwner() + repo := ghRepo.RepoName() + + query := ` + query FindRepoID($owner:String!, $name:String!) { + repository(owner:$owner, name:$name) { + id + } + }` + variables := map[string]interface{}{ + "owner": owner, + "name": repo, + } + + result := struct { + Repository struct { + Id string + } + }{} + err := client.GraphQL(query, variables, &result) + if err != nil || result.Repository.Id == "" { + return "", fmt.Errorf("failed to determine GH repo ID: %s", err) + } + + return result.Repository.Id, nil +} diff --git a/command/issue_test.go b/command/issue_test.go index 4571bc43e..29d494663 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -1,6 +1,9 @@ package command import ( + "bytes" + "encoding/json" + "io/ioutil" "os" "os/exec" "regexp" @@ -69,3 +72,46 @@ func TestIssueView(t *testing.T) { t.Errorf("got: %q", url) } } + +func TestIssueCreate(t *testing.T) { + initBlankContext("OWNER/REPO", "master") + http := initFakeHTTP() + + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "repository": { + "id": "REPOID" + } } } + `)) + http.StubResponse(200, bytes.NewBufferString(` + { "data": { "createIssue": { "issue": { + "URL": "https://github.com/OWNER/REPO/issues/12" + } } } } + `)) + + out := bytes.Buffer{} + issueCreateCmd.SetOut(&out) + + RootCmd.SetArgs([]string{"issue", "create", "-m", "hello", "-m", "ab", "-m", "cd"}) + _, err := RootCmd.ExecuteC() + if err != nil { + t.Errorf("error running command `issue create`: %v", err) + } + + bodyBytes, _ := ioutil.ReadAll(http.Requests[1].Body) + reqBody := struct { + Variables struct { + Input struct { + RepositoryID string + Title string + Body string + } + } + }{} + json.Unmarshal(bodyBytes, &reqBody) + + eq(t, reqBody.Variables.Input.RepositoryID, "REPOID") + eq(t, reqBody.Variables.Input.Title, "hello") + eq(t, reqBody.Variables.Input.Body, "ab\n\ncd") + + eq(t, out.String(), "https://github.com/OWNER/REPO/issues/12\n") +}