Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Structured events in place of comment strings #3771

Merged
merged 12 commits into from
Apr 14, 2024
2 changes: 0 additions & 2 deletions cmd/cli/docker/docker_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,8 +791,6 @@ func (s *DockerRunSuite) TestRun_InvalidImage() {
s.Require().NoError(err)
s.T().Log(info)

s.Len(info.State.Executions, 1)
s.Equal(model.ExecutionStateAskForBidRejected, info.State.Executions[0].State)
s.Contains(info.State.Executions[0].Status, `Could not inspect image "@" - could be due to repo/image not existing`)
}

Expand Down
59 changes: 58 additions & 1 deletion cmd/cli/job/describe.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package job

import (
"cmp"
"fmt"
"slices"
"time"

"github.com/bacalhau-project/bacalhau/pkg/lib/collections"
"github.com/bacalhau-project/bacalhau/pkg/models"
"github.com/bacalhau-project/bacalhau/pkg/util/idgen"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/samber/lo"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/i18n"

Expand Down Expand Up @@ -65,7 +70,7 @@ func (o *DescribeOptions) run(cmd *cobra.Command, args []string) error {
jobID := args[0]
response, err := util.GetAPIClientV2(cmd).Jobs().Get(ctx, &apimodels.GetJobRequest{
JobID: jobID,
Include: "executions",
Include: "executions,history",
})

if err != nil {
Expand All @@ -85,12 +90,39 @@ func (o *DescribeOptions) run(cmd *cobra.Command, args []string) error {
// TODO: #520 rename Executions.Executions to Executions.Items
executions = response.Executions.Executions
}
// Show most relevant execution first: sort by time DESC
slices.SortFunc(executions, func(a, b *models.Execution) int {
return cmp.Compare(b.CreateTime, a.CreateTime)
})

var history []*models.JobHistory
if response.History != nil {
history = response.History.History
}

o.printHeaderData(cmd, job)
o.printExecutionsSummary(cmd, executions)

jobHistory := lo.Filter(history, func(entry *models.JobHistory, _ int) bool {
return entry.Type == models.JobHistoryTypeJobLevel
})
if err = o.printHistory(cmd, "Job", jobHistory); err != nil {
util.Fatal(cmd, fmt.Errorf("failed to write job history: %w", err), 1)
}

if err = o.printExecutions(cmd, executions); err != nil {
return fmt.Errorf("failed to write job executions %s: %w", jobID, err)
}

for _, execution := range executions {
executionHistory := lo.Filter(history, func(item *models.JobHistory, _ int) bool {
return item.ExecutionID == execution.ID
})
if err = o.printHistory(cmd, "Execution "+idgen.ShortUUID(execution.ID), executionHistory); err != nil {
util.Fatal(cmd, fmt.Errorf("failed to write execution history for %s: %w", execution.ID, err), 1)
}
}

o.printOutputs(cmd, executions)

return nil
Expand Down Expand Up @@ -156,6 +188,31 @@ func (o *DescribeOptions) printExecutions(cmd *cobra.Command, executions []*mode
return output.Output(cmd, executionCols, tableOptions, executions)
}

func (o *DescribeOptions) printHistory(cmd *cobra.Command, label string, history []*models.JobHistory) error {
if len(history) < 1 {
return nil
}

timeCol := output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: historyTimeCol.ColumnConfig.Name, WidthMax: 20, WidthMaxEnforcer: text.WrapText},
Value: func(h *models.JobHistory) string { return h.Occurred().Format(time.DateTime) },
}

tableOptions := output.OutputOptions{
Format: output.TableFormat,
NoStyle: true,
}
jobHistoryCols := []output.TableColumn[*models.JobHistory]{
timeCol,
historyRevisionCol,
historyStateCol,
historyTopicCol,
historyEventCol,
}
output.Bold(cmd, fmt.Sprintf("\n%s History\n", label))
return output.Output(cmd, jobHistoryCols, tableOptions, history)
}

func (o *DescribeOptions) printOutputs(cmd *cobra.Command, executions []*models.Execution) {
outputs := make(map[string]string)
for _, e := range executions {
Expand Down
2 changes: 1 addition & 1 deletion cmd/cli/job/executions.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ var (
Value: func(e *models.Execution) string { return strconv.FormatUint(e.Revision, 10) },
}
executionColumnState = output.TableColumn[*models.Execution]{
ColumnConfig: table.ColumnConfig{Name: "State", WidthMax: 10, WidthMaxEnforcer: text.WrapText},
ColumnConfig: table.ColumnConfig{Name: "State", WidthMax: 17, WidthMaxEnforcer: text.WrapText},
Value: func(e *models.Execution) string { return e.ComputeState.StateType.String() },
}
executionColumnDesired = output.TableColumn[*models.Execution]{
Expand Down
95 changes: 64 additions & 31 deletions cmd/cli/job/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/jedib0t/go-pretty/v6/table"
"github.com/jedib0t/go-pretty/v6/text"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"k8s.io/kubectl/pkg/util/i18n"

Expand Down Expand Up @@ -77,50 +78,82 @@ func NewHistoryCmd() *cobra.Command {
return nodeCmd
}

var historyColumns = []output.TableColumn[*models.JobHistory]{
{
ColumnConfig: table.ColumnConfig{Name: "Time", WidthMax: 8, WidthMaxEnforcer: output.ShortenTime},
Value: func(j *models.JobHistory) string { return j.Time.Format(time.DateTime) },
},
{
var (
historyTimeCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Time", WidthMax: len(time.StampMilli), WidthMaxEnforcer: output.ShortenTime},
Value: func(j *models.JobHistory) string { return j.Occurred().Format(time.StampMilli) },
}
historyLevelCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Level", WidthMax: 15, WidthMaxEnforcer: text.WrapText},
Value: func(jwi *models.JobHistory) string { return jwi.Type.String() },
},
{
}
historyRevisionCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Rev.", WidthMax: 4, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string { return strconv.FormatUint(j.NewRevision, 10) },
}
historyExecIDCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Exec. ID", WidthMax: 10, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string { return idgen.ShortUUID(j.ExecutionID) },
},
{
}
historyNodeIDCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Node ID", WidthMax: 10, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string { return idgen.ShortNodeID(j.NodeID) },
},
{
ColumnConfig: table.ColumnConfig{Name: "Rev.", WidthMax: 4, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string { return strconv.FormatUint(j.NewRevision, 10) },
},
{
ColumnConfig: table.ColumnConfig{Name: "Previous State", WidthMax: 20, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string {
if j.Type == models.JobHistoryTypeJobLevel {
return j.JobState.Previous.String()
}
return j.ExecutionState.Previous.String()
},
},
{
ColumnConfig: table.ColumnConfig{Name: "New State", WidthMax: 20, WidthMaxEnforcer: text.WrapText},
}
historyStateCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "State", WidthMax: 20, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string {
if j.Type == models.JobHistoryTypeJobLevel {
return j.JobState.New.String()
}
return j.ExecutionState.New.String()
},
},
}
historyTopicCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Topic", WidthMax: 15, WidthMaxEnforcer: text.WrapSoft},
Value: func(jh *models.JobHistory) string { return string(jh.Event.Topic) },
}
historyEventCol = output.TableColumn[*models.JobHistory]{
ColumnConfig: table.ColumnConfig{Name: "Event", WidthMax: 60, WidthMaxEnforcer: text.WrapText},
Value: func(h *models.JobHistory) string {
res := h.Event.Message

if h.Event.Details != nil {
// if is error, then the event is in red
if h.Event.Details[models.DetailsKeyIsError] == "true" {
res = output.RedStr(res)
}

// print hint in green
if h.Event.Details[models.DetailsKeyHint] != "" {
res += "\n" + fmt.Sprintf(
"%s %s", output.BoldStr(output.GreenStr("* Hint:")), h.Event.Details[models.DetailsKeyHint])
}

// print all other details in debug mode
if zerolog.GlobalLevel() <= zerolog.DebugLevel {
for k, v := range h.Event.Details {
// don't print hint and error since they are already represented
if k == models.DetailsKeyHint || k == models.DetailsKeyIsError {
continue
}
res += "\n" + fmt.Sprintf("* %s %s", output.BoldStr(k+":"), v)
}
}
}
return res
},
}
)

