github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/cmd/syft/cli/ui/handle_cataloger_task.go (about)

     1  package ui
     2  
     3  import (
     4  	tea "github.com/charmbracelet/bubbletea"
     5  	"github.com/charmbracelet/lipgloss"
     6  	"github.com/google/uuid"
     7  	"github.com/wagoodman/go-partybus"
     8  	"github.com/wagoodman/go-progress"
     9  
    10  	"github.com/anchore/bubbly/bubbles/taskprogress"
    11  	"github.com/anchore/bubbly/bubbles/tree"
    12  	"github.com/anchore/syft/internal/log"
    13  	"github.com/anchore/syft/syft/event/monitor"
    14  	syftEventParsers "github.com/anchore/syft/syft/event/parsers"
    15  )
    16  
    17  // we standardize how rows are instantiated to ensure consistency in the appearance across the UI
    18  type taskModelFactory func(title taskprogress.Title, opts ...taskprogress.Option) taskprogress.Model
    19  
    20  var _ tea.Model = (*catalogerTaskModel)(nil)
    21  
    22  type catalogerTaskModel struct {
    23  	model        tree.Model
    24  	modelFactory taskModelFactory
    25  }
    26  
    27  func newCatalogerTaskTreeModel(f taskModelFactory) *catalogerTaskModel {
    28  	t := tree.NewModel()
    29  	t.Padding = "   "
    30  	t.RootsWithoutPrefix = true
    31  	return &catalogerTaskModel{
    32  		modelFactory: f,
    33  		model:        t,
    34  	}
    35  }
    36  
    37  type newCatalogerTaskRowEvent struct {
    38  	info monitor.GenericTask
    39  	prog progress.StagedProgressable
    40  }
    41  
    42  func (cts catalogerTaskModel) Init() tea.Cmd {
    43  	return cts.model.Init()
    44  }
    45  
    46  func (cts catalogerTaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    47  	event, ok := msg.(newCatalogerTaskRowEvent)
    48  	if !ok {
    49  		model, cmd := cts.model.Update(msg)
    50  		cts.model = model.(tree.Model)
    51  
    52  		return cts, cmd
    53  	}
    54  
    55  	info, prog := event.info, event.prog
    56  
    57  	tsk := cts.modelFactory(
    58  		taskprogress.Title{
    59  			Default: info.Title.Default,
    60  			Running: info.Title.WhileRunning,
    61  			Success: info.Title.OnSuccess,
    62  		},
    63  		taskprogress.WithStagedProgressable(prog),
    64  	)
    65  
    66  	if info.Context != "" {
    67  		tsk.Context = []string{info.Context}
    68  	}
    69  
    70  	tsk.HideOnSuccess = info.HideOnSuccess
    71  	tsk.HideStageOnSuccess = info.HideStageOnSuccess
    72  	tsk.HideProgressOnSuccess = true
    73  
    74  	if info.ParentID != "" {
    75  		tsk.TitleStyle = lipgloss.NewStyle()
    76  	}
    77  
    78  	if err := cts.model.Add(info.ParentID, info.ID, tsk); err != nil {
    79  		log.WithFields("error", err).Error("unable to add cataloger task to tree model")
    80  	}
    81  
    82  	return cts, tsk.Init()
    83  }
    84  
    85  func (cts catalogerTaskModel) View() string {
    86  	return cts.model.View()
    87  }
    88  
    89  func (m *Handler) handleCatalogerTaskStarted(e partybus.Event) ([]tea.Model, tea.Cmd) {
    90  	mon, info, err := syftEventParsers.ParseCatalogerTaskStarted(e)
    91  	if err != nil {
    92  		log.WithFields("error", err).Warn("unable to parse event")
    93  		return nil, nil
    94  	}
    95  
    96  	var models []tea.Model
    97  
    98  	// only create the new cataloger task tree once to manage all cataloger task events
    99  	m.onNewCatalogerTask.Do(func() {
   100  		models = append(models, newCatalogerTaskTreeModel(m.newTaskProgress))
   101  	})
   102  
   103  	// we need to update the cataloger task model with a new row. We should never update the model outside of the
   104  	// bubbletea update-render event loop. Instead, we return a command that will be executed by the bubbletea runtime,
   105  	// producing a message that is passed to the cataloger task model. This is the prescribed way to update models
   106  	// in bubbletea.
   107  
   108  	if info.ID == "" {
   109  		// ID is optional from the consumer perspective, but required internally
   110  		info.ID = uuid.Must(uuid.NewRandom()).String()
   111  	}
   112  
   113  	cmd := func() tea.Msg {
   114  		// this message will cause the cataloger task model to add a new row to the output based on the given task
   115  		// information and progress data.
   116  		return newCatalogerTaskRowEvent{
   117  			info: *info,
   118  			prog: mon,
   119  		}
   120  	}
   121  
   122  	return models, cmd
   123  }