mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
81e92bff95
A failed update check never reaches the update history, because nothing was applied, so telemetry could not tell an install whose check is broken from one that was offered an update and ignored it. The 6.4.3-rc.1 installs looked healthy while their check failed on every call (#2282). The update manager now records a closed outcome for every check on the install's effective channel (up_to_date, available, no_release, rate_limited, network_error, metadata_error, skipped, error), tagging lookup failures without changing their error text, and ignores previews of another channel from Settings. Telemetry schema v18 exports update_channel, update_check_outcome and update_available. No version, URL or error text leaves the install. The Settings preview type, privacy disclosure, adoption report and subsystem contracts are updated to match; the Pulse Pro receiver change lands separately and the parity check passes against it.
97 lines
2.9 KiB
Go
97 lines
2.9 KiB
Go
package updates
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// Update check outcomes are a closed vocabulary shared with usage telemetry.
|
|
// A failed check never reaches the update history (nothing was applied), so
|
|
// without this record an install whose check is broken is indistinguishable
|
|
// from one that was offered an update and ignored it (#2285).
|
|
const (
|
|
UpdateCheckOutcomeNotChecked = "not_checked"
|
|
UpdateCheckOutcomeUpToDate = "up_to_date"
|
|
UpdateCheckOutcomeAvailable = "available"
|
|
UpdateCheckOutcomeNoRelease = "no_release"
|
|
UpdateCheckOutcomeRateLimited = "rate_limited"
|
|
UpdateCheckOutcomeNetworkError = "network_error"
|
|
UpdateCheckOutcomeMetadataError = "metadata_error"
|
|
UpdateCheckOutcomeSkipped = "skipped"
|
|
UpdateCheckOutcomeError = "error"
|
|
)
|
|
|
|
// UpdateCheckObservation is the content-free result of the most recent update
|
|
// check on the install's effective channel. It carries no version strings,
|
|
// URLs, or error text.
|
|
type UpdateCheckObservation struct {
|
|
Channel string
|
|
Outcome string
|
|
Available bool
|
|
CheckedAt time.Time
|
|
}
|
|
|
|
// updateCheckError tags a release lookup failure with its outcome category
|
|
// while leaving the wrapped error message unchanged.
|
|
type updateCheckError struct {
|
|
outcome string
|
|
err error
|
|
}
|
|
|
|
func (e *updateCheckError) Error() string { return e.err.Error() }
|
|
func (e *updateCheckError) Unwrap() error { return e.err }
|
|
|
|
func withUpdateCheckOutcome(outcome string, err error) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return &updateCheckError{outcome: outcome, err: err}
|
|
}
|
|
|
|
func updateCheckErrorOutcome(err error) string {
|
|
if errors.Is(err, errGitHubRateLimited) {
|
|
return UpdateCheckOutcomeRateLimited
|
|
}
|
|
var tagged *updateCheckError
|
|
if errors.As(err, &tagged) {
|
|
return tagged.outcome
|
|
}
|
|
return UpdateCheckOutcomeError
|
|
}
|
|
|
|
// recordUpdateCheck stores the outcome of a check on the effective channel.
|
|
// Checks that preview another channel (an explicit UI override) are ignored so
|
|
// the observation always describes what the install itself would be offered.
|
|
func (m *Manager) recordUpdateCheck(channel string, effective bool, outcome string, available bool) {
|
|
if !effective {
|
|
return
|
|
}
|
|
m.statusMu.Lock()
|
|
m.lastCheck = UpdateCheckObservation{
|
|
Channel: channel,
|
|
Outcome: outcome,
|
|
Available: available,
|
|
CheckedAt: time.Now().UTC(),
|
|
}
|
|
m.statusMu.Unlock()
|
|
}
|
|
|
|
// LastUpdateCheck returns the most recent effective-channel check result, with
|
|
// the currently effective channel and a not_checked outcome before any check.
|
|
func (m *Manager) LastUpdateCheck() UpdateCheckObservation {
|
|
if m == nil {
|
|
return UpdateCheckObservation{Outcome: UpdateCheckOutcomeNotChecked}
|
|
}
|
|
currentInfo, _ := GetCurrentVersion()
|
|
channel := m.resolveChannel("", currentInfo)
|
|
|
|
m.statusMu.RLock()
|
|
observation := m.lastCheck
|
|
m.statusMu.RUnlock()
|
|
|
|
if observation.Outcome == "" || observation.Channel != channel {
|
|
return UpdateCheckObservation{Channel: channel, Outcome: UpdateCheckOutcomeNotChecked}
|
|
}
|
|
return observation
|
|
}
|