Fixed setup experience UI hanging when a step is removed from the payload (#29385)

This is one facet of https://github.com/fleetdm/fleet/issues/28664

When you run gitops or otherwise just do something to remove a software
installer from the setup experience list while it is running and then
delete that software installer, setup experience fails to proceed past
the "steps" screen because it is expecting all software in the initial
payload to complete installation even if those installers were not in
the current payload.

This now tracks the status of items in the current payload and as a
small enhancement deletes the items that disappear
from the payload, which seemed like the best thing to do

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Manual QA for all new/changed functionality
- For Orbit and Fleet Desktop changes:
- [x] Make sure fleetd is compatible with the latest released version of
Fleet (see [Must
rule](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/workflows/fleetd-development-and-release-strategy.md)).
- [x] Orbit runs on macOS, Linux and Windows. Check if the orbit
feature/bugfix should only apply to one platform (`runtime.GOOS`).
- [x] Manual QA must be performed in the three main OSs, macOS, Windows
and Linux.
- [x] Auto-update manual QA, from released version of component to new
version (see [tools/tuf/test](../tools/tuf/test/README.md)).
This commit is contained in:
Jordan Montgomery
2025-05-22 14:58:17 -04:00
committed by GitHub
parent a202e31929
commit f4e032b6cd
2 changed files with 28 additions and 23 deletions
@@ -0,0 +1 @@
* Fixed an issue where the setup experience window would never finish and close if a software installer was deleted while it was running
+27 -23
View File
@@ -33,9 +33,7 @@ type SetupExperiencer struct {
// its Run method is called within a WaitGroup,
// and no other parts of Orbit need access to this field (or any other parts of the
// SetupExperiencer), it's OK to not protect this with a lock.
sd *swiftdialog.SwiftDialog
// Name of each step -> is that step done
steps map[string]bool
sd *swiftdialog.SwiftDialog
uiSteps map[string]swiftdialog.ListItem
started bool
}
@@ -44,7 +42,6 @@ func NewSetupExperiencer(client Client, rootDirPath string) *SetupExperiencer {
return &SetupExperiencer{
OrbitClient: client,
closeChan: make(chan struct{}),
steps: make(map[string]bool),
uiSteps: make(map[string]swiftdialog.ListItem),
rootDirPath: rootDirPath,
}
@@ -109,15 +106,11 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
}
}
s.steps["bootstrap"] = true
if isPending, name := anyProfilePending(payload.ConfigurationProfiles); isPending {
log.Info().Msg(fmt.Sprintf("setup experience: profile pending: %s", name))
return nil
}
s.steps["config_profiles"] = true
if payload.AccountConfiguration != nil {
if payload.AccountConfiguration.Status != fleet.MDMAppleStatusAcknowledged &&
payload.AccountConfiguration.Status != fleet.MDMAppleStatusError &&
@@ -128,7 +121,9 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
}
}
s.steps["account_config"] = true
// Note that we are setting this based on the current payload only just in case something
// was removed from the payload that was there earlier(e.g. a deleted software title).
allStepsDone := true
// Now render the UI for the software and script.
if len(payload.Software) > 0 || payload.Script != nil {
@@ -145,6 +140,26 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
steps = append(steps, payload.Script)
}
// Check for any items that were in the payload that are no longer there. This can happen
// if a software title was deleted, for instance
for uiStepName, uiStep := range s.uiSteps {
uiStepExistsInPayload := false
for _, step := range steps {
if uiStep.Title == step.Name {
uiStepExistsInPayload = true
break
}
}
if !uiStepExistsInPayload {
log.Info().Msgf("Setup Experience: list item %s removed from payload", uiStep.Title)
err = s.sd.DeleteListItemByTitle(uiStep.Title)
if err != nil {
log.Info().Err(err).Msg("deleting list item removed from payload from setup experience UI")
}
delete(s.uiSteps, uiStepName)
}
}
for _, step := range steps {
currentStepState := resultToListItem(step)
if priorStepState, ok := s.uiSteps[step.Name]; ok {
@@ -156,7 +171,6 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
}
} else {
log.Info().Msgf("setup experience: no change in status for %s", step.Name)
continue
}
} else {
err = s.sd.AddListItem(currentStepState)
@@ -164,16 +178,16 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
log.Info().Err(err).Msg("adding list item in setup experience UI")
}
s.uiSteps[step.Name] = currentStepState
s.steps[step.Name] = false
}
if step.Status == fleet.SetupExperienceStatusFailure || step.Status == fleet.SetupExperienceStatusSuccess {
stepsDone++
s.steps[step.Name] = true
// The swiftDialog progress bar is out of 100
for range int(float32(1) / float32(len(steps)) * 100) {
prog++
}
} else {
allStepsDone = false
}
}
@@ -193,7 +207,7 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
// If we get here, we can render the "done" UI.
if s.allStepsDone() {
if allStepsDone {
if err := s.sd.SetMessage(doneMessage); err != nil {
log.Info().Err(err).Msg("setting message in setup experience UI")
}
@@ -229,16 +243,6 @@ func (s *SetupExperiencer) Run(oc *fleet.OrbitConfig) error {
return nil
}
func (s *SetupExperiencer) allStepsDone() bool {
for _, done := range s.steps {
if !done {
return false
}
}
return true
}
func anyProfilePending(profiles []*fleet.SetupExperienceConfigurationProfileResult) (bool, string) {
for _, p := range profiles {
if p.Status == fleet.MDMDeliveryPending {