Merge pull request #4316 from SiarheiFedartsou/sf-pulls-draft
Add `--draft` and `--non-draft` filters to `gh pr list`
This commit is contained in:
commit
02ed5a9666
6 changed files with 131 additions and 27 deletions
|
|
@ -10,8 +10,12 @@ import (
|
||||||
"github.com/cli/cli/v2/pkg/githubsearch"
|
"github.com/cli/cli/v2/pkg/githubsearch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func shouldUseSearch(filters prShared.FilterOptions) bool {
|
||||||
|
return filters.Draft != "" || 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, repo ghrepo.Interface, filters prShared.FilterOptions, limit int) (*api.PullRequestAndTotalCount, error) {
|
||||||
if filters.Author != "" || filters.Assignee != "" || filters.Search != "" || len(filters.Labels) > 0 {
|
if shouldUseSearch(filters) {
|
||||||
return searchPullRequests(httpClient, repo, filters, limit)
|
return searchPullRequests(httpClient, repo, filters, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -177,6 +181,10 @@ func searchPullRequests(httpClient *http.Client, repo ghrepo.Interface, filters
|
||||||
q.SetBaseBranch(filters.BaseBranch)
|
q.SetBaseBranch(filters.BaseBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if filters.Draft != "" {
|
||||||
|
q.SetDraft(filters.Draft)
|
||||||
|
}
|
||||||
|
|
||||||
pageLimit := min(limit, 100)
|
pageLimit := min(limit, 100)
|
||||||
variables := map[string]interface{}{
|
variables := map[string]interface{}{
|
||||||
"q": q.String(),
|
"q": q.String(),
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ type ListOptions struct {
|
||||||
Author string
|
Author string
|
||||||
Assignee string
|
Assignee string
|
||||||
Search string
|
Search string
|
||||||
|
Draft string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
||||||
|
|
@ -46,6 +47,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
|
||||||
Browser: f.Browser,
|
Browser: f.Browser,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var draft bool
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "list",
|
Use: "list",
|
||||||
Short: "List and filter pull requests in this repository",
|
Short: "List and filter pull requests in this repository",
|
||||||
|
|
@ -74,6 +77,10 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
|
||||||
return &cmdutil.FlagError{Err: fmt.Errorf("invalid value for --limit: %v", opts.LimitResults)}
|
return &cmdutil.FlagError{Err: fmt.Errorf("invalid value for --limit: %v", opts.LimitResults)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cmd.Flags().Changed("draft") {
|
||||||
|
opts.Draft = strconv.FormatBool(draft)
|
||||||
|
}
|
||||||
|
|
||||||
if runF != nil {
|
if runF != nil {
|
||||||
return runF(opts)
|
return runF(opts)
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +99,8 @@ func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Comman
|
||||||
cmd.Flags().StringVarP(&opts.Author, "author", "A", "", "Filter by author")
|
cmd.Flags().StringVarP(&opts.Author, "author", "A", "", "Filter by author")
|
||||||
cmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", "Filter by assignee")
|
cmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", "Filter by assignee")
|
||||||
cmd.Flags().StringVarP(&opts.Search, "search", "S", "", "Search pull requests with `query`")
|
cmd.Flags().StringVarP(&opts.Search, "search", "S", "", "Search pull requests with `query`")
|
||||||
|
cmd.Flags().BoolVarP(&draft, "draft", "d", false, "Filter by draft state")
|
||||||
|
|
||||||
cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.PullRequestFields)
|
cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.PullRequestFields)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
@ -132,12 +141,12 @@ func listRun(opts *ListOptions) error {
|
||||||
Labels: opts.Labels,
|
Labels: opts.Labels,
|
||||||
BaseBranch: opts.BaseBranch,
|
BaseBranch: opts.BaseBranch,
|
||||||
Search: opts.Search,
|
Search: opts.Search,
|
||||||
|
Draft: opts.Draft,
|
||||||
Fields: defaultFields,
|
Fields: defaultFields,
|
||||||
}
|
}
|
||||||
if opts.Exporter != nil {
|
if opts.Exporter != nil {
|
||||||
filters.Fields = opts.Exporter.Fields()
|
filters.Fields = opts.Exporter.Fields()
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.WebMode {
|
if opts.WebMode {
|
||||||
prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls")
|
prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls")
|
||||||
openURL, err := shared.ListURLWithQuery(prListURL, filters)
|
openURL, err := shared.ListURLWithQuery(prListURL, filters)
|
||||||
|
|
|
||||||
|
|
@ -176,39 +176,89 @@ func TestPRList_filteringAssignee(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRList_filteringAssigneeLabels(t *testing.T) {
|
func TestPRList_filteringDraft(t *testing.T) {
|
||||||
http := initFakeHTTP()
|
tests := []struct {
|
||||||
defer http.Verify(t)
|
name string
|
||||||
|
cli string
|
||||||
|
expectedQuery string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "draft",
|
||||||
|
cli: "--draft",
|
||||||
|
expectedQuery: `repo:OWNER/REPO is:pr is:open draft:true`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-draft",
|
||||||
|
cli: "--draft=false",
|
||||||
|
expectedQuery: `repo:OWNER/REPO is:pr is:open draft:false`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
_, err := runCommand(http, true, `-l one,two -a hubot`)
|
for _, test := range tests {
|
||||||
if err == nil && err.Error() != "multiple labels with --assignee are not supported" {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
t.Fatal(err)
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
|
http.Register(
|
||||||
|
httpmock.GraphQL(`query PullRequestSearch\b`),
|
||||||
|
httpmock.GraphQLQuery(`{}`, func(_ string, params map[string]interface{}) {
|
||||||
|
assert.Equal(t, test.expectedQuery, params["q"].(string))
|
||||||
|
}))
|
||||||
|
|
||||||
|
_, err := runCommand(http, true, test.cli)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRList_withInvalidLimitFlag(t *testing.T) {
|
func TestPRList_withInvalidLimitFlag(t *testing.T) {
|
||||||
http := initFakeHTTP()
|
http := initFakeHTTP()
|
||||||
defer http.Verify(t)
|
defer http.Verify(t)
|
||||||
|
|
||||||
_, err := runCommand(http, true, `--limit=0`)
|
_, err := runCommand(http, true, `--limit=0`)
|
||||||
if err == nil && err.Error() != "invalid limit: 0" {
|
assert.EqualError(t, err, "invalid value for --limit: 0")
|
||||||
t.Errorf("error running command `issue list`: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPRList_web(t *testing.T) {
|
func TestPRList_web(t *testing.T) {
|
||||||
http := initFakeHTTP()
|
tests := []struct {
|
||||||
defer http.Verify(t)
|
name string
|
||||||
|
cli string
|
||||||
_, cmdTeardown := run.Stub()
|
expectedBrowserURL string
|
||||||
defer cmdTeardown(t)
|
}{
|
||||||
|
{
|
||||||
output, err := runCommand(http, true, "--web -a peter -l bug -l docs -L 10 -s merged -B trunk")
|
name: "filters",
|
||||||
if err != nil {
|
cli: "-a peter -l bug -l docs -L 10 -s merged -B trunk",
|
||||||
t.Errorf("error running command `pr list` with `--web` flag: %v", err)
|
expectedBrowserURL: "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Amerged+assignee%3Apeter+label%3Abug+label%3Adocs+base%3Atrunk",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "draft",
|
||||||
|
cli: "--draft=true",
|
||||||
|
expectedBrowserURL: "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Aopen+draft%3Atrue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-draft",
|
||||||
|
cli: "--draft=0",
|
||||||
|
expectedBrowserURL: "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Aopen+draft%3Afalse",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, "", output.String())
|
for _, test := range tests {
|
||||||
assert.Equal(t, "Opening github.com/OWNER/REPO/pulls in your browser.\n", output.Stderr())
|
t.Run(test.name, func(t *testing.T) {
|
||||||
assert.Equal(t, "https://github.com/OWNER/REPO/pulls?q=is%3Apr+is%3Amerged+assignee%3Apeter+label%3Abug+label%3Adocs+base%3Atrunk", output.BrowsedURL)
|
http := initFakeHTTP()
|
||||||
|
defer http.Verify(t)
|
||||||
|
|
||||||
|
_, cmdTeardown := run.Stub()
|
||||||
|
defer cmdTeardown(t)
|
||||||
|
|
||||||
|
output, err := runCommand(http, true, "--web "+test.cli)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("error running command `pr list` with `--web` flag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "", output.String())
|
||||||
|
assert.Equal(t, "Opening github.com/OWNER/REPO/pulls in your browser.\n", output.Stderr())
|
||||||
|
assert.Equal(t, test.expectedBrowserURL, output.BrowsedURL)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,8 +157,8 @@ type FilterOptions struct {
|
||||||
Mention string
|
Mention string
|
||||||
Milestone string
|
Milestone string
|
||||||
Search string
|
Search string
|
||||||
|
Draft string
|
||||||
Fields []string
|
Fields []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *FilterOptions) IsDefault() bool {
|
func (opts *FilterOptions) IsDefault() bool {
|
||||||
|
|
@ -241,7 +241,9 @@ func SearchQueryBuild(options FilterOptions) string {
|
||||||
if options.Search != "" {
|
if options.Search != "" {
|
||||||
q.AddQuery(options.Search)
|
q.AddQuery(options.Search)
|
||||||
}
|
}
|
||||||
|
if options.Draft != "" {
|
||||||
|
q.SetDraft(options.Draft)
|
||||||
|
}
|
||||||
return q.String()
|
return q.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ func Test_listURLWithQuery(t *testing.T) {
|
||||||
listURL string
|
listURL string
|
||||||
options FilterOptions
|
options FilterOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
args args
|
args args
|
||||||
|
|
@ -34,6 +35,32 @@ func Test_listURLWithQuery(t *testing.T) {
|
||||||
want: "https://example.com/path?a=b&q=is%3Aissue+is%3Aopen",
|
want: "https://example.com/path?a=b&q=is%3Aissue+is%3Aopen",
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "draft",
|
||||||
|
args: args{
|
||||||
|
listURL: "https://example.com/path",
|
||||||
|
options: FilterOptions{
|
||||||
|
Entity: "pr",
|
||||||
|
State: "open",
|
||||||
|
Draft: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://example.com/path?q=is%3Apr+is%3Aopen+draft%3Atrue",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "non-draft",
|
||||||
|
args: args{
|
||||||
|
listURL: "https://example.com/path",
|
||||||
|
options: FilterOptions{
|
||||||
|
Entity: "pr",
|
||||||
|
State: "open",
|
||||||
|
Draft: "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "https://example.com/path?q=is%3Apr+is%3Aopen+draft%3Afalse",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "all",
|
name: "all",
|
||||||
args: args{
|
args: args{
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ type Query struct {
|
||||||
forkState string
|
forkState string
|
||||||
visibility string
|
visibility string
|
||||||
isArchived *bool
|
isArchived *bool
|
||||||
|
draft string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Query) InRepository(nameWithOwner string) {
|
func (q *Query) InRepository(nameWithOwner string) {
|
||||||
|
|
@ -139,6 +140,10 @@ func (q *Query) SetArchived(isArchived bool) {
|
||||||
q.isArchived = &isArchived
|
q.isArchived = &isArchived
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Query) SetDraft(draft string) {
|
||||||
|
q.draft = draft
|
||||||
|
}
|
||||||
|
|
||||||
func (q *Query) String() string {
|
func (q *Query) String() string {
|
||||||
var qs string
|
var qs string
|
||||||
|
|
||||||
|
|
@ -198,6 +203,9 @@ func (q *Query) String() string {
|
||||||
if q.headBranch != "" {
|
if q.headBranch != "" {
|
||||||
qs += fmt.Sprintf("head:%s ", quote(q.headBranch))
|
qs += fmt.Sprintf("head:%s ", quote(q.headBranch))
|
||||||
}
|
}
|
||||||
|
if q.draft != "" {
|
||||||
|
qs += fmt.Sprintf("draft:%v ", q.draft)
|
||||||
|
}
|
||||||
|
|
||||||
if q.sort != "" {
|
if q.sort != "" {
|
||||||
qs += fmt.Sprintf("sort:%s ", q.sort)
|
qs += fmt.Sprintf("sort:%s ", q.sort)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue