190 lines
5.7 KiB
Go
190 lines
5.7 KiB
Go
package list
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/MakeNowJust/heredoc"
|
|
|
|
"github.com/cli/cli/v2/api"
|
|
"github.com/cli/cli/v2/internal/ghrepo"
|
|
"github.com/cli/cli/v2/internal/tableprinter"
|
|
"github.com/cli/cli/v2/pkg/cmd/run/shared"
|
|
workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
|
"github.com/cli/cli/v2/pkg/cmdutil"
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
const (
|
|
defaultLimit = 20
|
|
)
|
|
|
|
type ListOptions struct {
|
|
IO *iostreams.IOStreams
|
|
HttpClient func() (*http.Client, error)
|
|
BaseRepo func() (ghrepo.Interface, error)
|
|
Prompter iprompter
|
|
|
|
Exporter cmdutil.Exporter
|
|
|
|
Limit int
|
|
WorkflowSelector string
|
|
Branch string
|
|
Actor string
|
|
Status string
|
|
Event string
|
|
Created string
|
|
Commit string
|
|
All bool
|
|
|
|
now time.Time
|
|
}
|
|
|
|
type iprompter interface {
|
|
Select(string, string, []string) (int, error)
|
|
}
|
|
|
|
func NewCmdList(f *cmdutil.Factory, runF func(*ListOptions) error) *cobra.Command {
|
|
opts := &ListOptions{
|
|
IO: f.IOStreams,
|
|
HttpClient: f.HttpClient,
|
|
Prompter: f.Prompter,
|
|
now: time.Now(),
|
|
}
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "list",
|
|
Short: "List recent workflow runs",
|
|
Long: heredoc.Docf(`
|
|
List recent workflow runs.
|
|
|
|
Note that providing the %[1]sworkflow_name%[1]s to the %[1]s-w%[1]s flag will not fetch disabled workflows.
|
|
Also pass the %[1]s-a%[1]s flag to fetch disabled workflow runs using the %[1]sworkflow_name%[1]s and the %[1]s-w%[1]s flag.
|
|
|
|
Runs created by organization and enterprise ruleset workflows will not display a workflow name due to GitHub API limitations.
|
|
|
|
To see runs associated with a pull request, users should run %[1]sgh pr checks%[1]s.
|
|
`, "`"),
|
|
Aliases: []string{"ls"},
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
// support `-R, --repo` override
|
|
opts.BaseRepo = f.BaseRepo
|
|
|
|
if opts.Limit < 1 {
|
|
return cmdutil.FlagErrorf("invalid limit: %v", opts.Limit)
|
|
}
|
|
|
|
if runF != nil {
|
|
return runF(opts)
|
|
}
|
|
|
|
return listRun(opts)
|
|
},
|
|
}
|
|
|
|
cmd.Flags().IntVarP(&opts.Limit, "limit", "L", defaultLimit, "Maximum number of runs to fetch")
|
|
cmd.Flags().StringVarP(&opts.WorkflowSelector, "workflow", "w", "", "Filter runs by workflow")
|
|
cmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "Filter runs by branch")
|
|
cmd.Flags().StringVarP(&opts.Actor, "user", "u", "", "Filter runs by user who triggered the run")
|
|
cmd.Flags().StringVarP(&opts.Event, "event", "e", "", "Filter runs by which `event` triggered the run")
|
|
cmd.Flags().StringVarP(&opts.Created, "created", "", "", "Filter runs by the `date` it was created")
|
|
cmd.Flags().StringVarP(&opts.Commit, "commit", "c", "", "Filter runs by the `SHA` of the commit")
|
|
cmd.Flags().BoolVarP(&opts.All, "all", "a", false, "Include disabled workflows")
|
|
cmdutil.StringEnumFlag(cmd, &opts.Status, "status", "s", "", shared.AllStatuses, "Filter runs by status")
|
|
cmdutil.AddJSONFlags(cmd, &opts.Exporter, shared.RunFields)
|
|
|
|
_ = cmdutil.RegisterBranchCompletionFlags(f.GitClient, cmd, "branch")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func listRun(opts *ListOptions) error {
|
|
baseRepo, err := opts.BaseRepo()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to determine base repo: %w", err)
|
|
}
|
|
|
|
c, err := opts.HttpClient()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create http client: %w", err)
|
|
}
|
|
client := api.NewClientFromHTTP(c)
|
|
|
|
filters := &shared.FilterOptions{
|
|
Branch: opts.Branch,
|
|
Actor: opts.Actor,
|
|
Status: opts.Status,
|
|
Event: opts.Event,
|
|
Created: opts.Created,
|
|
Commit: opts.Commit,
|
|
}
|
|
|
|
opts.IO.StartProgressIndicator()
|
|
defer opts.IO.StopProgressIndicator()
|
|
|
|
if opts.WorkflowSelector != "" {
|
|
// initially the workflow state is limited to 'active'
|
|
states := []workflowShared.WorkflowState{workflowShared.Active}
|
|
if opts.All {
|
|
// the all flag tells us to add the remaining workflow states
|
|
// note: this will be incomplete if more workflow states are added to `workflowShared`
|
|
states = append(states, workflowShared.DisabledManually, workflowShared.DisabledInactivity)
|
|
}
|
|
if workflow, err := workflowShared.ResolveWorkflow(opts.Prompter, opts.IO, client, baseRepo, false, opts.WorkflowSelector, states); err == nil {
|
|
filters.WorkflowID = workflow.ID
|
|
filters.WorkflowName = workflow.Name
|
|
} else {
|
|
return err
|
|
}
|
|
}
|
|
runsResult, err := shared.GetRuns(client, baseRepo, filters, opts.Limit)
|
|
opts.IO.StopProgressIndicator()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get runs: %w", err)
|
|
}
|
|
runs := runsResult.WorkflowRuns
|
|
if len(runs) == 0 && opts.Exporter == nil {
|
|
return cmdutil.NewNoResultsError("no runs found")
|
|
}
|
|
|
|
if err := opts.IO.StartPager(); err == nil {
|
|
defer opts.IO.StopPager()
|
|
} else {
|
|
fmt.Fprintf(opts.IO.ErrOut, "failed to start pager: %v\n", err)
|
|
}
|
|
|
|
if opts.Exporter != nil {
|
|
return opts.Exporter.Write(opts.IO, runs)
|
|
}
|
|
|
|
tp := tableprinter.New(opts.IO, tableprinter.WithHeader("STATUS", "TITLE", "WORKFLOW", "BRANCH", "EVENT", "ID", "ELAPSED", "AGE"))
|
|
|
|
cs := opts.IO.ColorScheme()
|
|
|
|
for _, run := range runs {
|
|
if tp.IsTTY() {
|
|
symbol, symbolColor := shared.Symbol(cs, run.Status, run.Conclusion)
|
|
tp.AddField(symbol, tableprinter.WithColor(symbolColor))
|
|
} else {
|
|
tp.AddField(string(run.Status))
|
|
tp.AddField(string(run.Conclusion))
|
|
}
|
|
tp.AddField(run.Title(), tableprinter.WithColor(cs.Bold))
|
|
tp.AddField(run.WorkflowName())
|
|
tp.AddField(run.HeadBranch, tableprinter.WithColor(cs.Bold))
|
|
tp.AddField(string(run.Event))
|
|
tp.AddField(fmt.Sprintf("%d", run.ID), tableprinter.WithColor(cs.Cyan))
|
|
tp.AddField(run.Duration(opts.now).String())
|
|
tp.AddTimeField(opts.now, run.StartedTime(), cs.Muted)
|
|
tp.EndRow()
|
|
}
|
|
|
|
if err := tp.Render(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|