Skip to content

Commit ee281fd

Browse files
authored
Add closedByPullRequestsReferences JSON field to issue view (cli#10941)
* [gh issue view] Expose `closedByPullRequestsReferences` JSON fields * Incorporate GitHub Copilot review suggestions * Incorporate review changes
1 parent 3158768 commit ee281fd

7 files changed

Lines changed: 186 additions & 10 deletions

File tree

api/export_pr.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,24 @@ func (issue *Issue) ExportData(fields []string) map[string]interface{} {
2828
})
2929
}
3030
data[f] = items
31+
case "closedByPullRequestsReferences":
32+
items := make([]map[string]interface{}, 0, len(issue.ClosedByPullRequestsReferences.Nodes))
33+
for _, n := range issue.ClosedByPullRequestsReferences.Nodes {
34+
items = append(items, map[string]interface{}{
35+
"id": n.ID,
36+
"number": n.Number,
37+
"url": n.URL,
38+
"repository": map[string]interface{}{
39+
"id": n.Repository.ID,
40+
"name": n.Repository.Name,
41+
"owner": map[string]interface{}{
42+
"id": n.Repository.Owner.ID,
43+
"login": n.Repository.Owner.Login,
44+
},
45+
},
46+
})
47+
}
48+
data[f] = items
3149
default:
3250
sf := fieldByName(v, f)
3351
data[f] = sf.Interface()
@@ -143,7 +161,6 @@ func (pr *PullRequest) ExportData(fields []string) map[string]interface{} {
143161
items := make([]map[string]interface{}, 0, len(pr.ClosingIssuesReferences.Nodes))
144162
for _, n := range pr.ClosingIssuesReferences.Nodes {
145163
items = append(items, map[string]interface{}{
146-
147164
"id": n.ID,
148165
"number": n.Number,
149166
"url": n.URL,

api/export_pr_test.go

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,70 @@ func TestIssue_ExportData(t *testing.T) {
107107
}
108108
`),
109109
},
110+
{
111+
name: "linked pull requests",
112+
fields: []string{"closedByPullRequestsReferences"},
113+
inputJSON: heredoc.Doc(`
114+
{ "closedByPullRequestsReferences": { "nodes": [
115+
{
116+
"id": "I_123",
117+
"number": 123,
118+
"url": "https://github.com/cli/cli/pull/123",
119+
"repository": {
120+
"id": "R_123",
121+
"name": "cli",
122+
"owner": {
123+
"id": "O_123",
124+
"login": "cli"
125+
}
126+
}
127+
},
128+
{
129+
"id": "I_456",
130+
"number": 456,
131+
"url": "https://github.com/cli/cli/pull/456",
132+
"repository": {
133+
"id": "R_456",
134+
"name": "cli",
135+
"owner": {
136+
"id": "O_456",
137+
"login": "cli"
138+
}
139+
}
140+
}
141+
] } }
142+
`),
143+
outputJSON: heredoc.Doc(`
144+
{ "closedByPullRequestsReferences": [
145+
{
146+
"id": "I_123",
147+
"number": 123,
148+
"repository": {
149+
"id": "R_123",
150+
"name": "cli",
151+
"owner": {
152+
"id": "O_123",
153+
"login": "cli"
154+
}
155+
},
156+
"url": "https://github.com/cli/cli/pull/123"
157+
},
158+
{
159+
"id": "I_456",
160+
"number": 456,
161+
"repository": {
162+
"id": "R_456",
163+
"name": "cli",
164+
"owner": {
165+
"id": "O_456",
166+
"login": "cli"
167+
}
168+
},
169+
"url": "https://github.com/cli/cli/pull/456"
170+
}
171+
] }
172+
`),
173+
},
110174
}
111175
for _, tt := range tests {
112176
t.Run(tt.name, func(t *testing.T) {
@@ -120,7 +184,14 @@ func TestIssue_ExportData(t *testing.T) {
120184
enc := json.NewEncoder(&buf)
121185
enc.SetIndent("", "\t")
122186
require.NoError(t, enc.Encode(exported))
123-
assert.Equal(t, tt.outputJSON, buf.String())
187+
188+
var gotData interface{}
189+
dec = json.NewDecoder(&buf)
190+
require.NoError(t, dec.Decode(&gotData))
191+
var expectData interface{}
192+
require.NoError(t, json.Unmarshal([]byte(tt.outputJSON), &expectData))
193+
194+
assert.Equal(t, expectData, gotData)
124195
})
125196
}
126197
}

api/queries_issue.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,28 @@ type Issue struct {
4444
Milestone *Milestone
4545
ReactionGroups ReactionGroups
4646
IsPinned bool
47+
48+
ClosedByPullRequestsReferences ClosedByPullRequestsReferences
49+
}
50+
51+
type ClosedByPullRequestsReferences struct {
52+
Nodes []struct {
53+
ID string
54+
Number int
55+
URL string
56+
Repository struct {
57+
ID string
58+
Name string
59+
Owner struct {
60+
ID string
61+
Login string
62+
}
63+
}
64+
}
65+
PageInfo struct {
66+
HasNextPage bool
67+
EndCursor string
68+
}
4769
}
4870

4971
// return values for Issue.Typename

api/query_builder.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,25 @@ var issueCommentLast = shortenQuery(`
5656
}
5757
`)
5858

59+
var issueClosedByPullRequestsReferences = shortenQuery(`
60+
closedByPullRequestsReferences(first: 100) {
61+
nodes {
62+
id,
63+
number,
64+
url,
65+
repository {
66+
id,
67+
name,
68+
owner {
69+
id,
70+
login
71+
}
72+
}
73+
}
74+
pageInfo{hasNextPage,endCursor}
75+
}
76+
`)
77+
5978
var prReviewRequests = shortenQuery(`
6079
reviewRequests(first: 100) {
6180
nodes {
@@ -296,6 +315,7 @@ var sharedIssuePRFields = []string{
296315
var issueOnlyFields = []string{
297316
"isPinned",
298317
"stateReason",
318+
"closedByPullRequestsReferences",
299319
}
300320

301321
var IssueFields = append(sharedIssuePRFields, issueOnlyFields...)
@@ -388,6 +408,8 @@ func IssueGraphQL(fields []string) string {
388408
q = append(q, StatusCheckRollupGraphQLWithCountByState())
389409
case "closingIssuesReferences":
390410
q = append(q, prClosingIssuesReferences)
411+
case "closedByPullRequestsReferences":
412+
q = append(q, issueClosedByPullRequestsReferences)
391413
default:
392414
q = append(q, field)
393415
}

pkg/cmd/issue/view/http.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,42 @@ func preloadIssueComments(client *http.Client, repo ghrepo.Interface, issue *api
5353
issue.Comments.PageInfo.HasNextPage = false
5454
return nil
5555
}
56+
57+
func preloadClosedByPullRequestsReferences(client *http.Client, repo ghrepo.Interface, issue *api.Issue) error {
58+
if !issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage {
59+
return nil
60+
}
61+
62+
type response struct {
63+
Node struct {
64+
Issue struct {
65+
ClosedByPullRequestsReferences api.ClosedByPullRequestsReferences `graphql:"closedByPullRequestsReferences(first: 100, after: $endCursor)"`
66+
} `graphql:"...on Issue"`
67+
} `graphql:"node(id: $id)"`
68+
}
69+
70+
variables := map[string]interface{}{
71+
"id": githubv4.ID(issue.ID),
72+
"endCursor": githubv4.String(issue.ClosedByPullRequestsReferences.PageInfo.EndCursor),
73+
}
74+
75+
gql := api.NewClientFromHTTP(client)
76+
77+
for {
78+
var query response
79+
err := gql.Query(repo.RepoHost(), "closedByPullRequestsReferences", &query, variables)
80+
if err != nil {
81+
return err
82+
}
83+
84+
issue.ClosedByPullRequestsReferences.Nodes = append(issue.ClosedByPullRequestsReferences.Nodes, query.Node.Issue.ClosedByPullRequestsReferences.Nodes...)
85+
86+
if !query.Node.Issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage {
87+
break
88+
}
89+
variables["endCursor"] = githubv4.String(query.Node.Issue.ClosedByPullRequestsReferences.PageInfo.EndCursor)
90+
}
91+
92+
issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage = false
93+
return nil
94+
}

pkg/cmd/issue/view/view.go

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package view
22

33
import (
4-
"errors"
54
"fmt"
65
"io"
76
"net/http"
@@ -134,6 +133,8 @@ func viewRun(opts *ViewOptions) error {
134133
opts.IO.DetectTerminalTheme()
135134

136135
opts.IO.StartProgressIndicator()
136+
defer opts.IO.StopProgressIndicator()
137+
137138
lookupFields.Add("id")
138139

139140
issue, err := issueShared.FindIssueOrPR(httpClient, baseRepo, opts.IssueNumber, lookupFields.ToSlice())
@@ -144,18 +145,21 @@ func viewRun(opts *ViewOptions) error {
144145
if lookupFields.Contains("comments") {
145146
// FIXME: this re-fetches the comments connection even though the initial set of 100 were
146147
// fetched in the previous request.
147-
err = preloadIssueComments(httpClient, baseRepo, issue)
148+
err := preloadIssueComments(httpClient, baseRepo, issue)
149+
if err != nil {
150+
return err
151+
}
148152
}
149-
opts.IO.StopProgressIndicator()
150-
if err != nil {
151-
var loadErr *issueShared.PartialLoadError
152-
if opts.Exporter == nil && errors.As(err, &loadErr) {
153-
fmt.Fprintf(opts.IO.ErrOut, "warning: %s\n", loadErr.Error())
154-
} else {
153+
154+
if lookupFields.Contains("closedByPullRequestsReferences") {
155+
err := preloadClosedByPullRequestsReferences(httpClient, baseRepo, issue)
156+
if err != nil {
155157
return err
156158
}
157159
}
158160

161+
opts.IO.StopProgressIndicator()
162+
159163
if opts.WebMode {
160164
openURL := issue.URL
161165
if opts.IO.IsStdoutTTY() {

pkg/cmd/issue/view/view_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ func TestJSONFields(t *testing.T) {
3131
"body",
3232
"closed",
3333
"comments",
34+
"closedByPullRequestsReferences",
3435
"createdAt",
3536
"closedAt",
3637
"id",

0 commit comments

Comments
 (0)