Fix and improve handling of "installed" software scenarios (#36516)

**Related issue:** Resolves #31973 

Addresses a few closely related issues around determining the
appropriate UI to display for installed software

- Pass the inherited host's `softwareUpdatedAt` to the handler
responsible for determining the software's "UI display status" so it can
calculate whether it is "recently installed", as intended.
- In the scenario where the above is _not_ passed in for some reason,
default to "installed" ui display status any time the software's status
is "installed"
- Add a check that ensures the above default "installed" ui status is
captured even when `installed_versions` is `null`. This scenario
previously returned an "uninstalled" ui display status, which caused
this bug initially

<img width="1720" height="880" alt="Screenshot 2025-12-01 at 12 02
29 PM"
src="https://github.com/user-attachments/assets/20874ff5-133c-4b74-8634-2cdc2d3a1497"
/>

- [x] Changes file added for user-visible changes in `changes/
- [x] Added/updated automated tests
- [x] QA'd all new/changed functionality manually
This commit is contained in:
jacobshandling 2025-12-01 13:17:42 -08:00 committed by GitHub
parent 5a0720cefc
commit d87f69563f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 20 additions and 12 deletions

View file

@ -0,0 +1,2 @@
- Improved handling of softawre install statuses in the UI, fixing a bug where "installed" software
sometimes showed up as "uninstalled" when certain other pieces of data were not also present

View file

@ -174,9 +174,9 @@ const HostSoftwareLibrary = ({
if (!hostSoftwareLibraryRes) return [];
return hostSoftwareLibraryRes.software.map((software) => ({
...software,
ui_status: getUiStatus(software, isHostOnline),
ui_status: getUiStatus(software, isHostOnline, softwareUpdatedAt),
}));
}, [hostSoftwareLibraryRes, isHostOnline]);
}, [hostSoftwareLibraryRes, isHostOnline, softwareUpdatedAt]);
const pendingSoftwareSetRef = useRef<Set<string>>(new Set()); // Track for polling
const pollingTimeoutIdRef = useRef<NodeJS.Timeout | null>(null);

View file

@ -309,6 +309,13 @@ describe("getUiStatus", () => {
});
expect(getUiStatus(sw, true)).toBe("installed");
});
it("returns 'installed' for regular package, no installed versions present", () => {
const sw = createMockHostSoftware({
status: "installed",
installed_versions: null,
});
expect(getUiStatus(sw, true)).toBe("installed");
});
it("returns 'installed' for regular package, installed version higher than library version", () => {
const sw = createMockHostSoftware({

View file

@ -249,7 +249,7 @@ export const getUiStatus = (
}
// **Recently_uninstalled check comes BEFORE update_available**
if (software.status === null && lastUninstallDate && hostSoftwareUpdatedAt) {
if (status === null && lastUninstallDate && hostSoftwareUpdatedAt) {
const newerDate = getNewerDate(hostSoftwareUpdatedAt, lastUninstallDate);
if (newerDate === lastUninstallDate || recentUserActionDetected) {
return "recently_uninstalled";
@ -276,19 +276,18 @@ export const getUiStatus = (
}
// 6. Recently installed (not an update)
if (
software.status === "installed" &&
lastInstallDate &&
hostSoftwareUpdatedAt
) {
const newerDate = getNewerDate(hostSoftwareUpdatedAt, lastInstallDate);
if (newerDate === lastInstallDate || recentUserActionDetected) {
return "recently_installed";
if (status === "installed") {
if (lastInstallDate && hostSoftwareUpdatedAt) {
const newerDate = getNewerDate(hostSoftwareUpdatedAt, lastInstallDate);
if (newerDate === lastInstallDate || recentUserActionDetected) {
return "recently_installed";
}
}
return "installed";
}
// 7. Tarballs edge case
if (software.source === "tgz_packages" && software.status === "installed") {
if (source === "tgz_packages" && status === "installed") {
return "installed";
}