github.com/ungtb10d/cli/v2@v2.0.0-20221110210412-98537dd9d6a1/pkg/cmd/pr/list/list.go (about)

     1  package list
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/MakeNowJust/heredoc"
    11  	"github.com/ungtb10d/cli/v2/api"
    12  	"github.com/ungtb10d/cli/v2/internal/browser"
    13  	"github.com/ungtb10d/cli/v2/internal/ghrepo"
    14  	"github.com/ungtb10d/cli/v2/internal/text"
    15  	"github.com/ungtb10d/cli/v2/pkg/cmd/pr/shared"
    16  	"github.com/ungtb10d/cli/v2/pkg/cmdutil"
    17  	"github.com/ungtb10d/cli/v2/pkg/iostreams"
    18  	"github.com/ungtb10d/cli/v2/utils"
    19  	"github.com/spf13/cobra"
    20  )
    21  
    22  type ListOptions struct {
    23  	HttpClient func() (*http.Client, error)
    24  	IO         *iostreams.IOStreams
    25  	BaseRepo   func() (ghrepo.Interface, error)
    26  	Browser    browser.Browser
    27  
    28  	WebMode      bool
    29  	LimitResults int
    30  	Exporter     cmdutil.Exporter
    31  
    32  	State      string
    33  	BaseBranch string
    34  	HeadBranch string
    35  	Labels     []string
    36  	Author     string
    37  	Assignee   string
    38  	Search     string
    39  	Draft      *bool
    40  
    41  	Now func() time.Time
    42  }
    43  
    44  func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
    45  	opts := &ListOptions{
    46  		IO:         f.IOStreams,
    47  		HttpClient: f.HttpClient,
    48  		Browser:    f.Browser,
    49  		Now:        time.Now,
    50  	}
    51  
    52  	var appAuthor string
    53  
    54  	cmd := &cobra.Command{
    55  		Use:   "list",
    56  		Short: "List pull requests in a repository",
    57  		Long: heredoc.Doc(`
    58  			List pull requests in a GitHub repository.
    59  
    60  			The search query syntax is documented here:
    61  			<https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests>
    62  		`),
    63  		Example: heredoc.Doc(`
    64  			List PRs authored by you
    65  			$ gh pr list --author "@me"
    66  
    67  			List only PRs with all of the given labels
    68  			$ gh pr list --label bug --label "priority 1"
    69  
    70  			Filter PRs using search syntax
    71  			$ gh pr list --search "status:success review:required"
    72  
    73  			Find a PR that introduced a given commit
    74  			$ gh pr list --search "<SHA>" --state merged
    75      	`),
    76  		Aliases: []string{"ls"},
    77  		Args:    cmdutil.NoArgsQuoteReminder,
    78  		RunE: func(cmd *cobra.Command, args []string) error {
    79  			// support `-R, --repo` override
    80  			opts.BaseRepo = f.BaseRepo
    81  
    82  			if opts.LimitResults < 1 {
    83  				return cmdutil.FlagErrorf("invalid value for --limit: %v", opts.LimitResults)
    84  			}
    85  
    86  			if cmd.Flags().Changed("author") && cmd.Flags().Changed("app") {
    87  				return cmdutil.FlagErrorf("specify only `--author` or `--app`")
    88  			}
    89  
    90  			if cmd.Flags().Changed("app") {
    91  				opts.Author = fmt.Sprintf("app/%s", appAuthor)
    92  			}
    93  
    94  			if runF != nil {
    95  				return runF(opts)
    96  			}
    97  			return listRun(opts)
    98  		},
    99  	}
   100  
   101  	cmd.Flags().BoolVarP(&opts.WebMode, "web", "w", false, "List pull requests in the web browser")
   102  	cmd.Flags().IntVarP(&opts.LimitResults, "limit", "L", 30, "Maximum number of items to fetch")
   103  	cmdutil.StringEnumFlag(cmd, &opts.State, "state", "s", "open", []string{"open", "closed", "merged", "all"}, "Filter by state")
   104  	cmd.Flags().StringVarP(&opts.BaseBranch, "base", "B", "", "Filter by base branch")
   105  	cmd.Flags().StringVarP(&opts.HeadBranch, "head", "H", "", "Filter by head branch")
   106  	cmd.Flags().StringSliceVarP(&opts.Labels, "label", "l", nil, "Filter by label")
   107  	cmd.Flags().StringVarP(&opts.Author, "author", "A", "", "Filter by author")
   108  	cmd.Flags().StringVar(&appAuthor, "app", "", "Filter by GitHub App author")
   109  	cmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", "Filter by assignee")
   110  	cmd.Flags().StringVarP(&opts.Search, "search", "S", "", "Search pull requests with `query`")
   111  	cmdutil.NilBoolFlag(cmd, &opts.Draft, "draft", "d", "Filter by draft state")
   112  
   113  	cmdutil.AddJSONFlags(cmd, &opts.Exporter, api.PullRequestFields)
   114  
   115  	return cmd
   116  }
   117  
   118  var defaultFields = []string{
   119  	"number",
   120  	"title",
   121  	"state",
   122  	"url",
   123  	"headRefName",
   124  	"headRepositoryOwner",
   125  	"isCrossRepository",
   126  	"isDraft",
   127  	"createdAt",
   128  }
   129  
   130  func listRun(opts *ListOptions) error {
   131  	httpClient, err := opts.HttpClient()
   132  	if err != nil {
   133  		return err
   134  	}
   135  
   136  	baseRepo, err := opts.BaseRepo()
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	prState := strings.ToLower(opts.State)
   142  	if prState == "open" && shared.QueryHasStateClause(opts.Search) {
   143  		prState = ""
   144  	}
   145  
   146  	filters := shared.FilterOptions{
   147  		Entity:     "pr",
   148  		State:      prState,
   149  		Author:     opts.Author,
   150  		Assignee:   opts.Assignee,
   151  		Labels:     opts.Labels,
   152  		BaseBranch: opts.BaseBranch,
   153  		HeadBranch: opts.HeadBranch,
   154  		Search:     opts.Search,
   155  		Draft:      opts.Draft,
   156  		Fields:     defaultFields,
   157  	}
   158  	if opts.Exporter != nil {
   159  		filters.Fields = opts.Exporter.Fields()
   160  	}
   161  	if opts.WebMode {
   162  		prListURL := ghrepo.GenerateRepoURL(baseRepo, "pulls")
   163  		openURL, err := shared.ListURLWithQuery(prListURL, filters)
   164  		if err != nil {
   165  			return err
   166  		}
   167  
   168  		if opts.IO.IsStdoutTTY() {
   169  			fmt.Fprintf(opts.IO.ErrOut, "Opening %s in your browser.\n", text.DisplayURL(openURL))
   170  		}
   171  		return opts.Browser.Browse(openURL)
   172  	}
   173  
   174  	listResult, err := listPullRequests(httpClient, baseRepo, filters, opts.LimitResults)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if len(listResult.PullRequests) == 0 && opts.Exporter == nil {
   179  		return shared.ListNoResults(ghrepo.FullName(baseRepo), "pull request", !filters.IsDefault())
   180  	}
   181  
   182  	err = opts.IO.StartPager()
   183  	if err != nil {
   184  		fmt.Fprintf(opts.IO.ErrOut, "error starting pager: %v\n", err)
   185  	}
   186  	defer opts.IO.StopPager()
   187  
   188  	if opts.Exporter != nil {
   189  		return opts.Exporter.Write(opts.IO, listResult.PullRequests)
   190  	}
   191  
   192  	if listResult.SearchCapped {
   193  		fmt.Fprintln(opts.IO.ErrOut, "warning: this query uses the Search API which is capped at 1000 results maximum")
   194  	}
   195  	if opts.IO.IsStdoutTTY() {
   196  		title := shared.ListHeader(ghrepo.FullName(baseRepo), "pull request", len(listResult.PullRequests), listResult.TotalCount, !filters.IsDefault())
   197  		fmt.Fprintf(opts.IO.Out, "\n%s\n\n", title)
   198  	}
   199  
   200  	cs := opts.IO.ColorScheme()
   201  	//nolint:staticcheck // SA1019: utils.NewTablePrinter is deprecated: use internal/tableprinter
   202  	table := utils.NewTablePrinter(opts.IO)
   203  	for _, pr := range listResult.PullRequests {
   204  		prNum := strconv.Itoa(pr.Number)
   205  		if table.IsTTY() {
   206  			prNum = "#" + prNum
   207  		}
   208  
   209  		table.AddField(prNum, nil, cs.ColorFromString(shared.ColorForPRState(pr)))
   210  		table.AddField(text.RemoveExcessiveWhitespace(pr.Title), nil, nil)
   211  		table.AddField(pr.HeadLabel(), nil, cs.Cyan)
   212  		if !table.IsTTY() {
   213  			table.AddField(prStateWithDraft(&pr), nil, nil)
   214  		}
   215  		if table.IsTTY() {
   216  			table.AddField(text.FuzzyAgo(opts.Now(), pr.CreatedAt), nil, cs.Gray)
   217  		} else {
   218  			table.AddField(pr.CreatedAt.String(), nil, nil)
   219  		}
   220  		table.EndRow()
   221  	}
   222  	err = table.Render()
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func prStateWithDraft(pr *api.PullRequest) string {
   231  	if pr.IsDraft && pr.State == "OPEN" {
   232  		return "DRAFT"
   233  	}
   234  
   235  	return pr.State
   236  }