diff --git a/pkg/cmd/issue/list/http.go b/pkg/cmd/issue/list/http.go index fcbfe7240..eb2c54330 100644 --- a/pkg/cmd/issue/list/http.go +++ b/pkg/cmd/issue/list/http.go @@ -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} diff --git a/pkg/cmd/issue/list/list.go b/pkg/cmd/issue/list/list.go index 46c0e2cb0..241fbee02 100644 --- a/pkg/cmd/issue/list/list.go +++ b/pkg/cmd/issue/list/list.go @@ -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 diff --git a/pkg/cmd/issue/list/list_test.go b/pkg/cmd/issue/list/list_test.go index 852f0a46b..b88414d43 100644 --- a/pkg/cmd/issue/list/list_test.go +++ b/pkg/cmd/issue/list/list_test.go @@ -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", diff --git a/pkg/cmd/pr/list/http.go b/pkg/cmd/pr/list/http.go index 4c69af708..24a303e43 100644 --- a/pkg/cmd/pr/list/http.go +++ b/pkg/cmd/pr/list/http.go @@ -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{}) diff --git a/pkg/cmd/pr/list/http_test.go b/pkg/cmd/pr/list/http_test.go index 1aa16ae1d..4f796906a 100644 --- a/pkg/cmd/pr/list/http_test.go +++ b/pkg/cmd/pr/list/http_test.go @@ -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 diff --git a/pkg/cmd/pr/list/list.go b/pkg/cmd/pr/list/list.go index 7188df1a5..383e2b5fb 100644 --- a/pkg/cmd/pr/list/list.go +++ b/pkg/cmd/pr/list/list.go @@ -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 } diff --git a/pkg/cmd/pr/list/list_test.go b/pkg/cmd/pr/list/list_test.go index ecd0326b5..6a7c2815a 100644 --- a/pkg/cmd/pr/list/list_test.go +++ b/pkg/cmd/pr/list/list_test.go @@ -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", diff --git a/pkg/cmd/pr/shared/params.go b/pkg/cmd/pr/shared/params.go index c3315aeae..742b3795b 100644 --- a/pkg/cmd/pr/shared/params.go +++ b/pkg/cmd/pr/shared/params.go @@ -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 { diff --git a/pkg/cmd/pr/shared/params_test.go b/pkg/cmd/pr/shared/params_test.go index 3c95a9a5d..5cc73bc2f 100644 --- a/pkg/cmd/pr/shared/params_test.go +++ b/pkg/cmd/pr/shared/params_test.go @@ -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