Fix looking up workflow name for workflow runs (#6266)
The API field `WorkflowRun.name` is not guaranteed to correspond to the workflow name anymore. This introduces additional API lookups that resolve Workflows by their ID and look up their name in a future-proof fashion. It also adds two new JSON fields for export: `displayTitle` and `workflowName`. Co-authored-by: Christina Guo <61271066+guo-chris@users.noreply.github.com> Co-authored-by: Mislav Marohnić <mislav@github.com>
This commit is contained in:
parent
113acf9245
commit
f1be4dc51c
17 changed files with 643 additions and 255 deletions
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"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/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -82,8 +83,8 @@ func TestNewCmdCancel(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunCancel(t *testing.T) {
|
||||
inProgressRun := shared.TestRun("more runs", 1234, shared.InProgress, "")
|
||||
completedRun := shared.TestRun("more runs", 4567, shared.Completed, shared.Failure)
|
||||
inProgressRun := shared.TestRun(1234, shared.InProgress, "")
|
||||
completedRun := shared.TestRun(4567, shared.Completed, shared.Failure)
|
||||
tests := []struct {
|
||||
name string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
|
|
@ -103,6 +104,9 @@ func TestRunCancel(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(inProgressRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/cancel"),
|
||||
httpmock.StatusStringResponse(202, "{}"))
|
||||
|
|
@ -133,6 +137,9 @@ func TestRunCancel(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/4567"),
|
||||
httpmock.JSONResponse(completedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/4567/cancel"),
|
||||
httpmock.StatusStringResponse(409, ""),
|
||||
|
|
@ -154,6 +161,13 @@ func TestRunCancel(t *testing.T) {
|
|||
completedRun,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -169,6 +183,13 @@ func TestRunCancel(t *testing.T) {
|
|||
inProgressRun,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/cancel"),
|
||||
httpmock.StatusStringResponse(202, "{}"))
|
||||
|
|
|
|||
|
|
@ -84,9 +84,6 @@ func listRun(opts *ListOptions) error {
|
|||
}
|
||||
client := api.NewClientFromHTTP(c)
|
||||
|
||||
var runs []shared.Run
|
||||
var workflow *workflowShared.Workflow
|
||||
|
||||
filters := &shared.FilterOptions{
|
||||
Branch: opts.Branch,
|
||||
Actor: opts.Actor,
|
||||
|
|
@ -95,18 +92,19 @@ func listRun(opts *ListOptions) error {
|
|||
opts.IO.StartProgressIndicator()
|
||||
if opts.WorkflowSelector != "" {
|
||||
states := []workflowShared.WorkflowState{workflowShared.Active}
|
||||
workflow, err = workflowShared.ResolveWorkflow(
|
||||
opts.IO, client, baseRepo, false, opts.WorkflowSelector, states)
|
||||
if err == nil {
|
||||
runs, err = shared.GetRunsByWorkflow(client, baseRepo, filters, opts.Limit, workflow.ID)
|
||||
if workflow, err := workflowShared.ResolveWorkflow(opts.IO, client, baseRepo, false, opts.WorkflowSelector, states); err == nil {
|
||||
filters.WorkflowID = workflow.ID
|
||||
filters.WorkflowName = workflow.Name
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
runs, err = shared.GetRuns(client, baseRepo, filters, opts.Limit)
|
||||
}
|
||||
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 err := opts.IO.StartPager(); err == nil {
|
||||
defer opts.IO.StopPager()
|
||||
|
|
@ -128,7 +126,7 @@ func listRun(opts *ListOptions) error {
|
|||
|
||||
if tp.IsTTY() {
|
||||
tp.AddField("STATUS", nil, nil)
|
||||
tp.AddField("NAME", nil, nil)
|
||||
tp.AddField("TITLE", nil, nil)
|
||||
tp.AddField("WORKFLOW", nil, nil)
|
||||
tp.AddField("BRANCH", nil, nil)
|
||||
tp.AddField("EVENT", nil, nil)
|
||||
|
|
@ -147,9 +145,9 @@ func listRun(opts *ListOptions) error {
|
|||
tp.AddField(string(run.Conclusion), nil, nil)
|
||||
}
|
||||
|
||||
tp.AddField(run.CommitMsg(), nil, cs.Bold)
|
||||
tp.AddField(run.Title(), nil, cs.Bold)
|
||||
|
||||
tp.AddField(run.Name, nil, nil)
|
||||
tp.AddField(run.WorkflowName(), nil, nil)
|
||||
tp.AddField(run.HeadBranch, nil, cs.Bold)
|
||||
tp.AddField(string(run.Event), nil, nil)
|
||||
tp.AddField(fmt.Sprintf("%d", run.ID), nil, cs.Cyan)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"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/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -180,6 +181,13 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun"),
|
||||
httpmock.StringResponse("{}"))
|
||||
|
|
@ -197,6 +205,13 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun-failed-jobs"),
|
||||
httpmock.StringResponse("{}"))
|
||||
|
|
@ -230,6 +245,13 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun"),
|
||||
httpmock.StringResponse("{}"))
|
||||
|
|
@ -249,6 +271,13 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun-failed-jobs"),
|
||||
httpmock.StringResponse("{}"))
|
||||
|
|
@ -289,6 +318,20 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/1234/rerun"),
|
||||
httpmock.StringResponse("{}"))
|
||||
|
|
@ -311,8 +354,15 @@ func TestRerun(t *testing.T) {
|
|||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: []shared.Run{
|
||||
shared.SuccessfulRun,
|
||||
shared.TestRun("in progress", 2, shared.InProgress, ""),
|
||||
shared.TestRun(2, shared.InProgress, ""),
|
||||
}}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
wantErr: true,
|
||||
errOut: "no recent runs have failed; please specify a specific `<run-id>`",
|
||||
|
|
@ -327,6 +377,13 @@ func TestRerun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("POST", "repos/OWNER/REPO/actions/runs/3/rerun"),
|
||||
httpmock.StatusStringResponse(403, "no"))
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
func RenderRunHeader(cs *iostreams.ColorScheme, run Run, ago, prNumber string) string {
|
||||
title := fmt.Sprintf("%s %s%s",
|
||||
cs.Bold(run.HeadBranch), run.Name, prNumber)
|
||||
cs.Bold(run.HeadBranch), run.WorkflowName(), prNumber)
|
||||
symbol, symbolColor := Symbol(cs, run.Status, run.Conclusion)
|
||||
id := cs.Cyanf("%d", run.ID)
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/cli/cli/v2/pkg/prompt"
|
||||
)
|
||||
|
|
@ -45,6 +46,7 @@ type Level string
|
|||
|
||||
var RunFields = []string{
|
||||
"name",
|
||||
"displayTitle",
|
||||
"headBranch",
|
||||
"headSha",
|
||||
"createdAt",
|
||||
|
|
@ -55,11 +57,13 @@ var RunFields = []string{
|
|||
"event",
|
||||
"databaseId",
|
||||
"workflowDatabaseId",
|
||||
"workflowName",
|
||||
"url",
|
||||
}
|
||||
|
||||
type Run struct {
|
||||
Name string
|
||||
Name string `json:"name"` // the semantics of this field are unclear
|
||||
DisplayTitle string `json:"display_title"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
StartedAt time.Time `json:"run_started_at"`
|
||||
|
|
@ -67,6 +71,7 @@ type Run struct {
|
|||
Conclusion Conclusion
|
||||
Event string
|
||||
ID int64
|
||||
workflowName string // cache column
|
||||
WorkflowID int64 `json:"workflow_id"`
|
||||
Attempts uint8 `json:"run_attempt"`
|
||||
HeadBranch string `json:"head_branch"`
|
||||
|
|
@ -107,7 +112,12 @@ type Commit struct {
|
|||
Message string
|
||||
}
|
||||
|
||||
func (r Run) CommitMsg() string {
|
||||
// Title is the display title for a run, falling back to the commit subject if unavailable
|
||||
func (r Run) Title() string {
|
||||
if r.DisplayTitle != "" {
|
||||
return r.DisplayTitle
|
||||
}
|
||||
|
||||
commitLines := strings.Split(r.HeadCommit.Message, "\n")
|
||||
if len(commitLines) > 0 {
|
||||
return commitLines[0]
|
||||
|
|
@ -116,6 +126,12 @@ func (r Run) CommitMsg() string {
|
|||
}
|
||||
}
|
||||
|
||||
// WorkflowName returns the human-readable name of the workflow that this run belongs to.
|
||||
// TODO: consider lazy-loading the underlying API data to avoid extra API calls unless necessary
|
||||
func (r Run) WorkflowName() string {
|
||||
return r.workflowName
|
||||
}
|
||||
|
||||
func (r *Run) ExportData(fields []string) map[string]interface{} {
|
||||
v := reflect.ValueOf(r).Elem()
|
||||
fieldByName := func(v reflect.Value, field string) reflect.Value {
|
||||
|
|
@ -131,6 +147,8 @@ func (r *Run) ExportData(fields []string) map[string]interface{} {
|
|||
data[f] = r.ID
|
||||
case "workflowDatabaseId":
|
||||
data[f] = r.WorkflowID
|
||||
case "workflowName":
|
||||
data[f] = r.WorkflowName()
|
||||
default:
|
||||
sf := fieldByName(v, f)
|
||||
data[f] = sf.Interface()
|
||||
|
|
@ -228,18 +246,22 @@ type RunsPayload struct {
|
|||
}
|
||||
|
||||
type FilterOptions struct {
|
||||
Branch string
|
||||
Actor string
|
||||
Branch string
|
||||
Actor string
|
||||
WorkflowID int64
|
||||
// avoid loading workflow name separately and use the provided one
|
||||
WorkflowName string
|
||||
}
|
||||
|
||||
// GetRunsWithFilter fetches 50 runs from the API and filters them in-memory
|
||||
func GetRunsWithFilter(client *api.Client, repo ghrepo.Interface, opts *FilterOptions, limit int, f func(Run) bool) ([]Run, error) {
|
||||
path := fmt.Sprintf("repos/%s/actions/runs", ghrepo.FullName(repo))
|
||||
runs, err := getRuns(client, repo, path, opts, 50)
|
||||
runs, err := GetRuns(client, repo, opts, 50)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
filtered := []Run{}
|
||||
for _, run := range runs {
|
||||
|
||||
var filtered []Run
|
||||
for _, run := range runs.WorkflowRuns {
|
||||
if f(run) {
|
||||
filtered = append(filtered, run)
|
||||
}
|
||||
|
|
@ -251,70 +273,89 @@ func GetRunsWithFilter(client *api.Client, repo ghrepo.Interface, opts *FilterOp
|
|||
return filtered, nil
|
||||
}
|
||||
|
||||
func GetRunsByWorkflow(client *api.Client, repo ghrepo.Interface, opts *FilterOptions, limit int, workflowID int64) ([]Run, error) {
|
||||
path := fmt.Sprintf("repos/%s/actions/workflows/%d/runs", ghrepo.FullName(repo), workflowID)
|
||||
return getRuns(client, repo, path, opts, limit)
|
||||
}
|
||||
|
||||
func GetRuns(client *api.Client, repo ghrepo.Interface, opts *FilterOptions, limit int) ([]Run, error) {
|
||||
func GetRuns(client *api.Client, repo ghrepo.Interface, opts *FilterOptions, limit int) (*RunsPayload, error) {
|
||||
path := fmt.Sprintf("repos/%s/actions/runs", ghrepo.FullName(repo))
|
||||
return getRuns(client, repo, path, opts, limit)
|
||||
}
|
||||
if opts != nil && opts.WorkflowID > 0 {
|
||||
path = fmt.Sprintf("repos/%s/actions/workflows/%d/runs", ghrepo.FullName(repo), opts.WorkflowID)
|
||||
}
|
||||
|
||||
func getRuns(client *api.Client, repo ghrepo.Interface, path string, opts *FilterOptions, limit int) ([]Run, error) {
|
||||
perPage := limit
|
||||
page := 1
|
||||
if limit > 100 {
|
||||
perPage = 100
|
||||
}
|
||||
path += fmt.Sprintf("?per_page=%d", perPage)
|
||||
|
||||
runs := []Run{}
|
||||
|
||||
for len(runs) < limit {
|
||||
var result RunsPayload
|
||||
|
||||
parsed, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if opts != nil {
|
||||
if opts.Branch != "" {
|
||||
path += fmt.Sprintf("&branch=%s", url.QueryEscape(opts.Branch))
|
||||
}
|
||||
query := parsed.Query()
|
||||
query.Set("per_page", fmt.Sprintf("%d", perPage))
|
||||
query.Set("page", fmt.Sprintf("%d", page))
|
||||
if opts != nil {
|
||||
if opts.Branch != "" {
|
||||
query.Set("branch", opts.Branch)
|
||||
}
|
||||
if opts.Actor != "" {
|
||||
query.Set("actor", opts.Actor)
|
||||
}
|
||||
if opts.Actor != "" {
|
||||
path += fmt.Sprintf("&actor=%s", url.QueryEscape(opts.Actor))
|
||||
}
|
||||
parsed.RawQuery = query.Encode()
|
||||
pagedPath := parsed.String()
|
||||
|
||||
err = client.REST(repo.RepoHost(), "GET", pagedPath, nil, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(result.WorkflowRuns) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, run := range result.WorkflowRuns {
|
||||
runs = append(runs, run)
|
||||
if len(runs) == limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(result.WorkflowRuns) < perPage {
|
||||
break
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
return runs, nil
|
||||
var result *RunsPayload
|
||||
|
||||
pagination:
|
||||
for path != "" {
|
||||
var response RunsPayload
|
||||
var err error
|
||||
path, err = client.RESTWithNext(repo.RepoHost(), "GET", path, nil, &response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result == nil {
|
||||
result = &response
|
||||
if len(result.WorkflowRuns) == limit {
|
||||
break pagination
|
||||
}
|
||||
} else {
|
||||
for _, run := range response.WorkflowRuns {
|
||||
result.WorkflowRuns = append(result.WorkflowRuns, run)
|
||||
if len(result.WorkflowRuns) == limit {
|
||||
break pagination
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if opts != nil && opts.WorkflowName != "" {
|
||||
for i := range result.WorkflowRuns {
|
||||
result.WorkflowRuns[i].workflowName = opts.WorkflowName
|
||||
}
|
||||
} else if len(result.WorkflowRuns) > 0 {
|
||||
if err := preloadWorkflowNames(client, repo, result.WorkflowRuns); err != nil {
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func preloadWorkflowNames(client *api.Client, repo ghrepo.Interface, runs []Run) error {
|
||||
workflows, err := workflowShared.GetWorkflows(client, repo, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
workflowMap := map[int64]string{}
|
||||
for _, wf := range workflows {
|
||||
workflowMap[wf.ID] = wf.Name
|
||||
}
|
||||
|
||||
for i, run := range runs {
|
||||
if _, ok := workflowMap[run.WorkflowID]; !ok {
|
||||
// Look up workflow by ID because it may have been deleted
|
||||
workflow, err := workflowShared.GetWorkflow(client, repo, run.WorkflowID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workflowMap[run.WorkflowID] = workflow.Name
|
||||
}
|
||||
runs[i].workflowName = workflowMap[run.WorkflowID]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type JobsPayload struct {
|
||||
|
|
@ -351,7 +392,7 @@ func PromptForRun(cs *iostreams.ColorScheme, runs []Run) (string, error) {
|
|||
symbol, _ := Symbol(cs, run.Status, run.Conclusion)
|
||||
candidates = append(candidates,
|
||||
// TODO truncate commit message, long ones look terrible
|
||||
fmt.Sprintf("%s %s, %s (%s) %s", symbol, run.CommitMsg(), run.Name, run.HeadBranch, preciseAgo(now, run.StartedTime())))
|
||||
fmt.Sprintf("%s %s, %s (%s) %s", symbol, run.Title(), run.WorkflowName(), run.HeadBranch, preciseAgo(now, run.StartedTime())))
|
||||
}
|
||||
|
||||
// TODO consider custom filter so it's fuzzier. right now matches start anywhere in string but
|
||||
|
|
@ -380,6 +421,14 @@ func GetRun(client *api.Client, repo ghrepo.Interface, runID string) (*Run, erro
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Set name to workflow name
|
||||
workflow, err := workflowShared.GetWorkflow(client, repo, result.WorkflowID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
result.workflowName = workflow.Name
|
||||
}
|
||||
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,13 +3,19 @@ package shared
|
|||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
workflowShared "github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
)
|
||||
|
||||
var TestRunStartTime, _ = time.Parse("2006-01-02 15:04:05", "2021-02-23 04:51:00")
|
||||
|
||||
func TestRun(name string, id int64, s Status, c Conclusion) Run {
|
||||
func TestRun(id int64, s Status, c Conclusion) Run {
|
||||
return TestRunWithCommit(id, s, c, "cool commit")
|
||||
}
|
||||
|
||||
func TestRunWithCommit(id int64, s Status, c Conclusion, commit string) Run {
|
||||
return Run{
|
||||
Name: name,
|
||||
WorkflowID: 123,
|
||||
ID: id,
|
||||
CreatedAt: TestRunStartTime,
|
||||
UpdatedAt: TestRunStartTime.Add(time.Minute*4 + time.Second*34),
|
||||
|
|
@ -19,7 +25,7 @@ func TestRun(name string, id int64, s Status, c Conclusion) Run {
|
|||
HeadBranch: "trunk",
|
||||
JobsURL: fmt.Sprintf("https://api.github.com/runs/%d/jobs", id),
|
||||
HeadCommit: Commit{
|
||||
Message: "cool commit",
|
||||
Message: commit,
|
||||
},
|
||||
HeadSha: "1234567890",
|
||||
URL: fmt.Sprintf("https://github.com/runs/%d", id),
|
||||
|
|
@ -30,24 +36,24 @@ func TestRun(name string, id int64, s Status, c Conclusion) Run {
|
|||
}
|
||||
}
|
||||
|
||||
var SuccessfulRun Run = TestRun("successful", 3, Completed, Success)
|
||||
var FailedRun Run = TestRun("failed", 1234, Completed, Failure)
|
||||
var SuccessfulRun Run = TestRun(3, Completed, Success)
|
||||
var FailedRun Run = TestRun(1234, Completed, Failure)
|
||||
|
||||
var TestRuns []Run = []Run{
|
||||
TestRun("timed out", 1, Completed, TimedOut),
|
||||
TestRun("in progress", 2, InProgress, ""),
|
||||
TestRun(1, Completed, TimedOut),
|
||||
TestRun(2, InProgress, ""),
|
||||
SuccessfulRun,
|
||||
TestRun("cancelled", 4, Completed, Cancelled),
|
||||
TestRun(4, Completed, Cancelled),
|
||||
FailedRun,
|
||||
TestRun("neutral", 6, Completed, Neutral),
|
||||
TestRun("skipped", 7, Completed, Skipped),
|
||||
TestRun("requested", 8, Requested, ""),
|
||||
TestRun("queued", 9, Queued, ""),
|
||||
TestRun("stale", 10, Completed, Stale),
|
||||
TestRun(6, Completed, Neutral),
|
||||
TestRun(7, Completed, Skipped),
|
||||
TestRun(8, Requested, ""),
|
||||
TestRun(9, Queued, ""),
|
||||
TestRun(10, Completed, Stale),
|
||||
}
|
||||
|
||||
var WorkflowRuns []Run = []Run{
|
||||
TestRun("in progress", 2, InProgress, ""),
|
||||
TestRun(2, InProgress, ""),
|
||||
SuccessfulRun,
|
||||
FailedRun,
|
||||
}
|
||||
|
|
@ -111,3 +117,8 @@ var FailedJobAnnotations []Annotation = []Annotation{
|
|||
StartLine: 420,
|
||||
},
|
||||
}
|
||||
|
||||
var TestWorkflow workflowShared.Workflow = workflowShared.Workflow{
|
||||
Name: "CI",
|
||||
ID: 123,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ func runView(opts *ViewOptions) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("failed to get runs: %w", err)
|
||||
}
|
||||
runID, err = shared.PromptForRun(cs, runs)
|
||||
runID, err = shared.PromptForRun(cs, runs.WorkflowRuns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -466,15 +466,17 @@ func logFilenameRegexp(job shared.Job, step shared.Step) *regexp.Regexp {
|
|||
|
||||
// This function takes a zip file of logs and a list of jobs.
|
||||
// Structure of zip file
|
||||
// zip/
|
||||
// ├── jobname1/
|
||||
// │ ├── 1_stepname.txt
|
||||
// │ ├── 2_anotherstepname.txt
|
||||
// │ ├── 3_stepstepname.txt
|
||||
// │ └── 4_laststepname.txt
|
||||
// └── jobname2/
|
||||
// ├── 1_stepname.txt
|
||||
// └── 2_somestepname.txt
|
||||
//
|
||||
// zip/
|
||||
// ├── jobname1/
|
||||
// │ ├── 1_stepname.txt
|
||||
// │ ├── 2_anotherstepname.txt
|
||||
// │ ├── 3_stepstepname.txt
|
||||
// │ └── 4_laststepname.txt
|
||||
// └── jobname2/
|
||||
// ├── 1_stepname.txt
|
||||
// └── 2_somestepname.txt
|
||||
//
|
||||
// It iterates through the list of jobs and trys to find the matching
|
||||
// log in the zip file. If the matching log is found it is attached
|
||||
// to the job.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/cli/cli/v2/internal/browser"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"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/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -180,22 +181,25 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/artifacts"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query PullRequestForRun`),
|
||||
httpmock.StringResponse(`{"data": {
|
||||
"repository": {
|
||||
"pullRequests": {
|
||||
"nodes": [
|
||||
{"number": 2898,
|
||||
"headRepository": {
|
||||
"owner": {
|
||||
"login": "OWNER"
|
||||
},
|
||||
"name": "REPO"}}
|
||||
]}}}}`))
|
||||
"repository": {
|
||||
"pullRequests": {
|
||||
"nodes": [
|
||||
{"number": 2898,
|
||||
"headRepository": {
|
||||
"owner": {
|
||||
"login": "OWNER"
|
||||
},
|
||||
"name": "REPO"}}
|
||||
]}}}}`))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
|
|
@ -207,7 +211,7 @@ func TestViewRun(t *testing.T) {
|
|||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
|
||||
httpmock.JSONResponse([]shared.Annotation{}))
|
||||
},
|
||||
wantOut: "\n✓ trunk successful #2898 · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI #2898 · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "exit status, failed run",
|
||||
|
|
@ -219,6 +223,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/artifacts"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
|
|
@ -236,7 +243,7 @@ func TestViewRun(t *testing.T) {
|
|||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
|
||||
httpmock.JSONResponse(shared.FailedJobAnnotations))
|
||||
},
|
||||
wantOut: "\nX trunk failed · 1234\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
|
||||
wantOut: "\nX trunk CI · 1234\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
|
|
@ -263,10 +270,13 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "runs/3/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: heredoc.Doc(`
|
||||
|
||||
✓ trunk successful · 3
|
||||
✓ trunk CI · 3
|
||||
Triggered via push about 59 minutes ago
|
||||
|
||||
JOBS
|
||||
|
|
@ -307,8 +317,11 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
|
||||
httpmock.JSONResponse([]shared.Annotation{}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "verbose",
|
||||
|
|
@ -342,8 +355,11 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
|
||||
httpmock.JSONResponse(shared.FailedJobAnnotations))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "\nX trunk failed · 1234\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
|
||||
wantOut: "\nX trunk CI · 1234\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nTo see what failed, try: gh run view 1234 --log-failed\nView this run on GitHub: https://github.com/runs/1234\n",
|
||||
},
|
||||
{
|
||||
name: "prompts for choice, one job",
|
||||
|
|
@ -373,6 +389,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
|
||||
httpmock.JSONResponse([]shared.Annotation{}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -381,7 +407,7 @@ func TestViewRun(t *testing.T) {
|
|||
opts: &ViewOptions{
|
||||
Prompt: true,
|
||||
},
|
||||
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n\nFor more information about the job, try: gh run view --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "interactive with log",
|
||||
|
|
@ -410,6 +436,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -435,6 +471,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: coolJobRunLogOutput,
|
||||
},
|
||||
|
|
@ -465,6 +504,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -496,6 +545,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: expectedRunLogOutput,
|
||||
},
|
||||
|
|
@ -526,6 +578,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -551,6 +613,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: quuxTheBarfLogOutput,
|
||||
},
|
||||
|
|
@ -581,6 +646,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -612,6 +687,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234/logs"),
|
||||
httpmock.FileResponse("./fixtures/run_log.zip"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: quuxTheBarfLogOutput,
|
||||
},
|
||||
|
|
@ -625,12 +703,15 @@ func TestViewRun(t *testing.T) {
|
|||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"),
|
||||
httpmock.JSONResponse(shared.TestRun("in progress", 2, shared.InProgress, "")))
|
||||
httpmock.JSONResponse(shared.TestRun(2, shared.InProgress, "")))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/2/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{
|
||||
Jobs: []shared.Job{},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "run 2 is still in progress; logs will be available when it is complete",
|
||||
|
|
@ -652,7 +733,10 @@ func TestViewRun(t *testing.T) {
|
|||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/2"),
|
||||
httpmock.JSONResponse(shared.TestRun("in progress", 2, shared.InProgress, "")))
|
||||
httpmock.JSONResponse(shared.TestRun(2, shared.InProgress, "")))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "job 20 is still in progress; logs will be available when it is complete",
|
||||
|
|
@ -672,8 +756,11 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
|
||||
httpmock.JSONResponse([]shared.Annotation{}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "interactive, multiple jobs, choose all jobs",
|
||||
|
|
@ -707,6 +794,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
|
||||
httpmock.JSONResponse(shared.FailedJobAnnotations))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -714,7 +811,7 @@ func TestViewRun(t *testing.T) {
|
|||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
as.StubOne(0)
|
||||
},
|
||||
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nFor more information about a job, try: gh run view --job=<job-id>\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nFor more information about a job, try: gh run view --job=<job-id>\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "interactive, multiple jobs, choose specific jobs",
|
||||
|
|
@ -742,6 +839,16 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/10/annotations"),
|
||||
httpmock.JSONResponse([]shared.Annotation{}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
|
|
@ -749,7 +856,7 @@ func TestViewRun(t *testing.T) {
|
|||
//nolint:staticcheck // SA1019: as.StubOne is deprecated: use StubPrompt
|
||||
as.StubOne(1)
|
||||
},
|
||||
wantOut: "\n✓ trunk successful · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
wantOut: "\n✓ trunk CI · 3\nTriggered via push about 59 minutes ago\n\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\nTo see the full job log, try: gh run view --log --job=10\nView this run on GitHub: https://github.com/runs/3\n",
|
||||
},
|
||||
{
|
||||
name: "web run",
|
||||
|
|
@ -762,6 +869,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
browsedURL: "https://github.com/runs/3",
|
||||
wantOut: "Opening github.com/runs/3 in your browser.\n",
|
||||
|
|
@ -780,6 +890,9 @@ func TestViewRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/3"),
|
||||
httpmock.JSONResponse(shared.SuccessfulRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
browsedURL: "https://github.com/jobs/10?check_suite_focus=true",
|
||||
wantOut: "Opening github.com/jobs/10 in your browser.\n",
|
||||
|
|
@ -793,15 +906,18 @@ func TestViewRun(t *testing.T) {
|
|||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123"),
|
||||
httpmock.JSONResponse(shared.TestRun("failed no job", 123, shared.Completed, shared.Failure)))
|
||||
httpmock.JSONResponse(shared.TestRun(123, shared.Completed, shared.Failure)))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/123/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{Jobs: []shared.Job{}}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123/artifacts"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "\nX trunk failed no job · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
|
||||
wantOut: "\nX trunk CI · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
|
||||
},
|
||||
{
|
||||
name: "hide job header, startup_failure",
|
||||
|
|
@ -812,15 +928,18 @@ func TestViewRun(t *testing.T) {
|
|||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123"),
|
||||
httpmock.JSONResponse(shared.TestRun("failed no job", 123, shared.Completed, shared.StartupFailure)))
|
||||
httpmock.JSONResponse(shared.TestRun(123, shared.Completed, shared.StartupFailure)))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "runs/123/jobs"),
|
||||
httpmock.JSONResponse(shared.JobsPayload{Jobs: []shared.Job{}}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/123/artifacts"),
|
||||
httpmock.StringResponse(`{}`))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "\nX trunk failed no job · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
|
||||
wantOut: "\nX trunk CI · 123\nTriggered via push about 59 minutes ago\n\nX This run likely failed because of a workflow file issue.\n\nFor more information, see: https://github.com/runs/123\n",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -879,13 +998,14 @@ func TestViewRun(t *testing.T) {
|
|||
}
|
||||
|
||||
// Structure of fixture zip file
|
||||
// run log/
|
||||
// ├── cool job/
|
||||
// │ ├── 1_fob the barz.txt
|
||||
// │ └── 2_barz the fob.txt
|
||||
// └── sad job/
|
||||
// ├── 1_barf the quux.txt
|
||||
// └── 2_quux the barf.txt
|
||||
//
|
||||
// run log/
|
||||
// ├── cool job/
|
||||
// │ ├── 1_fob the barz.txt
|
||||
// │ └── 2_barz the fob.txt
|
||||
// └── sad job/
|
||||
// ├── 1_barf the quux.txt
|
||||
// └── 2_quux the barf.txt
|
||||
func Test_attachRunLog(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ func watchRun(opts *WatchOptions) error {
|
|||
}
|
||||
|
||||
if run.Status == shared.Completed {
|
||||
fmt.Fprintf(opts.IO.Out, "Run %s (%s) has already completed with '%s'\n", cs.Bold(run.Name), cs.Cyanf("%d", run.ID), run.Conclusion)
|
||||
fmt.Fprintf(opts.IO.Out, "Run %s (%s) has already completed with '%s'\n", cs.Bold(run.WorkflowName()), cs.Cyanf("%d", run.ID), run.Conclusion)
|
||||
if opts.ExitStatus && run.Conclusion != shared.Success {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
|
@ -186,7 +186,7 @@ func watchRun(opts *WatchOptions) error {
|
|||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintln(opts.IO.Out)
|
||||
fmt.Fprintf(opts.IO.Out, "%s Run %s (%s) completed with '%s'\n", symbolColor(symbol), cs.Bold(run.Name), id, run.Conclusion)
|
||||
fmt.Fprintf(opts.IO.Out, "%s Run %s (%s) completed with '%s'\n", symbolColor(symbol), cs.Bold(run.WorkflowName()), id, run.Conclusion)
|
||||
}
|
||||
|
||||
if opts.ExitStatus && run.Conclusion != shared.Success {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"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/httpmock"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
|
|
@ -98,13 +99,13 @@ func TestNewCmdWatch(t *testing.T) {
|
|||
|
||||
func TestWatchRun(t *testing.T) {
|
||||
failedRunStubs := func(reg *httpmock.Registry) {
|
||||
inProgressRun := shared.TestRun("more runs", 2, shared.InProgress, "")
|
||||
completedRun := shared.TestRun("more runs", 2, shared.Completed, shared.Failure)
|
||||
inProgressRun := shared.TestRunWithCommit(2, shared.InProgress, "", "commit2")
|
||||
completedRun := shared.TestRun(2, shared.Completed, shared.Failure)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: []shared.Run{
|
||||
shared.TestRun("run", 1, shared.InProgress, ""),
|
||||
shared.TestRunWithCommit(1, shared.InProgress, "", "commit1"),
|
||||
inProgressRun,
|
||||
},
|
||||
}))
|
||||
|
|
@ -128,15 +129,28 @@ func TestWatchRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/check-runs/20/annotations"),
|
||||
httpmock.JSONResponse(shared.FailedJobAnnotations))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
}
|
||||
successfulRunStubs := func(reg *httpmock.Registry) {
|
||||
inProgressRun := shared.TestRun("more runs", 2, shared.InProgress, "")
|
||||
completedRun := shared.TestRun("more runs", 2, shared.Completed, shared.Success)
|
||||
inProgressRun := shared.TestRunWithCommit(2, shared.InProgress, "", "commit2")
|
||||
completedRun := shared.TestRun(2, shared.Completed, shared.Success)
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs"),
|
||||
httpmock.JSONResponse(shared.RunsPayload{
|
||||
WorkflowRuns: []shared.Run{
|
||||
shared.TestRun("run", 1, shared.InProgress, ""),
|
||||
shared.TestRunWithCommit(1, shared.InProgress, "", "commit1"),
|
||||
inProgressRun,
|
||||
},
|
||||
}))
|
||||
|
|
@ -163,6 +177,19 @@ func TestWatchRun(t *testing.T) {
|
|||
shared.SuccessfulJob,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
|
|
@ -184,8 +211,11 @@ func TestWatchRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "Run failed (1234) has already completed with 'failure'\n",
|
||||
wantOut: "Run CI (1234) has already completed with 'failure'\n",
|
||||
},
|
||||
{
|
||||
name: "already completed, exit status",
|
||||
|
|
@ -197,8 +227,11 @@ func TestWatchRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/runs/1234"),
|
||||
httpmock.JSONResponse(shared.FailedRun))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
|
||||
httpmock.JSONResponse(shared.TestWorkflow))
|
||||
},
|
||||
wantOut: "Run failed (1234) has already completed with 'failure'\n",
|
||||
wantOut: "Run CI (1234) has already completed with 'failure'\n",
|
||||
wantErr: true,
|
||||
errMsg: "SilentError",
|
||||
},
|
||||
|
|
@ -219,6 +252,13 @@ func TestWatchRun(t *testing.T) {
|
|||
shared.SuccessfulRun,
|
||||
},
|
||||
}))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(workflowShared.WorkflowsPayload{
|
||||
Workflows: []workflowShared.Workflow{
|
||||
shared.TestWorkflow,
|
||||
},
|
||||
}))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -231,10 +271,10 @@ func TestWatchRun(t *testing.T) {
|
|||
httpStubs: successfulRunStubs,
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubPrompt("Select a workflow run").
|
||||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
AssertOptions([]string{"* commit1, CI (trunk) Feb 23, 2021", "* commit2, CI (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* commit2, CI (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[?1049l✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run more runs (2) completed with 'success'\n",
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[?1049l✓ trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run CI (2) completed with 'success'\n",
|
||||
},
|
||||
{
|
||||
name: "exit status respected",
|
||||
|
|
@ -247,10 +287,10 @@ func TestWatchRun(t *testing.T) {
|
|||
httpStubs: failedRunStubs,
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubPrompt("Select a workflow run").
|
||||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
AssertOptions([]string{"* commit1, CI (trunk) Feb 23, 2021", "* commit2, CI (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* commit2, CI (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[?1049lX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run more runs (2) completed with 'failure'\n",
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk CI · 2\nTriggered via push about 59 minutes ago\n\n\x1b[?1049lX trunk CI · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run CI (2) completed with 'failure'\n",
|
||||
wantErr: true,
|
||||
errMsg: "SilentError",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -132,9 +132,6 @@ func TestDisableRun(t *testing.T) {
|
|||
},
|
||||
tty: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -157,9 +154,6 @@ func TestDisableRun(t *testing.T) {
|
|||
},
|
||||
tty: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/another%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -216,9 +210,6 @@ func TestDisableRun(t *testing.T) {
|
|||
Selector: "a workflow",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
|
|||
|
|
@ -132,9 +132,6 @@ func TestEnableRun(t *testing.T) {
|
|||
},
|
||||
tty: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/terrible%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -158,9 +155,6 @@ func TestEnableRun(t *testing.T) {
|
|||
},
|
||||
tty: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20disabled%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -187,9 +181,6 @@ func TestEnableRun(t *testing.T) {
|
|||
},
|
||||
tty: true,
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20disabled%20inactivity%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -242,9 +233,6 @@ func TestEnableRun(t *testing.T) {
|
|||
Selector: "terrible workflow",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/terrible%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -267,9 +255,6 @@ func TestEnableRun(t *testing.T) {
|
|||
Selector: "a disabled inactivity workflow",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20disabled%20inactivity%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
@ -291,9 +276,6 @@ func TestEnableRun(t *testing.T) {
|
|||
Selector: "a disabled workflow",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry) {
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a%20disabled%20workflow"),
|
||||
httpmock.StatusStringResponse(404, "not found"))
|
||||
reg.Register(
|
||||
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
|
||||
httpmock.JSONResponse(shared.WorkflowsPayload{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
|
|
@ -117,32 +118,33 @@ func SelectWorkflow(workflows []Workflow, promptMsg string, states []WorkflowSta
|
|||
return &filtered[selected], nil
|
||||
}
|
||||
|
||||
// FindWorkflow looks up a workflow either by numeric database ID, file name, or its Name field
|
||||
func FindWorkflow(client *api.Client, repo ghrepo.Interface, workflowSelector string, states []WorkflowState) ([]Workflow, error) {
|
||||
if workflowSelector == "" {
|
||||
return nil, errors.New("empty workflow selector")
|
||||
}
|
||||
|
||||
workflow, err := getWorkflowByID(client, repo, workflowSelector)
|
||||
if err == nil {
|
||||
return []Workflow{*workflow}, nil
|
||||
} else {
|
||||
var httpErr api.HTTPError
|
||||
if !errors.As(err, &httpErr) || httpErr.StatusCode != 404 {
|
||||
if _, err := strconv.Atoi(workflowSelector); err == nil || strings.HasSuffix(workflowSelector, ".yml") {
|
||||
workflow, err := getWorkflowByID(client, repo, workflowSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []Workflow{*workflow}, nil
|
||||
}
|
||||
|
||||
return getWorkflowsByName(client, repo, workflowSelector, states)
|
||||
}
|
||||
|
||||
func GetWorkflow(client *api.Client, repo ghrepo.Interface, workflowID int64) (*Workflow, error) {
|
||||
return getWorkflowByID(client, repo, strconv.FormatInt(workflowID, 10))
|
||||
}
|
||||
|
||||
// ID can be either a numeric database ID or the workflow file name
|
||||
func getWorkflowByID(client *api.Client, repo ghrepo.Interface, ID string) (*Workflow, error) {
|
||||
var workflow Workflow
|
||||
|
||||
err := client.REST(repo.RepoHost(), "GET",
|
||||
fmt.Sprintf("repos/%s/actions/workflows/%s", ghrepo.FullName(repo), url.PathEscape(ID)),
|
||||
nil, &workflow)
|
||||
|
||||
if err != nil {
|
||||
path := fmt.Sprintf("repos/%s/actions/workflows/%s", ghrepo.FullName(repo), url.PathEscape(ID))
|
||||
if err := client.REST(repo.RepoHost(), "GET", path, nil, &workflow); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -154,24 +156,17 @@ func getWorkflowsByName(client *api.Client, repo ghrepo.Interface, name string,
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("couldn't fetch workflows for %s: %w", ghrepo.FullName(repo), err)
|
||||
}
|
||||
filtered := []Workflow{}
|
||||
|
||||
var filtered []Workflow
|
||||
for _, workflow := range workflows {
|
||||
desiredState := false
|
||||
for _, state := range states {
|
||||
if workflow.State == state {
|
||||
desiredState = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !desiredState {
|
||||
if !strings.EqualFold(workflow.Name, name) {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO consider fuzzy or prefix match
|
||||
if strings.EqualFold(workflow.Name, name) {
|
||||
filtered = append(filtered, workflow)
|
||||
for _, state := range states {
|
||||
if workflow.State == state {
|
||||
filtered = append(filtered, workflow)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
runShared "github.com/cli/cli/v2/pkg/cmd/run/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmd/workflow/shared"
|
||||
)
|
||||
|
||||
type workflowRuns struct {
|
||||
Total int
|
||||
Runs []runShared.Run
|
||||
}
|
||||
|
||||
func getWorkflowRuns(client *api.Client, repo ghrepo.Interface, workflow *shared.Workflow) (workflowRuns, error) {
|
||||
var wr workflowRuns
|
||||
var result runShared.RunsPayload
|
||||
path := fmt.Sprintf("repos/%s/actions/workflows/%d/runs?per_page=%d&page=%d", ghrepo.FullName(repo), workflow.ID, 5, 1)
|
||||
|
||||
err := client.REST(repo.RepoHost(), "GET", path, nil, &result)
|
||||
if err != nil {
|
||||
return wr, err
|
||||
}
|
||||
|
||||
wr.Total = result.TotalCount
|
||||
wr.Runs = append(wr.Runs, result.WorkflowRuns...)
|
||||
|
||||
return wr, nil
|
||||
}
|
||||
|
|
@ -195,7 +195,10 @@ func viewWorkflowContent(opts *ViewOptions, client *api.Client, repo ghrepo.Inte
|
|||
}
|
||||
|
||||
func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interface, workflow *shared.Workflow) error {
|
||||
wr, err := getWorkflowRuns(client, repo, workflow)
|
||||
wr, err := runShared.GetRuns(client, repo, &runShared.FilterOptions{
|
||||
WorkflowID: workflow.ID,
|
||||
WorkflowName: workflow.Name,
|
||||
}, 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get runs: %w", err)
|
||||
}
|
||||
|
|
@ -210,13 +213,13 @@ func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interfa
|
|||
fmt.Fprintf(out, "ID: %s\n\n", cs.Cyanf("%d", workflow.ID))
|
||||
|
||||
// Runs
|
||||
fmt.Fprintf(out, "Total runs %d\n", wr.Total)
|
||||
fmt.Fprintf(out, "Total runs %d\n", wr.TotalCount)
|
||||
|
||||
if wr.Total != 0 {
|
||||
if wr.TotalCount != 0 {
|
||||
fmt.Fprintln(out, "Recent runs")
|
||||
}
|
||||
|
||||
for _, run := range wr.Runs {
|
||||
for _, run := range wr.WorkflowRuns {
|
||||
if opts.Raw {
|
||||
tp.AddField(string(run.Status), nil, nil)
|
||||
tp.AddField(string(run.Conclusion), nil, nil)
|
||||
|
|
@ -225,9 +228,9 @@ func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interfa
|
|||
tp.AddField(symbol, nil, symbolColor)
|
||||
}
|
||||
|
||||
tp.AddField(run.CommitMsg(), nil, cs.Bold)
|
||||
tp.AddField(run.Title(), nil, cs.Bold)
|
||||
|
||||
tp.AddField(run.Name, nil, nil)
|
||||
tp.AddField(run.WorkflowName(), nil, nil)
|
||||
tp.AddField(run.HeadBranch, nil, cs.Bold)
|
||||
tp.AddField(string(run.Event), nil, nil)
|
||||
|
||||
|
|
@ -248,7 +251,7 @@ func viewWorkflowInfo(opts *ViewOptions, client *api.Client, repo ghrepo.Interfa
|
|||
fmt.Fprintln(out)
|
||||
|
||||
// Footer
|
||||
if wr.Total != 0 {
|
||||
if wr.TotalCount != 0 {
|
||||
fmt.Fprintf(out, "To see more runs for this workflow, try: gh run list --workflow %s\n", filename)
|
||||
}
|
||||
fmt.Fprintf(out, "To see the YAML for this workflow, try: gh workflow view %s --yaml\n", filename)
|
||||
|
|
|
|||
|
|
@ -176,10 +176,10 @@ func TestViewRun(t *testing.T) {
|
|||
|
||||
Total runs 10
|
||||
Recent runs
|
||||
X cool commit timed out trunk push 1
|
||||
* cool commit in progress trunk push 2
|
||||
✓ cool commit successful trunk push 3
|
||||
X cool commit cancelled trunk push 4
|
||||
X cool commit a workflow trunk push 1
|
||||
* cool commit a workflow trunk push 2
|
||||
✓ cool commit a workflow trunk push 3
|
||||
X cool commit a workflow trunk push 4
|
||||
|
||||
To see more runs for this workflow, try: gh run list --workflow flow.yml
|
||||
To see the YAML for this workflow, try: gh workflow view flow.yml --yaml
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue