Skip to content

Commit a0b5e3a

Browse files
committed
Enhance search functionality with new query filters and utility functions
- Added multiple test cases for searching issues with various query filters in `issues_test.go`. - Introduced utility functions in `search_utils.go` to check for specific filters and extract repository information from queries. - Created comprehensive tests in `search_utils_test.go` to validate the new filtering logic and ensure accurate query parsing.
1 parent 33a63a0 commit a0b5e3a

File tree

3 files changed

+423
-2
lines changed

3 files changed

+423
-2
lines changed

pkg/github/issues_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,100 @@ func Test_SearchIssues(t *testing.T) {
410410
expectError: false,
411411
expectedResult: mockSearchResult,
412412
},
413+
{
414+
name: "query with existing is:issue filter - no duplication",
415+
mockedClient: mock.NewMockedHTTPClient(
416+
mock.WithRequestMatchHandler(
417+
mock.GetSearchIssues,
418+
expectQueryParams(
419+
t,
420+
map[string]string{
421+
"q": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)",
422+
"page": "1",
423+
"per_page": "30",
424+
},
425+
).andThen(
426+
mockResponse(t, http.StatusOK, mockSearchResult),
427+
),
428+
),
429+
),
430+
requestArgs: map[string]interface{}{
431+
"query": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)",
432+
},
433+
expectError: false,
434+
expectedResult: mockSearchResult,
435+
},
436+
{
437+
name: "query with existing repo: filter and conflicting owner/repo params - uses query filter",
438+
mockedClient: mock.NewMockedHTTPClient(
439+
mock.WithRequestMatchHandler(
440+
mock.GetSearchIssues,
441+
expectQueryParams(
442+
t,
443+
map[string]string{
444+
"q": "is:issue repo:github/github-mcp-server critical",
445+
"page": "1",
446+
"per_page": "30",
447+
},
448+
).andThen(
449+
mockResponse(t, http.StatusOK, mockSearchResult),
450+
),
451+
),
452+
),
453+
requestArgs: map[string]interface{}{
454+
"query": "repo:github/github-mcp-server critical",
455+
"owner": "different-owner",
456+
"repo": "different-repo",
457+
},
458+
expectError: false,
459+
expectedResult: mockSearchResult,
460+
},
461+
{
462+
name: "query with both is: and repo: filters already present",
463+
mockedClient: mock.NewMockedHTTPClient(
464+
mock.WithRequestMatchHandler(
465+
mock.GetSearchIssues,
466+
expectQueryParams(
467+
t,
468+
map[string]string{
469+
"q": "is:issue repo:octocat/Hello-World bug",
470+
"page": "1",
471+
"per_page": "30",
472+
},
473+
).andThen(
474+
mockResponse(t, http.StatusOK, mockSearchResult),
475+
),
476+
),
477+
),
478+
requestArgs: map[string]interface{}{
479+
"query": "is:issue repo:octocat/Hello-World bug",
480+
},
481+
expectError: false,
482+
expectedResult: mockSearchResult,
483+
},
484+
{
485+
name: "complex query with multiple OR operators and existing filters",
486+
mockedClient: mock.NewMockedHTTPClient(
487+
mock.WithRequestMatchHandler(
488+
mock.GetSearchIssues,
489+
expectQueryParams(
490+
t,
491+
map[string]string{
492+
"q": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)",
493+
"page": "1",
494+
"per_page": "30",
495+
},
496+
).andThen(
497+
mockResponse(t, http.StatusOK, mockSearchResult),
498+
),
499+
),
500+
),
501+
requestArgs: map[string]interface{}{
502+
"query": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)",
503+
},
504+
expectError: false,
505+
expectedResult: mockSearchResult,
506+
},
413507
{
414508
name: "search issues fails",
415509
mockedClient: mock.NewMockedHTTPClient(

pkg/github/search_utils.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,35 @@ import (
66
"fmt"
77
"io"
88
"net/http"
9+
"regexp"
910

1011
"github.com/google/go-github/v73/github"
1112
"github.com/mark3labs/mcp-go/mcp"
1213
)
1314

15+
func hasFilter(query, filterType string) bool {
16+
pattern := fmt.Sprintf(`(^|\s)%s:\S+`, regexp.QuoteMeta(filterType))
17+
matched, _ := regexp.MatchString(pattern, query)
18+
return matched
19+
}
20+
21+
func hasSpecificFilter(query, filterType, filterValue string) bool {
22+
pattern := fmt.Sprintf(`(^|\s)%s:%s($|\s)`, regexp.QuoteMeta(filterType), regexp.QuoteMeta(filterValue))
23+
matched, _ := regexp.MatchString(pattern, query)
24+
return matched
25+
}
26+
27+
func extractRepoFilter(query string) (owner, repo string, found bool) {
28+
pattern := `(?:^|\s)repo:([^/\s]+)/([^\s]+)`
29+
re := regexp.MustCompile(pattern)
30+
matches := re.FindStringSubmatch(query)
31+
32+
if len(matches) >= 3 {
33+
return matches[1], matches[2], true
34+
}
35+
return "", "", false
36+
}
37+
1438
func searchHandler(
1539
ctx context.Context,
1640
getClient GetClientFn,
@@ -22,7 +46,10 @@ func searchHandler(
2246
if err != nil {
2347
return mcp.NewToolResultError(err.Error()), nil
2448
}
25-
query = fmt.Sprintf("is:%s %s", searchType, query)
49+
50+
if !hasSpecificFilter(query, "is", searchType) {
51+
query = fmt.Sprintf("is:%s %s", searchType, query)
52+
}
2653

2754
owner, err := OptionalParam[string](request, "owner")
2855
if err != nil {
@@ -35,7 +62,13 @@ func searchHandler(
3562
}
3663

3764
if owner != "" && repo != "" {
38-
query = fmt.Sprintf("repo:%s/%s %s", owner, repo, query)
65+
_, _, hasRepoFilter := extractRepoFilter(query)
66+
67+
// TODO: Existing owner and existing repo?
68+
if !hasRepoFilter {
69+
query = fmt.Sprintf("repo:%s/%s %s", owner, repo, query)
70+
}
71+
3972
}
4073

4174
sort, err := OptionalParam[string](request, "sort")

0 commit comments

Comments
 (0)