Skip to content

Commit 59ba508

Browse files
authored
Merge pull request cli#12854 from cli/kw/pr-view-copilot-friendly-name
`gh pr view` and `gh issue view`: show friendly display names for all actors
2 parents 93c4340 + e047fa6 commit 59ba508

File tree

7 files changed

+76
-33
lines changed

7 files changed

+76
-33
lines changed

api/queries_comments.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ func (c Comment) Identifier() string {
129129
}
130130

131131
func (c Comment) AuthorLogin() string {
132-
return c.Author.Login
132+
return c.Author.DisplayName()
133133
}
134134

135135
func (c Comment) Association() string {

api/queries_issue.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,11 @@ type Author struct {
234234
Login string
235235
}
236236

237+
// DisplayName returns a user-friendly name via actorDisplayName.
238+
func (a Author) DisplayName() string {
239+
return actorDisplayName("", a.Login, a.Name)
240+
}
241+
237242
func (author Author) MarshalJSON() ([]byte, error) {
238243
if author.ID == "" {
239244
return json.Marshal(map[string]interface{}{
@@ -260,6 +265,11 @@ type CommentAuthor struct {
260265
// } `graphql:"... on User"`
261266
}
262267

268+
// DisplayName returns a user-friendly name via actorDisplayName.
269+
func (a CommentAuthor) DisplayName() string {
270+
return actorDisplayName("", a.Login, "")
271+
}
272+
263273
// IssueCreate creates an issue in a GitHub repository
264274
func IssueCreate(client *Client, repo *Repository, params map[string]interface{}) (*Issue, error) {
265275
query := `

api/queries_pr_review.go

Lines changed: 6 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (prr PullRequestReview) Identifier() string {
5252
}
5353

5454
func (prr PullRequestReview) AuthorLogin() string {
55-
return prr.Author.Login
55+
return prr.Author.DisplayName()
5656
}
5757

5858
func (prr PullRequestReview) Association() string {
@@ -143,6 +143,7 @@ type RequestedReviewer struct {
143143

144144
const teamTypeName = "Team"
145145
const botTypeName = "Bot"
146+
const userTypeName = "User"
146147

147148
func (r RequestedReviewer) LoginOrSlug() string {
148149
if r.TypeName == teamTypeName {
@@ -151,20 +152,13 @@ func (r RequestedReviewer) LoginOrSlug() string {
151152
return r.Login
152153
}
153154

154-
// DisplayName returns a user-friendly name for the reviewer.
155-
// For Copilot bot, returns "Copilot (AI)". For teams, returns "org/slug".
156-
// For users, returns "login (Name)" if name is available, otherwise just login.
155+
// DisplayName returns a user-friendly name for the reviewer via actorDisplayName.
156+
// Teams are handled separately as "org/slug".
157157
func (r RequestedReviewer) DisplayName() string {
158158
if r.TypeName == teamTypeName {
159159
return fmt.Sprintf("%s/%s", r.Organization.Login, r.Slug)
160160
}
161-
if r.TypeName == botTypeName && r.Login == CopilotReviewerLogin {
162-
return "Copilot (AI)"
163-
}
164-
if r.Name != "" {
165-
return fmt.Sprintf("%s (%s)", r.Login, r.Name)
166-
}
167-
return r.Login
161+
return actorDisplayName(r.TypeName, r.Login, r.Name)
168162
}
169163

170164
func (r ReviewRequests) Logins() []string {
@@ -221,10 +215,7 @@ func NewReviewerBot(login string) ReviewerBot {
221215
}
222216

223217
func (b ReviewerBot) DisplayName() string {
224-
if b.login == CopilotReviewerLogin {
225-
return fmt.Sprintf("%s (AI)", CopilotActorName)
226-
}
227-
return b.Login()
218+
return actorDisplayName("Bot", b.login, "")
228219
}
229220

230221
func (r ReviewerBot) sealedReviewerCandidate() {}

api/queries_repo.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ type GitHubUser struct {
147147
DatabaseID int64 `json:"databaseId"`
148148
}
149149

150+
// DisplayName returns a user-friendly name via actorDisplayName.
151+
func (u GitHubUser) DisplayName() string {
152+
return actorDisplayName("", u.Login, u.Name)
153+
}
154+
150155
// Actor is a superset of User and Bot, among others.
151156
// At the time of writing, some of these fields
152157
// are not directly supported by the Actor type and
@@ -1087,6 +1092,24 @@ const CopilotAssigneeLogin = "copilot-swe-agent"
10871092
const CopilotReviewerLogin = "copilot-pull-request-reviewer"
10881093
const CopilotActorName = "Copilot"
10891094

1095+
// actorDisplayName returns a user-friendly display name for any actor.
1096+
// It handles bots (e.g. Copilot → "Copilot (AI)"), users with names
1097+
// ("login (Name)"), and falls back to just login. Empty typeName is
1098+
// treated as a possible bot or user — the login is checked against
1099+
// known bot logins first.
1100+
func actorDisplayName(typeName, login, name string) string {
1101+
if login == CopilotReviewerLogin || login == CopilotAssigneeLogin || login == CopilotActorName {
1102+
return fmt.Sprintf("%s (AI)", CopilotActorName)
1103+
}
1104+
if typeName == botTypeName {
1105+
return login
1106+
}
1107+
if name != "" {
1108+
return fmt.Sprintf("%s (%s)", login, name)
1109+
}
1110+
return login
1111+
}
1112+
10901113
type AssignableActor interface {
10911114
DisplayName() string
10921115
ID() string
@@ -1110,12 +1133,9 @@ func NewAssignableUser(id, login, name string) AssignableUser {
11101133
}
11111134
}
11121135

1113-
// DisplayName returns a formatted string that uses Login and Name to be displayed e.g. 'Login (Name)' or 'Login'
1136+
// DisplayName returns a user-friendly name via actorDisplayName.
11141137
func (u AssignableUser) DisplayName() string {
1115-
if u.name != "" {
1116-
return fmt.Sprintf("%s (%s)", u.login, u.name)
1117-
}
1118-
return u.login
1138+
return actorDisplayName(userTypeName, u.login, u.name)
11191139
}
11201140

11211141
func (u AssignableUser) ID() string {
@@ -1145,10 +1165,7 @@ func NewAssignableBot(id, login string) AssignableBot {
11451165
}
11461166

11471167
func (b AssignableBot) DisplayName() string {
1148-
if b.login == CopilotAssigneeLogin {
1149-
return fmt.Sprintf("%s (AI)", CopilotActorName)
1150-
}
1151-
return b.Login()
1168+
return actorDisplayName(botTypeName, b.login, "")
11521169
}
11531170

11541171
func (b AssignableBot) ID() string {

api/queries_repo_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,31 @@ func TestDisplayName(t *testing.T) {
563563
}
564564
}
565565

566+
func TestActorDisplayName(t *testing.T) {
567+
tests := []struct {
568+
name string
569+
typeName string
570+
login string
571+
actName string
572+
want string
573+
}{
574+
{name: "copilot reviewer", typeName: "Bot", login: "copilot-pull-request-reviewer", want: "Copilot (AI)"},
575+
{name: "copilot assignee", typeName: "Bot", login: "copilot-swe-agent", want: "Copilot (AI)"},
576+
{name: "copilot without typename", typeName: "", login: "copilot-pull-request-reviewer", want: "Copilot (AI)"},
577+
{name: "copilot actor name login", typeName: "", login: "Copilot", want: "Copilot (AI)"},
578+
{name: "regular bot", typeName: "Bot", login: "dependabot", want: "dependabot"},
579+
{name: "user with name", typeName: "User", login: "octocat", actName: "Mona Lisa", want: "octocat (Mona Lisa)"},
580+
{name: "user without name", typeName: "User", login: "octocat", want: "octocat"},
581+
{name: "unknown type with name", typeName: "", login: "octocat", actName: "Mona Lisa", want: "octocat (Mona Lisa)"},
582+
{name: "empty login", typeName: "", login: "", want: ""},
583+
}
584+
for _, tt := range tests {
585+
t.Run(tt.name, func(t *testing.T) {
586+
require.Equal(t, tt.want, actorDisplayName(tt.typeName, tt.login, tt.actName))
587+
})
588+
}
589+
}
590+
566591
func TestRepoExists(t *testing.T) {
567592
tests := []struct {
568593
name string

pkg/cmd/issue/view/view.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ func printRawIssuePreview(out io.Writer, issue *api.Issue) error {
197197
// processing many issues with head and grep.
198198
fmt.Fprintf(out, "title:\t%s\n", issue.Title)
199199
fmt.Fprintf(out, "state:\t%s\n", issue.State)
200-
fmt.Fprintf(out, "author:\t%s\n", issue.Author.Login)
200+
fmt.Fprintf(out, "author:\t%s\n", issue.Author.DisplayName())
201201
fmt.Fprintf(out, "labels:\t%s\n", labels)
202202
fmt.Fprintf(out, "comments:\t%d\n", issue.Comments.TotalCount)
203203
fmt.Fprintf(out, "assignees:\t%s\n", assignees)
@@ -222,7 +222,7 @@ func printHumanIssuePreview(opts *ViewOptions, baseRepo ghrepo.Interface, issue
222222
fmt.Fprintf(out,
223223
"%s • %s opened %s • %s\n",
224224
issueStateTitleWithColor(cs, issue),
225-
issue.Author.Login,
225+
issue.Author.DisplayName(),
226226
text.FuzzyAgo(opts.Now(), issue.CreatedAt),
227227
text.Pluralize(issue.Comments.TotalCount, "comment"),
228228
)
@@ -298,7 +298,7 @@ func issueAssigneeList(issue api.Issue) string {
298298

299299
AssigneeNames := make([]string, 0, len(issue.Assignees.Nodes))
300300
for _, assignee := range issue.Assignees.Nodes {
301-
AssigneeNames = append(AssigneeNames, assignee.Login)
301+
AssigneeNames = append(AssigneeNames, assignee.DisplayName())
302302
}
303303

304304
list := strings.Join(AssigneeNames, ", ")

pkg/cmd/pr/view/view.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func printRawPrPreview(io *iostreams.IOStreams, pr *api.PullRequest) error {
149149

150150
fmt.Fprintf(out, "title:\t%s\n", pr.Title)
151151
fmt.Fprintf(out, "state:\t%s\n", prStateWithDraft(pr))
152-
fmt.Fprintf(out, "author:\t%s\n", pr.Author.Login)
152+
fmt.Fprintf(out, "author:\t%s\n", pr.Author.DisplayName())
153153
fmt.Fprintf(out, "labels:\t%s\n", labels)
154154
fmt.Fprintf(out, "assignees:\t%s\n", assignees)
155155
fmt.Fprintf(out, "reviewers:\t%s\n", reviewers)
@@ -188,7 +188,7 @@ func printHumanPrPreview(opts *ViewOptions, baseRepo ghrepo.Interface, pr *api.P
188188
fmt.Fprintf(out,
189189
"%s • %s wants to merge %s into %s from %s • %s\n",
190190
shared.StateTitleWithColor(cs, *pr),
191-
pr.Author.Login,
191+
pr.Author.DisplayName(),
192192
text.Pluralize(pr.Commits.TotalCount, "commit"),
193193
pr.BaseRefName,
194194
pr.HeadRefName,
@@ -351,7 +351,7 @@ func parseReviewers(pr api.PullRequest) []*reviewerState {
351351

352352
for _, review := range pr.Reviews.Nodes {
353353
if review.Author.Login != pr.Author.Login {
354-
name := review.Author.Login
354+
name := review.AuthorLogin()
355355
if name == "" {
356356
name = ghostName
357357
}
@@ -364,7 +364,7 @@ func parseReviewers(pr api.PullRequest) []*reviewerState {
364364

365365
// Overwrite reviewer's state if a review request for the same reviewer exists.
366366
for _, reviewRequest := range pr.ReviewRequests.Nodes {
367-
name := reviewRequest.RequestedReviewer.LoginOrSlug()
367+
name := reviewRequest.RequestedReviewer.DisplayName()
368368
reviewerStates[name] = &reviewerState{
369369
Name: name,
370370
State: requestedReviewState,
@@ -406,7 +406,7 @@ func prAssigneeList(pr api.PullRequest) string {
406406

407407
AssigneeNames := make([]string, 0, len(pr.Assignees.Nodes))
408408
for _, assignee := range pr.Assignees.Nodes {
409-
AssigneeNames = append(AssigneeNames, assignee.Login)
409+
AssigneeNames = append(AssigneeNames, assignee.DisplayName())
410410
}
411411

412412
list := strings.Join(AssigneeNames, ", ")

0 commit comments

Comments
 (0)