From 86c87bc0bcfe7e2601f3058cc48552afd8968f3f Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 3 Jan 2025 16:36:47 -0500 Subject: [PATCH 1/8] Fleet UI: Add timestamps to host count on software detail pages --- changes/24795-host-count | 1 + .../SoftwareOSDetailsPage.tsx | 17 +++++++++---- .../SoftwareTitleDetailsPage.tsx | 1 + .../SoftwareVulnSummary.tsx | 23 +++++++++++++++++- .../SoftwareDetailsSummary.tsx | 24 ++++++++++++++++++- .../services/entities/operating_systems.ts | 1 + 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 changes/24795-host-count diff --git a/changes/24795-host-count b/changes/24795-host-count new file mode 100644 index 000000000000..20dd5dcf4c09 --- /dev/null +++ b/changes/24795-host-count @@ -0,0 +1 @@ +- Fleet UI: Added timestamp for software, OS, and vulnerability detail pages for host count last update time diff --git a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx index 39a69bb01fd2..922ec9a69b67 100644 --- a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx @@ -49,6 +49,11 @@ type ISoftwareOSDetailsPageProps = RouteComponentProps< ISoftwareOSDetailsRouteParams >; +type QueryResult = { + os_version: IOperatingSystemVersion; + counts_updated_at?: string; +}; + const SoftwareOSDetailsPage = ({ routeParams, router, @@ -72,13 +77,13 @@ const SoftwareOSDetailsPage = ({ }); const { - data: osVersionDetails, + data: { os_version: osVersionDetails, counts_updated_at } = {}, isLoading, isError: isOsVersionError, } = useQuery< IOSVersionResponse, AxiosError, - IOperatingSystemVersion, + QueryResult, IGetOsVersionQueryKey[] >( [ @@ -93,7 +98,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); @@ -154,7 +162,7 @@ const SoftwareOSDetailsPage = ({ onTeamChange={onTeamChange} /> )} - {isOsVersionError ? ( + {isOsVersionError || !osVersionDetails ? ( } /> - + + {hosts_count}{" "} + {hosts_count_updated_at && ( + + The last time host data was updated.
+ Click View all hosts to see the most +
up-to-date host count. + + } + /> + )} + + } + /> ); diff --git a/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx b/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx index 2dd8c25dc114..6ae1dc7c8110 100644 --- a/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx +++ b/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx @@ -10,6 +10,7 @@ import { QueryParams } from "utilities/url"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import DataSet from "components/DataSet"; +import LastUpdatedText from "components/LastUpdatedText"; import SoftwareIcon from "../icons/SoftwareIcon"; @@ -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; @@ -31,6 +33,7 @@ const SoftwareDetailsSummary = ({ title, type, hosts, + countsUpdatedAt, queryParams, name, source, @@ -46,7 +49,26 @@ const SoftwareDetailsSummary = ({ {!!type && } {!!versions && } - + + {hosts === 0 ? "---" : hosts}{" "} + {countsUpdatedAt && ( + + The last time host data was updated.
+ Click View all hosts to see the most +
up-to-date host count. + + } + /> + )} + + } + />
diff --git a/frontend/services/entities/operating_systems.ts b/frontend/services/entities/operating_systems.ts index 2aa36227bd83..df843067cf22 100644 --- a/frontend/services/entities/operating_systems.ts +++ b/frontend/services/entities/operating_systems.ts @@ -48,6 +48,7 @@ export interface IGetOsVersionQueryKey extends IGetOsVersionOptions { } export interface IOSVersionResponse { + counts_updated_at?: string; os_version: IOperatingSystemVersion; } From e1b5221627213002a716e851ea8082b107c6c76d Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Fri, 3 Jan 2025 16:39:24 -0500 Subject: [PATCH 2/8] Clean typings --- .../SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx index 922ec9a69b67..889f232cb9d7 100644 --- a/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx +++ b/frontend/pages/SoftwarePage/SoftwareOSDetailsPage/SoftwareOSDetailsPage.tsx @@ -49,11 +49,6 @@ type ISoftwareOSDetailsPageProps = RouteComponentProps< ISoftwareOSDetailsRouteParams >; -type QueryResult = { - os_version: IOperatingSystemVersion; - counts_updated_at?: string; -}; - const SoftwareOSDetailsPage = ({ routeParams, router, @@ -83,7 +78,7 @@ const SoftwareOSDetailsPage = ({ } = useQuery< IOSVersionResponse, AxiosError, - QueryResult, + IOSVersionResponse, IGetOsVersionQueryKey[] >( [ From 9f29ef8400bfc649dc1383893f05f9ab07fb39ee Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 10:13:07 -0500 Subject: [PATCH 3/8] Componentize, styling nits --- .../LastUpdatedHostCount.stories.tsx | 23 ++++++++++++ .../LastUpdatedHostCount.tests.tsx | 37 +++++++++++++++++++ .../LastUpdatedHostCount.tsx | 36 ++++++++++++++++++ .../LastUpdatedHostCount/_styles.scss | 9 +++++ .../components/LastUpdatedHostCount/index.ts | 1 + .../SoftwareVulnSummary.tsx | 21 +++-------- .../SoftwareDetailsSummary.tsx | 21 +++-------- 7 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx create mode 100644 frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx create mode 100644 frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx create mode 100644 frontend/components/LastUpdatedHostCount/_styles.scss create mode 100644 frontend/components/LastUpdatedHostCount/index.ts diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx new file mode 100644 index 000000000000..5f54e925083d --- /dev/null +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from "@storybook/react"; + +import LastUpdatedHostCount from "./LastUpdatedHostCount"; + +const meta: Meta = { + title: "Components/LastUpdatedHostCount", + component: LastUpdatedHostCount, + args: { + // whatToRetrieve: "apples", + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Basic: Story = {}; + +export const WithLastUpdatedAt: Story = { + args: { + lastUpdatedAt: "2021-01-01T00:00:00Z", + }, +}; diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx new file mode 100644 index 000000000000..ecde3d189679 --- /dev/null +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tests.tsx @@ -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(); + + 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(); + + const text = screen.getByText("Updated never"); + + expect(text).toBeInTheDocument(); + }); + + it("renders tooltip on hover", async () => { + render(); + + await fireEvent.mouseEnter(screen.getByText("Updated never")); + + expect( + screen.getByText(/last time host data was updated/i) + ).toBeInTheDocument(); + }); +}); diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx new file mode 100644 index 000000000000..4746ee8aff57 --- /dev/null +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx @@ -0,0 +1,36 @@ +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.
+ Click View all hosts to see the most +
up-to-date host count. + + ); + + return ( +
+ <>{hostCount} + {lastUpdatedAt && ( + + )} +
+ ); +}; + +export default LastUpdatedHostCount; diff --git a/frontend/components/LastUpdatedHostCount/_styles.scss b/frontend/components/LastUpdatedHostCount/_styles.scss new file mode 100644 index 000000000000..336bc67c3dde --- /dev/null +++ b/frontend/components/LastUpdatedHostCount/_styles.scss @@ -0,0 +1,9 @@ +.last-updated-host-count { + display: flex; + align-items: baseline; + gap: $pad-small; + + .component__tooltip-wrapper__underline { + font-weight: $bold; + } +} diff --git a/frontend/components/LastUpdatedHostCount/index.ts b/frontend/components/LastUpdatedHostCount/index.ts new file mode 100644 index 000000000000..d4955ba278ed --- /dev/null +++ b/frontend/components/LastUpdatedHostCount/index.ts @@ -0,0 +1 @@ +export { default } from "./LastUpdatedHostCount"; diff --git a/frontend/pages/SoftwarePage/SoftwareVulnerabilityDetailsPage/SoftwareVulnSummary/SoftwareVulnSummary.tsx b/frontend/pages/SoftwarePage/SoftwareVulnerabilityDetailsPage/SoftwareVulnSummary/SoftwareVulnSummary.tsx index 1acc1688de1a..c7cbef556e34 100644 --- a/frontend/pages/SoftwarePage/SoftwareVulnerabilityDetailsPage/SoftwareVulnSummary/SoftwareVulnSummary.tsx +++ b/frontend/pages/SoftwarePage/SoftwareVulnerabilityDetailsPage/SoftwareVulnSummary/SoftwareVulnSummary.tsx @@ -11,7 +11,7 @@ import TooltipWrapper from "components/TooltipWrapper"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import ProbabilityOfExploit from "components/ProbabilityOfExploit"; import { HumanTimeDiffWithDateTip } from "components/HumanTimeDiffWithDateTip"; -import LastUpdatedText from "components/LastUpdatedText"; +import LastUpdatedHostCount from "components/LastUpdatedHostCount"; const baseClass = "software-vuln-summary"; @@ -131,21 +131,10 @@ const SoftwareVulnSummary = ({ - {hosts_count}{" "} - {hosts_count_updated_at && ( - - The last time host data was updated.
- Click View all hosts to see the most -
up-to-date host count. - - } - /> - )} - + } /> diff --git a/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx b/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx index 6ae1dc7c8110..52222a5e7e2e 100644 --- a/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx +++ b/frontend/pages/SoftwarePage/components/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx @@ -10,7 +10,7 @@ import { QueryParams } from "utilities/url"; import ViewAllHostsLink from "components/ViewAllHostsLink"; import DataSet from "components/DataSet"; -import LastUpdatedText from "components/LastUpdatedText"; +import LastUpdatedHostCount from "components/LastUpdatedHostCount"; import SoftwareIcon from "../icons/SoftwareIcon"; @@ -52,21 +52,10 @@ const SoftwareDetailsSummary = ({ - {hosts === 0 ? "---" : hosts}{" "} - {countsUpdatedAt && ( - - The last time host data was updated.
- Click View all hosts to see the most -
up-to-date host count. - - } - /> - )} - + } /> From 3be15896b85e635a865788b47ab10dc3ca23bd18 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 10:52:32 -0500 Subject: [PATCH 4/8] Add storybook --- .../LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx | 2 +- frontend/components/LastUpdatedHostCount/_styles.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx index 5f54e925083d..2516d5311217 100644 --- a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.stories.tsx @@ -6,7 +6,7 @@ const meta: Meta = { title: "Components/LastUpdatedHostCount", component: LastUpdatedHostCount, args: { - // whatToRetrieve: "apples", + hostCount: 40, }, }; diff --git a/frontend/components/LastUpdatedHostCount/_styles.scss b/frontend/components/LastUpdatedHostCount/_styles.scss index 336bc67c3dde..68af9a1f2222 100644 --- a/frontend/components/LastUpdatedHostCount/_styles.scss +++ b/frontend/components/LastUpdatedHostCount/_styles.scss @@ -2,6 +2,7 @@ display: flex; align-items: baseline; gap: $pad-small; + font-size: $x-small; .component__tooltip-wrapper__underline { font-weight: $bold; From 03a8808ac20dcf4d10f404eff766836bef8c6b68 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 10:55:24 -0500 Subject: [PATCH 5/8] Allow updated never --- .../LastUpdatedHostCount/LastUpdatedHostCount.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx index 4746ee8aff57..8cd29f6d16a1 100644 --- a/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx +++ b/frontend/components/LastUpdatedHostCount/LastUpdatedHostCount.tsx @@ -23,12 +23,10 @@ const LastUpdatedHostCount = ({ return (
<>{hostCount} - {lastUpdatedAt && ( - - )} +
); }; From 319ec3fc4560c2339bacf4d8038dc147cacb9471 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 11:42:17 -0500 Subject: [PATCH 6/8] Unbold updated text --- frontend/components/LastUpdatedHostCount/_styles.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/components/LastUpdatedHostCount/_styles.scss b/frontend/components/LastUpdatedHostCount/_styles.scss index 68af9a1f2222..9dd4eb3b3344 100644 --- a/frontend/components/LastUpdatedHostCount/_styles.scss +++ b/frontend/components/LastUpdatedHostCount/_styles.scss @@ -3,8 +3,4 @@ align-items: baseline; gap: $pad-small; font-size: $x-small; - - .component__tooltip-wrapper__underline { - font-weight: $bold; - } } From 334e794ddc0d513a52509e1c6eaec1aa096f2f1c Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 12:01:30 -0500 Subject: [PATCH 7/8] Fix all updated text to not be bold by accident --- frontend/components/LastUpdatedText/_styles.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/components/LastUpdatedText/_styles.scss b/frontend/components/LastUpdatedText/_styles.scss index 1198fd096bb6..3094d4753bbf 100644 --- a/frontend/components/LastUpdatedText/_styles.scss +++ b/frontend/components/LastUpdatedText/_styles.scss @@ -1,5 +1,6 @@ .component__last-updated-text { font-size: $xx-small; + font-weight: $regular; color: $ui-fleet-black-75; .tooltip { From 8dc8ec74d38a3318ec2cecd5f1c9d7de753e9628 Mon Sep 17 00:00:00 2001 From: RachelElysia Date: Mon, 6 Jan 2025 16:31:13 -0500 Subject: [PATCH 8/8] Remove local fix since global fix is in --- frontend/pages/policies/ManagePoliciesPage/_styles.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/pages/policies/ManagePoliciesPage/_styles.scss b/frontend/pages/policies/ManagePoliciesPage/_styles.scss index 1de07894b2ad..4edf0c5432d9 100644 --- a/frontend/pages/policies/ManagePoliciesPage/_styles.scss +++ b/frontend/pages/policies/ManagePoliciesPage/_styles.scss @@ -295,8 +295,4 @@ } } } - - .component__last-updated-text { - font-weight: normal; - } }