Fleet UI: Fix vulnfilter not supported (#31401)

---------

Co-authored-by: Ian Littman <iansltx@gmail.com>
This commit is contained in:
RachelElysia 2025-08-04 09:31:15 -04:00 committed by GitHub
parent e94d23e9db
commit 3ce6768845
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 190 additions and 15 deletions

View file

@ -998,6 +998,7 @@ const HostDetailsPage = ({
onSetSelectedHostSWForInventoryVersions
}
hostTeamId={host.team_id || 0}
hostMdmEnrollmentStatus={host.mdm.enrollment_status}
/>
{isDarwinHost && macadmins?.munki?.version && (
<MunkiIssuesCard

View file

@ -13,6 +13,7 @@ import deviceAPI, {
} from "services/entities/device_user";
import { IHostSoftware, ISoftware } from "interfaces/software";
import { HostPlatform, isAndroid, isIPadOrIPhone } from "interfaces/platform";
import { MdmEnrollmentStatus } from "interfaces/mdm";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import { getNextLocationPath } from "utilities/helpers";
@ -35,6 +36,7 @@ import {
import { generateSoftwareTableHeaders as generateHostSoftwareTableConfig } from "./HostSoftwareTableConfig";
import { generateSoftwareTableHeaders as generateDeviceSoftwareTableConfig } from "./DeviceSoftwareTableConfig";
import HostSoftwareTable from "./HostSoftwareTable";
import { getSoftwareSubheader } from "./helpers";
const baseClass = "software-card";
@ -59,6 +61,8 @@ interface IHostSoftwareProps {
onShowInventoryVersions: (software: IHostSoftware) => void;
isSoftwareEnabled?: boolean;
isMyDevicePage?: boolean;
/** Used to show custom Software card header */
hostMdmEnrollmentStatus?: MdmEnrollmentStatus | null;
}
const DEFAULT_SEARCH_QUERY = "";
@ -121,6 +125,7 @@ const HostSoftware = ({
onShowInventoryVersions,
isSoftwareEnabled = false,
isMyDevicePage = false,
hostMdmEnrollmentStatus = null,
}: IHostSoftwareProps) => {
const { isPremiumTier } = useContext(AppContext);
@ -334,7 +339,11 @@ const HostSoftware = ({
>
<CardHeader
header="Software"
subheader="Software installed on your device"
subheader={getSoftwareSubheader({
platform,
isMyDevicePage: true,
hostMdmEnrollmentStatus,
})}
/>
{renderHostSoftware()}
</Card>
@ -344,7 +353,13 @@ const HostSoftware = ({
return (
<div className={baseClass}>
{!isAndroid(platform) && (
<CardHeader subheader="Software installed on this host" />
<CardHeader
subheader={getSoftwareSubheader({
platform,
isMyDevicePage: false,
hostMdmEnrollmentStatus,
})}
/>
)}
{renderHostSoftware()}
</div>

View file

@ -65,4 +65,31 @@ describe("HostSoftwareTable", () => {
});
expect(screen.getByRole("button", { name: /filter/i })).toBeInTheDocument();
});
it("renders VulnsNotSupported when vulns filter applied and platform is iPad/iPhone", () => {
renderWithContext({
platform: "ipados",
vulnFilters: { vulnerable: true },
data: createMockGetHostSoftwareResponse({
count: 0,
software: [],
}),
});
expect(
screen.getByText(/vulnerabilities are not supported/i)
).toBeInTheDocument();
});
// This includes empty state for BYOD iphone/ipads
it("renders generic empty state when no filters are applied and platform is iPad/iPhone", () => {
renderWithContext({
platform: "ipados",
data: createMockGetHostSoftwareResponse({
count: 0,
software: [],
}),
});
expect(screen.getByText(/no software detected/i)).toBeInTheDocument();
});
});

View file

@ -42,6 +42,27 @@ const baseClass = "host-software-table";
interface IHostSoftwareRowProps extends Row {
original: IHostSoftware;
}
interface IEmptyComponentProps {
hasVulnFilters: boolean;
platform: HostPlatform;
searchQuery: string;
}
const EmptyComponent = React.memo(
({ hasVulnFilters, platform, searchQuery }: IEmptyComponentProps) => {
const vulnFilterAndNotSupported =
hasVulnFilters && isIPadOrIPhone(platform);
return vulnFilterAndNotSupported ? (
<VulnsNotSupported
platformText={APPLE_PLATFORM_DISPLAY_NAMES[platform as ApplePlatform]}
/>
) : (
<EmptySoftwareTable noSearchQuery={searchQuery === ""} />
);
}
);
interface IHostSoftwareTableProps {
tableConfig: any; // TODO: type
data?: IGetHostSoftwareResponse | IGetDeviceSoftwareResponse;
@ -149,17 +170,6 @@ const HostSoftwareTable = ({
return <TableCount name="items" count={count} />;
}, [count, isSoftwareNotDetected]);
const memoizedEmptyComponent = useCallback(() => {
const vulnFilterAndNotSupported = isIPadOrIPhone(platform);
return vulnFilterAndNotSupported ? (
<VulnsNotSupported
platformText={APPLE_PLATFORM_DISPLAY_NAMES[platform as ApplePlatform]}
/>
) : (
<EmptySoftwareTable noSearchQuery={searchQuery === ""} />
);
}, [platform, searchQuery]);
// Determines if a user should be able to filter or search in the table
const hasData = data && data.software.length > 0;
const hasQuery = searchQuery !== "";
@ -223,7 +233,13 @@ const HostSoftwareTable = ({
pageSize={DEFAULT_PAGE_SIZE}
inputPlaceHolder="Search by name or vulnerability (CVE)"
onQueryChange={onQueryChange}
emptyComponent={memoizedEmptyComponent}
emptyComponent={() => (
<EmptyComponent
hasVulnFilters={hasVulnFilters}
platform={platform}
searchQuery={searchQuery}
/>
)}
customFiltersButton={
showFilterHeaders ? renderCustomFiltersButton : undefined
}

View file

@ -2,7 +2,7 @@ import {
createMockHostSoftware,
createMockHostSoftwarePackage,
} from "__mocks__/hostMock";
import { compareVersions, getUiStatus } from "./helpers";
import { compareVersions, getUiStatus, getSoftwareSubheader } from "./helpers";
describe("compareVersions", () => {
it("correctly compares patch increments", () => {
@ -212,3 +212,85 @@ describe("getUiStatus", () => {
expect(getUiStatus(sw, true)).toBe("uninstalled");
});
});
describe("getSoftwareSubheader", () => {
test("iOS device, MDM status 'On (personal)', my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (personal)",
isMyDevicePage: true,
});
expect(result).toBe(
"Software installed on your work profile (Managed Apple Account)."
);
});
test("iOS device, MDM status 'On (personal)', NOT my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (personal)",
isMyDevicePage: false,
});
expect(result).toBe(
"Software installed on work profile (Managed Apple Account)."
);
});
test("iOS device, MDM status 'On (manual)', my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (manual)",
isMyDevicePage: true,
});
expect(result).toBe(
"Software installed on your device. Built-in apps (e.g. Calculator) aren't included."
);
});
test("iOS device, MDM status 'On (manual)', NOT my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "On (manual)",
isMyDevicePage: false,
});
expect(result).toBe(
"Software installed on this host. Built-in apps (e.g. Calculator) aren't included."
);
});
test("iOS device, MDM status not special, my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "Off",
isMyDevicePage: true,
});
expect(result).toBe("Software installed on your device.");
});
test("iOS device, MDM status not special, NOT my device page", () => {
const result = getSoftwareSubheader({
platform: "ios",
hostMdmEnrollmentStatus: "Off",
isMyDevicePage: false,
});
expect(result).toBe("Software installed on this host.");
});
test("default (NOT iOS device) my device page", () => {
const result = getSoftwareSubheader({
platform: "windows",
hostMdmEnrollmentStatus: "Off",
isMyDevicePage: true,
});
expect(result).toBe("Software installed on your device.");
});
test("default (NOT iOS device) NOT my device page", () => {
const result = getSoftwareSubheader({
platform: "windows",
hostMdmEnrollmentStatus: "Off",
isMyDevicePage: false,
});
expect(result).toBe("Software installed on this host.");
});
});

