Clarify "Not supported" on Hosts page by adding tooltip (#49301)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #39987 - Added tooltips to the "Agent," "Last restarted," and "Status" column headers on the Hosts page explaining which platforms are supported and why. - On the Host details page, vitals with a "Not supported" value are now hidden instead of shown. - Fixed the "Last restarted" vital showing on ChromeOS hosts, where it's not actually collected. - Updated the "Last opened" tooltip on the Host details Software table to explain why it's only supported for native macOS, Windows, and Linux apps and packages. - Remove cellProps.rows.length === 1 workaround (which suppresses the tooltip whenever the table has exactly one row) by adding the correct CSS which removes the tooltip overflowing if host table is only 1 row # Checklist for submitter If some of the following don't apply, delete the relevant line. - [x] Changes file added for user-visible changes in `changes/`, `orbit/changes/` or `ee/fleetd-chrome/changes`. See [Changes files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files) for more information. ## Testing - [x] Added/updated automated tests - [x] QA'd all new/changed functionality manually <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added/updated explanatory tooltips for Hosts table column headers (Agent, Last restarted, Status) with clearer supported-platform wording. - Clarified “Last opened” tooltip scope to native macOS, Windows, and Linux app/package entries. - **Bug Fixes** - Removed “Last restarted” from Host details for ChromeOS hosts when the value isn’t collected. - Prevented vitals rows from rendering when their values resolve to “Not supported,” and tightened “Last restarted” platform visibility. - Fixed tooltip overflow/positioning in the single-row Host software table case. - **Tests** - Updated and expanded vitals/header coverage to match the new display rules. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -397,26 +397,23 @@ const allHostTableHeaders = (teamId?: number): IHostTableColumnConfig[] => [
|
||||
// Status
|
||||
{
|
||||
title: "Status",
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
Header: () => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Online hosts will respond to a live report. Currently only
|
||||
supported for macOS, Windows, and Linux.
|
||||
Only supported on hosts that run Fleet's agent: macOS,
|
||||
Windows, Linux, and ChromeOS.
|
||||
</>
|
||||
}
|
||||
className="status-header"
|
||||
tooltipClass="host-table-header-tooltip"
|
||||
fixedPositionStrategy
|
||||
>
|
||||
Status
|
||||
</TooltipWrapper>
|
||||
);
|
||||
return (
|
||||
<HeaderCell
|
||||
value={cellProps.rows.length === 1 ? "Status" : titleWithToolTip}
|
||||
disableSortBy
|
||||
/>
|
||||
);
|
||||
return <HeaderCell value={titleWithToolTip} disableSortBy />;
|
||||
},
|
||||
disableSortBy: true,
|
||||
accessor: "status",
|
||||
@@ -592,9 +589,23 @@ const allHostTableHeaders = (teamId?: number): IHostTableColumnConfig[] => [
|
||||
// Agent
|
||||
{
|
||||
title: "Agent",
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell value="Agent" isSortedDesc={cellProps.column.isSortedDesc} />
|
||||
),
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent="Only supported on hosts that run Fleet's agent: macOS, Windows, Linux, and ChromeOS."
|
||||
tooltipClass="host-table-header-tooltip"
|
||||
fixedPositionStrategy
|
||||
>
|
||||
Agent
|
||||
</TooltipWrapper>
|
||||
);
|
||||
return (
|
||||
<HeaderCell
|
||||
value={titleWithToolTip}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
);
|
||||
},
|
||||
accessor: (row) => row.orbit_version || row.osquery_version,
|
||||
id: "agent",
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
@@ -686,12 +697,23 @@ const allHostTableHeaders = (teamId?: number): IHostTableColumnConfig[] => [
|
||||
// Last restarted
|
||||
{
|
||||
title: "Last restarted",
|
||||
Header: (cellProps: IHostTableHeaderProps) => (
|
||||
<HeaderCell
|
||||
value="Last restarted"
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
),
|
||||
Header: (cellProps: IHostTableHeaderProps) => {
|
||||
const titleWithToolTip = (
|
||||
<TooltipWrapper
|
||||
tipContent="Only supported on macOS, Windows, and Linux, where Fleet's agent can measure system uptime."
|
||||
tooltipClass="host-table-header-tooltip"
|
||||
fixedPositionStrategy
|
||||
>
|
||||
Last restarted
|
||||
</TooltipWrapper>
|
||||
);
|
||||
return (
|
||||
<HeaderCell
|
||||
value={titleWithToolTip}
|
||||
isSortedDesc={cellProps.column.isSortedDesc}
|
||||
/>
|
||||
);
|
||||
},
|
||||
accessor: "last_restarted_at",
|
||||
id: "last_restarted_at",
|
||||
Cell: (cellProps: IHostTableStringCellProps) => {
|
||||
|
||||
@@ -251,3 +251,9 @@
|
||||
padding-bottom: $pad-large;
|
||||
}
|
||||
}
|
||||
|
||||
// The tooltip renders in a portal outside .manage-hosts, so this can't be
|
||||
// nested under that selector.
|
||||
.host-table-header-tooltip {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -305,9 +305,8 @@ const HostSoftware = ({
|
||||
router,
|
||||
teamId: hostTeamId,
|
||||
onShowInventoryVersions,
|
||||
platform,
|
||||
});
|
||||
}, [isMyDevicePage, router, hostTeamId, onShowInventoryVersions, platform]);
|
||||
}, [isMyDevicePage, router, hostTeamId, onShowInventoryVersions]);
|
||||
|
||||
const isLoading = isMyDevicePage
|
||||
? deviceSoftwareLoading
|
||||
|
||||
@@ -13,7 +13,6 @@ describe("HostSoftwareTableConfig - Last opened column", () => {
|
||||
router: mockRouter,
|
||||
teamId: 1,
|
||||
onShowInventoryVersions: noop,
|
||||
platform: "windows",
|
||||
});
|
||||
|
||||
const lastOpenedColumn = headers.find((h) => h.id === "Last opened") as any;
|
||||
|
||||
@@ -7,13 +7,6 @@ import {
|
||||
IHostSoftware,
|
||||
isIpadOrIphoneSoftwareSource,
|
||||
} from "interfaces/software";
|
||||
import {
|
||||
HostPlatform,
|
||||
isIPadOrIPhone,
|
||||
isLinuxLike,
|
||||
isMacOS,
|
||||
isWindows,
|
||||
} from "interfaces/platform";
|
||||
import { IHeaderProps, IStringCellProps } from "interfaces/datatable_config";
|
||||
|
||||
import PATHS from "router/paths";
|
||||
@@ -47,7 +40,6 @@ interface ISoftwareTableHeadersProps {
|
||||
router: InjectedRouter;
|
||||
teamId: number;
|
||||
onShowInventoryVersions: (software: IHostSoftware) => void;
|
||||
platform: HostPlatform;
|
||||
}
|
||||
|
||||
// NOTE: cellProps come from react-table
|
||||
@@ -56,7 +48,6 @@ export const generateSoftwareTableHeaders = ({
|
||||
router,
|
||||
teamId,
|
||||
onShowInventoryVersions,
|
||||
platform,
|
||||
}: ISoftwareTableHeadersProps): ISoftwareTableConfig[] => {
|
||||
const tableHeaders: ISoftwareTableConfig[] = [
|
||||
{
|
||||
@@ -132,24 +123,18 @@ export const generateSoftwareTableHeaders = ({
|
||||
},
|
||||
{
|
||||
Header: (): JSX.Element => {
|
||||
let tooltipContent = <></>;
|
||||
|
||||
if (isMacOS(platform)) {
|
||||
tooltipContent = (
|
||||
<>When the version installed most recently was last opened.</>
|
||||
);
|
||||
} else if (isLinuxLike(platform) || isWindows(platform)) {
|
||||
tooltipContent = <>When any version was last opened.</>;
|
||||
} else if (isIPadOrIPhone(platform)) {
|
||||
tooltipContent = <>Date and time of last open.</>;
|
||||
}
|
||||
|
||||
const lastOpenedHeader = tooltipContent ? (
|
||||
<TooltipWrapper tipContent={tooltipContent}>
|
||||
const lastOpenedHeader = (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Only supported for macOS, Windows, and Linux native apps and
|
||||
packages. Browser extensions, other package managers, and mobile
|
||||
apps don't report this information.
|
||||
</>
|
||||
}
|
||||
>
|
||||
Last opened
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
"Last opened"
|
||||
);
|
||||
return <HeaderCell value={lastOpenedHeader} disableSortBy />;
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createCustomRenderer } from "test/test-utils";
|
||||
|
||||
import createMockHost, { createMockHostGeolocation } from "__mocks__/hostMock";
|
||||
import { createMockHostMdmData } from "__mocks__/mdmMock";
|
||||
import { HostPlatform } from "interfaces/platform";
|
||||
|
||||
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
|
||||
import Vitals from "./Vitals";
|
||||
@@ -578,6 +579,47 @@ describe("Agent data", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Last restarted vital", () => {
|
||||
it.each(["darwin", "windows", "ubuntu"])(
|
||||
"renders Last restarted for supported platform: %s",
|
||||
(platform) => {
|
||||
const mockHost = createMockHost({
|
||||
platform: platform as HostPlatform,
|
||||
last_restarted_at: "2023-01-01T00:00:00Z",
|
||||
});
|
||||
|
||||
render(<Vitals vitalsData={mockHost} />);
|
||||
|
||||
expect(screen.getByText("Last restarted")).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(["chrome", "ios", "ipados", "android"])(
|
||||
"does not render Last restarted for unsupported platform: %s",
|
||||
(platform) => {
|
||||
const mockHost = createMockHost({
|
||||
platform: platform as HostPlatform,
|
||||
last_restarted_at: "2023-01-01T00:00:00Z",
|
||||
});
|
||||
|
||||
render(<Vitals vitalsData={mockHost} />);
|
||||
|
||||
expect(screen.queryByText("Last restarted")).not.toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe("Munki version vital", () => {
|
||||
it("renders the Munki version vital when its value is a normal version string", () => {
|
||||
const mockHost = createMockHost({ platform: "darwin" });
|
||||
|
||||
render(<Vitals vitalsData={mockHost} munki={{ version: "5.5.1" }} />);
|
||||
|
||||
expect(screen.getByText("Munki version")).toBeInTheDocument();
|
||||
expect(screen.getByText("5.5.1")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Disk space field visibility", () => {
|
||||
it("hides disk space field when storage measurement is not supported (sentinel value -1)", () => {
|
||||
const mockHost = createMockHost({
|
||||
|
||||
@@ -371,7 +371,7 @@ const Vitals = ({
|
||||
});
|
||||
|
||||
// Last restarted
|
||||
if (!isIosOrIpadosHost && !isAndroidHost) {
|
||||
if (!isIosOrIpadosHost && !isAndroidHost && !isChromeHost) {
|
||||
vitals.push({
|
||||
sortKey: "Last restarted",
|
||||
element: (
|
||||
|
||||
Reference in New Issue
Block a user