diff --git a/api/queries_issue.go b/api/queries_issue.go index 2c2010307..cc01c039f 100644 --- a/api/queries_issue.go +++ b/api/queries_issue.go @@ -18,6 +18,7 @@ type IssuesAndTotalCount struct { TotalCount int } +// Ref. https://developer.github.com/v4/object/issue/ type Issue struct { Number int Title string @@ -32,15 +33,32 @@ type Issue struct { Author struct { Login string } - - Labels struct { - Nodes []IssueLabel + Assignees struct { + Nodes []struct { + Login string + } TotalCount int } -} - -type IssueLabel struct { - Name string + Labels struct { + Nodes []struct { + Name string + } + TotalCount int + } + ProjectCards struct { + Nodes []struct { + Project struct { + Name string + } + Column struct { + Name string + } + } + TotalCount int + } + Milestone struct { + Title string + } } const fragments = ` @@ -287,14 +305,35 @@ func IssueByNumber(client *Client, repo ghrepo.Interface, number int) (*Issue, e comments { totalCount } - labels(first: 3) { - nodes { - name - } - } number url createdAt + assignees(first: 100) { + nodes { + login + } + totalCount + } + labels(first: 100) { + nodes { + name + } + totalCount + } + projectCards(first: 100) { + nodes { + project { + name + } + column { + name + } + } + totalCount + } + milestone{ + title + } } } }` diff --git a/command/issue.go b/command/issue.go index fd2e03eef..2f4e145af 100644 --- a/command/issue.go +++ b/command/issue.go @@ -259,8 +259,9 @@ func printIssuePreview(out io.Writer, issue *api.Issue) error { now := time.Now() ago := now.Sub(issue.CreatedAt) + // Header (Title and State) fmt.Fprintln(out, utils.Bold(issue.Title)) - fmt.Fprintf(out, "%s", issueStateTitleWithColor(issue.State)) + fmt.Fprint(out, issueStateTitleWithColor(issue.State)) fmt.Fprintln(out, utils.Gray(fmt.Sprintf( " • %s opened %s • %s", issue.Author.Login, @@ -268,6 +269,26 @@ func printIssuePreview(out io.Writer, issue *api.Issue) error { utils.Pluralize(issue.Comments.TotalCount, "comment"), ))) + // Metadata + fmt.Fprintln(out) + if assignees := issueAssigneeList(*issue); assignees != "" { + fmt.Fprint(out, utils.Bold("Assignees: ")) + fmt.Fprintln(out, assignees) + } + if labels := issueLabelList(*issue); labels != "" { + fmt.Fprint(out, utils.Bold("Labels: ")) + fmt.Fprintln(out, labels) + } + if projects := issueProjectList(*issue); projects != "" { + fmt.Fprint(out, utils.Bold("Projects: ")) + fmt.Fprintln(out, projects) + } + if issue.Milestone.Title != "" { + fmt.Fprint(out, utils.Bold("Milestone: ")) + fmt.Fprintln(out, issue.Milestone.Title) + } + + // Body if issue.Body != "" { fmt.Fprintln(out) md, err := utils.RenderMarkdown(issue.Body) @@ -275,9 +296,10 @@ func printIssuePreview(out io.Writer, issue *api.Issue) error { return err } fmt.Fprintln(out, md) - fmt.Fprintln(out) } + fmt.Fprintln(out) + // Footer fmt.Fprintf(out, utils.Gray("View this issue on GitHub: %s\n"), issue.URL) return nil } @@ -422,7 +444,7 @@ func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) issueNum = "#" + issueNum } issueNum = prefix + issueNum - labels := labelList(issue) + labels := issueLabelList(issue) if labels != "" && table.IsTTY() { labels = fmt.Sprintf("(%s)", labels) } @@ -441,7 +463,24 @@ func printIssues(w io.Writer, prefix string, totalCount int, issues []api.Issue) } } -func labelList(issue api.Issue) string { +func issueAssigneeList(issue api.Issue) string { + if len(issue.Assignees.Nodes) == 0 { + return "" + } + + AssigneeNames := make([]string, 0, len(issue.Assignees.Nodes)) + for _, assignee := range issue.Assignees.Nodes { + AssigneeNames = append(AssigneeNames, assignee.Login) + } + + list := strings.Join(AssigneeNames, ", ") + if issue.Assignees.TotalCount > len(issue.Assignees.Nodes) { + list += ", …" + } + return list +} + +func issueLabelList(issue api.Issue) string { if len(issue.Labels.Nodes) == 0 { return "" } @@ -458,6 +497,23 @@ func labelList(issue api.Issue) string { return list } +func issueProjectList(issue api.Issue) string { + if len(issue.ProjectCards.Nodes) == 0 { + return "" + } + + projectNames := make([]string, 0, len(issue.ProjectCards.Nodes)) + for _, project := range issue.ProjectCards.Nodes { + projectNames = append(projectNames, fmt.Sprintf("%s (%s)", project.Project.Name, project.Column.Name)) + } + + list := strings.Join(projectNames, ", ") + if issue.ProjectCards.TotalCount > len(issue.ProjectCards.Nodes) { + list += ", …" + } + return list +} + func displayURL(urlStr string) string { u, err := url.Parse(urlStr) if err != nil { diff --git a/command/issue_test.go b/command/issue_test.go index 099f80f17..0f2befcb8 100644 --- a/command/issue_test.go +++ b/command/issue_test.go @@ -292,26 +292,30 @@ func TestIssueView_Preview(t *testing.T) { fixture string expectedOutputs []string }{ - "Open issue": { + "Open issue without metadata": { ownerRepo: "master", command: "issue view 123", fixture: "../test/fixtures/issueView_preview.json", expectedOutputs: []string{ - "ix of coins", - "Open • marseilles opened about 292 years ago • 9 comments", - "bold story", - "View this issue on GitHub: https://github.com/OWNER/REPO/issues/123", + `ix of coins`, + `Open • marseilles opened about 292 years ago • 9 comments`, + `bold story`, + `View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`, }, }, - "Open issue with no label": { + "Open issue with metadata": { ownerRepo: "master", command: "issue view 123", - fixture: "../test/fixtures/issueView_previewNoLabel.json", + fixture: "../test/fixtures/issueView_previewWithMetadata.json", expectedOutputs: []string{ - "ix of coins", - "Open • marseilles opened about 292 years ago • 9 comments", - "bold story", - "View this issue on GitHub: https://github.com/OWNER/REPO/issues/123", + `ix of coins`, + `Open • marseilles opened about 292 years ago • 9 comments`, + `Assignees: marseilles, monaco\n`, + `Labels: one, two, three, four, five\n`, + `Projects: Project 1 \(column A\), Project 2 \(column B\), Project 3 \(column C\)\n`, + `Milestone: uluru\n`, + `bold story`, + `View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`, }, }, "Open issue with empty body": { @@ -319,9 +323,9 @@ func TestIssueView_Preview(t *testing.T) { command: "issue view 123", fixture: "../test/fixtures/issueView_previewWithEmptyBody.json", expectedOutputs: []string{ - "ix of coins", - "Open • marseilles opened about 292 years ago • 9 comments", - "View this issue on GitHub: https://github.com/OWNER/REPO/issues/123", + `ix of coins`, + `Open • marseilles opened about 292 years ago • 9 comments`, + `View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`, }, }, "Closed issue": { @@ -329,10 +333,10 @@ func TestIssueView_Preview(t *testing.T) { command: "issue view 123", fixture: "../test/fixtures/issueView_previewClosedState.json", expectedOutputs: []string{ - "ix of coins", - "Closed • marseilles opened about 292 years ago • 9 comments", - "bold story", - "View this issue on GitHub: https://github.com/OWNER/REPO/issues/123", + `ix of coins`, + `Closed • marseilles opened about 292 years ago • 9 comments`, + `bold story`, + `View this issue on GitHub: https://github.com/OWNER/REPO/issues/123`, }, }, } diff --git a/test/fixtures/issueView_preview.json b/test/fixtures/issueView_preview.json index db971d849..e25090a61 100644 --- a/test/fixtures/issueView_preview.json +++ b/test/fixtures/issueView_preview.json @@ -11,12 +11,20 @@ "author": { "login": "marseilles" }, + "assignees": { + "nodes": [], + "totalcount": 0 + }, "labels": { - "nodes": [ - { - "name": "tarot" - } - ] + "nodes": [], + "totalcount": 0 + }, + "projectcards": { + "nodes": [], + "totalcount": 0 + }, + "milestone": { + "title": "" }, "comments": { "totalCount": 9 diff --git a/test/fixtures/issueView_previewNoLabel.json b/test/fixtures/issueView_previewNoLabel.json deleted file mode 100644 index 1891100eb..000000000 --- a/test/fixtures/issueView_previewNoLabel.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "data": { - "repository": { - "hasIssuesEnabled": true, - "issue": { - "number": 123, - "body": "**bold story**", - "title": "ix of coins", - "state": "OPEN", - "created_at": "2011-01-26T19:01:12Z", - "author": { - "login": "marseilles" - }, - "labels": { - "nodes": [ - ] - }, - "comments": { - "totalCount": 9 - }, - "url": "https://github.com/OWNER/REPO/issues/123" - } - } - } -} diff --git a/test/fixtures/issueView_previewWithMetadata.json b/test/fixtures/issueView_previewWithMetadata.json new file mode 100644 index 000000000..a420eec66 --- /dev/null +++ b/test/fixtures/issueView_previewWithMetadata.json @@ -0,0 +1,84 @@ +{ + "data": { + "repository": { + "hasIssuesEnabled": true, + "issue": { + "number": 123, + "body": "**bold story**", + "title": "ix of coins", + "state": "OPEN", + "created_at": "2011-01-26T19:01:12Z", + "author": { + "login": "marseilles" + }, + "assignees": { + "nodes": [ + { + "login": "marseilles" + }, + { + "login": "monaco" + } + ], + "totalcount": 2 + }, + "labels": { + "nodes": [ + { + "name": "one" + }, + { + "name": "two" + }, + { + "name": "three" + }, + { + "name": "four" + }, + { + "name": "five" + } + ], + "totalcount": 5 + }, + "projectcards": { + "nodes": [ + { + "project": { + "name": "Project 1" + }, + "column": { + "name": "column A" + } + }, + { + "project": { + "name": "Project 2" + }, + "column": { + "name": "column B" + } + }, + { + "project": { + "name": "Project 3" + }, + "column": { + "name": "column C" + } + } + ], + "totalcount": 3 + }, + "milestone": { + "title": "uluru" + }, + "comments": { + "totalcount": 9 + }, + "url": "https://github.com/OWNER/REPO/issues/123" + } + } + } +}