Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fleet UI: Add timestamps to host count on software detail pages #25143

Merged
merged 8 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/24795-host-count
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fleet UI: Added timestamp for software, OS, and vulnerability detail pages for host count last update time
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Meta, StoryObj } from "@storybook/react";

import LastUpdatedHostCount from "./LastUpdatedHostCount";

const meta: Meta<typeof LastUpdatedHostCount> = {
title: "Components/LastUpdatedHostCount",
component: LastUpdatedHostCount,
args: {
hostCount: 40,
},
};

export default meta;

type Story = StoryObj<typeof LastUpdatedHostCount>;

export const Basic: Story = {};

export const WithLastUpdatedAt: Story = {
args: {
lastUpdatedAt: "2021-01-01T00:00:00Z",
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";

import LastUpdatedHostCount from ".";

describe("Last updated host count", () => {
it("renders host count and updated text", () => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 2);
const twoDaysAgo = currentDate.toISOString();

render(<LastUpdatedHostCount hostCount={40} lastUpdatedAt={twoDaysAgo} />);

const hostCount = screen.getByText(/40/i);
const updateText = screen.getByText("Updated 2 days ago");

expect(hostCount).toBeInTheDocument();
expect(updateText).toBeInTheDocument();
});
it("renders never if missing timestamp", () => {
render(<LastUpdatedHostCount />);

const text = screen.getByText("Updated never");

expect(text).toBeInTheDocument();
});

it("renders tooltip on hover", async () => {
render(<LastUpdatedHostCount hostCount={0} />);

await fireEvent.mouseEnter(screen.getByText("Updated never"));

expect(
screen.getByText(/last time host data was updated/i)
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from "react";
import LastUpdatedText from "components/LastUpdatedText";

const baseClass = "last-updated-host-count";

interface ILastUpdatedHostCount {
hostCount?: string | number;
lastUpdatedAt?: string;
}

const LastUpdatedHostCount = ({
hostCount,
lastUpdatedAt,
}: ILastUpdatedHostCount): JSX.Element => {
const tooltipContent = (
<>
The last time host data was updated. <br />
Click <b>View all hosts</b> to see the most
<br /> up-to-date host count.
</>
);

return (
<div className={baseClass}>
<>{hostCount}</>
<LastUpdatedText
lastUpdatedAt={lastUpdatedAt}
customTooltipText={tooltipContent}
/>
</div>
);
};

export default LastUpdatedHostCount;
6 changes: 6 additions & 0 deletions frontend/components/LastUpdatedHostCount/_styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.last-updated-host-count {
display: flex;
align-items: baseline;
gap: $pad-small;
font-size: $x-small;
}
1 change: 1 addition & 0 deletions frontend/components/LastUpdatedHostCount/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./LastUpdatedHostCount";
1 change: 1 addition & 0 deletions frontend/components/LastUpdatedText/_styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.component__last-updated-text {
font-size: $xx-small;
font-weight: $regular;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated globally here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Rachel. Can you also please remove these lines (same fix, locally) with merging this global update

color: $ui-fleet-black-75;

.tooltip {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,13 @@ const SoftwareOSDetailsPage = ({
});

const {
data: osVersionDetails,
data: { os_version: osVersionDetails, counts_updated_at } = {},
isLoading,
isError: isOsVersionError,
} = useQuery<
IOSVersionResponse,
AxiosError,
IOperatingSystemVersion,
IOSVersionResponse,
IGetOsVersionQueryKey[]
>(
[
Expand All @@ -93,7 +93,10 @@ const SoftwareOSDetailsPage = ({
...DEFAULT_USE_QUERY_OPTIONS,
retry: false,
enabled: !!osVersionIdFromURL,
select: (data) => data.os_version,
select: (data) => ({
os_version: data.os_version,
counts_updated_at: data.counts_updated_at,
}),
onError: (error) => {
if (!ignoreAxiosError(error, [403, 404])) {
handlePageError(error);
Expand Down Expand Up @@ -154,7 +157,7 @@ const SoftwareOSDetailsPage = ({
onTeamChange={onTeamChange}
/>
)}
{isOsVersionError ? (
{isOsVersionError || !osVersionDetails ? (
<DetailsNoHosts
header="OS not detected"
details="No hosts have this OS installed."
Expand All @@ -164,6 +167,7 @@ const SoftwareOSDetailsPage = ({
<SoftwareDetailsSummary
title={osVersionDetails.name}
hosts={osVersionDetails.hosts_count}
countsUpdatedAt={counts_updated_at}
queryParams={{
os_name: osVersionDetails.name_only,
os_version: osVersionDetails.version,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ const SoftwareTitleDetailsPage = ({
type={formatSoftwareType(softwareTitle)}
versions={softwareTitle.versions?.length ?? 0}
hosts={softwareTitle.hosts_count}
countsUpdatedAt={softwareTitle.counts_updated_at}
queryParams={{
software_title_id: softwareId,
team_id: teamIdForApi,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TooltipWrapper from "components/TooltipWrapper";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import ProbabilityOfExploit from "components/ProbabilityOfExploit";
import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip";
import LastUpdatedHostCount from "components/LastUpdatedHostCount";

const baseClass = "software-vuln-summary";

Expand All @@ -35,6 +36,7 @@ const SoftwareVulnSummary = ({
cve_published,
created_at,
hosts_count,
hosts_count_updated_at,
} = vuln;

return (
Expand Down Expand Up @@ -126,7 +128,15 @@ const SoftwareVulnSummary = ({
/>
}
/>
<DataSet title="Affected hosts" value={hosts_count} />
<DataSet
title="Affected hosts"
value={
<LastUpdatedHostCount
hostCount={hosts_count}
lastUpdatedAt={hosts_count_updated_at}
/>
}
/>
</dl>
</Card>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { QueryParams } from "utilities/url";

import ViewAllHostsLink from "components/ViewAllHostsLink";
import DataSet from "components/DataSet";
import LastUpdatedHostCount from "components/LastUpdatedHostCount";

import SoftwareIcon from "../icons/SoftwareIcon";

Expand All @@ -19,6 +20,7 @@ interface ISoftwareDetailsSummaryProps {
title: string;
type?: string;
hosts: number;
countsUpdatedAt?: string;
/** The query param that will be added when user clicks on "View all hosts" link */
queryParams: QueryParams;
name?: string;
Expand All @@ -31,6 +33,7 @@ const SoftwareDetailsSummary = ({
title,
type,
hosts,
countsUpdatedAt,
queryParams,
name,
source,
Expand All @@ -46,7 +49,15 @@ const SoftwareDetailsSummary = ({
{!!type && <DataSet title="Type" value={type} />}

{!!versions && <DataSet title="Versions" value={versions} />}
<DataSet title="Hosts" value={hosts === 0 ? "---" : hosts} />
<DataSet
title="Hosts"
value={
<LastUpdatedHostCount
hostCount={hosts === 0 ? "---" : hosts}
lastUpdatedAt={countsUpdatedAt}
/>
}
/>
</dl>
</dl>
<div>
Expand Down
4 changes: 0 additions & 4 deletions frontend/pages/policies/ManagePoliciesPage/_styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -295,8 +295,4 @@
}
}
}

.component__last-updated-text {
font-weight: normal;
}
}
1 change: 1 addition & 0 deletions frontend/services/entities/operating_systems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export interface IGetOsVersionQueryKey extends IGetOsVersionOptions {
}

export interface IOSVersionResponse {
counts_updated_at?: string;
os_version: IOperatingSystemVersion;
}

Expand Down
Loading