feat: add Issues 2.0 fields to issue view

Display new issue metadata in TTY view:
- Issue type on state line (gray, before Open/Closed)
- Type, Parent, Blocked by, Blocking metadata rows
- Sub-issues section with completion progress (X/Y, Z%)
- Cross-repo references show full owner/repo#N format

All new fields included in defaultFields and JSON export.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kynan Ware 2026-03-29 16:40:08 -06:00
parent c00257386e
commit 7dae882c9d
2 changed files with 76 additions and 2 deletions

View file

@ -92,6 +92,7 @@ func NewCmdView(f *cmdutil.Factory, runF func(*ViewOptions) error) *cobra.Comman
var defaultFields = []string{
"number", "url", "state", "createdAt", "title", "body", "author", "milestone",
"assignees", "labels", "reactionGroups", "lastComment", "stateReason",
"issueType", "parent", "subIssues", "subIssuesSummary", "blockedBy", "blocking",
}
func viewRun(opts *ViewOptions) error {
@ -219,9 +220,15 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
// Header (Title and State)
fmt.Fprintf(out, "%s %s#%d\n", cs.Bold(issue.Title), ghrepo.FullName(baseRepo), issue.Number)
// State line — include issue type prefix when present
stateLine := issueStateTitleWithColor(cs, issue)
if issue.IssueType != nil {
stateLine = cs.Muted(issue.IssueType.Name) + " · " + stateLine
}
fmt.Fprintf(out,
"%s • %s opened %s • %s\n",
issueStateTitleWithColor(cs, issue),
"%s · %s opened %s · %s\n",
stateLine,
issue.Author.DisplayName(),
text.FuzzyAgo(opts.Now(), issue.CreatedAt),
text.Pluralize(issue.Comments.TotalCount, "comment"),
@ -242,6 +249,22 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
fmt.Fprint(out, cs.Bold("Labels: "))
fmt.Fprintln(out, labels)
}
if issue.IssueType != nil {
fmt.Fprint(out, cs.Bold("Type: "))
fmt.Fprintln(out, issue.IssueType.Name)
}
if issue.Parent != nil {
fmt.Fprint(out, cs.Bold("Parent: "))
fmt.Fprintln(out, formatLinkedIssueRef(baseRepo, issue.Parent)+" "+issue.Parent.Title)
}
if blockedBy := formatLinkedIssueList(baseRepo, issue.BlockedBy.Nodes); blockedBy != "" {
fmt.Fprint(out, cs.Bold("Blocked by: "))
fmt.Fprintln(out, blockedBy)
}
if blocking := formatLinkedIssueList(baseRepo, issue.Blocking.Nodes); blocking != "" {
fmt.Fprint(out, cs.Bold("Blocking: "))
fmt.Fprintln(out, blocking)
}
if projects := issueProjectList(*issue); projects != "" {
fmt.Fprint(out, cs.Bold("Projects: "))
fmt.Fprintln(out, projects)
@ -266,6 +289,30 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
}
fmt.Fprintf(out, "\n%s\n", md)
// Sub-issues section
if issue.SubIssuesSummary.Total > 0 {
fmt.Fprintf(out, "%s · %d/%d (%d%%)\n",
cs.Bold("Sub-issues"),
issue.SubIssuesSummary.Completed,
issue.SubIssuesSummary.Total,
int(issue.SubIssuesSummary.PercentCompleted),
)
for _, sub := range issue.SubIssues.Nodes {
stateColor := cs.Green
stateLabel := "Open"
if sub.State == "CLOSED" {
stateColor = cs.Magenta
stateLabel = "Closed"
}
fmt.Fprintf(out, "%s %s %s\n",
stateColor(stateLabel),
formatLinkedIssueRef(baseRepo, &sub),
sub.Title,
)
}
fmt.Fprintln(out)
}
// Comments
if issue.Comments.TotalCount > 0 {
preview := !opts.Comments
@ -282,6 +329,27 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
return nil
}
// formatLinkedIssueRef formats an issue reference, using just #N for same-repo
// or owner/repo#N for cross-repo references.
func formatLinkedIssueRef(baseRepo ghrepo.Interface, issue *api.LinkedIssue) string {
if issue.Repository.NameWithOwner != "" && issue.Repository.NameWithOwner != ghrepo.FullName(baseRepo) {
return fmt.Sprintf("%s#%d", issue.Repository.NameWithOwner, issue.Number)
}
return fmt.Sprintf("%s#%d", ghrepo.FullName(baseRepo), issue.Number)
}
// formatLinkedIssueList formats a comma-separated list of linked issue references with titles.
func formatLinkedIssueList(baseRepo ghrepo.Interface, issues []api.LinkedIssue) string {
if len(issues) == 0 {
return ""
}
parts := make([]string, len(issues))
for i, issue := range issues {
parts[i] = formatLinkedIssueRef(baseRepo, &issue) + " " + issue.Title
}
return strings.Join(parts, ", ")
}
func issueStateTitleWithColor(cs *iostreams.ColorScheme, issue *api.Issue) string {
colorFunc := cs.ColorFromString(prShared.ColorForIssueState(*issue))
state := "Open"

View file

@ -46,6 +46,12 @@ func TestJSONFields(t *testing.T) {
"url",
"isPinned",
"stateReason",
"issueType",
"parent",
"subIssues",
"subIssuesSummary",
"blockedBy",
"blocking",
})
}