Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 150 additions & 6 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,12 +507,41 @@ type IssueFragment struct {
} `graphql:"issueFieldValues(first: 25)"`
}

type issueFragmentWithoutFieldValues struct {
Number githubv4.Int
Title githubv4.String
Body githubv4.String
State githubv4.String
DatabaseID int64

Author struct {
Login githubv4.String
}
CreatedAt githubv4.DateTime
UpdatedAt githubv4.DateTime
Labels struct {
Nodes []struct {
Name githubv4.String
ID githubv4.String
Description githubv4.String
}
} `graphql:"labels(first: 100)"`
Comments struct {
TotalCount githubv4.Int
} `graphql:"comments"`
}

// Common interface for all issue query types
type IssueQueryResult interface {
GetIssueFragment() IssueQueryFragment
GetIsPrivate() bool
}

type issueQueryResultWithoutFieldValues interface {
getIssueFragmentWithoutFieldValues() issueQueryFragmentWithoutFieldValues
GetIsPrivate() bool
}

type IssueQueryFragment struct {
Nodes []IssueFragment `graphql:"nodes"`
PageInfo struct {
Expand All @@ -524,6 +553,17 @@ type IssueQueryFragment struct {
TotalCount int
}

type issueQueryFragmentWithoutFieldValues struct {
Nodes []issueFragmentWithoutFieldValues `graphql:"nodes"`
PageInfo struct {
HasNextPage githubv4.Boolean
HasPreviousPage githubv4.Boolean
StartCursor githubv4.String
EndCursor githubv4.String
}
TotalCount int
}

// ListIssuesQuery is the root query structure for fetching issues with optional label filtering.
type ListIssuesQuery struct {
Repository struct {
Expand Down Expand Up @@ -556,6 +596,34 @@ type ListIssuesQueryTypeWithLabelsWithSince struct {
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type listIssuesQueryWithoutFieldValues struct {
Repository struct {
Issues issueQueryFragmentWithoutFieldValues `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
IsPrivate githubv4.Boolean
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type listIssuesQueryWithLabelsWithoutFieldValues struct {
Repository struct {
Issues issueQueryFragmentWithoutFieldValues `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
IsPrivate githubv4.Boolean
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type listIssuesQueryWithSinceWithoutFieldValues struct {
Repository struct {
Issues issueQueryFragmentWithoutFieldValues `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
IsPrivate githubv4.Boolean
} `graphql:"repository(owner: $owner, name: $repo)"`
}

type listIssuesQueryWithLabelsAndSinceWithoutFieldValues struct {
Repository struct {
Issues issueQueryFragmentWithoutFieldValues `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction}, filterBy: {since: $since})"`
IsPrivate githubv4.Boolean
} `graphql:"repository(owner: $owner, name: $repo)"`
}

// IssueFieldValueFilter mirrors the GraphQL IssueFieldValueFilter input. Exactly one typed value
// field should be set per filter (the monolith resolver rejects multiple).
type IssueFieldValueFilter struct {
Expand Down Expand Up @@ -593,6 +661,38 @@ func (q *ListIssuesQueryTypeWithLabelsWithSince) GetIsPrivate() bool {
return bool(q.Repository.IsPrivate)
}

func (q *listIssuesQueryWithoutFieldValues) getIssueFragmentWithoutFieldValues() issueQueryFragmentWithoutFieldValues {
return q.Repository.Issues
}

func (q *listIssuesQueryWithoutFieldValues) GetIsPrivate() bool {
return bool(q.Repository.IsPrivate)
}

func (q *listIssuesQueryWithLabelsWithoutFieldValues) getIssueFragmentWithoutFieldValues() issueQueryFragmentWithoutFieldValues {
return q.Repository.Issues
}

func (q *listIssuesQueryWithLabelsWithoutFieldValues) GetIsPrivate() bool {
return bool(q.Repository.IsPrivate)
}

func (q *listIssuesQueryWithSinceWithoutFieldValues) getIssueFragmentWithoutFieldValues() issueQueryFragmentWithoutFieldValues {
return q.Repository.Issues
}

func (q *listIssuesQueryWithSinceWithoutFieldValues) GetIsPrivate() bool {
return bool(q.Repository.IsPrivate)
}

func (q *listIssuesQueryWithLabelsAndSinceWithoutFieldValues) getIssueFragmentWithoutFieldValues() issueQueryFragmentWithoutFieldValues {
return q.Repository.Issues
}

func (q *listIssuesQueryWithLabelsAndSinceWithoutFieldValues) GetIsPrivate() bool {
return bool(q.Repository.IsPrivate)
}

func getIssueQueryType(hasLabels bool, hasSince bool) any {
switch {
case hasLabels && hasSince:
Expand All @@ -606,6 +706,29 @@ func getIssueQueryType(hasLabels bool, hasSince bool) any {
}
}

func getIssueQueryTypeWithoutFieldValues(hasLabels bool, hasSince bool) issueQueryResultWithoutFieldValues {
switch {
case hasLabels && hasSince:
return &listIssuesQueryWithLabelsAndSinceWithoutFieldValues{}
case hasLabels:
return &listIssuesQueryWithLabelsWithoutFieldValues{}
case hasSince:
return &listIssuesQueryWithSinceWithoutFieldValues{}
default:
return &listIssuesQueryWithoutFieldValues{}
}
}

func isUnsupportedListIssuesIssueFieldsError(err error) bool {
switch err.Error() {
case "IssueFieldValueFilter isn't a defined input type (on $issueFieldValues)",
"Field 'issueFieldValues' doesn't exist on type 'Issue'":
return true
default:
return false
}
}

// IssueRead creates a tool to get details of a specific issue in a GitHub repository.
func IssueRead(t translations.TranslationHelperFunc) inventory.ServerTool {
schema := &jsonschema.Schema{
Expand Down Expand Up @@ -3003,16 +3126,37 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
// is a no-op once the flags are globally rolled out.
ctxWithFeatures := ghcontext.WithGraphQLFeatures(ctx, "issue_fields", "repo_issue_fields")
if err := client.Query(ctxWithFeatures, issueQuery, vars); err != nil {
return ghErrors.NewGitHubGraphQLErrorResponse(
ctx,
"failed to list issues",
err,
), nil, nil
if len(fieldFilters) > 0 || !isUnsupportedListIssuesIssueFieldsError(err) {
return ghErrors.NewGitHubGraphQLErrorResponse(
ctx,
"failed to list issues",
err,
), nil, nil
}

issueQueryWithoutFieldValues := getIssueQueryTypeWithoutFieldValues(hasLabels, hasSince)
varsWithoutFieldValues := make(map[string]any, len(vars)-1)
for name, value := range vars {
if name != "issueFieldValues" {
varsWithoutFieldValues[name] = value
}
}
if err := client.Query(ctx, issueQueryWithoutFieldValues, varsWithoutFieldValues); err != nil {
return ghErrors.NewGitHubGraphQLErrorResponse(
ctx,
"failed to list issues",
err,
), nil, nil
}
issueQuery = issueQueryWithoutFieldValues
}

var resp MinimalIssuesResponse
var isPrivate bool
if queryResult, ok := issueQuery.(IssueQueryResult); ok {
if queryResult, ok := issueQuery.(issueQueryResultWithoutFieldValues); ok {
resp = convertToMinimalIssuesResponseWithoutFieldValues(queryResult.getIssueFragmentWithoutFieldValues())
isPrivate = queryResult.GetIsPrivate()
} else if queryResult, ok := issueQuery.(IssueQueryResult); ok {
resp = convertToMinimalIssuesResponse(queryResult.GetIssueFragment())
isPrivate = queryResult.GetIsPrivate()
}
Expand Down
Loading
Loading