Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions api/queries_issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,16 @@ type ProjectItems struct {
}

type ProjectInfo struct {
Project struct {
Name string `json:"name"`
} `json:"project"`
Column struct {
Name string `json:"name"`
} `json:"column"`
Project ProjectV1ProjectName `json:"project"`
Column ProjectV1ProjectColumn `json:"column"`
}

type ProjectV1ProjectName struct {
Name string `json:"name"`
}

type ProjectV1ProjectColumn struct {
Name string `json:"name"`
}

type ProjectV2Item struct {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/issue/shared/display.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func PrintIssues(io *iostreams.IOStreams, now time.Time, prefix string, totalCou
issueNum = "#" + issueNum
}
issueNum = prefix + issueNum
table.AddField(issueNum, tableprinter.WithColor(cs.ColorFromString(prShared.ColorForIssueState(issue))))
table.AddField(issueNum, tableprinter.WithColor(cs.ColorFromString(prShared.ColorForIssueState(issue.State, issue.StateReason))))
if !isTTY {
table.AddField(issue.State)
}
Expand Down
122 changes: 122 additions & 0 deletions pkg/cmd/issue/view/presentation_issue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package view

import (
"fmt"
"slices"
"strings"
"time"

"github.com/cli/cli/v2/api"
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
"github.com/cli/cli/v2/pkg/iostreams"
)

type PresentationIssue struct {
Title string
Number int
CreatedAt time.Time
Comments api.Comments
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a bit leaky that we've gone to all this work the separate our PresentationIssue from our api domain and we're holding on to api.Comments in here. I'd need to take some time to investigate a bit more before offering any kind of suggestion though.

Author string
State string
StateReason string
Reactions string
AssigneesList string
LabelsList string
ProjectsList string
MilestoneTitle string
Body string
URL string
}

// Creates a new PresentationIssue from an api.Issue.
// The PresentationIssue is what the issue printers need to display an
// issue to the user.
func MapApiIssueToPresentationIssue(issue *api.Issue, colorScheme *iostreams.ColorScheme) (PresentationIssue, error) {
presentationIssue := PresentationIssue{
Title: issue.Title,
Number: issue.Number,
CreatedAt: issue.CreatedAt,
Comments: issue.Comments,
Author: issue.Author.Login,
State: issue.State,
StateReason: issue.StateReason,
Reactions: prShared.ReactionGroupList(issue.ReactionGroups),
AssigneesList: stringifyAssignees(issue.Assignees),
LabelsList: stringifyAndColorizeLabels(sortAlphabeticallyIgnoreCase(issue.Labels), colorScheme),
ProjectsList: stringifyProjects(issue.ProjectCards, issue.ProjectItems),
Body: issue.Body,
URL: issue.URL,
}

if issue.Milestone != nil {
presentationIssue.MilestoneTitle = issue.Milestone.Title
}

return presentationIssue, nil
}

func stringifyProjects(projectCards api.ProjectCards, projectItems api.ProjectItems) string {
if len(projectCards.Nodes) == 0 && len(projectItems.Nodes) == 0 {
return ""
}

projectNames := make([]string, len(projectCards.Nodes)+len(projectItems.Nodes))
for i, project := range projectCards.Nodes {
colName := project.Column.Name
if colName == "" {
colName = "Awaiting triage"
}
projectNames[i] = fmt.Sprintf("%s (%s)", project.Project.Name, colName)
}

for i, project := range projectItems.Nodes {
statusName := project.Status.Name
if statusName == "" {
statusName = "Backlog"
}
projectNames[i+len(projectCards.Nodes)] = fmt.Sprintf("%s (%s)", project.Project.Title, statusName)
}

list := strings.Join(projectNames, ", ")
if projectCards.TotalCount > len(projectCards.Nodes) {
list += ", …"
}
return list
}

func stringifyAssignees(issueAssignees api.Assignees) string {
if len(issueAssignees.Nodes) == 0 {
return ""
}

AssigneeNames := make([]string, 0, len(issueAssignees.Nodes))
for _, assignee := range issueAssignees.Nodes {
AssigneeNames = append(AssigneeNames, assignee.Login)
}

list := strings.Join(AssigneeNames, ", ")
if issueAssignees.TotalCount > len(issueAssignees.Nodes) {
list += ", …"
}
return list
}

func stringifyAndColorizeLabels(issueLabels api.Labels, colorScheme *iostreams.ColorScheme) string {
labelNames := make([]string, len(issueLabels.Nodes))
for j, label := range issueLabels.Nodes {
if colorScheme == nil {
labelNames[j] = label.Name
} else {
labelNames[j] = colorScheme.HexToRGB(label.Color, label.Name)
}
}

return strings.Join(labelNames, ", ")
}

func sortAlphabeticallyIgnoreCase(l api.Labels) api.Labels {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function signature is a bit confusing given the implementation.

sortAlphabeticallyIgnoreCase(l api.Labels) api.Labels

This indicates that I give it a labels and it gives me back sorted labels. This is true but it's kind of hiding the fact that the slice of Nodes is being mutated in place. This is also surprising in the way we use it because it's mutating the original api.Issue as well.

slices.SortStableFunc(l.Nodes, func(a, b api.IssueLabel) int {
return strings.Compare(strings.ToLower(a.Name), strings.ToLower(b.Name))
})
return l
}
Loading