View file

@ -1,6 +1,8 @@
import { QueryParams } from "utilities/url";
import { Row } from "react-table";
import { flatMap } from "lodash";
import { HostPlatform, isIPadOrIPhone } from "interfaces/platform";
import { MdmEnrollmentStatus } from "interfaces/mdm";
import {
IHostSoftware,
IHostSoftwareUiStatus,
@ -341,3 +343,35 @@ export const installStatusSortType = (
if (safeIndexA > safeIndexB) return 1;
return 0;
};
interface IGetSoftwareSubheader {
platform: HostPlatform;
hostMdmEnrollmentStatus: MdmEnrollmentStatus | null;
isMyDevicePage?: boolean;
}
/**
* Returns a subheader string for the software page based on platform and MDM enrollment status.
* Handles iOS-specific cases for personal and manual MDM enrollment.
*/
export const getSoftwareSubheader = ({
platform,
hostMdmEnrollmentStatus,
isMyDevicePage,
}: IGetSoftwareSubheader): string => {
if (isIPadOrIPhone(platform)) {
if (hostMdmEnrollmentStatus === "On (personal)") {
return isMyDevicePage
? "Software installed on your work profile (Managed Apple Account)."
: "Software installed on work profile (Managed Apple Account).";
}
if (hostMdmEnrollmentStatus === "On (manual)") {
return isMyDevicePage
? "Software installed on your device. Built-in apps (e.g. Calculator) aren't included."
: "Software installed on this host. Built-in apps (e.g. Calculator) aren't included.";
}
}
return isMyDevicePage
? "Software installed on your device."
: "Software installed on this host.";
};