refactor(issue/pr list): support advanced issue search

Signed-off-by: Babak K. Shandiz <babakks@github.com>
This commit is contained in:
Babak K. Shandiz 2025-08-31 18:15:56 +01:00
parent 04cce6b35e
commit 6d148400a8
No known key found for this signature in database
GPG key ID: 9472CAEFF56C742E
9 changed files with 158 additions and 71 deletions

View file

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/cli/cli/v2/api"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/ghrepo"
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
)
@ -112,7 +113,12 @@ loop:
return &res, nil
}
func searchIssues(client *api.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
func searchIssues(client *api.Client, detector fd.Detector, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
features, err := detector.SearchFeatures()
if err != nil {
return nil, err
}
fragments := fmt.Sprintf("fragment issue on Issue {%s}", api.IssueGraphQL(filters.Fields))
query := fragments +
`query IssueSearch($repo: String!, $owner: String!, $type: SearchType!, $limit: Int, $after: String, $query: String!) {
@ -143,18 +149,27 @@ func searchIssues(client *api.Client, repo ghrepo.Interface, filters prShared.Fi
}
}
filters.Repo = ghrepo.FullName(repo)
filters.Entity = "issue"
q := prShared.SearchQueryBuild(filters)
perPage := min(limit, 100)
variables := map[string]interface{}{
"owner": repo.RepoOwner(),
"repo": repo.RepoName(),
"type": "ISSUE",
"limit": perPage,
"query": q,
}
filters.Repo = ghrepo.FullName(repo)
filters.Entity = "issue"
if features.AdvancedIssueSearchAPI {
variables["query"] = prShared.SearchQueryBuild(filters, true)
if features.AdvancedIssueSearchAPIOptIn {
variables["type"] = "ISSUE_ADVANCED"
} else {
variables["type"] = "ISSUE"
}
} else {
variables["query"] = prShared.SearchQueryBuild(filters, false)
variables["type"] = "ISSUE"
}
ic := api.IssuesAndTotalCount{SearchCapped: limit > 1000}

View file

@ -165,8 +165,15 @@ func listRun(opts *ListOptions) error {
isTerminal := opts.IO.IsStdoutTTY()
if opts.WebMode {
searchFeatures, err := opts.Detector.SearchFeatures()
if err != nil {
return err
}
advancedSyntaxSupported := searchFeatures.AdvancedIssueSearchAPI && searchFeatures.AdvancedIssueSearchWebInIssuesTab
issueListURL := ghrepo.GenerateRepoURL(baseRepo, "issues")
openURL, err := prShared.ListURLWithQuery(issueListURL, filterOptions)
openURL, err := prShared.ListURLWithQuery(issueListURL, filterOptions, advancedSyntaxSupported)
if err != nil {
return err
}
@ -181,7 +188,7 @@ func listRun(opts *ListOptions) error {
filterOptions.Fields = opts.Exporter.Fields()
}
listResult, err := issueList(httpClient, baseRepo, filterOptions, opts.LimitResults)
listResult, err := issueList(httpClient, opts.Detector, baseRepo, filterOptions, opts.LimitResults)
if err != nil {
return err
}
@ -212,7 +219,7 @@ func listRun(opts *ListOptions) error {
return nil
}
func issueList(client *http.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
func issueList(client *http.Client, detector fd.Detector, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.IssuesAndTotalCount, error) {
apiClient := api.NewClientFromHTTP(client)
if filters.Search != "" || len(filters.Labels) > 0 || filters.Milestone != "" {
@ -224,7 +231,7 @@ func issueList(client *http.Client, repo ghrepo.Interface, filters prShared.Filt
filters.Milestone = milestone.Title
}
return searchIssues(apiClient, repo, filters, limit)
return searchIssues(apiClient, detector, repo, filters, limit)
}
var err error

View file

@ -11,6 +11,7 @@ import (
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/browser"
"github.com/cli/cli/v2/internal/config"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/gh"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/run"
@ -210,6 +211,7 @@ func TestIssueList_web(t *testing.T) {
BaseRepo: func() (ghrepo.Interface, error) {
return ghrepo.New("OWNER", "REPO"), nil
},
Detector: fd.AdvancedIssueSearchUnsupported(),
WebMode: true,
State: "all",
Assignee: "peter",
@ -230,9 +232,10 @@ func TestIssueList_web(t *testing.T) {
func Test_issueList(t *testing.T) {
type args struct {
repo ghrepo.Interface
filters prShared.FilterOptions
limit int
detector fd.Detector
repo ghrepo.Interface
filters prShared.FilterOptions
limit int
}
tests := []struct {
name string
@ -243,8 +246,9 @@ func Test_issueList(t *testing.T) {
{
name: "default",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -270,8 +274,9 @@ func Test_issueList(t *testing.T) {
{
name: "milestone by number",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -309,8 +314,9 @@ func Test_issueList(t *testing.T) {
{
name: "milestone by title",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -341,8 +347,9 @@ func Test_issueList(t *testing.T) {
{
name: "@me syntax",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -377,8 +384,9 @@ func Test_issueList(t *testing.T) {
{
name: "@me with search",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -412,8 +420,9 @@ func Test_issueList(t *testing.T) {
{
name: "with labels",
args: args{
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
detector: fd.AdvancedIssueSearchUnsupported(),
limit: 30,
repo: ghrepo.New("OWNER", "REPO"),
filters: prShared.FilterOptions{
Entity: "issue",
State: "open",
@ -450,7 +459,7 @@ func Test_issueList(t *testing.T) {
tt.httpStubs(httpreg)
}
client := &http.Client{Transport: httpreg}
_, err := issueList(client, tt.args.repo, tt.args.filters, tt.args.limit)
_, err := issueList(client, tt.args.detector, tt.args.repo, tt.args.filters, tt.args.limit)
if tt.wantErr {
assert.Error(t, err)
} else {
@ -507,6 +516,7 @@ func TestIssueList_withProjectItems(t *testing.T) {
client := &http.Client{Transport: reg}
issuesAndTotalCount, err := issueList(
client,
fd.AdvancedIssueSearchUnsupported(),
ghrepo.New("OWNER", "REPO"),
prShared.FilterOptions{
Entity: "issue",
@ -581,6 +591,7 @@ func TestIssueList_Search_withProjectItems(t *testing.T) {
client := &http.Client{Transport: reg}
issuesAndTotalCount, err := issueList(
client,
fd.AdvancedIssueSearchUnsupported(),
ghrepo.New("OWNER", "REPO"),
prShared.FilterOptions{
Entity: "issue",

View file

@ -5,6 +5,7 @@ import (
"net/http"
"github.com/cli/cli/v2/api"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/ghrepo"
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
)
@ -13,9 +14,9 @@ func shouldUseSearch(filters prShared.FilterOptions) bool {
return filters.Draft != nil || filters.Author != "" || filters.Assignee != "" || filters.Search != "" || len(filters.Labels) > 0
}
func listPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
func listPullRequests(httpClient *http.Client, detector fd.Detector, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
if shouldUseSearch(filters) {
return searchPullRequests(httpClient, repo, filters, limit)
return searchPullRequests(httpClient, detector, repo, filters, limit)
}
return prShared.NewLister(httpClient).List(prShared.ListOptions{
@ -28,7 +29,12 @@ func listPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters pr
})
}
func searchPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
func searchPullRequests(httpClient *http.Client, detector fd.Detector, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
features, err := detector.SearchFeatures()
if err != nil {
return nil, err
}
type response struct {
Search struct {
Nodes []api.PullRequest
@ -44,10 +50,11 @@ func searchPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters
query := fragment + `
query PullRequestSearch(
$q: String!,
$type: SearchType!,
$limit: Int!,
$endCursor: String,
) {
search(query: $q, type: ISSUE, first: $limit, after: $endCursor) {
search(query: $q, type: $type, first: $limit, after: $endCursor) {
issueCount
nodes {
...pr
@ -59,12 +66,24 @@ func searchPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters
}
}`
variables := map[string]interface{}{}
filters.Repo = ghrepo.FullName(repo)
filters.Entity = "pr"
q := prShared.SearchQueryBuild(filters)
if features.AdvancedIssueSearchAPI {
variables["q"] = prShared.SearchQueryBuild(filters, true)
if features.AdvancedIssueSearchAPIOptIn {
variables["type"] = "ISSUE_ADVANCED"
} else {
variables["type"] = "ISSUE"
}
} else {
variables["q"] = prShared.SearchQueryBuild(filters, false)
variables["type"] = "ISSUE"
}
pageLimit := min(limit, 100)
variables := map[string]interface{}{"q": q}
res := api.PullRequestAndTotalCount{SearchCapped: limit > 1000}
var check = make(map[int]struct{})

View file

@ -5,6 +5,7 @@ import (
"reflect"
"testing"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/ghrepo"
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
"github.com/cli/cli/v2/pkg/httpmock"
@ -12,9 +13,10 @@ import (
func Test_ListPullRequests(t *testing.T) {
type args struct {
repo ghrepo.Interface
filters prShared.FilterOptions
limit int
detector fd.Detector
repo ghrepo.Interface
filters prShared.FilterOptions
limit int
}
tests := []struct {
name string
@ -25,8 +27,9 @@ func Test_ListPullRequests(t *testing.T) {
{
name: "default",
args: args{
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
detector: fd.AdvancedIssueSearchUnsupported(),
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
filters: prShared.FilterOptions{
State: "open",
},
@ -50,8 +53,9 @@ func Test_ListPullRequests(t *testing.T) {
{
name: "closed",
args: args{
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
detector: fd.AdvancedIssueSearchUnsupported(),
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
filters: prShared.FilterOptions{
State: "closed",
},
@ -75,8 +79,9 @@ func Test_ListPullRequests(t *testing.T) {
{
name: "with labels",
args: args{
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
detector: fd.AdvancedIssueSearchUnsupported(),
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
filters: prShared.FilterOptions{
State: "open",
Labels: []string{"hello", "one world"},
@ -88,6 +93,7 @@ func Test_ListPullRequests(t *testing.T) {
httpmock.GraphQLQuery(`{"data":{}}`, func(query string, vars map[string]interface{}) {
want := map[string]interface{}{
"q": `label:"one world" label:hello repo:OWNER/REPO state:open type:pr`,
"type": "ISSUE",
"limit": float64(30),
}
if !reflect.DeepEqual(vars, want) {
@ -99,8 +105,9 @@ func Test_ListPullRequests(t *testing.T) {
{
name: "with author",
args: args{
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
detector: fd.AdvancedIssueSearchUnsupported(),
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
filters: prShared.FilterOptions{
State: "open",
Author: "monalisa",
@ -112,6 +119,7 @@ func Test_ListPullRequests(t *testing.T) {
httpmock.GraphQLQuery(`{"data":{}}`, func(query string, vars map[string]interface{}) {
want := map[string]interface{}{
"q": "author:monalisa repo:OWNER/REPO state:open type:pr",
"type": "ISSUE",
"limit": float64(30),
}
if !reflect.DeepEqual(vars, want) {
@ -123,8 +131,9 @@ func Test_ListPullRequests(t *testing.T) {
{
name: "with search",
args: args{
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
detector: fd.AdvancedIssueSearchUnsupported(),
repo: ghrepo.New("OWNER", "REPO"),
limit: 30,
filters: prShared.FilterOptions{
State: "open",
Search: "one world in:title",
@ -136,6 +145,7 @@ func Test_ListPullRequests(t *testing.T) {
httpmock.GraphQLQuery(`{"data":{}}`, func(query string, vars map[string]interface{}) {
want := map[string]interface{}{
"q": "one world in:title repo:OWNER/REPO state:open type:pr",
"type": "ISSUE",
"limit": float64(30),
}
if !reflect.DeepEqual(vars, want) {
@ -153,7 +163,7 @@ func Test_ListPullRequests(t *testing.T) {
}
httpClient := &http.Client{Transport: reg}
_, err := listPullRequests(httpClient, tt.args.repo, tt.args.filters, tt.args.limit)
_, err := listPullRequests(httpClient, tt.args.detector, tt.args.repo, tt.args.filters, tt.args.limit)
if (err != nil) != tt.wantErr {
t.Errorf("ListPullRequests() error = %v, wantErr %v", err, tt.wantErr)
return

View file

@ -10,6 +10,7 @@ import (
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/browser"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/tableprinter"
"github.com/cli/cli/v2/internal/text"
@ -24,6 +25,7 @@ type ListOptions struct {
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
Browser browser.Browser
Detector fd.Detector
WebMode bool
LimitResults int
@ -142,6 +144,11 @@ func listRun(opts *ListOptions) error {
return err
}
if opts.Detector == nil {
cachedClient := api.NewCachedHTTPClient(httpClient, time.Hour*24)
opts.Detector = fd.NewDetector(cachedClient, baseRepo.RepoHost())
}
prState := strings.ToLower(opts.State)
if prState == "open" && shared.QueryHasStateClause(opts.Search) {
prState = ""
@ -164,7 +171,11 @@ func listRun(opts *ListOptions) error {
}
if opts.WebMode {
prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls")
openURL, err := shared.ListURLWithQuery(prListURL, filters)
// TODO(babakks): As of August 2025, the advanced issue search syntax is
// not supported in Pull Requests tab of repositories. When it's supported
// we can change the argument to true.
openURL, err := shared.ListURLWithQuery(prListURL, filters, false)
if err != nil {
return err
}
@ -175,7 +186,7 @@ func listRun(opts *ListOptions) error {
return opts.Browser.Browse(openURL)
}
listResult, err := listPullRequests(httpClient, baseRepo, filters, opts.LimitResults)
listResult, err := listPullRequests(httpClient, opts.Detector, baseRepo, filters, opts.LimitResults)
if err != nil {
return err
}

View file

@ -11,6 +11,7 @@ import (
"github.com/MakeNowJust/heredoc"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/browser"
fd "github.com/cli/cli/v2/internal/featuredetection"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/internal/run"
prShared "github.com/cli/cli/v2/pkg/cmd/pr/shared"
@ -23,7 +24,7 @@ import (
"github.com/stretchr/testify/require"
)
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
func runCommand(rt http.RoundTripper, detector fd.Detector, isTTY bool, cli string) (*test.CmdOut, error) {
ios, _, stdout, stderr := iostreams.Test()
ios.SetStdoutTTY(isTTY)
ios.SetStdinTTY(isTTY)
@ -47,6 +48,7 @@ func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, err
cmd := NewCmdList(factory, func(opts *ListOptions) error {
opts.Now = fakeNow
opts.Detector = detector
return listRun(opts)
})
@ -78,7 +80,7 @@ func TestPRList(t *testing.T) {
http.Register(httpmock.GraphQL(`query PullRequestList\b`), httpmock.FileResponse("./fixtures/prList.json"))
output, err := runCommand(http, true, "")
output, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, "")
if err != nil {
t.Fatal(err)
}
@ -101,7 +103,7 @@ func TestPRList_nontty(t *testing.T) {
http.Register(httpmock.GraphQL(`query PullRequestList\b`), httpmock.FileResponse("./fixtures/prList.json"))
output, err := runCommand(http, false, "")
output, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), false, "")
if err != nil {
t.Fatal(err)
}
@ -124,7 +126,7 @@ func TestPRList_filtering(t *testing.T) {
assert.Equal(t, []interface{}{"OPEN", "CLOSED", "MERGED"}, params["state"].([]interface{}))
}))
output, err := runCommand(http, true, `-s all`)
output, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, `-s all`)
assert.Error(t, err)
assert.Equal(t, "", output.String())
@ -139,7 +141,7 @@ func TestPRList_filteringRemoveDuplicate(t *testing.T) {
httpmock.GraphQL(`query PullRequestList\b`),
httpmock.FileResponse("./fixtures/prListWithDuplicates.json"))
output, err := runCommand(http, true, "")
output, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, "")
if err != nil {
t.Fatal(err)
}
@ -162,7 +164,7 @@ func TestPRList_filteringClosed(t *testing.T) {
assert.Equal(t, []interface{}{"CLOSED", "MERGED"}, params["state"].([]interface{}))
}))
_, err := runCommand(http, true, `-s closed`)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, `-s closed`)
assert.Error(t, err)
}
@ -176,7 +178,7 @@ func TestPRList_filteringHeadBranch(t *testing.T) {
assert.Equal(t, interface{}("bug-fix"), params["headBranch"])
}))
_, err := runCommand(http, true, `-H bug-fix`)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, `-H bug-fix`)
assert.Error(t, err)
}
@ -190,7 +192,7 @@ func TestPRList_filteringAssignee(t *testing.T) {
assert.Equal(t, `assignee:hubot base:develop is:merged label:"needs tests" repo:OWNER/REPO type:pr`, params["q"].(string))
}))
_, err := runCommand(http, true, `-s merged -l "needs tests" -a hubot -B develop`)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, `-s merged -l "needs tests" -a hubot -B develop`)
assert.Error(t, err)
}
@ -223,7 +225,7 @@ func TestPRList_filteringDraft(t *testing.T) {
assert.Equal(t, test.expectedQuery, params["q"].(string))
}))
_, err := runCommand(http, true, test.cli)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, test.cli)
assert.Error(t, err)
})
}
@ -268,7 +270,7 @@ func TestPRList_filteringAuthor(t *testing.T) {
assert.Equal(t, test.expectedQuery, params["q"].(string))
}))
_, err := runCommand(http, true, test.cli)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, test.cli)
assert.Error(t, err)
})
}
@ -277,7 +279,7 @@ func TestPRList_filteringAuthor(t *testing.T) {
func TestPRList_withInvalidLimitFlag(t *testing.T) {
http := initFakeHTTP()
defer http.Verify(t)
_, err := runCommand(http, true, `--limit=0`)
_, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, `--limit=0`)
assert.EqualError(t, err, "invalid value for --limit: 0")
}
@ -312,7 +314,7 @@ func TestPRList_web(t *testing.T) {
_, cmdTeardown := run.Stub()
defer cmdTeardown(t)
output, err := runCommand(http, true, "--web "+test.cli)
output, err := runCommand(http, fd.AdvancedIssueSearchUnsupported(), true, "--web "+test.cli)
if err != nil {
t.Errorf("error running command `pr list` with `--web` flag: %v", err)
}
@ -370,6 +372,7 @@ func TestPRList_withProjectItems(t *testing.T) {
client := &http.Client{Transport: reg}
prsAndTotalCount, err := listPullRequests(
client,
fd.AdvancedIssueSearchUnsupported(),
ghrepo.New("OWNER", "REPO"),
prShared.FilterOptions{
Entity: "pr",
@ -433,12 +436,14 @@ func TestPRList_Search_withProjectItems(t *testing.T) {
require.Equal(t, map[string]interface{}{
"limit": float64(30),
"q": "just used to force the search API branch repo:OWNER/REPO state:open type:pr",
"type": "ISSUE",
}, params)
}))
client := &http.Client{Transport: reg}
prsAndTotalCount, err := listPullRequests(
client,
fd.AdvancedIssueSearchUnsupported(),
ghrepo.New("OWNER", "REPO"),
prShared.FilterOptions{
Entity: "pr",

View file

@ -186,20 +186,20 @@ func (opts *FilterOptions) IsDefault() bool {
return true
}
func ListURLWithQuery(listURL string, options FilterOptions) (string, error) {
func ListURLWithQuery(listURL string, options FilterOptions, advancedIssueSearchSyntax bool) (string, error) {
u, err := url.Parse(listURL)
if err != nil {
return "", err
}
params := u.Query()
params.Set("q", SearchQueryBuild(options))
params.Set("q", SearchQueryBuild(options, advancedIssueSearchSyntax))
u.RawQuery = params.Encode()
return u.String(), nil
}
func SearchQueryBuild(options FilterOptions) string {
func SearchQueryBuild(options FilterOptions, advancedIssueSearchSyntax bool) string {
var is, state string
switch options.State {
case "open", "closed":
@ -207,7 +207,7 @@ func SearchQueryBuild(options FilterOptions) string {
case "merged":
is = "merged"
}
q := search.Query{
query := search.Query{
Qualifiers: search.Qualifiers{
Assignee: options.Assignee,
Author: options.Author,
@ -223,10 +223,18 @@ func SearchQueryBuild(options FilterOptions) string {
Type: options.Entity,
},
}
if options.Search != "" {
return fmt.Sprintf("%s %s", options.Search, q.String())
var q string
if advancedIssueSearchSyntax {
q = query.AdvancedIssueSearchString()
} else {
q = query.String()
}
return q.String()
if options.Search != "" {
return fmt.Sprintf("%s %s", options.Search, q)
}
return q
}
func QueryHasStateClause(searchQuery string) bool {

View file

@ -19,8 +19,9 @@ func Test_listURLWithQuery(t *testing.T) {
falseBool := false
type args struct {
listURL string
options FilterOptions
listURL string
options FilterOptions
advancedIssueSearchSyntax bool
}
tests := []struct {
@ -101,7 +102,7 @@ func Test_listURLWithQuery(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ListURLWithQuery(tt.args.listURL, tt.args.options)
got, err := ListURLWithQuery(tt.args.listURL, tt.args.options, tt.args.advancedIssueSearchSyntax)
if (err != nil) != tt.wantErr {
t.Errorf("listURLWithQuery() error = %v, wantErr %v", err, tt.wantErr)
return