github.com/cli/cli@v1.14.1-0.20210902173923-1af6a669e342/pkg/cmd/pr/list/list.go (about)

     1  package list
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  
     9  	"github.com/MakeNowJust/heredoc"
    10  	"github.com/cli/cli/api"
    11  	"github.com/cli/cli/internal/ghrepo"
    12  	"github.com/cli/cli/pkg/cmd/pr/shared"
    13  	"github.com/cli/cli/pkg/cmdutil"
    14  	"github.com/cli/cli/pkg/iostreams"
    15  	"github.com/cli/cli/pkg/text"
    16  	"github.com/cli/cli/utils"
    17  	"github.com/spf13/cobra"
    18  )
    19  
    20  type browser interface {
    21  	Browse(string) error
    22  }
    23  
    24  type ListOptions struct {
    25  	HttpClient func() (*http.Client, error)
    26  	IO         *iostreams.IOStreams
    27  	BaseRepo   func() (ghrepo.Interface, error)
    28  	Browser    browser
    29  
    30  	WebMode      bool
    31  	LimitResults int
    32  	Exporter     cmdutil.Exporter
    33  
    34  	State      string
    35  	BaseBranch string
    36  	Labels     []string
    37  	Author     string
    38  	Assignee   string
    39  	Search     string
    40  }
    41  
    42  func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
    43  	opts := &ListOptions{
    44  		IO:         f.IOStreams,
    45  		HttpClient: f.HttpClient,
    46  		Browser:    f.Browser,
    47  	}
    48  
    49  	cmd := &cobra.Command{
    50  		Use:   "list",
    51  		Short: "List and filter pull requests in this repository",
    52  		Example: heredoc.Doc(`
    53  			List PRs authored by you
    54  			$ gh pr list --author "@me"
    55  
    56  			List PRs assigned to you
    57  			$ gh pr list --assignee "@me"
    58  
    59  			List PRs by label, combining multiple labels with AND
    60  			$ gh pr list --label bug --label "priority 1"
    61  
    62  			List PRs using search syntax
    63  			$ gh pr list --search "status:success review:required"
    64  
    65  			Open the list of PRs in a web browser
    66  			$ gh pr list --web
    67      	`),
    68  		Args: cmdutil.NoArgsQuoteReminder,
    69  		RunE: func(cmd *cobra.Command, args []string) error {
    70  			// support `-R, --repo` override
    71  			opts.BaseRepo = f.BaseRepo
    72  
    73  			if opts.LimitResults < 1 {
    74  				return &cmdutil.FlagError{Err: fmt.Errorf("invalid value for --limit: %v", opts.LimitResults)}
    75  			}
    76  
    77  			if runF != nil {
    78  				return runF(opts)
    79  			}
    80  			return listRun(opts)
    81  		},
    82  	}
    83  
    84  	cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "Open the browser to list the pull requests")
    85  	cmd.Flags().IntVarP(&opts.LimitResults, "limit", "L", 30, "Maximum number of items to fetch")
    86  	cmd.Flags().StringVarP(&opts.State, "state", "s", "open", "Filter by state: {open|closed|merged|all}")
    87  	_ = cmd.RegisterFlagCompletionFunc("state", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    88  		return []string{"open", "closed", "merged", "all"}, cobra.ShellCompDirectiveNoFileComp
    89  	})
    90  	cmd.Flags().StringVarP(&opts.BaseBranch, "base", "B", "", "Filter by base branch")
    91  	cmd.Flags().StringSliceVarP(&opts.Labels, "label", "l", nil, "Filter by labels")
    92  	cmd.Flags().StringVarP(&opts.Author, "author", "A", "", "Filter by author")
    93  	cmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", "Filter by assignee")
    94  	cmd.Flags().StringVarP(&opts.Search, "search", "S", "", "Search pull requests with `query`")
    95  	cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.PullRequestFields)
    96  
    97  	return cmd
    98  }
    99  
   100  var defaultFields = []string{
   101  	"number",
   102  	"title",
   103  	"state",
   104  	"url",
   105  	"headRefName",
   106  	"headRepositoryOwner",
   107  	"isCrossRepository",
   108  	"isDraft",
   109  }
   110  
   111  func listRun(opts *ListOptions) error {
   112  	httpClient, err := opts.HttpClient()
   113  	if err != nil {
   114  		return err
   115  	}
   116  
   117  	baseRepo, err := opts.BaseRepo()
   118  	if err != nil {
   119  		return err
   120  	}
   121  
   122  	prState := strings.ToLower(opts.State)
   123  	if prState == "open" && shared.QueryHasStateClause(opts.Search) {
   124  		prState = ""
   125  	}
   126  
   127  	filters := shared.FilterOptions{
   128  		Entity:     "pr",
   129  		State:      prState,
   130  		Author:     opts.Author,
   131  		Assignee:   opts.Assignee,
   132  		Labels:     opts.Labels,
   133  		BaseBranch: opts.BaseBranch,
   134  		Search:     opts.Search,
   135  		Fields:     defaultFields,
   136  	}
   137  	if opts.Exporter != nil {
   138  		filters.Fields = opts.Exporter.Fields()
   139  	}
   140  
   141  	if opts.WebMode {
   142  		prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls")
   143  		openURL, err := shared.ListURLWithQuery(prListURL, filters)
   144  		if err != nil {
   145  			return err
   146  		}
   147  
   148  		if opts.IO.IsStdoutTTY() {
   149  			fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", utils.DisplayURL(openURL))
   150  		}
   151  		return opts.Browser.Browse(openURL)
   152  	}
   153  
   154  	listResult, err := listPullRequests(httpClient, baseRepo, filters, opts.LimitResults)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	err = opts.IO.StartPager()
   160  	if err != nil {
   161  		fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err)
   162  	}
   163  	defer opts.IO.StopPager()
   164  
   165  	if opts.Exporter != nil {
   166  		return opts.Exporter.Write(opts.IO, listResult.PullRequests)
   167  	}
   168  
   169  	if opts.IO.IsStdoutTTY() {
   170  		title := shared.ListHeader(ghrepo.FullName(baseRepo), "pull request", len(listResult.PullRequests), listResult.TotalCount, !filters.IsDefault())
   171  		fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title)
   172  	}
   173  
   174  	cs := opts.IO.ColorScheme()
   175  	table := utils.NewTablePrinter(opts.IO)
   176  	for _, pr := range listResult.PullRequests {
   177  		prNum := strconv.Itoa(pr.Number)
   178  		if table.IsTTY() {
   179  			prNum = "#" + prNum
   180  		}
   181  		table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPR(pr)))
   182  		table.AddField(text.ReplaceExcessiveWhitespace(pr.Title), nil, nil)
   183  		table.AddField(pr.HeadLabel(), nil, cs.Cyan)
   184  		if !table.IsTTY() {
   185  			table.AddField(prStateWithDraft(&pr), nil, nil)
   186  		}
   187  		table.EndRow()
   188  	}
   189  	err = table.Render()
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	return nil
   195  }
   196  
   197  func prStateWithDraft(pr *api.PullRequest) string {
   198  	if pr.IsDraft && pr.State == "OPEN" {
   199  		return "DRAFT"
   200  	}
   201  
   202  	return pr.State
   203  }