{
ColumnConfig: table.ColumnConfig{Name: "Comment", WidthMax: 40, WidthMaxEnforcer: text.WrapText},
Value: func(j *models.JobHistory) string { return j.Comment },
},
var historyColumns = []output.TableColumn[*models.JobHistory]{
historyTimeCol,
historyLevelCol,
historyRevisionCol,
historyExecIDCol,
historyNodeIDCol,
historyStateCol,
historyTopicCol,
historyEventCol,
}

func (o *HistoryOptions) run(cmd *cobra.Command, args []string) error {
Expand Down
17 changes: 17 additions & 0 deletions cmd/util/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ const (

const (
bold = "\033[1m"
red = "\033[31m"
green = "\033[32m"
reset = "\033[0m"
)

Expand Down Expand Up @@ -147,6 +149,21 @@ func Bold(cmd *cobra.Command, s string) {
cmd.Print(bold + s + reset)
}

// BoldStr returns the given string in bold
func BoldStr(s any) string {
return bold + fmt.Sprint(s) + reset
}

// RedStr returns the given string in red
func RedStr(s any) string {
return red + fmt.Sprint(s) + reset
}

// GreenStr returns the given string in green
func GreenStr(s any) string {
return green + fmt.Sprint(s) + reset
}

func OutputOneNonTabular[T any](cmd *cobra.Command, options NonTabularOutputOptions, item T) error {
switch options.Format {
case JSONFormat:
Expand Down
Loading
Loading