The subIssues, blockedBy, and blocking JSON output is currently shaped
as a flat array, which silently truncates when there are more entries
than the GraphQL fragment fetches. That's misleading once a user
crosses the page size, so this switches each connection to a
{nodes, totalCount} object so consumers can see when there's more.
While confirming page sizes, the GitHub limits turn out to be:
- sub-issues: up to 100 per parent
https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/adding-sub-issues
- blocked-by / blocking: up to 50 per relationship type
https://github.blog/changelog/2025-08-21-dependencies-on-issues/
So subIssues moves to first:100 to fetch the full set; blockedBy and
blocking stay at first:50, which already covers their cap.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
256 lines
7.1 KiB
Go
256 lines
7.1 KiB
Go
package api
|
|
|
|
import (
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
func (issue *Issue) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(issue).Elem()
|
|
data := map[string]interface{}{}
|
|
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "comments":
|
|
data[f] = issue.Comments.Nodes
|
|
case "assignees":
|
|
data[f] = issue.Assignees.Nodes
|
|
case "labels":
|
|
data[f] = issue.Labels.Nodes
|
|
case "projectCards":
|
|
data[f] = issue.ProjectCards.Nodes
|
|
case "projectItems":
|
|
items := make([]map[string]interface{}, 0, len(issue.ProjectItems.Nodes))
|
|
for _, n := range issue.ProjectItems.Nodes {
|
|
items = append(items, map[string]interface{}{
|
|
"status": n.Status,
|
|
"title": n.Project.Title,
|
|
})
|
|
}
|
|
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
|
|
case "issueType":
|
|
data[f] = issue.IssueType
|
|
case "parent":
|
|
if issue.Parent != nil {
|
|
data[f] = map[string]interface{}{
|
|
"id": issue.Parent.ID,
|
|
"number": issue.Parent.Number,
|
|
"title": issue.Parent.Title,
|
|
"url": issue.Parent.URL,
|
|
"state": issue.Parent.State,
|
|
}
|
|
} else {
|
|
data[f] = nil
|
|
}
|
|
case "subIssues":
|
|
items := make([]map[string]interface{}, 0, len(issue.SubIssues.Nodes))
|
|
for _, n := range issue.SubIssues.Nodes {
|
|
items = append(items, map[string]interface{}{
|
|
"id": n.ID,
|
|
"number": n.Number,
|
|
"title": n.Title,
|
|
"url": n.URL,
|
|
"state": n.State,
|
|
})
|
|
}
|
|
data[f] = map[string]interface{}{
|
|
"nodes": items,
|
|
"totalCount": issue.SubIssues.TotalCount,
|
|
}
|
|
case "subIssuesSummary":
|
|
data[f] = map[string]interface{}{
|
|
"total": issue.SubIssuesSummary.Total,
|
|
"completed": issue.SubIssuesSummary.Completed,
|
|
"percentCompleted": issue.SubIssuesSummary.PercentCompleted,
|
|
}
|
|
case "blockedBy":
|
|
items := make([]map[string]interface{}, 0, len(issue.BlockedBy.Nodes))
|
|
for _, n := range issue.BlockedBy.Nodes {
|
|
items = append(items, map[string]interface{}{
|
|
"id": n.ID,
|
|
"number": n.Number,
|
|
"title": n.Title,
|
|
"url": n.URL,
|
|
"state": n.State,
|
|
})
|
|
}
|
|
data[f] = map[string]interface{}{
|
|
"nodes": items,
|
|
"totalCount": issue.BlockedBy.TotalCount,
|
|
}
|
|
case "blocking":
|
|
items := make([]map[string]interface{}, 0, len(issue.Blocking.Nodes))
|
|
for _, n := range issue.Blocking.Nodes {
|
|
items = append(items, map[string]interface{}{
|
|
"id": n.ID,
|
|
"number": n.Number,
|
|
"title": n.Title,
|
|
"url": n.URL,
|
|
"state": n.State,
|
|
})
|
|
}
|
|
data[f] = map[string]interface{}{
|
|
"nodes": items,
|
|
"totalCount": issue.Blocking.TotalCount,
|
|
}
|
|
default:
|
|
sf := fieldByName(v, f)
|
|
data[f] = sf.Interface()
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func (pr *PullRequest) ExportData(fields []string) map[string]interface{} {
|
|
v := reflect.ValueOf(pr).Elem()
|
|
data := map[string]interface{}{}
|
|
|
|
for _, f := range fields {
|
|
switch f {
|
|
case "headRepository":
|
|
data[f] = pr.HeadRepository
|
|
case "statusCheckRollup":
|
|
if n := pr.StatusCheckRollup.Nodes; len(n) > 0 {
|
|
checks := make([]interface{}, 0, len(n[0].Commit.StatusCheckRollup.Contexts.Nodes))
|
|
for _, c := range n[0].Commit.StatusCheckRollup.Contexts.Nodes {
|
|
if c.TypeName == "CheckRun" {
|
|
checks = append(checks, map[string]interface{}{
|
|
"__typename": c.TypeName,
|
|
"name": c.Name,
|
|
"workflowName": c.CheckSuite.WorkflowRun.Workflow.Name,
|
|
"status": c.Status,
|
|
"conclusion": c.Conclusion,
|
|
"startedAt": c.StartedAt,
|
|
"completedAt": c.CompletedAt,
|
|
"detailsUrl": c.DetailsURL,
|
|
})
|
|
} else {
|
|
checks = append(checks, map[string]interface{}{
|
|
"__typename": c.TypeName,
|
|
"context": c.Context,
|
|
"state": c.State,
|
|
"targetUrl": c.TargetURL,
|
|
"startedAt": c.CreatedAt,
|
|
})
|
|
}
|
|
}
|
|
data[f] = checks
|
|
} else {
|
|
data[f] = nil
|
|
}
|
|
case "commits":
|
|
commits := make([]interface{}, 0, len(pr.Commits.Nodes))
|
|
for _, c := range pr.Commits.Nodes {
|
|
commit := c.Commit
|
|
authors := make([]interface{}, 0, len(commit.Authors.Nodes))
|
|
for _, author := range commit.Authors.Nodes {
|
|
authors = append(authors, map[string]interface{}{
|
|
"name": author.Name,
|
|
"email": author.Email,
|
|
"id": author.User.ID,
|
|
"login": author.User.Login,
|
|
})
|
|
}
|
|
commits = append(commits, map[string]interface{}{
|
|
"oid": commit.OID,
|
|
"messageHeadline": commit.MessageHeadline,
|
|
"messageBody": commit.MessageBody,
|
|
"committedDate": commit.CommittedDate,
|
|
"authoredDate": commit.AuthoredDate,
|
|
"authors": authors,
|
|
})
|
|
}
|
|
data[f] = commits
|
|
case "comments":
|
|
data[f] = pr.Comments.Nodes
|
|
case "assignees":
|
|
data[f] = pr.Assignees.Nodes
|
|
case "labels":
|
|
data[f] = pr.Labels.Nodes
|
|
case "projectCards":
|
|
data[f] = pr.ProjectCards.Nodes
|
|
case "projectItems":
|
|
items := make([]map[string]interface{}, 0, len(pr.ProjectItems.Nodes))
|
|
for _, n := range pr.ProjectItems.Nodes {
|
|
items = append(items, map[string]interface{}{
|
|
"status": n.Status,
|
|
"title": n.Project.Title,
|
|
})
|
|
}
|
|
data[f] = items
|
|
case "reviews":
|
|
data[f] = pr.Reviews.Nodes
|
|
case "latestReviews":
|
|
data[f] = pr.LatestReviews.Nodes
|
|
case "files":
|
|
data[f] = pr.Files.Nodes
|
|
case "reviewRequests":
|
|
requests := make([]interface{}, 0, len(pr.ReviewRequests.Nodes))
|
|
for _, req := range pr.ReviewRequests.Nodes {
|
|
r := req.RequestedReviewer
|
|
switch r.TypeName {
|
|
case "User":
|
|
requests = append(requests, map[string]string{
|
|
"__typename": r.TypeName,
|
|
"login": r.Login,
|
|
})
|
|
case "Team":
|
|
requests = append(requests, map[string]string{
|
|
"__typename": r.TypeName,
|
|
"name": r.Name,
|
|
"slug": r.LoginOrSlug(),
|
|
})
|
|
}
|
|
}
|
|
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()
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func fieldByName(v reflect.Value, field string) reflect.Value {
|
|
return v.FieldByNameFunc(func(s string) bool {
|
|
return strings.EqualFold(field, s)
|
|
})
|
|
}
|