From 40f5dc335852da62ac03ace75c0c76e0ea490659 Mon Sep 17 00:00:00 2001 From: Steven Palmesano <3100993+spalmesano0@users.noreply.github.com> Date: Thu, 16 Jul 2026 06:33:05 -0500 Subject: [PATCH] Add installed and available versions to self-service (#47526) **Related issue:** Resolves #45729 # Checklist for submitter - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually ## Summary by CodeRabbit * **New Features** * Self-service software table on My device page now shows both installed and available version columns for each app, making it easier to see current and updateable versions at a glance. * **Tests** * Added/updated tests covering installed and available version display scenarios, including cases with missing installed versions and when both versions are present to ensure correct UI rendering. --- changes/45729-self-service-show-version | 1 + .../SelfService/SelfService.tests.tsx | 49 +++++++++++++++++++ .../SelfServiceTableConfig.tsx | 37 ++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 changes/45729-self-service-show-version diff --git a/changes/45729-self-service-show-version b/changes/45729-self-service-show-version new file mode 100644 index 0000000000..e23795823d --- /dev/null +++ b/changes/45729-self-service-show-version @@ -0,0 +1 @@ +- Added installed version and available version columns to the self-service software table on the My device page. diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx index b88c9cd42c..ad71865b39 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/SelfService.tests.tsx @@ -289,6 +289,55 @@ describe("SelfService", () => { expect(moreDropdown).toBeDisabled(); }); + it("shows empty cell for installed version and package version for available version when installed_versions is null", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + installed_versions: null, + software_package: createMockHostSoftwarePackage({ + version: "1.1.0", + }), + }), + ], + }) + ); + + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + await screen.findAllByText("mock software 1.app"); + + expect(screen.getAllByText("---")).toHaveLength(2); + // TooltipTruncatedTextCell renders the value twice (visible + tooltip div) + expect(screen.getAllByText("1.1.0")).toHaveLength(2); + }); + + it("shows installed version and available version when both are present", async () => { + mockServer.use( + customDeviceSoftwareHandler({ + software: [ + createMockDeviceSoftware({ + installed_versions: [DEFAULT_INSTALLED_VERSION], // "1.0.0" + software_package: createMockHostSoftwarePackage({ + version: "1.1.0", + }), + }), + ], + }) + ); + + const render = createCustomRenderer({ withBackendMock: true }); + render(); + + await screen.findAllByText("mock software 1.app"); + + // TooltipTruncatedTextCell renders each value twice (visible + tooltip div); + // available version also appears in the update card above the table + expect(screen.getAllByText("1.0.0")).toHaveLength(2); + expect(screen.getAllByText("1.1.0")).toHaveLength(3); + }); + it("renders the self-service list for BYOD Account-Driven User Enrollment on mobile view", async () => { mockServer.use( customDeviceSoftwareHandler({ diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTableConfig.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTableConfig.tsx index a9a9e9489f..c72b175377 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTableConfig.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/components/SelfServiceTable/SelfServiceTableConfig.tsx @@ -12,6 +12,7 @@ import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config"; import HeaderCell from "components/TableContainer/DataTable/HeaderCell/HeaderCell"; import SoftwareNameCell from "components/TableContainer/DataTable/SoftwareNameCell"; +import VersionCell from "pages/SoftwarePage/components/tables/VersionCell"; import { ISWUninstallDetailsParentState } from "components/ActivityDetails/InstallDetails/SoftwareUninstallDetailsModal/SoftwareUninstallDetailsModal"; import InstallStatusCell from "../../../InstallStatusCell/InstallStatusCell"; @@ -25,6 +26,15 @@ type IStatusCellProps = CellProps< IDeviceSoftwareWithUiStatus, IDeviceSoftwareWithUiStatus["ui_status"] >; +type IVersionsCellProps = CellProps< + IDeviceSoftwareWithUiStatus, + IDeviceSoftwareWithUiStatus["installed_versions"] +>; +type IAvailableVersionCellProps = CellProps< + IDeviceSoftwareWithUiStatus, + | IDeviceSoftwareWithUiStatus["software_package"] + | IDeviceSoftwareWithUiStatus["app_store_app"] +>; type IActionCellProps = CellProps< IDeviceSoftwareWithUiStatus, IDeviceSoftwareWithUiStatus["status"] @@ -114,6 +124,33 @@ export const generateSoftwareTableHeaders = ({ /> ), }, + { + Header: "Installed version", + id: "version", + disableSortBy: true, + // we use function as accessor because we have two columns that + // need to access the same data. This is not supported with a string + // accessor. + accessor: (originalRow) => originalRow.installed_versions, + Cell: (cellProps: IVersionsCellProps) => { + return ; + }, + }, + { + Header: "Available version", + id: "available_version", + disableSortBy: true, + accessor: (originalRow) => + originalRow.software_package || originalRow.app_store_app, + Cell: (cellProps: IAvailableVersionCellProps) => { + const softwareTitle = cellProps.row.original; + const installerData = + softwareTitle.software_package ?? softwareTitle.app_store_app; + return ( + + ); + }, + }, { Header: "Actions", accessor: "status",