Show Issues 2.0 fields in non-tty view output
Add issue-type, parent, sub-issues, sub-issues-completed, blocked-by, and blocking lines to the raw issue preview. Empty values still print to keep line counts stable for head|grep workflows. Pull the issue-ref formatting into a small set of helpers so the human and machine renderers share a single owner/repo#N source of truth. formatLinkedIssueRef no longer takes baseRepo: callers in this package always have repository.nameWithOwner on the LinkedIssue, so the disambiguation between same-repo and cross-repo references is no longer needed and the resulting refs are unambiguous. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
d1363a9e5b
commit
1a2293b6e0
2 changed files with 127 additions and 14 deletions
|
|
@ -213,6 +213,24 @@ func printRawIssuePreview(out io.Writer, issue *api.Issue) error {
|
|||
milestoneTitle = issue.Milestone.Title
|
||||
}
|
||||
fmt.Fprintf(out, "milestone:\t%s\n", milestoneTitle)
|
||||
var issueTypeName string
|
||||
if issue.IssueType != nil {
|
||||
issueTypeName = issue.IssueType.Name
|
||||
}
|
||||
fmt.Fprintf(out, "issue-type:\t%s\n", issueTypeName)
|
||||
var parentRef string
|
||||
if issue.Parent != nil {
|
||||
parentRef = formatLinkedIssueRef(issue.Parent)
|
||||
}
|
||||
fmt.Fprintf(out, "parent:\t%s\n", parentRef)
|
||||
fmt.Fprintf(out, "sub-issues:\t%s\n", formatLinkedIssueRefs(issue.SubIssues.Nodes))
|
||||
var subIssuesCompleted string
|
||||
if issue.SubIssuesSummary.Total > 0 {
|
||||
subIssuesCompleted = fmt.Sprintf("%d/%d", issue.SubIssuesSummary.Completed, issue.SubIssuesSummary.Total)
|
||||
}
|
||||
fmt.Fprintf(out, "sub-issues-completed:\t%s\n", subIssuesCompleted)
|
||||
fmt.Fprintf(out, "blocked-by:\t%s\n", formatLinkedIssueRefs(issue.BlockedBy.Nodes))
|
||||
fmt.Fprintf(out, "blocking:\t%s\n", formatLinkedIssueRefs(issue.Blocking.Nodes))
|
||||
fmt.Fprintf(out, "number:\t%d\n", issue.Number)
|
||||
fmt.Fprintln(out, "--")
|
||||
fmt.Fprintln(out, issue.Body)
|
||||
|
|
@ -260,13 +278,13 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
|
|||
}
|
||||
if issue.Parent != nil {
|
||||
fmt.Fprint(out, cs.Bold("Parent: "))
|
||||
fmt.Fprintln(out, formatLinkedIssueRef(baseRepo, issue.Parent)+" "+issue.Parent.Title)
|
||||
fmt.Fprintln(out, formatLinkedIssueRef(issue.Parent)+" "+issue.Parent.Title)
|
||||
}
|
||||
if blockedBy := formatLinkedIssueList(baseRepo, issue.BlockedBy.Nodes); blockedBy != "" {
|
||||
if blockedBy := formatLinkedIssueListWithTitle(issue.BlockedBy.Nodes); blockedBy != "" {
|
||||
fmt.Fprint(out, cs.Bold("Blocked by: "))
|
||||
fmt.Fprintln(out, blockedBy)
|
||||
}
|
||||
if blocking := formatLinkedIssueList(baseRepo, issue.Blocking.Nodes); blocking != "" {
|
||||
if blocking := formatLinkedIssueListWithTitle(issue.Blocking.Nodes); blocking != "" {
|
||||
fmt.Fprint(out, cs.Bold("Blocking: "))
|
||||
fmt.Fprintln(out, blocking)
|
||||
}
|
||||
|
|
@ -311,7 +329,7 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
|
|||
}
|
||||
fmt.Fprintf(out, "%s %s %s\n",
|
||||
stateColor(stateLabel),
|
||||
formatLinkedIssueRef(baseRepo, &sub),
|
||||
formatLinkedIssueRef(&sub),
|
||||
sub.Title,
|
||||
)
|
||||
}
|
||||
|
|
@ -335,23 +353,32 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
|
|||
}
|
||||
|
||||
// formatLinkedIssueRef formats an issue reference as owner/repo#N.
|
||||
// Cross-repo references use the issue's own repository; same-repo
|
||||
// references use the base repo name for consistency.
|
||||
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)
|
||||
func formatLinkedIssueRef(issue *api.LinkedIssue) string {
|
||||
return fmt.Sprintf("%s#%d", issue.Repository.NameWithOwner, issue.Number)
|
||||
}
|
||||
|
||||
// formatLinkedIssueList formats a comma-separated list of linked issue references with titles.
|
||||
func formatLinkedIssueList(baseRepo ghrepo.Interface, issues []api.LinkedIssue) string {
|
||||
// formatLinkedIssueRefs formats a comma-separated list of linked issue
|
||||
// references without titles.
|
||||
func formatLinkedIssueRefs(issues []api.LinkedIssue) string {
|
||||
return joinLinkedIssues(issues, false)
|
||||
}
|
||||
|
||||
// formatLinkedIssueListWithTitle formats a comma-separated list of linked
|
||||
// issue references with each title appended after the reference.
|
||||
func formatLinkedIssueListWithTitle(issues []api.LinkedIssue) string {
|
||||
return joinLinkedIssues(issues, true)
|
||||
}
|
||||
|
||||
func joinLinkedIssues(issues []api.LinkedIssue, withTitle bool) string {
|
||||
if len(issues) == 0 {
|
||||
return ""
|
||||
}
|
||||
parts := make([]string, len(issues))
|
||||
for i, issue := range issues {
|
||||
parts[i] = formatLinkedIssueRef(baseRepo, &issue) + " " + issue.Title
|
||||
parts[i] = formatLinkedIssueRef(&issue)
|
||||
if withTitle {
|
||||
parts[i] += " " + issue.Title
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -781,6 +781,48 @@ func TestIssueView_tty_Issues2AllFields(t *testing.T) {
|
|||
assert.Contains(t, out, "View this issue on GitHub: https://github.com/OWNER/REPO/issues/123")
|
||||
}
|
||||
|
||||
func TestIssueView_nontty_Issues2AllFields(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
httpReg := &httpmock.Registry{}
|
||||
defer httpReg.Verify(t)
|
||||
|
||||
httpReg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(issueResponseAllIssues2Fields()),
|
||||
)
|
||||
mockEmptyV2ProjectItems(t, httpReg)
|
||||
|
||||
opts := ViewOptions{
|
||||
IO: ios,
|
||||
Now: func() time.Time {
|
||||
t, _ := time.Parse(time.RFC822, "03 Nov 24 15:04 UTC")
|
||||
return t
|
||||
},
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: httpReg}, nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
},
|
||||
IssueNumber: 123,
|
||||
}
|
||||
|
||||
err := viewRun(&opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "", stderr.String())
|
||||
|
||||
out := stdout.String()
|
||||
|
||||
assert.Contains(t, out, "issue-type:\tBug\n")
|
||||
assert.Contains(t, out, "parent:\tOWNER/REPO#100\n")
|
||||
assert.Contains(t, out, "sub-issues:\tOWNER/REPO#101, OWNER/REPO#102\n")
|
||||
assert.Contains(t, out, "sub-issues-completed:\t1/2\n")
|
||||
assert.Contains(t, out, "blocked-by:\tOWNER/REPO#200\n")
|
||||
assert.Contains(t, out, "blocking:\tOWNER/REPO#300\n")
|
||||
}
|
||||
|
||||
func TestIssueView_tty_Issues2NoFields(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
ios.SetStdoutTTY(true)
|
||||
|
|
@ -833,6 +875,50 @@ func TestIssueView_tty_Issues2NoFields(t *testing.T) {
|
|||
assert.NotContains(t, out, "Sub-issues")
|
||||
}
|
||||
|
||||
func TestIssueView_nontty_Issues2NoFields(t *testing.T) {
|
||||
ios, _, stdout, stderr := iostreams.Test()
|
||||
|
||||
httpReg := &httpmock.Registry{}
|
||||
defer httpReg.Verify(t)
|
||||
|
||||
httpReg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(issueResponseNoIssues2Fields()),
|
||||
)
|
||||
mockEmptyV2ProjectItems(t, httpReg)
|
||||
|
||||
opts := ViewOptions{
|
||||
IO: ios,
|
||||
Now: func() time.Time {
|
||||
t, _ := time.Parse(time.RFC822, "03 Nov 24 15:04 UTC")
|
||||
return t
|
||||
},
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: httpReg}, nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.New("OWNER", "REPO"), nil
|
||||
},
|
||||
IssueNumber: 456,
|
||||
}
|
||||
|
||||
err := viewRun(&opts)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "", stderr.String())
|
||||
|
||||
out := stdout.String()
|
||||
|
||||
// Issues 2.0 keys appear with empty values to keep line counts stable
|
||||
// for `head | grep` workflows.
|
||||
assert.Contains(t, out, "issue-type:\t\n")
|
||||
assert.Contains(t, out, "parent:\t\n")
|
||||
assert.Contains(t, out, "sub-issues:\t\n")
|
||||
assert.Contains(t, out, "sub-issues-completed:\t\n")
|
||||
assert.Contains(t, out, "blocked-by:\t\n")
|
||||
assert.Contains(t, out, "blocking:\t\n")
|
||||
}
|
||||
|
||||
func TestIssueView_json_IssueType(t *testing.T) {
|
||||
httpReg := &httpmock.Registry{}
|
||||
defer httpReg.Verify(t)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue