cli/commands/issuable/list/issuable_list.go

297 lines
8.6 KiB
Go

package list
import (
"encoding/json"
"errors"
"fmt"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/MakeNowJust/heredoc"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/commands/flag"
"gitlab.com/gitlab-org/cli/commands/issuable"
"gitlab.com/gitlab-org/cli/commands/issue/issueutils"
"gitlab.com/gitlab-org/cli/pkg/utils"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
)
type ListOptions struct {
// metadata
Assignee string
NotAssignee []string
Author string
NotAuthor []string
Labels []string
NotLabels []string
Milestone string
Mine bool
Search string
Group string
IssueType string
// issue states
State string
Closed bool
Opened bool
All bool
Confidential bool
// Pagination
Page int
PerPage int
// Other
In string
// display opts
ListType string
TitleQualifier string
OutputFormat string
Output string
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
HTTPClient func() (*gitlab.Client, error)
JSONOutput bool
}
func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error, issueType issuable.IssueType) *cobra.Command {
opts := &ListOptions{
IO: f.IO,
IssueType: string(issueType),
}
issueListCmd := &cobra.Command{
Use: "list [flags]",
Short: fmt.Sprintf(`List project %ss`, issueType),
Long: ``,
Aliases: []string{"ls"},
Example: heredoc.Doc(fmt.Sprintf(`
glab %[1]s list --all
glab %[1]s ls --all
glab %[1]s list --assignee=@me
glab %[1]s list --milestone release-2.0.0 --opened
`, issueType)),
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
// support repo override
opts.BaseRepo = f.BaseRepo
opts.HTTPClient = f.HttpClient
if len(opts.Labels) != 0 && len(opts.NotLabels) != 0 {
return cmdutils.FlagError{
Err: errors.New("flags --label and --not-label are mutually exclusive"),
}
}
if opts.Author != "" && len(opts.NotAuthor) != 0 {
return cmdutils.FlagError{
Err: errors.New("flags --author and --not-author are mutually exclusive"),
}
}
if opts.Assignee != "" && len(opts.NotAssignee) != 0 {
return cmdutils.FlagError{
Err: errors.New("flags --assignee and --not-assignee are mutually exclusive"),
}
}
if opts.All {
opts.State = "all"
} else if opts.Closed {
opts.State = "closed"
opts.TitleQualifier = "closed"
} else {
opts.State = "opened"
opts.TitleQualifier = "open"
}
group, err := flag.GroupOverride(cmd)
if err != nil {
return err
}
opts.Group = group
if runE != nil {
return runE(opts)
}
return listRun(opts)
},
}
cmdutils.EnableRepoOverride(issueListCmd, f)
issueListCmd.Flags().StringVarP(&opts.Assignee, "assignee", "a", "", fmt.Sprintf("Filter %s by assignee <username>", issueType))
issueListCmd.Flags().StringSliceVar(&opts.NotAssignee, "not-assignee", []string{}, fmt.Sprintf("Filter %s by not being assigneed to <username>", issueType))
issueListCmd.Flags().StringVar(&opts.Author, "author", "", fmt.Sprintf("Filter %s by author <username>", issueType))
issueListCmd.Flags().StringSliceVar(&opts.NotAuthor, "not-author", []string{}, "Filter by not being by author(s) <username>")
issueListCmd.Flags().StringVar(&opts.Search, "search", "", "Search <string> in the fields defined by --in")
issueListCmd.Flags().StringVar(&opts.In, "in", "title,description", "search in {title|description}")
issueListCmd.Flags().StringSliceVarP(&opts.Labels, "label", "l", []string{}, fmt.Sprintf("Filter %s by label <name>", issueType))
issueListCmd.Flags().StringSliceVar(&opts.NotLabels, "not-label", []string{}, fmt.Sprintf("Filter %s by lack of label <name>", issueType))
issueListCmd.Flags().StringVarP(&opts.Milestone, "milestone", "m", "", fmt.Sprintf("Filter %s by milestone <id>", issueType))
issueListCmd.Flags().BoolVarP(&opts.All, "all", "A", false, fmt.Sprintf("Get all %ss", issueType))
issueListCmd.Flags().BoolVarP(&opts.Closed, "closed", "c", false, fmt.Sprintf("Get only closed %ss", issueType))
issueListCmd.Flags().BoolVarP(&opts.Confidential, "confidential", "C", false, fmt.Sprintf("Filter by confidential %ss", issueType))
issueListCmd.Flags().StringVarP(&opts.OutputFormat, "output-format", "F", "details", "One of 'details', 'ids', 'urls'")
issueListCmd.Flags().StringVarP(&opts.Output, "output", "O", "text", "One of 'text' or 'json'")
issueListCmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number")
issueListCmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 30, "Number of items to list per page.")
issueListCmd.PersistentFlags().StringP("group", "g", "", "Select a group/subgroup. This option is ignored if a repo argument is set.")
issueListCmd.MarkFlagsMutuallyExclusive("output", "output-format")
if issueType == issuable.TypeIssue {
issueListCmd.Flags().StringVarP(&opts.IssueType, "issue-type", "t", "", "Filter issue by its type {issue|incident|test_case}")
}
issueListCmd.Flags().BoolP("opened", "o", false, fmt.Sprintf("Get only open %ss", issueType))
_ = issueListCmd.Flags().MarkHidden("opened")
_ = issueListCmd.Flags().MarkDeprecated("opened", "default if --closed is not used")
issueListCmd.Flags().BoolVarP(&opts.Mine, "mine", "M", false, fmt.Sprintf("Filter only %ss assigned to me", issueType))
_ = issueListCmd.Flags().MarkHidden("mine")
_ = issueListCmd.Flags().MarkDeprecated("mine", "use --assignee=@me")
return issueListCmd
}
func listRun(opts *ListOptions) error {
apiClient, err := opts.HTTPClient()
if err != nil {
return err
}
repo, err := opts.BaseRepo()
if err != nil {
return err
}
listOpts := &gitlab.ListProjectIssuesOptions{
State: gitlab.Ptr(opts.State),
In: gitlab.Ptr(opts.In),
}
listOpts.Page = 1
listOpts.PerPage = 30
if opts.Assignee != "" || opts.Mine {
if opts.Assignee == "@me" || opts.Mine {
u, err := api.CurrentUser(nil)
if err != nil {
return err
}
opts.Assignee = u.Username
}
listOpts.AssigneeUsername = gitlab.Ptr(opts.Assignee)
}
if len(opts.NotAssignee) != 0 {
u, err := api.UsersByNames(apiClient, opts.NotAssignee)
if err != nil {
return err
}
listOpts.NotAssigneeID = cmdutils.IDsFromUsers(u)
}
if opts.Author != "" {
u, err := api.UserByName(apiClient, opts.Author)
if err != nil {
return err
}
listOpts.AuthorID = gitlab.Ptr(u.ID)
}
if len(opts.NotAuthor) != 0 {
u, err := api.UsersByNames(apiClient, opts.NotAuthor)
if err != nil {
return err
}
listOpts.NotAuthorID = cmdutils.IDsFromUsers(u)
}
if opts.Search != "" {
listOpts.Search = gitlab.Ptr(opts.Search)
opts.ListType = "search"
}
if len(opts.Labels) != 0 {
listOpts.Labels = (*gitlab.LabelOptions)(&opts.Labels)
opts.ListType = "search"
}
if len(opts.NotLabels) != 0 {
listOpts.NotLabels = (*gitlab.LabelOptions)(&opts.NotLabels)
opts.ListType = "search"
}
if opts.Milestone != "" {
listOpts.Milestone = gitlab.Ptr(opts.Milestone)
opts.ListType = "search"
}
if opts.Confidential {
listOpts.Confidential = gitlab.Ptr(opts.Confidential)
opts.ListType = "search"
}
if opts.Page != 0 {
listOpts.Page = opts.Page
opts.ListType = "search"
}
if opts.PerPage != 0 {
listOpts.PerPage = opts.PerPage
opts.ListType = "search"
}
issueType := "issue"
if opts.IssueType != "" {
listOpts.IssueType = gitlab.Ptr(opts.IssueType)
opts.ListType = "search"
issueType = opts.IssueType
}
var issues []*gitlab.Issue
title := utils.NewListTitle(fmt.Sprintf("%s %s", opts.TitleQualifier, issueType))
title.RepoName = repo.FullName()
if opts.Group != "" {
issues, err = api.ListGroupIssues(apiClient, opts.Group, api.ProjectListIssueOptionsToGroup(listOpts))
if err != nil {
return err
}
title.RepoName = opts.Group
} else {
issues, err = api.ListIssues(apiClient, repo.FullName(), listOpts)
if err != nil {
return err
}
}
title.Page = listOpts.Page
title.ListActionType = opts.ListType
title.CurrentPageTotal = len(issues)
if opts.Output == "json" {
issueListJSON, _ := json.Marshal(issues)
fmt.Fprintln(opts.IO.StdOut, string(issueListJSON))
return nil
}
if opts.OutputFormat == "ids" {
for _, i := range issues {
fmt.Fprintf(opts.IO.StdOut, "%d\n", i.IID)
}
return nil
}
if opts.OutputFormat == "urls" {
for _, i := range issues {
fmt.Fprintf(opts.IO.StdOut, "%s\n", i.WebURL)
}
return nil
}
if opts.IO.StartPager() != nil {
return fmt.Errorf("failed to start pager: %q", err)
}
defer opts.IO.StopPager()
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), issueutils.DisplayIssueList(opts.IO, issues, repo.FullName()))
return nil
}