diff --git a/api/export_pr.go b/api/export_pr.go index bb3310811..7ae1a4ff4 100644 --- a/api/export_pr.go +++ b/api/export_pr.go @@ -139,6 +139,25 @@ func (pr *PullRequest) ExportData(fields []string) map[string]interface{} { } } data[f] = &requests + case "closingIssuesReferences": + 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, + "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() diff --git a/api/export_pr_test.go b/api/export_pr_test.go index b7f4dcddb..09a1dffe8 100644 --- a/api/export_pr_test.go +++ b/api/export_pr_test.go @@ -245,6 +245,70 @@ func TestPullRequest_ExportData(t *testing.T) { } `), }, + { + name: "linked issues", + fields: []string{"closingIssuesReferences"}, + inputJSON: heredoc.Doc(` + { "closingIssuesReferences": { "nodes": [ + { + "id": "I_123", + "number": 123, + "url": "https://github.com/cli/cli/issues/123", + "repository": { + "id": "R_123", + "name": "cli", + "owner": { + "id": "O_123", + "login": "cli" + } + } + }, + { + "id": "I_456", + "number": 456, + "url": "https://github.com/cli/cli/issues/456", + "repository": { + "id": "R_456", + "name": "cli", + "owner": { + "id": "O_456", + "login": "cli" + } + } + } + ] } } + `), + outputJSON: heredoc.Doc(` + { "closingIssuesReferences": [ + { + "id": "I_123", + "number": 123, + "repository": { + "id": "R_123", + "name": "cli", + "owner": { + "id": "O_123", + "login": "cli" + } + }, + "url": "https://github.com/cli/cli/issues/123" + }, + { + "id": "I_456", + "number": 456, + "repository": { + "id": "R_456", + "name": "cli", + "owner": { + "id": "O_456", + "login": "cli" + } + }, + "url": "https://github.com/cli/cli/issues/456" + } + ] } + `), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/api/queries_pr.go b/api/queries_pr.go index aa493b5e9..5b941bb42 100644 --- a/api/queries_pr.go +++ b/api/queries_pr.go @@ -93,6 +93,8 @@ type PullRequest struct { Reviews PullRequestReviews LatestReviews PullRequestReviews ReviewRequests ReviewRequests + + ClosingIssuesReferences ClosingIssuesReferences } type StatusCheckRollupNode struct { @@ -107,6 +109,26 @@ type CommitStatusCheckRollup struct { Contexts CheckContexts } +type ClosingIssuesReferences 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 + } +} + // https://docs.github.com/en/graphql/reference/enums#checkrunstate type CheckRunState string diff --git a/api/query_builder.go b/api/query_builder.go index 2112367e3..4c45da3c1 100644 --- a/api/query_builder.go +++ b/api/query_builder.go @@ -132,6 +132,25 @@ var prCommits = shortenQuery(` } `) +var prClosingIssuesReferences = shortenQuery(` + closingIssuesReferences(first: 100) { + nodes { + id, + number, + url, + repository { + id, + name, + owner { + id, + login + } + } + } + pageInfo{hasNextPage,endCursor} + } +`) + var autoMergeRequest = shortenQuery(` autoMergeRequest { authorEmail, @@ -287,6 +306,7 @@ var PullRequestFields = append(sharedIssuePRFields, "baseRefName", "baseRefOid", "changedFiles", + "closingIssuesReferences", "commits", "deletions", "files", @@ -366,6 +386,8 @@ func IssueGraphQL(fields []string) string { q = append(q, StatusCheckRollupGraphQLWithoutCountByState("")) case "statusCheckRollupWithCountByState": // pseudo-field q = append(q, StatusCheckRollupGraphQLWithCountByState()) + case "closingIssuesReferences": + q = append(q, prClosingIssuesReferences) default: q = append(q, field) } diff --git a/pkg/cmd/pr/shared/finder.go b/pkg/cmd/pr/shared/finder.go index 6d36ef816..e6bb7d66a 100644 --- a/pkg/cmd/pr/shared/finder.go +++ b/pkg/cmd/pr/shared/finder.go @@ -239,6 +239,11 @@ func (f *finder) Find(opts FindOptions) (*api.PullRequest, ghrepo.Interface, err return preloadPrComments(httpClient, f.baseRefRepo, pr) }) } + if fields.Contains("closingIssuesReferences") { + g.Go(func() error { + return preloadPrClosingIssuesReferences(httpClient, f.baseRefRepo, pr) + }) + } if fields.Contains("statusCheckRollup") { g.Go(func() error { return preloadPrChecks(httpClient, f.baseRefRepo, pr) @@ -452,6 +457,45 @@ func preloadPrComments(client *http.Client, repo ghrepo.Interface, pr *api.PullR return nil } +func preloadPrClosingIssuesReferences(client *http.Client, repo ghrepo.Interface, pr *api.PullRequest) error { + if !pr.ClosingIssuesReferences.PageInfo.HasNextPage { + return nil + } + + type response struct { + Node struct { + PullRequest struct { + ClosingIssuesReferences api.ClosingIssuesReferences `graphql:"closingIssuesReferences(first: 100, after: $endCursor)"` + } `graphql:"...on PullRequest"` + } `graphql:"node(id: $id)"` + } + + variables := map[string]interface{}{ + "id": githubv4.ID(pr.ID), + "endCursor": githubv4.String(pr.ClosingIssuesReferences.PageInfo.EndCursor), + } + + gql := api.NewClientFromHTTP(client) + + for { + var query response + err := gql.Query(repo.RepoHost(), "closingIssuesReferences", &query, variables) + if err != nil { + return err + } + + pr.ClosingIssuesReferences.Nodes = append(pr.ClosingIssuesReferences.Nodes, query.Node.PullRequest.ClosingIssuesReferences.Nodes...) + + if !query.Node.PullRequest.ClosingIssuesReferences.PageInfo.HasNextPage { + break + } + variables["endCursor"] = githubv4.String(query.Node.PullRequest.ClosingIssuesReferences.PageInfo.EndCursor) + } + + pr.ClosingIssuesReferences.PageInfo.HasNextPage = false + return nil +} + func preloadPrChecks(client *http.Client, repo ghrepo.Interface, pr *api.PullRequest) error { if len(pr.StatusCheckRollup.Nodes) == 0 { return nil diff --git a/pkg/cmd/pr/view/view_test.go b/pkg/cmd/pr/view/view_test.go index e7f572c76..2cd4066b8 100644 --- a/pkg/cmd/pr/view/view_test.go +++ b/pkg/cmd/pr/view/view_test.go @@ -37,6 +37,7 @@ func TestJSONFields(t *testing.T) { "changedFiles", "closed", "closedAt", + "closingIssuesReferences", "comments", "commits", "createdAt",