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",