Skip to content

Commit 9ff9639

Browse files
committed
Enhance plugin installation UX
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.
1 parent 397ea59 commit 9ff9639

4 files changed

Lines changed: 88 additions & 28 deletions

File tree

pkg/command/context.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,18 +317,22 @@ func syncContextPlugins(cmd *cobra.Command, contextType configtypes.ContextType,
317317

318318
errList := make([]error, 0)
319319
log.Infof("Installing the following plugins recommended by context '%s':", ctxName)
320+
pluginmanager.SetTotalPluginsToInstall(len(pluginsNeedToBeInstalled))
320321
displayToBeInstalledPluginsAsTable(plugins, cmd.ErrOrStderr())
321322
for i := range pluginsNeedToBeInstalled {
322323
err = pluginmanager.InstallStandalonePlugin(pluginsNeedToBeInstalled[i].Name, pluginsNeedToBeInstalled[i].RecommendedVersion, pluginsNeedToBeInstalled[i].Target)
323324
if err != nil {
324325
errList = append(errList, err)
326+
} else {
327+
pluginmanager.IncrementPluginsInstalledCount(1)
325328
}
326329
}
327330
err = kerrors.NewAggregate(errList)
328331
if err == nil {
329332
log.Success("Successfully installed all recommended plugins.")
330333
}
331-
334+
pluginmanager.SetTotalPluginsToInstall(0)
335+
pluginmanager.SetPluginsInstalledCount(0)
332336
return err
333337
}
334338

pkg/command/plugin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,8 +276,10 @@ func newInstallPluginCmd() *cobra.Command {
276276
return fmt.Errorf("the '%s' argument can only be used with the '--group' flag", cli.AllPlugins)
277277
}
278278

279+
pluginmanager.SetTotalPluginsToInstall(1)
279280
pluginVersion := version
280281
err = pluginmanager.InstallStandalonePlugin(pluginName, pluginVersion, getTarget())
282+
pluginmanager.StopSpinner()
281283
if err != nil {
282284
return err
283285
}

