Skip to content

Commit 011b56f

Browse files
committed
Adding dynamic label queries
1 parent 653c7f9 commit 011b56f

File tree

2 files changed

+330
-65
lines changed

2 files changed

+330
-65
lines changed

pkg/github/issues.go

Lines changed: 100 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,7 @@ import (
1818
"github.com/shurcooL/githubv4"
1919
)
2020

21-
var ListIssuesQuery struct {
22-
Repository struct {
23-
Issues struct {
24-
Nodes []IssueFragment `graphql:"nodes"`
25-
PageInfo struct {
26-
HasNextPage githubv4.Boolean
27-
HasPreviousPage githubv4.Boolean
28-
StartCursor githubv4.String
29-
EndCursor githubv4.String
30-
}
31-
TotalCount int
32-
} `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
33-
} `graphql:"repository(owner: $owner, name: $repo)"`
34-
}
35-
36-
// NodeFragment represents a fragment of an issue node in the GraphQL API.
21+
// IssueFragment represents a fragment of an issue node in the GraphQL API.
3722
type IssueFragment struct {
3823
Number githubv4.Int
3924
Title githubv4.String
@@ -55,11 +40,57 @@ type IssueFragment struct {
5540
} `graphql:"labels(first: 10)"`
5641
}
5742

43+
// Common interface for all issue query types
44+
type IssueQueryResult interface {
45+
GetIssueFragment() IssueQueryFragment
46+
}
47+
48+
type IssueQueryFragment struct {
49+
Nodes []IssueFragment `graphql:"nodes"`
50+
PageInfo struct {
51+
HasNextPage githubv4.Boolean
52+
HasPreviousPage githubv4.Boolean
53+
StartCursor githubv4.String
54+
EndCursor githubv4.String
55+
}
56+
TotalCount int
57+
}
58+
59+
// ListIssuesQuery is the root query structure for fetching issues with optional label filtering.
60+
type ListIssuesQueryType struct {
61+
Repository struct {
62+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, labels: $labels, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
63+
} `graphql:"repository(owner: $owner, name: $repo)"`
64+
}
65+
66+
// ListIssuesQueryNoLabels is the query structure for fetching issues without label filtering.
67+
type ListIssuesQueryNoLabelsType struct {
68+
Repository struct {
69+
Issues IssueQueryFragment `graphql:"issues(first: $first, after: $after, states: $states, orderBy: {field: $orderBy, direction: $direction})"`
70+
} `graphql:"repository(owner: $owner, name: $repo)"`
71+
}
72+
73+
// Implement the interface for both query types
74+
func (q *ListIssuesQueryType) GetIssueFragment() IssueQueryFragment {
75+
return q.Repository.Issues
76+
}
77+
78+
func (q *ListIssuesQueryNoLabelsType) GetIssueFragment() IssueQueryFragment {
79+
return q.Repository.Issues
80+
}
81+
82+
func getIssueQueryType(hasLabels bool) any {
83+
if hasLabels {
84+
return &ListIssuesQueryType{}
85+
}
86+
return &ListIssuesQueryNoLabelsType{}
87+
}
88+
5889
func fragmentToIssue(fragment IssueFragment) *github.Issue {
5990
// Convert GraphQL labels to GitHub API labels format
60-
var labels []*github.Label
91+
var foundLabels []*github.Label
6192
for _, labelNode := range fragment.Labels.Nodes {
62-
labels = append(labels, &github.Label{
93+
foundLabels = append(foundLabels, &github.Label{
6394
Name: github.Ptr(string(labelNode.Name)),
6495
NodeID: github.Ptr(string(labelNode.Id)),
6596
Description: github.Ptr(string(labelNode.Description)),
@@ -77,7 +108,7 @@ func fragmentToIssue(fragment IssueFragment) *github.Issue {
77108
State: github.Ptr(string(fragment.State)),
78109
ID: github.Ptr(fragment.DatabaseID),
79110
Body: github.Ptr(string(fragment.Body)),
80-
Labels: labels,
111+
Labels: foundLabels,
81112
}
82113
}
83114

@@ -850,6 +881,11 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
850881
return mcp.NewToolResultError(err.Error()), nil
851882
}
852883

884+
//If labels is empty, default to nil for gql query
885+
if len(labels) == 0 {
886+
labels = nil
887+
}
888+
853889
orderBy, err := OptionalParam[string](request, "orderBy")
854890
if err != nil {
855891
return mcp.NewToolResultError(err.Error()), nil
@@ -868,18 +904,19 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
868904
direction = "DESC"
869905
}
870906

871-
since, err := OptionalParam[string](request, "since")
872-
if err != nil {
873-
return mcp.NewToolResultError(err.Error()), nil
874-
}
907+
// since, err := OptionalParam[string](request, "since")
908+
// if err != nil {
909+
// return mcp.NewToolResultError(err.Error()), nil
910+
// }
911+
912+
// var sinceTime time.Time
913+
// if since != "" {
914+
// sinceTime, err = parseISOTimestamp(since)
915+
// if err != nil {
916+
// return mcp.NewToolResultError(fmt.Sprintf("failed to list issues: %s", err.Error())), nil
917+
// }
918+
// }
875919

876-
var sinceTime time.Time
877-
if since != "" {
878-
sinceTime, err = parseISOTimestamp(since)
879-
if err != nil {
880-
return mcp.NewToolResultError(fmt.Sprintf("failed to list issues: %s", err.Error())), nil
881-
}
882-
}
883920
// Get pagination parameters and convert to GraphQL format
884921
pagination, err := OptionalCursorPaginationParams(request)
885922
if err != nil {
@@ -926,54 +963,52 @@ func ListIssues(getGQLClient GetGQLClientFn, t translations.TranslationHelperFun
926963
vars["after"] = (*githubv4.String)(nil)
927964
}
928965

929-
if err := client.Query(ctx, &ListIssuesQuery, vars); err != nil {
966+
// Choose the appropriate query based on whether labels are specified
967+
hasLabels := len(labels) > 0
968+
969+
if hasLabels {
970+
// Use query with labels filtering - convert string labels to githubv4.String slice
971+
labelStrings := make([]githubv4.String, len(labels))
972+
for i, label := range labels {
973+
labelStrings[i] = githubv4.String(label)
974+
}
975+
vars["labels"] = labelStrings
976+
}
977+
978+
issueQuery := getIssueQueryType(hasLabels)
979+
if err := client.Query(ctx, issueQuery, vars); err != nil {
930980
return mcp.NewToolResultError(err.Error()), nil
931981
}
932982

933-
//We must filter based on labels after fetching all issues
983+
// Extract and convert all issue nodes using the common interface
934984
var issues []*github.Issue
935-
for _, issue := range ListIssuesQuery.Repository.Issues.Nodes {
936-
var issueLabels []string
937-
for _, label := range issue.Labels.Nodes {
938-
issueLabels = append(issueLabels, string(label.Name))
939-
}
940-
941-
// Filter by since date if specified
942-
if !sinceTime.IsZero() && issue.CreatedAt.Time.Before(sinceTime) {
943-
continue // Skip issues created before the since date
944-
}
985+
var pageInfo struct {
986+
HasNextPage githubv4.Boolean
987+
HasPreviousPage githubv4.Boolean
988+
StartCursor githubv4.String
989+
EndCursor githubv4.String
990+
}
991+
var totalCount int
945992

946-
// Filter by labels if specified
947-
if len(labels) > 0 {
948-
hasMatchingLabel := false
949-
for _, requestedLabel := range labels {
950-
for _, issueLabel := range issueLabels {
951-
if strings.EqualFold(requestedLabel, issueLabel) {
952-
hasMatchingLabel = true
953-
break
954-
}
955-
}
956-
if hasMatchingLabel {
957-
break
958-
}
959-
}
960-
if !hasMatchingLabel {
961-
continue // Skip this issue as it doesn't match any requested labels
962-
}
993+
if queryResult, ok := issueQuery.(IssueQueryResult); ok {
994+
fragment := queryResult.GetIssueFragment()
995+
for _, issue := range fragment.Nodes {
996+
issues = append(issues, fragmentToIssue(issue))
963997
}
964-
issues = append(issues, fragmentToIssue(issue))
998+
pageInfo = fragment.PageInfo
999+
totalCount = fragment.TotalCount
9651000
}
9661001

9671002
// Create response with issues
9681003
response := map[string]interface{}{
9691004
"issues": issues,
9701005
"pageInfo": map[string]interface{}{
971-
"hasNextPage": ListIssuesQuery.Repository.Issues.PageInfo.HasNextPage,
972-
"hasPreviousPage": ListIssuesQuery.Repository.Issues.PageInfo.HasPreviousPage,
973-
"startCursor": string(ListIssuesQuery.Repository.Issues.PageInfo.StartCursor),
974-
"endCursor": string(ListIssuesQuery.Repository.Issues.PageInfo.EndCursor),
1006+
"hasNextPage": pageInfo.HasNextPage,
1007+
"hasPreviousPage": pageInfo.HasPreviousPage,
1008+
"startCursor": string(pageInfo.StartCursor),
1009+
"endCursor": string(pageInfo.EndCursor),
9751010
},
976-
"totalCount": ListIssuesQuery.Repository.Issues.TotalCount,
1011+
"totalCount": totalCount,
9771012
}
9781013
out, err := json.Marshal(response)
9791014
if err != nil {

0 commit comments

Comments
 (0)