diff --git a/api/queries.go b/api/queries.go index 5daf10244..68a9d14ba 100644 --- a/api/queries.go +++ b/api/queries.go @@ -2,7 +2,6 @@ package api import ( "fmt" - "time" ) type PullRequestsPayload struct { @@ -24,109 +23,6 @@ type Repo interface { RepoOwner() string } -type IssuesPayload struct { - Assigned []Issue - Mentioned []Issue - Recent []Issue -} - -type Issue struct { - Number int - Title string - URL 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, orderBy: {field: CREATED_AT, direction: DESC}) { - edges { - node { - ...issue - } - } - } - } - mentioned: repository(owner: $owner, name: $repo) { - issues(filterBy: {mentioned: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { - edges { - node { - ...issue - } - } - } - } - recent: repository(owner: $owner, name: $repo) { - issues(filterBy: {since: $since}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { - 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 { diff --git a/api/queries_issue.go b/api/queries_issue.go index 56a235c89..96d0e1b2b 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -1,5 +1,10 @@ package api +import ( + "fmt" + "time" +) + func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*Issue, error) { repoID, err := GitHubRepoId(client, ghRepo) if err != nil { @@ -38,3 +43,162 @@ func IssueCreate(client *Client, ghRepo Repo, params map[string]interface{}) (*I return &result.CreateIssue.Issue, nil } + +type IssuesPayload struct { + Assigned []Issue + Mentioned []Issue + Recent []Issue +} + +type Issue struct { + Number int + Title string + URL string +} + +type apiIssues struct { + Issues struct { + Edges []struct { + Node Issue + } + } +} + +func IssueStatus(client *Client, ghRepo Repo, currentUsername string) (*IssuesPayload, error) { + type response struct { + Assigned apiIssues + Mentioned apiIssues + Recent apiIssues + } + + 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, orderBy: {field: CREATED_AT, direction: DESC}) { + edges { + node { + ...issue + } + } + } + } + mentioned: repository(owner: $owner, name: $repo) { + issues(filterBy: {mentioned: $viewer}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { + edges { + node { + ...issue + } + } + } + } + recent: repository(owner: $owner, name: $repo) { + issues(filterBy: {since: $since}, first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}) { + 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 IssueList(client *Client, ghRepo Repo, state string) ([]Issue, error) { + var states []string + switch state { + case "open", "": + states = []string{"OPEN"} + case "closed": + states = []string{"CLOSED"} + case "all": + states = []string{"OPEN", "CLOSED"} + default: + return nil, fmt.Errorf("invalid state: %s", state) + } + + query := ` + fragment issue on Issue { + number + title + } + query($owner: String!, $repo: String!, $per_page: Int = 10, $states: [IssueState!] = OPEN) { + repository(owner: $owner, name: $repo) { + issues(first: $per_page, orderBy: {field: CREATED_AT, direction: DESC}, states: $states) { + edges { + node { + ...issue + } + } + } + } + } + ` + + owner := ghRepo.RepoOwner() + repo := ghRepo.RepoName() + variables := map[string]interface{}{ + "owner": owner, + "repo": repo, + "states": states, + } + + var resp struct { + Repository apiIssues + } + + err := client.GraphQL(query, variables, &resp) + if err != nil { + return nil, err + } + + var issues []Issue + for _, edge := range resp.Repository.Issues.Edges { + issues = append(issues, edge.Node) + } + + return issues, nil +} diff --git a/command/issue.go b/command/issue.go index 8d1fcb1d7..ac7ccbf97 100644 --- a/command/issue.go +++ b/command/issue.go @@ -19,7 +19,7 @@ func init() { &cobra.Command{ Use: "status", Short: "Show status of relevant issues", - RunE: issueList, + RunE: issueStatus, }, &cobra.Command{ Use: "view ", @@ -31,6 +31,16 @@ func init() { 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") + + issueListCmd := &cobra.Command{ + Use: "list", + Short: "List open issues", + RunE: issueList, + } + issueListCmd.Flags().StringP("assignee", "a", "", "filter by assignee") + issueListCmd.Flags().StringP("label", "l", "", "Filter by assignee") + issueListCmd.Flags().StringP("state", "s", "", "Filter by state") + issueCmd.AddCommand((issueListCmd)) } var issueCmd = &cobra.Command{ @@ -56,12 +66,43 @@ func issueList(cmd *cobra.Command, args []string) error { return err } + state, err := cmd.Flags().GetString("state") + if err != nil { + return err + } + + issues, err := api.IssueList(apiClient, baseRepo, state) + if err != nil { + return err + } + + if len(issues) > 0 { + printIssues(issues...) + } else { + message := fmt.Sprintf("There are no open issues") + printMessage(message) + } + return nil +} + +func issueStatus(cmd *cobra.Command, args []string) error { + 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) + issuePayload, err := api.IssueStatus(apiClient, baseRepo, currentUser) if err != nil { return err } diff --git a/command/issue_test.go b/command/issue_test.go index 29d494663..4acc76f6e 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -40,6 +40,32 @@ func TestIssueStatus(t *testing.T) { } } +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(`#1.*won`), + regexp.MustCompile(`#2.*too`), + regexp.MustCompile(`#4.*fore`), + } + + 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() diff --git a/test/fixtures/issueList.json b/test/fixtures/issueList.json index 784c0cc8f..b249f1103 100644 --- a/test/fixtures/issueList.json +++ b/test/fixtures/issueList.json @@ -1,47 +1,27 @@ { "data": { - "assigned": { - "issues": { - "edges": [ - { - "node": { - "number": 9, - "title": "corey thinks squash tastes bad" - } - }, - { - "node": { - "number": 10, - "title": "broccoli is a superfood" - } + "issues": { + "edges": [ + { + "node": { + "number": 1, + "title": "number won" } - ] - } - }, - "mentioned": { - "issues": { - "edges": [ - { - "node": { - "number": 8, - "title": "rabbits eat carrots" - } - }, - { - "node": { - "number": 11, - "title": "swiss chard is neutral" - } + }, + { + "node": { + "number": 2, + "title": "number too" } - ] - } + }, + { + "node": { + "number": 4, + "title": "number fore" + } + } + ] }, - "recent": { - "issues": { - "edges": [] - } - }, - "pageInfo": { "hasNextPage": false } } }