pkg/pluginmanager/essentials.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func installPluginsFromEssentialPluginGroup(name, version string) (string, error
6969
if err != nil {
7070
return "", fmt.Errorf("failed to install plugins from group: %w", err)
7171
}
72+
log.Successf("successfully installed all plugins from group '%s'", groupWithVersion)
7273

7374
// If the installation is successful, return the group with version.
7475
return groupWithVersion, nil

pkg/pluginmanager/manager.go

Lines changed: 80 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"os/exec"
1313
"path/filepath"
14+
"runtime"
1415
"strings"
1516

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

60+
var totalPluginsToInstall = 0
61+
var pluginsInstalledCount = 0
5962
var execCommand = exec.Command
63+
var spinner component.OutputWriterSpinner
64+
65+
func init() {
66+
// Initialize global spinner
67+
spinner = component.NewOutputWriterSpinner(component.WithOutputStream(os.Stderr))
68+
runtime.SetFinalizer(spinner, func(s component.OutputWriterSpinner) {
69+
if s != nil {
70+
s.StopSpinner()
71+
}
72+
})
73+
}
74+
75+
func StopSpinner() {
76+
if spinner != nil {
77+
spinner.StopSpinner()
78+
}
79+
}
6080

6181
type DeletePluginOptions struct {
6282
Target configtypes.Target
@@ -475,6 +495,21 @@ func InstallStandalonePlugin(pluginName, version string, target configtypes.Targ
475495
return installPlugin(pluginName, version, target, "")
476496
}
477497

498+
// SetTotalPluginsToInstall sets the total number of plugins to install
499+
func SetTotalPluginsToInstall(total int) {
500+
totalPluginsToInstall = total
501+
}
502+
503+
// SetPluginsInstalledCount sets the number of plugins installed already
504+
func SetPluginsInstalledCount(count int) {
505+
pluginsInstalledCount = count
506+
}
507+
508+
// IncrementPluginsInstalledCount increments the number of plugins installed
509+
func IncrementPluginsInstalledCount(count int) {
510+
pluginsInstalledCount += count
511+
}
512+
478513
// installs a plugin by name, version and target.
479514
// If the contextName is not empty, it implies the plugin is a context-scope plugin, otherwise
480515
// we are installing a standalone plugin.
@@ -560,7 +595,7 @@ func installPlugin(pluginName, version string, target configtypes.Target, contex
560595
if len(matchedPlugins) == 1 {
561596
return installOrUpgradePlugin(&matchedPlugins[0], matchedPlugins[0].RecommendedVersion, false)
562597
}
563-
598+
// there can be only one plugin with the same name and target, so we can safely return the first one
564599
for i := range matchedPlugins {
565600
if matchedPlugins[i].Target == target {
566601
return installOrUpgradePlugin(&matchedPlugins[i], matchedPlugins[i].RecommendedVersion, false)
@@ -592,31 +627,37 @@ func InstallPluginsFromGroup(pluginName, groupIDAndVersion string, options ...Pl
592627
// from the database
593628
groupIDAndVersion = fmt.Sprintf("%s-%s/%s:%s", pg.Vendor, pg.Publisher, pg.Name, pg.RecommendedVersion)
594629
log.Infof("Installing plugins from plugin group '%s'", groupIDAndVersion)
595-
596630
return InstallPluginsFromGivenPluginGroup(pluginName, groupIDAndVersion, pg)
597631
}
598632

599633
// InstallPluginsFromGivenPluginGroup installs either the specified plugin or all plugins from given plugin group plugins.
600634
func InstallPluginsFromGivenPluginGroup(pluginName, groupIDAndVersion string, pg *plugininventory.PluginGroup) (string, error) {
601635
numErrors := 0
602-
numInstalled := 0
603636
mandatoryPluginsExist := false
604637
pluginExist := false
638+
pluginsInstalledCount = 0
639+
640+
pluginsToInstall := make([]*plugininventory.PluginGroupPluginEntry, 0)
605641
for _, plugin := range pg.Versions[pg.RecommendedVersion] {
606642
if pluginName == cli.AllPlugins || pluginName == plugin.Name {
607643
pluginExist = true
608644
if plugin.Mandatory {
609645
mandatoryPluginsExist = true
610-
err := InstallStandalonePlugin(plugin.Name, plugin.Version, plugin.Target)
611-
if err != nil {
612-
numErrors++
613-
log.Warningf("unable to install plugin '%s': %v", plugin.Name, err.Error())
614-
} else {
615-
numInstalled++
616-
}
646+
pluginsToInstall = append(pluginsToInstall, plugin) // Add mandatory plugin to the slice
617647
}
618648
}
619649
}
650+
SetTotalPluginsToInstall(len(pluginsToInstall))
651+
SetPluginsInstalledCount(0)
652+
for _, plugin := range pluginsToInstall {
653+
err := InstallStandalonePlugin(plugin.Name, plugin.Version, plugin.Target)
654+
if err != nil {
655+
numErrors++
656+
log.Warningf("unable to install plugin '%s': %v", plugin.Name, err.Error())
657+
} else {
658+
IncrementPluginsInstalledCount(1)
659+
}
660+
}
620661

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

636-
if numInstalled == 0 {
677+
if pluginsInstalledCount == 0 {
637678
return groupIDAndVersion, fmt.Errorf("plugin '%s' is not part of the group '%s'", pluginName, groupIDAndVersion)
638679
}
680+
SetTotalPluginsToInstall(0)
681+
SetPluginsInstalledCount(0)
682+
defer StopSpinner()
639683

640684
return groupIDAndVersion, nil
641685
}
@@ -704,7 +748,7 @@ func getPluginInstallationMessage(p *discovery.Discovered, version string, isPlu
704748
installingMsg = fmt.Sprintf("Installing plugin '%v:%v' %v(from cache)", p.Name, version, withTarget)
705749
installedMsg = fmt.Sprintf("Installed plugin '%v:%v' %v(from cache)", p.Name, version, withTarget)
706750
} else {
707-
installingMsg = fmt.Sprintf("Plugin '%v:%v' %vis already installed. Reinitializing...", p.Name, version, withTarget)
751+
installingMsg = fmt.Sprintf("Already installed : Plugin '%v:%v' %v", p.Name, version, withTarget)
708752
installedMsg = fmt.Sprintf("Reinitialized plugin '%v:%v' %v", p.Name, version, withTarget)
709753
}
710754
} else {
@@ -734,25 +778,27 @@ func installOrUpgradePlugin(p *discovery.Discovered, version string, installTest
734778
}
735779

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

739-
var spinner component.OutputWriterSpinner
783+
installingMsg = fmt.Sprintf("[%v/%v] %v", pluginsInstalledCount, totalPluginsToInstall, installingMsg)
784+
errMsg = fmt.Sprintf("[%v/%v] %v", pluginsInstalledCount, totalPluginsToInstall, errMsg)
785+
numPluginsInstalled := fmt.Sprintf("%d plugins installed out of %d", pluginsInstalledCount, totalPluginsToInstall)
786+
numPluginsInstalledWithLog := fmt.Sprintf("%s%s", log.GetLogTypeIndicator(log.LogTypeERROR), numPluginsInstalled)
787+
errMsg = fmt.Sprintf("%s\n%s", errMsg, numPluginsInstalledWithLog)
740788

741789
// Initialize the spinner if the spinner is allowed
742-
if component.IsTTYEnabled() {
743-
// Initialize the spinner
744-
spinner = component.NewOutputWriterSpinner(component.WithOutputStream(os.Stderr),
745-
component.WithSpinnerText(installingMsg),
746-
component.WithSpinnerStarted())
790+
if component.IsTTYEnabled() && spinner != nil {
791+
spinner.SetText(installingMsg)
747792
spinner.SetFinalText(errMsg, log.LogTypeERROR)
748-
defer spinner.StopSpinner()
793+
spinner.StartSpinner()
749794
} else {
750795
log.Info(installingMsg)
751796
}
752797

753798
pluginErr := verifyInstallAndInitializePlugin(plugin, p, version, installTestPlugin)
754799
if pluginErr == nil && spinner != nil {
755-
spinner.SetFinalText(installedMsg, log.LogTypeINFO)
800+
// unset the final text to avoid the spinner from showing the final text
801+
spinner.SetFinalText("", "")
756802
}
757803
return pluginErr
758804
}
@@ -1104,7 +1150,6 @@ func InstallPluginsFromLocalSource(pluginName, version string, target configtype
11041150
if err != nil {
11051151
return errors.Wrap(err, "unable to discover plugins")
11061152
}
1107-
11081153
var errList []error
11091154

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

1180+
var pluginsToInstall []*discovery.Discovered
11351181
for i := range matchedPlugins {
11361182
// Install all plugins otherwise include all matching plugins
11371183
if pluginName == cli.AllPlugins || matchedPlugins[i].Target == target {
1138-
err = installOrUpgradePlugin(&matchedPlugins[i], version, installTestPlugin)
1139-
if err != nil {
1140-
errList = append(errList, err)
1141-
}
1184+
plugin := matchedPlugins[i]
1185+
pluginsToInstall = append(pluginsToInstall, &plugin)
11421186
}
11431187
}
1144-
1188+
SetTotalPluginsToInstall(len(pluginsToInstall))
1189+
SetPluginsInstalledCount(0)
1190+
for _, plugin := range pluginsToInstall {
1191+
err = installOrUpgradePlugin(plugin, version, installTestPlugin)
1192+
if err != nil {
1193+
errList = append(errList, err)
1194+
}
1195+
}
1196+
SetTotalPluginsToInstall(0)
1197+
SetPluginsInstalledCount(0)
11451198
err = kerrors.NewAggregate(errList)
11461199
if err != nil {
11471200
return err

0 commit comments

Comments
 (0)