Skip to content

Commit

Permalink
Enhance plugin installation UX
Browse files Browse the repository at this point in the history
PR updates the plugin installation UX,
- As part of plugin installation in-progress message,
  it adds total plugins needs to installed and the number of plugins being installed
  The in-progress message will disappear once the installation
  completes.
  Next plugin in-progress will be shown.
- No log messages will be showed once the plugin installation completes.
  • Loading branch information
chandrareddyp committed Apr 30, 2024
1 parent 79cfc16 commit 704b240
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 28 deletions.
6 changes: 5 additions & 1 deletion pkg/command/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -323,18 +323,22 @@ func syncContextPlugins(cmd *cobra.Command, contextType configtypes.ContextType,

errList := make([]error, 0)
log.Infof("Installing the following plugins recommended by context '%s':", ctxName)
pluginmanager.SetTotalPluginsToInstall(len(pluginsNeedToBeInstalled))
displayToBeInstalledPluginsAsTable(plugins, cmd.ErrOrStderr())
for i := range pluginsNeedToBeInstalled {
err = pluginmanager.InstallStandalonePlugin(pluginsNeedToBeInstalled[i].Name, pluginsNeedToBeInstalled[i].RecommendedVersion, pluginsNeedToBeInstalled[i].Target)
if err != nil {
errList = append(errList, err)
} else {
pluginmanager.IncrementPluginsInstalledCount(1)
}
}
err = kerrors.NewAggregate(errList)
if err == nil {
log.Success("Successfully installed all recommended plugins.")
}

pluginmanager.SetTotalPluginsToInstall(0)
pluginmanager.SetPluginsInstalledCount(0)
return err
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/command/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,10 @@ func newInstallPluginCmd() *cobra.Command {
return fmt.Errorf("the '%s' argument can only be used with the '--group' flag", cli.AllPlugins)
}

pluginmanager.SetTotalPluginsToInstall(1)
pluginVersion := version
err = pluginmanager.InstallStandalonePlugin(pluginName, pluginVersion, getTarget())
pluginmanager.StopSpinner()
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/pluginmanager/essentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func installPluginsFromEssentialPluginGroup(name, version string) (string, error
if err != nil {
return "", fmt.Errorf("failed to install plugins from group: %w", err)
}
log.Successf("successfully installed all plugins from group '%s'", groupWithVersion)

// If the installation is successful, return the group with version.
return groupWithVersion, nil
Expand Down
107 changes: 80 additions & 27 deletions pkg/pluginmanager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/Masterminds/semver"
Expand Down Expand Up @@ -56,7 +57,26 @@ const (
errorNoActiveContexForGivenContextType = "there is no active context for the given context type `%v`"
)

var totalPluginsToInstall = 0
var pluginsInstalledCount = 0
var execCommand = exec.Command
var spinner component.OutputWriterSpinner

func init() {
// Initialize global spinner
spinner = component.NewOutputWriterSpinner(component.WithOutputStream(os.Stderr))
runtime.SetFinalizer(spinner, func(s component.OutputWriterSpinner) {
if s != nil {
s.StopSpinner()
}
})
}

func StopSpinner() {
if spinner != nil {
spinner.StopSpinner()
}
}

type DeletePluginOptions struct {
Target configtypes.Target
Expand Down Expand Up @@ -475,6 +495,21 @@ func InstallStandalonePlugin(pluginName, version string, target configtypes.Targ
return installPlugin(pluginName, version, target, "")
}

// SetTotalPluginsToInstall sets the total number of plugins to install
func SetTotalPluginsToInstall(total int) {
totalPluginsToInstall = total
}

// SetPluginsInstalledCount sets the number of plugins installed already
func SetPluginsInstalledCount(count int) {
pluginsInstalledCount = count
}

// IncrementPluginsInstalledCount increments the number of plugins installed
func IncrementPluginsInstalledCount(count int) {
pluginsInstalledCount += count
}

// installs a plugin by name, version and target.
// If the contextName is not empty, it implies the plugin is a context-scope plugin, otherwise
// we are installing a standalone plugin.
Expand Down Expand Up @@ -560,7 +595,7 @@ func installPlugin(pluginName, version string, target configtypes.Target, contex
if len(matchedPlugins) == 1 {
return installOrUpgradePlugin(&matchedPlugins[0], matchedPlugins[0].RecommendedVersion, false)
}

// there can be only one plugin with the same name and target, so we can safely return the first one
for i := range matchedPlugins {
if matchedPlugins[i].Target == target {
return installOrUpgradePlugin(&matchedPlugins[i], matchedPlugins[i].RecommendedVersion, false)
Expand Down Expand Up @@ -592,31 +627,37 @@ func InstallPluginsFromGroup(pluginName, groupIDAndVersion string, options ...Pl
// from the database
groupIDAndVersion = fmt.Sprintf("%s-%s/%s:%s", pg.Vendor, pg.Publisher, pg.Name, pg.RecommendedVersion)
log.Infof("Installing plugins from plugin group '%s'", groupIDAndVersion)

return InstallPluginsFromGivenPluginGroup(pluginName, groupIDAndVersion, pg)
}

// InstallPluginsFromGivenPluginGroup installs either the specified plugin or all plugins from given plugin group plugins.
func InstallPluginsFromGivenPluginGroup(pluginName, groupIDAndVersion string, pg *plugininventory.PluginGroup) (string, error) {
numErrors := 0
numInstalled := 0
mandatoryPluginsExist := false
pluginExist := false
pluginsInstalledCount = 0

pluginsToInstall := make([]*plugininventory.PluginGroupPluginEntry, 0)
for _, plugin := range pg.Versions[pg.RecommendedVersion] {
if pluginName == cli.AllPlugins || pluginName == plugin.Name {
pluginExist = true
if plugin.Mandatory {
mandatoryPluginsExist = true
err := InstallStandalonePlugin(plugin.Name, plugin.Version, plugin.Target)
if err != nil {
numErrors++
log.Warningf("unable to install plugin '%s': %v", plugin.Name, err.Error())
} else {
numInstalled++
}
pluginsToInstall = append(pluginsToInstall, plugin) // Add mandatory plugin to the slice
}
}
}
SetTotalPluginsToInstall(len(pluginsToInstall))
SetPluginsInstalledCount(0)
for _, plugin := range pluginsToInstall {
err := InstallStandalonePlugin(plugin.Name, plugin.Version, plugin.Target)
if err != nil {
numErrors++
log.Warningf("unable to install plugin '%s': %v", plugin.Name, err.Error())
} else {
IncrementPluginsInstalledCount(1)
}
}

if !pluginExist {
return groupIDAndVersion, fmt.Errorf("plugin '%s' is not part of the group '%s'", pluginName, groupIDAndVersion)
Expand All @@ -633,9 +674,12 @@ func InstallPluginsFromGivenPluginGroup(pluginName, groupIDAndVersion string, pg
return groupIDAndVersion, fmt.Errorf("could not install %d plugin(s) from group '%s'", numErrors, groupIDAndVersion)
}

if numInstalled == 0 {
if pluginsInstalledCount == 0 {
return groupIDAndVersion, fmt.Errorf("plugin '%s' is not part of the group '%s'", pluginName, groupIDAndVersion)
}
SetTotalPluginsToInstall(0)
SetPluginsInstalledCount(0)
defer StopSpinner()

return groupIDAndVersion, nil
}
Expand Down Expand Up @@ -704,7 +748,7 @@ func getPluginInstallationMessage(p *discovery.Discovered, version string, isPlu
installingMsg = fmt.Sprintf("Installing plugin '%v:%v' %v(from cache)", p.Name, version, withTarget)
installedMsg = fmt.Sprintf("Installed plugin '%v:%v' %v(from cache)", p.Name, version, withTarget)
} else {
installingMsg = fmt.Sprintf("Plugin '%v:%v' %vis already installed. Reinitializing...", p.Name, version, withTarget)
installingMsg = fmt.Sprintf("Already installed : Plugin '%v:%v' %v", p.Name, version, withTarget)
installedMsg = fmt.Sprintf("Reinitialized plugin '%v:%v' %v", p.Name, version, withTarget)
}
} else {
Expand Down Expand Up @@ -734,25 +778,27 @@ func installOrUpgradePlugin(p *discovery.Discovered, version string, installTest
}

// Log message based on different installation conditions
installingMsg, installedMsg, errMsg := getPluginInstallationMessage(p, version, plugin != nil, isPluginAlreadyInstalled)
installingMsg, _, errMsg := getPluginInstallationMessage(p, version, plugin != nil, isPluginAlreadyInstalled)

var spinner component.OutputWriterSpinner
installingMsg = fmt.Sprintf("[%v/%v] %v", pluginsInstalledCount, totalPluginsToInstall, installingMsg)
errMsg = fmt.Sprintf("[%v/%v] %v", pluginsInstalledCount, totalPluginsToInstall, errMsg)
numPluginsInstalled := fmt.Sprintf("%d plugins installed out of %d", pluginsInstalledCount, totalPluginsToInstall)
numPluginsInstalledWithLog := fmt.Sprintf("%s%s", log.GetLogTypeIndicator(log.LogTypeERROR), numPluginsInstalled)
errMsg = fmt.Sprintf("%s\n%s", errMsg, numPluginsInstalledWithLog)

// Initialize the spinner if the spinner is allowed
if component.IsTTYEnabled() {
// Initialize the spinner
spinner = component.NewOutputWriterSpinner(component.WithOutputStream(os.Stderr),
component.WithSpinnerText(installingMsg),
component.WithSpinnerStarted())
if component.IsTTYEnabled() && spinner != nil {
spinner.SetText(installingMsg)
spinner.SetFinalText(errMsg, log.LogTypeERROR)
defer spinner.StopSpinner()
spinner.StartSpinner()
} else {
log.Info(installingMsg)
}

pluginErr := verifyInstallAndInitializePlugin(plugin, p, version, installTestPlugin)
if pluginErr == nil && spinner != nil {
spinner.SetFinalText(installedMsg, log.LogTypeINFO)
// unset the final text to avoid the spinner from showing the final text
spinner.SetFinalText("", "")
}
return pluginErr
}
Expand Down Expand Up @@ -1104,7 +1150,6 @@ func InstallPluginsFromLocalSource(pluginName, version string, target configtype
if err != nil {
return errors.Wrap(err, "unable to discover plugins")
}

var errList []error

var matchedPlugins []discovery.Discovered
Expand Down Expand Up @@ -1132,16 +1177,24 @@ func InstallPluginsFromLocalSource(pluginName, version string, target configtype
return installOrUpgradePlugin(&matchedPlugins[0], version, installTestPlugin)
}

var pluginsToInstall []*discovery.Discovered
for i := range matchedPlugins {
// Install all plugins otherwise include all matching plugins
if pluginName == cli.AllPlugins || matchedPlugins[i].Target == target {
err = installOrUpgradePlugin(&matchedPlugins[i], version, installTestPlugin)
if err != nil {
errList = append(errList, err)
}
plugin := matchedPlugins[i]
pluginsToInstall = append(pluginsToInstall, &plugin)
}
}

SetTotalPluginsToInstall(len(pluginsToInstall))
SetPluginsInstalledCount(0)
for _, plugin := range pluginsToInstall {
err = installOrUpgradePlugin(plugin, version, installTestPlugin)
if err != nil {
errList = append(errList, err)
}
}
SetTotalPluginsToInstall(0)
SetPluginsInstalledCount(0)
err = kerrors.NewAggregate(errList)
if err != nil {
return err
Expand Down

0 comments on commit 704b240

Please sign in to comment.