Add closedByPullRequestsReferences JSON field to issue view (#10941)

* [gh issue view] Expose `closedByPullRequestsReferences` JSON fields

* Incorporate GitHub Copilot review suggestions

* Incorporate review changes
This commit is contained in:
Azeem Sajid 2025-05-07 17:59:22 +05:00 committed by GitHub
parent 315876852a
commit ee281fd9ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 187 additions and 11 deletions

View file

@ -28,6 +28,24 @@ func (issue *Issue) ExportData(fields []string) map[string]interface{} {
})
}
data[f] = items
case "closedByPullRequestsReferences":
items := make([]map[string]interface{}, 0, len(issue.ClosedByPullRequestsReferences.Nodes))
for _, n := range issue.ClosedByPullRequestsReferences.Nodes {
items = append(items, map[string]interface{}{
"id": n.ID,
"number": n.Number,
"url": n.URL,
"repository": map[string]interface{}{
"id": n.Repository.ID,
"name": n.Repository.Name,
"owner": map[string]interface{}{
"id": n.Repository.Owner.ID,
"login": n.Repository.Owner.Login,
},
},
})
}
data[f] = items
default:
sf := fieldByName(v, f)
data[f] = sf.Interface()
@ -143,7 +161,6 @@ func (pr *PullRequest) ExportData(fields []string) map[string]interface{} {
items := make([]map[string]interface{}, 0, len(pr.ClosingIssuesReferences.Nodes))
for _, n := range pr.ClosingIssuesReferences.Nodes {
items = append(items, map[string]interface{}{
"id": n.ID,
"number": n.Number,
"url": n.URL,

View file

@ -107,6 +107,70 @@ func TestIssue_ExportData(t *testing.T) {
}
`),
},
{
name: "linked pull requests",
fields: []string{"closedByPullRequestsReferences"},
inputJSON: heredoc.Doc(`
{ "closedByPullRequestsReferences": { "nodes": [
{
"id": "I_123",
"number": 123,
"url": "https://github.com/cli/cli/pull/123",
"repository": {
"id": "R_123",
"name": "cli",
"owner": {
"id": "O_123",
"login": "cli"
}
}
},
{
"id": "I_456",
"number": 456,
"url": "https://github.com/cli/cli/pull/456",
"repository": {
"id": "R_456",
"name": "cli",
"owner": {
"id": "O_456",
"login": "cli"
}
}
}
] } }
`),
outputJSON: heredoc.Doc(`
{ "closedByPullRequestsReferences": [
{
"id": "I_123",
"number": 123,
"repository": {
"id": "R_123",
"name": "cli",
"owner": {
"id": "O_123",
"login": "cli"
}
},
"url": "https://github.com/cli/cli/pull/123"
},
{
"id": "I_456",
"number": 456,
"repository": {
"id": "R_456",
"name": "cli",
"owner": {
"id": "O_456",
"login": "cli"
}
},
"url": "https://github.com/cli/cli/pull/456"
}
] }
`),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@ -120,7 +184,14 @@ func TestIssue_ExportData(t *testing.T) {
enc := json.NewEncoder(&buf)
enc.SetIndent("", "\t")
require.NoError(t, enc.Encode(exported))
assert.Equal(t, tt.outputJSON, buf.String())
var gotData interface{}
dec = json.NewDecoder(&buf)
require.NoError(t, dec.Decode(&gotData))
var expectData interface{}
require.NoError(t, json.Unmarshal([]byte(tt.outputJSON), &expectData))
assert.Equal(t, expectData, gotData)
})
}
}

View file

@ -44,6 +44,28 @@ type Issue struct {
Milestone *Milestone
ReactionGroups ReactionGroups
IsPinned bool
ClosedByPullRequestsReferences ClosedByPullRequestsReferences
}
type ClosedByPullRequestsReferences struct {
Nodes []struct {
ID string
Number int
URL string
Repository struct {
ID string
Name string
Owner struct {
ID string
Login string
}
}
}
PageInfo struct {
HasNextPage bool
EndCursor string
}
}
// return values for Issue.Typename

View file

@ -56,6 +56,25 @@ var issueCommentLast = shortenQuery(`
}
`)
var issueClosedByPullRequestsReferences = shortenQuery(`
closedByPullRequestsReferences(first: 100) {
nodes {
id,
number,
url,
repository {
id,
name,
owner {
id,
login
}
}
}
pageInfo{hasNextPage,endCursor}
}
`)
var prReviewRequests = shortenQuery(`
reviewRequests(first: 100) {
nodes {
@ -296,6 +315,7 @@ var sharedIssuePRFields = []string{
var issueOnlyFields = []string{
"isPinned",
"stateReason",
"closedByPullRequestsReferences",
}
var IssueFields = append(sharedIssuePRFields, issueOnlyFields...)
@ -388,6 +408,8 @@ func IssueGraphQL(fields []string) string {
q = append(q, StatusCheckRollupGraphQLWithCountByState())
case "closingIssuesReferences":
q = append(q, prClosingIssuesReferences)
case "closedByPullRequestsReferences":
q = append(q, issueClosedByPullRequestsReferences)
default:
q = append(q, field)
}

View file

@ -53,3 +53,42 @@ func preloadIssueComments(client *http.Client, repo ghrepo.Interface, issue *api
issue.Comments.PageInfo.HasNextPage = false
return nil
}
func preloadClosedByPullRequestsReferences(client *http.Client, repo ghrepo.Interface, issue *api.Issue) error {
if !issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage {
return nil
}
type response struct {
Node struct {
Issue struct {
ClosedByPullRequestsReferences api.ClosedByPullRequestsReferences `graphql:"closedByPullRequestsReferences(first: 100, after: $endCursor)"`
} `graphql:"...on Issue"`
} `graphql:"node(id: $id)"`
}
variables := map[string]interface{}{
"id": githubv4.ID(issue.ID),
"endCursor": githubv4.String(issue.ClosedByPullRequestsReferences.PageInfo.EndCursor),
}
gql := api.NewClientFromHTTP(client)
for {
var query response
err := gql.Query(repo.RepoHost(), "closedByPullRequestsReferences", &query, variables)
if err != nil {
return err
}
issue.ClosedByPullRequestsReferences.Nodes = append(issue.ClosedByPullRequestsReferences.Nodes, query.Node.Issue.ClosedByPullRequestsReferences.Nodes...)
if !query.Node.Issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage {
break
}
variables["endCursor"] = githubv4.String(query.Node.Issue.ClosedByPullRequestsReferences.PageInfo.EndCursor)
}
issue.ClosedByPullRequestsReferences.PageInfo.HasNextPage = false
return nil
}

View file

@ -1,7 +1,6 @@
package view
import (
"errors"
"fmt"
"io"
"net/http"
@ -134,6 +133,8 @@ func viewRun(opts *ViewOptions) error {
opts.IO.DetectTerminalTheme()
opts.IO.StartProgressIndicator()
defer opts.IO.StopProgressIndicator()
lookupFields.Add("id")
issue, err := issueShared.FindIssueOrPR(httpClient, baseRepo, opts.IssueNumber, lookupFields.ToSlice())
@ -144,18 +145,21 @@ func viewRun(opts *ViewOptions) error {
if lookupFields.Contains("comments") {
// FIXME: this re-fetches the comments connection even though the initial set of 100 were
// fetched in the previous request.
err = preloadIssueComments(httpClient, baseRepo, issue)
}
opts.IO.StopProgressIndicator()
if err != nil {
var loadErr *issueShared.PartialLoadError
if opts.Exporter == nil && errors.As(err, &loadErr) {
fmt.Fprintf(opts.IO.ErrOut, "warning: %s\n", loadErr.Error())
} else {
err := preloadIssueComments(httpClient, baseRepo, issue)
if err != nil {
return err
}
}
if lookupFields.Contains("closedByPullRequestsReferences") {
err := preloadClosedByPullRequestsReferences(httpClient, baseRepo, issue)
if err != nil {
return err
}
}
opts.IO.StopProgressIndicator()
if opts.WebMode {
openURL := issue.URL
if opts.IO.IsStdoutTTY() {

View file

@ -31,6 +31,7 @@ func TestJSONFields(t *testing.T) {
"body",
"closed",
"comments",
"closedByPullRequestsReferences",
"createdAt",
"closedAt",
"id",