diff --git a/pkg/local_workflows/code_workflow/native_workflow.go b/pkg/local_workflows/code_workflow/native_workflow.go index a038f3652..ce755b516 100644 --- a/pkg/local_workflows/code_workflow/native_workflow.go +++ b/pkg/local_workflows/code_workflow/native_workflow.go @@ -53,29 +53,38 @@ const ( type OptionalAnalysisFunctions func(string, func() *http.Client, *zerolog.Logger, configuration.Configuration, ui.UserInterface) (*sarif.SarifResponse, *scan.ResultMetaData, error) -type ProgressTrackerFactory struct { +func NewTrackerFactory(userInterface ui.UserInterface, logger *zerolog.Logger) scan.TrackerFactory { + return progressTrackerFactory{ + userInterface: userInterface, + logger: logger, + } +} + +type progressTrackerFactory struct { userInterface ui.UserInterface logger *zerolog.Logger } -func (p ProgressTrackerFactory) GenerateTracker() scan.Tracker { - return &ProgressTrackerAdapter{ - bar: p.userInterface.NewProgressBar(), - logger: p.logger, +func (p progressTrackerFactory) GenerateTracker() scan.Tracker { + return &progressTrackerAdapter{ + userInterface: p.userInterface, + logger: p.logger, } } -type ProgressTrackerAdapter struct { - bar ui.ProgressBar - logger *zerolog.Logger +type progressTrackerAdapter struct { + userInterface ui.UserInterface + logger *zerolog.Logger + bar ui.ProgressBar } -func (p ProgressTrackerAdapter) Begin(title, message string) { - if len(message) > 0 { - p.bar.SetTitle(title + " - " + message) - } else { - p.bar.SetTitle(title) +func (p *progressTrackerAdapter) Begin(title, message string) { + if p.bar != nil { + p.logger.Error().Msg("progress tracker already begun") + return } + p.bar = p.userInterface.NewProgressBar(title) + p.bar.SetMessage(message) err := p.bar.UpdateProgress(ui.InfiniteProgress) if err != nil { @@ -83,8 +92,11 @@ func (p ProgressTrackerAdapter) Begin(title, message string) { } } -func (p ProgressTrackerAdapter) End(message string) { - p.bar.SetTitle(message) +func (p *progressTrackerAdapter) End(message string) { + if p.bar == nil { + return + } + p.bar.SetMessage(message) err := p.bar.Clear() if err != nil { p.logger.Err(err).Msg("Failed to clear progress") @@ -204,10 +216,7 @@ func defaultAnalyzeFunction(path string, httpClientFunc func() *http.Client, log localConfiguration: config, } - progressFactory := ProgressTrackerFactory{ - userInterface: userInterface, - logger: logger, - } + progressFactory := NewTrackerFactory(userInterface, logger) analysisOptions := []codeclient.AnalysisOption{} diff --git a/pkg/local_workflows/data_transformation_workflow.go b/pkg/local_workflows/data_transformation_workflow.go index b381d95b6..65b4c440e 100644 --- a/pkg/local_workflows/data_transformation_workflow.go +++ b/pkg/local_workflows/data_transformation_workflow.go @@ -40,8 +40,7 @@ func dataTransformationEntryPoint(invocationCtx workflow.InvocationContext, inpu return output, nil } - progress := invocationCtx.GetUserInterface().NewProgressBar() - progress.SetTitle("Transforming data") + progress := invocationCtx.GetUserInterface().NewProgressBar("Transforming data") progressError := progress.UpdateProgress(ui.InfiniteProgress) if progressError != nil { logger.Err(progressError).Msgf("Error when setting progress") diff --git a/pkg/mocks/progressbar.go b/pkg/mocks/progressbar.go index 660638d7c..7c673995b 100644 --- a/pkg/mocks/progressbar.go +++ b/pkg/mocks/progressbar.go @@ -47,16 +47,16 @@ func (mr *MockProgressBarMockRecorder) Clear() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Clear", reflect.TypeOf((*MockProgressBar)(nil).Clear)) } -// SetTitle mocks base method. -func (m *MockProgressBar) SetTitle(title string) { +// SetMessage mocks base method. +func (m *MockProgressBar) SetMessage(message string) { m.ctrl.T.Helper() - m.ctrl.Call(m, "SetTitle", title) + m.ctrl.Call(m, "SetMessage", message) } -// SetTitle indicates an expected call of SetTitle. -func (mr *MockProgressBarMockRecorder) SetTitle(title interface{}) *gomock.Call { +// SetMessage indicates an expected call of SetMessage. +func (mr *MockProgressBarMockRecorder) SetMessage(message interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetTitle", reflect.TypeOf((*MockProgressBar)(nil).SetTitle), title) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetMessage", reflect.TypeOf((*MockProgressBar)(nil).SetMessage), message) } // UpdateProgress mocks base method. diff --git a/pkg/mocks/userinterface.go b/pkg/mocks/userinterface.go index 6b3e44b89..8adc2accc 100644 --- a/pkg/mocks/userinterface.go +++ b/pkg/mocks/userinterface.go @@ -50,17 +50,17 @@ func (mr *MockUserInterfaceMockRecorder) Input(prompt interface{}) *gomock.Call } // NewProgressBar mocks base method. -func (m *MockUserInterface) NewProgressBar() ui.ProgressBar { +func (m *MockUserInterface) NewProgressBar(title string) ui.ProgressBar { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NewProgressBar") + ret := m.ctrl.Call(m, "NewProgressBar", title) ret0, _ := ret[0].(ui.ProgressBar) return ret0 } // NewProgressBar indicates an expected call of NewProgressBar. -func (mr *MockUserInterfaceMockRecorder) NewProgressBar() *gomock.Call { +func (mr *MockUserInterfaceMockRecorder) NewProgressBar(title interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewProgressBar", reflect.TypeOf((*MockUserInterface)(nil).NewProgressBar)) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewProgressBar", reflect.TypeOf((*MockUserInterface)(nil).NewProgressBar), title) } // Output mocks base method. diff --git a/pkg/ui/consoleui_test.go b/pkg/ui/consoleui_test.go index 644464f24..69df47de8 100644 --- a/pkg/ui/consoleui_test.go +++ b/pkg/ui/consoleui_test.go @@ -16,8 +16,7 @@ func Test_ProgressBar_Spinner(t *testing.T) { var err error writer := &bytes.Buffer{} - bar := newProgressBar(writer, SpinnerType, false) - bar.SetTitle("Hello") + bar := newProgressBar(writer, "Hello", SpinnerType, false) err = bar.UpdateProgress(0) assert.NoError(t, err) @@ -40,8 +39,7 @@ func Test_ProgressBar_Spinner_Infinite(t *testing.T) { var err error writer := &bytes.Buffer{} - bar := newProgressBar(writer, SpinnerType, false) - bar.SetTitle("Hello") + bar := newProgressBar(writer, "Hello", SpinnerType, false) err = bar.UpdateProgress(InfiniteProgress) assert.NoError(t, err) @@ -64,8 +62,7 @@ func Test_ProgressBar_Bar(t *testing.T) { var err error writer := &bytes.Buffer{} - bar := newProgressBar(writer, BarType, false) - bar.SetTitle("Hello") + bar := newProgressBar(writer, "Hello", BarType, false) err = bar.UpdateProgress(0) assert.NoError(t, err) @@ -88,8 +85,7 @@ func Test_ProgressBar_Unknown(t *testing.T) { var err error writer := &bytes.Buffer{} - bar := newProgressBar(writer, "Unknown", false) - bar.SetTitle("Hello") + bar := newProgressBar(writer, "Hello", "Unknown", false) err = bar.UpdateProgress(0) assert.NoError(t, err) @@ -120,11 +116,10 @@ func Test_DefaultUi(t *testing.T) { stdin.WriteString(name + "\n") ui := newConsoleUi(stdin, stdout, stderr) - bar := ui.NewProgressBar() + bar := ui.NewProgressBar("A Title") assert.NotNil(t, bar) // the bar will not render since the writer is not a TTY - bar.SetTitle("Hello") err := bar.UpdateProgress(InfiniteProgress) assert.NoError(t, err) @@ -161,7 +156,7 @@ func Test_OutputError(t *testing.T) { t.Run("Error Catalog error", func(t *testing.T) { err := snyk.NewBadRequestError("If you carry on like this you will ensure the wrath of OWASP, the God of Code Injection.") - err.Links = []string{"http://example.com/docs"} + err.Links = []string{"https://example.com/docs"} lipgloss.SetColorProfile(termenv.TrueColor) uiError := ui.OutputError(err) diff --git a/pkg/ui/progressbar.go b/pkg/ui/progressbar.go index d857091ee..cb0e76574 100644 --- a/pkg/ui/progressbar.go +++ b/pkg/ui/progressbar.go @@ -29,28 +29,29 @@ const ( // It is used to show the progress of some running task (or multiple). // Example (Infinite Progress without a value): // -// var pBar ProgressBar = ui.DefaultUi().NewProgressBar() +// pBar := ui.DefaultUi().NewProgressBar("CLI Downloader") // defer pBar.Clear() -// pBar.SetTitle("Downloading...") +// pBar.SetMessage("Downloading...") // _ = pBar.UpdateProgress(ui.InfiniteProgress) // // Example (with a value): // -// var pBar ProgressBar = ui.DefaultUi().NewProgressBar() +// pBar := ui.DefaultUi().NewProgressBar("CLI Downloader") // defer pBar.Clear() -// pBar.SetTitle("Downloading...") +// pBar.SetMessage("Downloading...") // for i := 0; i <= 50; i++ { // pBar.UpdateProgress(float64(i) / 100.0) // time.Sleep(time.Millisecond * 50) // } // -// pBar.SetTitle("Installing...") +// pBar.SetMessage("Installing...") // for i := 50; i <= 100; i++ { // pBar.UpdateProgress(float64(i) / 100.0) // time.Sleep(time.Millisecond * 50) // } // -// The title can be changed in the middle of the progress bar. +// The message can be changed in the middle of the progress bar. +// In some implementations no progress bar will be shown until the first UpdateProgress is called. type ProgressBar interface { // UpdateProgress updates the state of the progress bar. // The argument `progress` should be a float64 between 0 and 1, @@ -58,11 +59,13 @@ type ProgressBar interface { // Returns an error if the update operation fails. UpdateProgress(progress float64) error - // SetTitle sets the title of the progress bar, which is displayed next to the bar. - // The title provides context or description for the operation that is being tracked. - SetTitle(title string) + // SetMessage sets the message of the progress bar, which is displayed next to the bar. + // The message is displayed alongside the title passed in by `NewProgressBar(title string)`. + // The message provides context or description for the operation that is being tracked. + // Set to "" to clear the message. + SetMessage(message string) - // Clear removes the progress bar from the terminal. + // Clear removes the progress bar from the user interface. // Returns an error if the clearing operation fails. Clear() error } @@ -70,11 +73,14 @@ type ProgressBar interface { type emptyProgressBar struct{} func (emptyProgressBar) UpdateProgress(float64) error { return nil } -func (emptyProgressBar) SetTitle(string) {} +func (emptyProgressBar) SetMessage(string) {} func (emptyProgressBar) Clear() error { return nil } -func newProgressBar(writer io.Writer, t ProgressType, animated bool) *consoleProgressBar { - p := &consoleProgressBar{writer: writer} +func newProgressBar(writer io.Writer, title string, t ProgressType, animated bool) *consoleProgressBar { + p := &consoleProgressBar{ + writer: writer, + title: title, + } p.active.Store(true) p.animationRunning = false p.progressType = t @@ -85,6 +91,7 @@ func newProgressBar(writer io.Writer, t ProgressType, animated bool) *consolePro type consoleProgressBar struct { writer io.Writer title string + message string state int progress atomic.Pointer[float64] active atomic.Bool @@ -95,7 +102,7 @@ type consoleProgressBar struct { func (p *consoleProgressBar) UpdateProgress(progress float64) error { if !p.active.Load() { - return fmt.Errorf("progress not active") + return fmt.Errorf("progress bar not active") } p.progress.Store(&progress) @@ -110,7 +117,7 @@ func (p *consoleProgressBar) UpdateProgress(progress float64) error { return nil } -func (p *consoleProgressBar) SetTitle(title string) { p.title = title } +func (p *consoleProgressBar) SetMessage(message string) { p.message = message } func (p *consoleProgressBar) Clear() error { if !p.active.Load() { @@ -141,6 +148,12 @@ func (p *consoleProgressBar) update() { if len(p.title) > 0 { progressString += p.title + if len(p.message) > 0 { + progressString += " - " + } + } + if len(p.message) > 0 { + progressString += p.message } p.state++ diff --git a/pkg/ui/userinterface.go b/pkg/ui/userinterface.go index 78c6e456f..6930581c9 100644 --- a/pkg/ui/userinterface.go +++ b/pkg/ui/userinterface.go @@ -22,7 +22,7 @@ import ( type UserInterface interface { Output(output string) error OutputError(err error, opts ...Opts) error - NewProgressBar() ProgressBar + NewProgressBar(title string) ProgressBar Input(prompt string) (string, error) } @@ -38,10 +38,10 @@ func newConsoleUi(in io.Reader, out io.Writer, err io.Writer) UserInterface { reader: bufio.NewReader(in), } - defaultUi.progressBarFactory = func() ProgressBar { + defaultUi.progressBarFactory = func(title string) ProgressBar { if stderr, ok := err.(*os.File); ok { if isatty.IsTerminal(stderr.Fd()) || isatty.IsCygwinTerminal(stderr.Fd()) { - return newProgressBar(err, SpinnerType, true) + return newProgressBar(err, title, SpinnerType, true) } } @@ -54,7 +54,7 @@ func newConsoleUi(in io.Reader, out io.Writer, err io.Writer) UserInterface { type consoleUi struct { writer io.Writer errorWriter io.Writer - progressBarFactory func() ProgressBar + progressBarFactory func(title string) ProgressBar reader *bufio.Reader } @@ -102,8 +102,8 @@ func (ui *consoleUi) OutputError(err error, opts ...Opts) error { return utils.ErrorOf(fmt.Fprintln(ui.errorWriter, err.Error())) } -func (ui *consoleUi) NewProgressBar() ProgressBar { - return ui.progressBarFactory() +func (ui *consoleUi) NewProgressBar(title string) ProgressBar { + return ui.progressBarFactory(title) } func (ui *consoleUi) Input(prompt string) (string, error) {