From 226df9277cebde3ad0aac9edb0cf3cda3bc41aad Mon Sep 17 00:00:00 2001 From: RachelElysia <71795832+RachelElysia@users.noreply.github.com> Date: Thu, 2 Apr 2026 13:32:30 -0400 Subject: [PATCH] Fleet UI: Some of many tooltip updates to react-tooltip 5.x (#42830) --- .../components/FeedListItem/FeedListItem.tsx | 30 ++--- .../ProbabilityOfExploit.tsx | 44 +++---- .../HostMdmStatusCell/HostMdmStatusCell.tsx | 37 +++--- .../DataTable/HostMdmStatusCell/_styles.scss | 5 +- .../LiveQueryIssueCell/LiveQueryIssueCell.tsx | 61 +++++----- .../PerformanceImpactCell.tests.tsx | 6 +- .../PerformanceImpactCell.tsx | 53 ++++----- .../DataTable/TextCell/TextCell.tsx | 27 ++--- .../TableContainer/DataTable/_styles.scss | 6 +- .../TableContainer/TableContainer.tsx | 91 +++++++------- frontend/components/TargetsInput/_styles.scss | 4 +- .../components/tables/IconCell/IconCell.tsx | 44 ------- .../components/tables/IconCell/_styles.scss | 3 - .../components/tables/IconCell/index.ts | 1 - .../VulnerabilitiesCell.tsx | 66 +++++------ .../tables/VulnerabilitiesCell/_styles.scss | 1 + .../IntegrationForm/IntegrationForm.tsx | 45 +++---- .../VppTable/TeamsCell/TeamsCell.tsx | 60 +++------- .../components/VppTable/VppTableConfig.tsx | 2 +- .../hosts/ManageHostsPage/HostTableConfig.tsx | 2 - .../IssuesIndicator/IssuesIndicator.tests.tsx | 16 +-- .../IssuesIndicator/IssuesIndicator.tsx | 59 +++++---- .../components/IssuesIndicator/_styles.scss | 7 ++ .../HostDetailsPage/HostDetailsPage.tsx | 4 +- .../OSSettingStatusCell.tests.tsx | 47 +++++--- .../OSSettingStatusCell.tsx | 33 ++---- .../ProfileStatusIndicator.tests.tsx | 68 ----------- .../ProfileStatusIndicator.tsx | 107 ----------------- .../ProfileStatusIndicator/_styles.scss | 18 --- .../details/ProfileStatusIndicator/index.ts | 1 - .../ReportUpdatedCell.tests.tsx | 63 ++++++---- .../ReportUpdatedCell/ReportUpdatedCell.tsx | 74 +++++------- .../UpdateSoftwareItem.tests.tsx | 12 +- .../UpdateSoftwareItem/UpdateSoftwareItem.tsx | 70 +++++------ .../PolicyForm/PolicyForm.tests.tsx | 11 +- .../components/PolicyForm/PolicyForm.tsx | 112 ++++++++---------- .../components/PolicyForm/_styles.scss | 14 +-- .../SaveNewPolicyModal/SaveNewPolicyModal.tsx | 103 +++++++--------- .../QueriesTable/QueriesTableConfig.tsx | 1 - .../QueryDetailsPage/QueryDetailsPage.tsx | 65 +++++----- .../EditQueryForm/EditQueryForm.tsx | 2 - frontend/utilities/constants.tsx | 18 ++- frontend/utilities/helpers.tsx | 6 +- 43 files changed, 568 insertions(+), 931 deletions(-) delete mode 100644 frontend/pages/SoftwarePage/components/tables/IconCell/IconCell.tsx delete mode 100644 frontend/pages/SoftwarePage/components/tables/IconCell/_styles.scss delete mode 100644 frontend/pages/SoftwarePage/components/tables/IconCell/index.ts create mode 100644 frontend/pages/hosts/components/IssuesIndicator/_styles.scss delete mode 100644 frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tests.tsx delete mode 100644 frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tsx delete mode 100644 frontend/pages/hosts/details/ProfileStatusIndicator/_styles.scss delete mode 100644 frontend/pages/hosts/details/ProfileStatusIndicator/index.ts diff --git a/frontend/components/FeedListItem/FeedListItem.tsx b/frontend/components/FeedListItem/FeedListItem.tsx index 482447e1b6..788253a10d 100644 --- a/frontend/components/FeedListItem/FeedListItem.tsx +++ b/frontend/components/FeedListItem/FeedListItem.tsx @@ -1,15 +1,14 @@ import React, { ReactNode } from "react"; -import ReactTooltip from "react-tooltip"; import classnames from "classnames"; -import { noop, uniqueId } from "lodash"; +import { noop } from "lodash"; -import { COLORS } from "styles/var/colors"; import { dateAgo } from "utilities/date_format"; import { internationalTimeFormat } from "utilities/helpers"; import Avatar from "components/Avatar"; import Button from "components/buttons/Button"; import Icon from "components/Icon"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "feed-list-item"; @@ -47,8 +46,6 @@ const FeedListItem = ({ [`${baseClass}__no-details`]: !allowShowDetails, }); - const tooltipId = uniqueId(); - return (
@@ -73,25 +70,16 @@ const FeedListItem = ({ {children}
- {createdAt && dateAgo(createdAt)} - - {createdAt && ( - - {internationalTimeFormat(createdAt)} - - )} +
{allowShowDetails && ( diff --git a/frontend/components/ProbabilityOfExploit/ProbabilityOfExploit.tsx b/frontend/components/ProbabilityOfExploit/ProbabilityOfExploit.tsx index f6afd00e74..a13279a72e 100644 --- a/frontend/components/ProbabilityOfExploit/ProbabilityOfExploit.tsx +++ b/frontend/components/ProbabilityOfExploit/ProbabilityOfExploit.tsx @@ -1,13 +1,10 @@ import React from "react"; -import { uniqueId } from "lodash"; -import ReactTooltip from "react-tooltip"; - import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import { formatFloatAsPercentage } from "utilities/helpers"; import Icon from "components/Icon"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "probability-of-exploit"; @@ -32,33 +29,24 @@ const ProbabilityOfExploit = ({ } const renderExploitedIcon = () => { - const tooltipId = uniqueId(); return ( - <> - + + The vulnerability has been actively exploited in the
+ wild. This data is reported by the Cybersecurity
+ and Infrastructure Security Agency (CISA). +
+ } + position={tooltipPosition} + underline={false} + showArrow + tipOffset={8} + > + - - - - <> - The vulnerability has been actively exploited in the wild. This - data is reported by the Cybersecurity and Infrastructure Security - Agency (CISA). - - - - + ); }; diff --git a/frontend/components/TableContainer/DataTable/HostMdmStatusCell/HostMdmStatusCell.tsx b/frontend/components/TableContainer/DataTable/HostMdmStatusCell/HostMdmStatusCell.tsx index 36917063a2..e711fde03e 100644 --- a/frontend/components/TableContainer/DataTable/HostMdmStatusCell/HostMdmStatusCell.tsx +++ b/frontend/components/TableContainer/DataTable/HostMdmStatusCell/HostMdmStatusCell.tsx @@ -1,9 +1,8 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; import Icon from "components/Icon"; import NotSupported from "components/NotSupported"; +import TooltipWrapper from "components/TooltipWrapper"; import { IHost } from "interfaces/host"; const baseClass = "host-mdm-status-cell"; @@ -29,28 +28,20 @@ const HostMdmStatusCell = ({ {value} {mdm?.dep_profile_error && ( - <> - - - - - - Fleet hit Apple’s API rate limit when preparing the macOS Setup - Assistant for this host. Fleet will try again every hour. + + Fleet hit Apple's API rate limit when preparing the macOS + Setup Assistant for this host. Fleet will try again every hour. - - + } + position="top" + underline={false} + showArrow + tipOffset={8} + > + + )} ); diff --git a/frontend/components/TableContainer/DataTable/HostMdmStatusCell/_styles.scss b/frontend/components/TableContainer/DataTable/HostMdmStatusCell/_styles.scss index fdb976ae47..8e9a8b2f94 100644 --- a/frontend/components/TableContainer/DataTable/HostMdmStatusCell/_styles.scss +++ b/frontend/components/TableContainer/DataTable/HostMdmStatusCell/_styles.scss @@ -3,8 +3,5 @@ flex-direction: row; text-wrap: nowrap; gap: $pad-small; - - .tooltip__tooltip-text { - text-wrap: wrap; - } + align-items: center; } diff --git a/frontend/components/TableContainer/DataTable/LiveQueryIssueCell/LiveQueryIssueCell.tsx b/frontend/components/TableContainer/DataTable/LiveQueryIssueCell/LiveQueryIssueCell.tsx index f460cb527b..1a81ed878e 100644 --- a/frontend/components/TableContainer/DataTable/LiveQueryIssueCell/LiveQueryIssueCell.tsx +++ b/frontend/components/TableContainer/DataTable/LiveQueryIssueCell/LiveQueryIssueCell.tsx @@ -1,8 +1,7 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; import Icon from "components/Icon"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; interface ILiveQueryIssueCellProps { displayName: string; @@ -24,39 +23,35 @@ const LiveQueryIssueCell = ({ return ( <> {displayName}{" "} - + {status === "offline" ? ( + <> + Offline hosts will not
+ respond to a live report. + + ) : ( + <> + This host might take up to +
{distributedInterval} seconds to respond. + + )} +
+ } + position="top" + underline={false} + showArrow + tipOffset={8} > - - - - - {status === "offline" ? ( - <> - Offline hosts will not
- respond to a live report. - - ) : ( - <> - This host might take up to -
{distributedInterval} seconds to respond. - - )} + + -
+ ); }; diff --git a/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tests.tsx b/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tests.tsx index 7cfdd011f9..bd502921c5 100644 --- a/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tests.tsx +++ b/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import PerformanceImpactCell from "./PerformanceImpactCell"; @@ -14,6 +14,8 @@ describe("Query performance cell", () => { await user.hover(screen.getByText("Minimal")); - expect(screen.getByText(/little to no impact/i)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/little to no impact/i)).toBeInTheDocument(); + }); }); }); diff --git a/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tsx b/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tsx index d659efb693..2d0f326fbd 100644 --- a/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tsx +++ b/frontend/components/TableContainer/DataTable/PerformanceImpactCell/PerformanceImpactCell.tsx @@ -1,24 +1,20 @@ import React from "react"; import classnames from "classnames"; -import { uniqueId } from "lodash"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; -import { getPerformanceImpactIndicatorTooltip } from "utilities/helpers"; import { isPerformanceImpactIndicator, PerformanceImpactIndicatorValue, } from "interfaces/schedulable_query"; +import { getPerformanceImpactIndicatorTooltip } from "utilities/helpers"; interface IPerformanceImpactCellValue { indicator: string; - id?: number; } interface IPerformanceImpactCellProps { value: IPerformanceImpactCellValue; isHostSpecific?: boolean; - customIdPrefix?: string; } const generateClassTag = (rawValue: string): string => { @@ -30,9 +26,8 @@ const baseClass = "performance-impact-cell"; const PerformanceImpactCell = ({ value, isHostSpecific = false, - customIdPrefix, }: IPerformanceImpactCellProps): JSX.Element => { - const { indicator, id } = value; + const { indicator } = value; const pillClassName = classnames( "data-table__pill", `data-table__pill--${generateClassTag(indicator || "")}`, @@ -46,36 +41,34 @@ const PerformanceImpactCell = ({ "Undetermined", ].includes(indicator); - const tooltipId = uniqueId(); - const indicatorValue = isPerformanceImpactIndicator(indicator) ? indicator : PerformanceImpactIndicatorValue.UNDETERMINED; return ( - + {getPerformanceImpactIndicatorTooltip( + indicatorValue, + isHostSpecific + )} + + } + position="top" + disableTooltip={disableTooltip} + underline={false} + showArrow + // Pills require more gap from text to tooltip + tipOffset={indicatorValue === "Undetermined" ? 8 : 12} > {indicatorValue} - - - - {getPerformanceImpactIndicatorTooltip(indicatorValue, isHostSpecific)} - - + ); }; diff --git a/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx b/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx index be7d470f5f..a5426db7f2 100644 --- a/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx +++ b/frontend/components/TableContainer/DataTable/TextCell/TextCell.tsx @@ -1,8 +1,6 @@ import classnames from "classnames"; -import { uniqueId } from "lodash"; import React from "react"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; const baseClass = "text-cell"; @@ -46,24 +44,17 @@ const TextCell = ({ [grey, italic] = [true, true]; } - // TODO: Refactor to use TooltipWrapper component const renderEmptyCell = () => { if (emptyCellTooltipText) { - const tooltipId = uniqueId(); return ( - <> - - {DEFAULT_EMPTY_CELL_VALUE} - - - {emptyCellTooltipText} - - + + {DEFAULT_EMPTY_CELL_VALUE} + ); } return DEFAULT_EMPTY_CELL_VALUE; diff --git a/frontend/components/TableContainer/DataTable/_styles.scss b/frontend/components/TableContainer/DataTable/_styles.scss index 0d7e57e1c3..1b2644b6c8 100644 --- a/frontend/components/TableContainer/DataTable/_styles.scss +++ b/frontend/components/TableContainer/DataTable/_styles.scss @@ -408,7 +408,11 @@ $shadow-transition-width: 10px; } .italic-cell { font-style: italic; - .__react_component_tooltip { + + // old react tooltip version + .__react_component_tooltip, + // new react tooltip version + .react-tooltip { font-style: normal; } } diff --git a/frontend/components/TableContainer/TableContainer.tsx b/frontend/components/TableContainer/TableContainer.tsx index c23accd09c..aa8979adf1 100644 --- a/frontend/components/TableContainer/TableContainer.tsx +++ b/frontend/components/TableContainer/TableContainer.tsx @@ -1,7 +1,6 @@ import React, { useState, useCallback, useRef, useEffect } from "react"; import classnames from "classnames"; import { Row } from "react-table"; -import ReactTooltip from "react-tooltip"; import useDeepEffect from "hooks/useDeepEffect"; import { noop } from "lodash"; @@ -11,8 +10,6 @@ import Button from "components/buttons/Button"; import Icon from "components/Icon/Icon"; import TooltipWrapper from "components/TooltipWrapper"; -import { COLORS } from "styles/var/colors"; - import DataTable from "./DataTable/DataTable"; import { IActionButtonProps } from "./DataTable/ActionButton/ActionButton"; @@ -76,7 +73,7 @@ interface ITableContainerProps { primarySelectAction?: IActionButtonProps; /** Secondary button/s after selecting a row */ secondarySelectActions?: IActionButtonProps[]; // TODO: Combine with primarySelectAction as these are all rendered in the same spot - searchToolTipText?: string; + searchToolTipText?: JSX.Element; // TODO - consolidate this functionality within `filters` searchQueryColumn?: string; // TODO - consolidate this functionality within `filters` @@ -384,28 +381,26 @@ const TableContainer = ({ {customControl && customControl()} {searchable && !wideSearch && (
-
+ {searchToolTipText} + + } + disableTooltip={!searchToolTipText} + underline={false} + position="top" + tipOffset={8} + showArrow > - -
- - - {searchToolTipText} - - +
+ +
+
)} {customFiltersButton && customFiltersButton()} @@ -457,30 +452,30 @@ const TableContainer = ({ {/* Render search bar only if not empty component */} {searchable && !wideSearch && (
-
+ {searchToolTipText} + + } + disableTooltip={!searchToolTipText} + underline={false} + position="top" + tipOffset={8} + showArrow > - -
- - - {searchToolTipText} - - +
+ +
+
)}
diff --git a/frontend/components/TargetsInput/_styles.scss b/frontend/components/TargetsInput/_styles.scss index f5d591bdfe..eb8d1c8044 100644 --- a/frontend/components/TargetsInput/_styles.scss +++ b/frontend/components/TargetsInput/_styles.scss @@ -29,9 +29,9 @@ display: inline-flex; align-items: center; white-space: nowrap; + gap: $pad-xsmall; .host-issue { - margin-left: $pad-xsmall; display: inline-flex; } } @@ -81,9 +81,9 @@ display: inline-flex; align-items: center; white-space: nowrap; + gap: $pad-xsmall; .host-issue { - margin-left: $pad-xsmall; display: inline-flex; } } diff --git a/frontend/pages/SoftwarePage/components/tables/IconCell/IconCell.tsx b/frontend/pages/SoftwarePage/components/tables/IconCell/IconCell.tsx deleted file mode 100644 index 198d61edc8..0000000000 --- a/frontend/pages/SoftwarePage/components/tables/IconCell/IconCell.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React from "react"; -import ReactTooltip from "react-tooltip"; -import { uniqueId } from "lodash"; - -import Icon from "components/Icon"; -import { COLORS } from "styles/var/colors"; -import { IconNames } from "components/icons"; - -const baseClass = "icon-cell"; - -interface IIconCellProps { - iconName: IconNames; -} - -const IconCell = ({ iconName }: IIconCellProps) => { - const tooltipID = uniqueId(); - - return ( -
- - - - - - {/* TODO: enhance to be dynmaic */} - Software can be installed on Host details page. - - -
- ); -}; - -export default IconCell; diff --git a/frontend/pages/SoftwarePage/components/tables/IconCell/_styles.scss b/frontend/pages/SoftwarePage/components/tables/IconCell/_styles.scss deleted file mode 100644 index 149d08840d..0000000000 --- a/frontend/pages/SoftwarePage/components/tables/IconCell/_styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.icon-cell { - text-align: center; -} diff --git a/frontend/pages/SoftwarePage/components/tables/IconCell/index.ts b/frontend/pages/SoftwarePage/components/tables/IconCell/index.ts deleted file mode 100644 index 4406703d62..0000000000 --- a/frontend/pages/SoftwarePage/components/tables/IconCell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./IconCell"; diff --git a/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/VulnerabilitiesCell.tsx b/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/VulnerabilitiesCell.tsx index f54e103367..f26c3850fa 100644 --- a/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/VulnerabilitiesCell.tsx +++ b/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/VulnerabilitiesCell.tsx @@ -1,8 +1,8 @@ import React from "react"; -import { uniqueId } from "lodash"; -import ReactTooltip from "react-tooltip"; +import { PlacesType } from "react-tooltip-5"; import TextCell from "components/TableContainer/DataTable/TextCell"; +import TooltipWrapper from "components/TooltipWrapper"; import { ISoftwareVulnerability } from "interfaces/software"; const NUM_VULNERABILITIES_IN_TOOLTIP = 3; @@ -21,6 +21,7 @@ const generateCell = ( let text = ""; let italicize = true; + if (totalCount === 1) { italicize = false; text = @@ -34,8 +35,8 @@ const generateCell = ( return ; }; -const getName = (vulnerabiltiy: ISoftwareVulnerability | string) => { - return typeof vulnerabiltiy === "string" ? vulnerabiltiy : vulnerabiltiy.cve; +const getName = (vulnerability: ISoftwareVulnerability | string) => { + return typeof vulnerability === "string" ? vulnerability : vulnerability.cve; }; const condenseVulnerabilities = ( @@ -43,12 +44,12 @@ const condenseVulnerabilities = ( totalCount?: number ) => { const condensed = - (vulnerabilities?.length && - vulnerabilities - .slice(-NUM_VULNERABILITIES_IN_TOOLTIP) - .map(getName) - .reverse()) || - []; + vulnerabilities.length > 0 + ? vulnerabilities + .slice(-NUM_VULNERABILITIES_IN_TOOLTIP) + .map(getName) + .reverse() + : []; const count = totalCount ?? vulnerabilities.length; @@ -59,10 +60,10 @@ const condenseVulnerabilities = ( const generateTooltip = ( vulnerabilities: ISoftwareVulnerability[] | string[], - tooltipId: string, totalCount?: number ) => { const count = totalCount ?? vulnerabilities.length; + if (count <= 1) { return null; } @@ -73,57 +74,48 @@ const generateTooltip = ( ); return ( - -
    - {condensedVulnerabilities.map((vulnerability) => { - const key = - typeof vulnerability === "string" ? vulnerability : uniqueId(); - return
  • {vulnerability}
  • ; - })} -
-
+ ); }; + interface IVulnerabilitiesCellProps { vulnerabilities: ISoftwareVulnerability[] | string[] | null; vulnerabilitiesCount?: number; + tooltipPosition?: PlacesType; } const VulnerabilitiesCell = ({ vulnerabilities, vulnerabilitiesCount, + tooltipPosition = "top", }: IVulnerabilitiesCellProps) => { - const tooltipId = uniqueId(); - - // only one vulnerability, no need for tooltip const cell = generateCell(vulnerabilities, vulnerabilitiesCount); const count = vulnerabilitiesCount ?? vulnerabilities?.length ?? 0; + if (vulnerabilities === null || count <= 1) { return <>{cell}; } const vulnerabilityTooltip = generateTooltip( vulnerabilities, - tooltipId, vulnerabilitiesCount ); return ( - <> -
+ +
{cell}
- {vulnerabilityTooltip} - +
); }; diff --git a/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/_styles.scss b/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/_styles.scss index a1f3c87066..0846a0206b 100644 --- a/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/_styles.scss +++ b/frontend/pages/SoftwarePage/components/tables/VulnerabilitiesCell/_styles.scss @@ -1,6 +1,7 @@ .vulnerabilities-cell { &__vulnerability-text-with-tooltip { display: inline-block; + vertical-align: middle; } &__vulnerability-list { diff --git a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx index c64bd9b99b..832eabeccb 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/Integrations/components/IntegrationForm/IntegrationForm.tsx @@ -1,5 +1,4 @@ import React, { FormEvent, useState, useEffect } from "react"; -import ReactTooltip from "react-tooltip"; import { IIntegrationFormData, @@ -15,7 +14,7 @@ import InputField from "components/forms/fields/InputField"; import validUrl from "components/forms/validators/valid_url"; import Spinner from "components/Spinner"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; import GitOpsModeTooltipWrapper from "components/GitOpsModeTooltipWrapper"; import { IInputFieldParseTarget } from "interfaces/form_field"; @@ -281,35 +280,27 @@ const IntegrationForm = ({ formData.email === "" || formData.apiToken === "" || formData.groupId === 0; - // TODO - refactor below to use TooltipWrapper return ( - <> -
- -
- + Complete all fields to save
the integration. -
- + } + tooltipClass="add-integration-tooltip" + position="top" + disableTooltip={!formInvalid || disableChildren} + underline={false} + showArrow + tipOffset={8} + > + + ); }} /> diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx index f6bde36374..31849d20ef 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/TeamsCell/TeamsCell.tsx @@ -5,8 +5,7 @@ import { ITokenTeam } from "interfaces/mdm"; import { getTeamDisplayName } from "interfaces/team"; import TextCell from "components/TableContainer/DataTable/TextCell"; -import { uniqueId } from "lodash"; -import ReactTooltip from "react-tooltip"; +import TooltipWrapper from "components/TooltipWrapper"; const baseClass = "teams-cell"; @@ -47,38 +46,11 @@ const condenseTeams = (teams: ITokenTeam[]) => { : condensed; }; -const generateTooltip = (teams: ITokenTeam[] | null, tooltipId: string) => { - if (teams === null || teams.length <= 1) { - return null; - } - - const condensedTeams = condenseTeams(teams); - - return ( - -
    - {condensedTeams.map((teamName) => { - return
  • {teamName}
  • ; - })} -
-
- ); -}; - interface ITeamsCellProps { teams: ITokenTeam[] | null; - className?: string; } -const TeamsCell = ({ teams, className }: ITeamsCellProps) => { - const tooltipId = uniqueId(); - const classNames = classnames(baseClass, className); - +const TeamsCell = ({ teams }: ITeamsCellProps) => { if (!teams) { return ; } @@ -92,19 +64,25 @@ const TeamsCell = ({ teams, className }: ITeamsCellProps) => { } const cell = generateCell(teams); - const tooltip = generateTooltip(teams, tooltipId); + const condensedTeams = condenseTeams(teams); return ( - <> -
- {cell} -
- {tooltip} - + + {condensedTeams.map((teamName) => { + return
  • {teamName}
  • ; + })} + + } + underline={false} + showArrow + position="top" + className={`${baseClass}__team-text-with-tooltip`} + tipOffset={8} + > + {cell} +
    ); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/VppTableConfig.tsx b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/VppTableConfig.tsx index febde386bc..f57d778c59 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/VppTableConfig.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/MdmSettings/VppPage/components/VppTable/VppTableConfig.tsx @@ -111,7 +111,7 @@ export const generateTableConfig = ( Header: "Fleets", disableSortBy: true, Cell: (cellProps: ITeamsCellProps) => ( - + ), }, { diff --git a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx index 84e54dc1c4..2a79e3f0ed 100644 --- a/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx +++ b/frontend/pages/hosts/ManageHostsPage/HostTableConfig.tsx @@ -3,7 +3,6 @@ // definitions for the selection row for some reason when we dont really need it. import React from "react"; import { CellProps, Column } from "react-table"; -import ReactTooltip from "react-tooltip"; import { IDeviceUser, IHost } from "interfaces/host"; import { @@ -34,7 +33,6 @@ import { hostTeamName, tooltipTextWithLineBreaks, } from "utilities/helpers"; -import { COLORS } from "styles/var/colors"; import { IHeaderProps, IStringCellProps, diff --git a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx index 3a5985a705..5fdce90cc6 100644 --- a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx +++ b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tests.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { renderWithSetup } from "test/test-utils"; import IssuesIndicator from "./IssuesIndicator"; @@ -16,12 +16,14 @@ describe("Issues indicator", () => { ); await user.hover(screen.getByText("5")); - const vulnerabilitiesTooltip = screen.getByText( - /Critical vulnerabilities/i - ); - const policiesTooltip = screen.getByText(/Failing policies/i); + await waitFor(() => { + const vulnerabilitiesTooltip = screen.getByText( + /Critical vulnerabilities/i + ); + const policiesTooltip = screen.getByText(/Failing policies/i); - expect(vulnerabilitiesTooltip).toBeInTheDocument(); - expect(policiesTooltip).toBeInTheDocument(); + expect(vulnerabilitiesTooltip).toBeInTheDocument(); + expect(policiesTooltip).toBeInTheDocument(); + }); }); }); diff --git a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tsx b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tsx index c7ebe09753..720f2cd58d 100644 --- a/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tsx +++ b/frontend/pages/hosts/components/IssuesIndicator/IssuesIndicator.tsx @@ -1,7 +1,8 @@ import React from "react"; +import { PlacesType } from "react-tooltip-5"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; +import classnames from "classnames"; +import TooltipWrapper from "components/TooltipWrapper"; import Icon from "components/Icon"; interface IIssuesIndicatorProps { @@ -9,45 +10,43 @@ interface IIssuesIndicatorProps { failingPoliciesCount?: number; /** Premium only */ criticalVulnerabilitiesCount?: number; - rowId?: number; - tooltipPosition?: "top" | "bottom"; + tooltipPosition?: PlacesType; + rowId?: string | number; } +const baseClass = "issues-indicator"; + const IssuesIndicator = ({ totalIssuesCount, failingPoliciesCount, criticalVulnerabilitiesCount, - rowId, tooltipPosition = "top", + rowId, }: IIssuesIndicatorProps): JSX.Element => { - return ( + const classNames = classnames(baseClass, { + [`${baseClass}--${rowId}`]: !!rowId, + }); + const tipContent = ( <> - - {" "} - {totalIssuesCount} - - - - {!!criticalVulnerabilitiesCount && - `Critical vulnerabilities (${criticalVulnerabilitiesCount})`} - {!!criticalVulnerabilitiesCount && !!failingPoliciesCount &&
    } - {!!failingPoliciesCount && - `Failing policies (${failingPoliciesCount})`} -
    -
    + {!!criticalVulnerabilitiesCount && + `Critical vulnerabilities (${criticalVulnerabilitiesCount})`} + {!!criticalVulnerabilitiesCount && !!failingPoliciesCount &&
    } + {!!failingPoliciesCount && `Failing policies (${failingPoliciesCount})`} ); + + return ( + + {totalIssuesCount} + + ); }; export default IssuesIndicator; diff --git a/frontend/pages/hosts/components/IssuesIndicator/_styles.scss b/frontend/pages/hosts/components/IssuesIndicator/_styles.scss new file mode 100644 index 0000000000..1b765fef1a --- /dev/null +++ b/frontend/pages/hosts/components/IssuesIndicator/_styles.scss @@ -0,0 +1,7 @@ +.issues-indicator { + .component__tooltip-wrapper__element { + display: inline-flex; + align-items: center; + gap: $pad-small; + } +} diff --git a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx index a3ca0b32df..191df9dcf8 100644 --- a/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx +++ b/frontend/pages/hosts/details/HostDetailsPage/HostDetailsPage.tsx @@ -1218,21 +1218,21 @@ const HostDetailsPage = ({ {isBYODAccountDrivenUserEnrollment(host.mdm.enrollment_status) || isAndroidHost ? ( Software install is coming soon.{" "} } + header="Software library is currently not supported on this host." /> ) : ( { const statusText = screen.getByText("Verifying"); await user.hover(statusText); - - expect(screen.getByText(/verifying/)).toBeInTheDocument(); + await waitFor(() => { + expect(screen.getByText(/verifying/)).toBeInTheDocument(); + }); }); // Android cert statuses @@ -62,9 +63,11 @@ describe("OS setting status cell", () => { expect(statusText).toBeInTheDocument(); await user.hover(statusText); - expect( - screen.getByText(/The host is running the command/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/The host is running the command/) + ).toBeInTheDocument(); + }); }); it("Displays Pending UI for 'delivering' status with optype 'install'", async () => { const customRender = createCustomRenderer(); @@ -83,9 +86,11 @@ describe("OS setting status cell", () => { expect(statusText).toBeInTheDocument(); await user.hover(statusText); - expect( - screen.getByText(/The host is running the command/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/The host is running the command/) + ).toBeInTheDocument(); + }); }); it("Displays Pending UI for 'delivered' status with optype 'install'", async () => { const customRender = createCustomRenderer(); @@ -104,9 +109,11 @@ describe("OS setting status cell", () => { expect(statusText).toBeInTheDocument(); await user.hover(statusText); - expect( - screen.getByText(/The host is running the command/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/The host is running the command/) + ).toBeInTheDocument(); + }); }); it("Displays Pending UI for 'delivering' status with optype 'remove'", async () => { const customRender = createCustomRenderer(); @@ -125,9 +132,11 @@ describe("OS setting status cell", () => { expect(statusText).toBeInTheDocument(); await user.hover(statusText); - expect( - screen.getByText(/The host is running the command/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/The host is running the command/) + ).toBeInTheDocument(); + }); }); it("Displays Pending UI for 'delivered' status with optype 'remove'", async () => { const customRender = createCustomRenderer(); @@ -146,8 +155,10 @@ describe("OS setting status cell", () => { expect(statusText).toBeInTheDocument(); await user.hover(statusText); - expect( - screen.getByText(/The host is running the command/) - ).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(/The host is running the command/) + ).toBeInTheDocument(); + }); }); }); diff --git a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/OSSettingStatusCell.tsx b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/OSSettingStatusCell.tsx index 7b848f4f10..aaa3c8c4f9 100644 --- a/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/OSSettingStatusCell.tsx +++ b/frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingStatusCell/OSSettingStatusCell.tsx @@ -1,6 +1,4 @@ import React from "react"; -import ReactTooltip from "react-tooltip"; -import { uniqueId } from "lodash"; import { REC_LOCK_SYNTHETIC_PROFILE_UUID } from "pages/hosts/details/helpers"; @@ -13,7 +11,7 @@ import { ProfilePlatform, RecoveryLockPasswordStatus, } from "interfaces/mdm"; -import { COLORS } from "styles/var/colors"; +import TooltipWrapper from "components/TooltipWrapper"; import { OsSettingsTableStatusValue } from "../OSSettingsTableConfig"; import TooltipContent from "./components/Tooltip/TooltipContent"; @@ -122,27 +120,12 @@ const OSSettingStatusCell = ({ if (displayOption) { const { statusText, iconName, tooltip } = displayOption; - const tooltipId = uniqueId(); return ( {tooltip ? ( - <> - - {statusText} - - + {status !== "action_required" ? ( )} - - + } + position="top" + underline={false} + showArrow + tipOffset={8} + > + {statusText} + ) : ( {statusText} )} diff --git a/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tests.tsx b/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tests.tsx deleted file mode 100644 index 73044dd218..0000000000 --- a/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tests.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from "react"; -import { fireEvent, render, screen } from "@testing-library/react"; -import ProfileStatusIndicator from "./ProfileStatusIndicator"; - -describe("ProfileStatusIndicator component", () => { - it("Renders the text and icon", () => { - const indicatorText = "test text"; - render( - - ); - const renderedIndicatorText = screen.getByText(indicatorText); - const renderedIcon = screen.getByTestId("success-icon"); - - expect(renderedIndicatorText).toBeInTheDocument(); - expect(renderedIcon).toBeInTheDocument(); - }); - - it("Renders text, icon, and tooltip", () => { - const indicatorText = "test text"; - const tooltipText = "test tooltip text"; - render( - - ); - const renderedIndicatorText = screen.getByText(indicatorText); - const renderedIcon = screen.getByTestId("success-icon"); - const renderedTooltipText = screen.getByText(tooltipText); - - expect(renderedIndicatorText).toBeInTheDocument(); - expect(renderedIcon).toBeInTheDocument(); - expect(renderedTooltipText).toBeInTheDocument(); - }); - - it("Renders text, icon, and onClick", () => { - const indicatorText = "test text"; - const onClick = () => { - const newDiv = document.createElement("div"); - newDiv.appendChild(document.createTextNode("onClick called")); - document.body.appendChild(newDiv); - }; - render( - { - onClick(); - }} - /> - ); - - const renderedIndicatorText = screen.getByText(indicatorText); - const renderedIcon = screen.getByTestId("success-icon"); - const renderedButton = screen.getByRole("button"); - - expect(renderedIndicatorText).toBeInTheDocument(); - expect(renderedIcon).toBeInTheDocument(); - expect(renderedButton).toBeInTheDocument(); - - fireEvent.click(renderedButton); - expect(screen.getByText("onClick called")).toBeInTheDocument(); - }); -}); diff --git a/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tsx b/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tsx deleted file mode 100644 index 1dd5559a9e..0000000000 --- a/frontend/pages/hosts/details/ProfileStatusIndicator/ProfileStatusIndicator.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React from "react"; -import ReactTooltip from "react-tooltip"; -import { IconNames } from "components/icons"; -import Icon from "components/Icon"; -import Button from "components/buttons/Button"; -import { COLORS } from "styles/var/colors"; - -const baseClass = "profile-status-indicator"; - -export interface IProfileStatusIndicatorProps { - indicatorText: string; - iconName: IconNames; - onClick?: () => void; - tooltip?: { - tooltipText: string | null; - position?: "top" | "bottom"; - }; -} - -const ProfileStatusIndicator = ({ - indicatorText, - iconName, - onClick, - tooltip, -}: IProfileStatusIndicatorProps) => { - const getIndicatorTextWrapped = () => { - if (onClick && tooltip?.tooltipText) { - return ( - <> - - - - - {tooltip.tooltipText} - - - ); - } - - // onclick without tooltip - if (onClick) { - return ( - - ); - } - - // tooltip without onclick - if (tooltip?.tooltipText) { - return ( - <> - - {indicatorText} - - - {tooltip.tooltipText} - - - ); - } - - // no tooltip, no onclick - return indicatorText; - }; - - return ( - - - {getIndicatorTextWrapped()} - - ); -}; - -export default ProfileStatusIndicator; diff --git a/frontend/pages/hosts/details/ProfileStatusIndicator/_styles.scss b/frontend/pages/hosts/details/ProfileStatusIndicator/_styles.scss deleted file mode 100644 index a7ed40ad91..0000000000 --- a/frontend/pages/hosts/details/ProfileStatusIndicator/_styles.scss +++ /dev/null @@ -1,18 +0,0 @@ -.profile-status-indicator { - display: flex; - gap: 4px; - - &__button { - font-weight: normal; - } - - .icon { - width: 16px; - height: 16px; - align-self: center; - } - - .__react_component_tooltip { - white-space: normal; - } -} diff --git a/frontend/pages/hosts/details/ProfileStatusIndicator/index.ts b/frontend/pages/hosts/details/ProfileStatusIndicator/index.ts deleted file mode 100644 index 99de4100ca..0000000000 --- a/frontend/pages/hosts/details/ProfileStatusIndicator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./ProfileStatusIndicator"; diff --git a/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tests.tsx b/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tests.tsx index a2f13f99c0..21689bc52a 100644 --- a/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tests.tsx +++ b/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tests.tsx @@ -1,14 +1,15 @@ import React from "react"; -import { render, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; +import { renderWithSetup } from "test/test-utils"; import ReportUpdatedCell from "./ReportUpdatedCell"; const HUMAN_READABLE_DATETIME_REGEX = /\d{1,2}\/\d{1,2}\/\d\d\d\d, \d{1,2}:\d{1,2}:\d{1,2}\s(A|P)M/; describe("ReportUpdatedCell component", () => { - it("Renders 'No report' with tooltip and no link when run on an interval with discard data and automations enabled", () => { - render( + it("Renders 'No report' with tooltip and no link when run on an interval with discard data and automations enabled", async () => { + const { user } = renderWithSetup( { hostId={4} /> ); + const noReportText = screen.getByText(/No report/); + expect(noReportText).toBeInTheDocument(); + await user.hover(noReportText); - expect(screen.getByText(/No report/)).toBeInTheDocument(); - expect(screen.getByText(/Results from this report/)).toBeInTheDocument(); + waitFor(() => { + expect(screen.getByText(/Results from this report/)).toBeInTheDocument(); + }); expect(screen.queryByText(/View report/)).toBeNull(); }); - it("Renders '---' with tooltip and link to report when run on an interval with discard data off and no last_fetched time", () => { - render( + it("Renders '---' with tooltip and link to report when run on an interval with discard data off and no last_fetched time", async () => { + const { user } = renderWithSetup( { /> ); - expect(screen.getByText(/---/)).toBeInTheDocument(); - expect( - screen.getByText(/Fleet is collecting report results\./) - ).toBeInTheDocument(); - expect(screen.getByText(/Check back later./)).toBeInTheDocument(); + const noReportText = screen.getByText(/---/); + expect(noReportText).toBeInTheDocument(); + await user.hover(noReportText); + + waitFor(() => { + expect( + screen.getByText(/Fleet is collecting report results\./) + ).toBeInTheDocument(); + expect(screen.getByText(/Check back later./)).toBeInTheDocument(); + }); }); - it("Renders a last-updated timestamp with tooltip and link to report when a last_fetched date is present", () => { + it("Renders a last-updated timestamp with tooltip and link to report when a last_fetched date is present", async () => { const tenDaysAgo = new Date(); tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); - render( + const { user } = renderWithSetup( { hostId={4} /> ); + const timeAgo = screen.getByText(/\d+.+ago/); - expect(screen.getByText(HUMAN_READABLE_DATETIME_REGEX)).toBeInTheDocument(); - expect(screen.getByText(/\d+.+ago/)).toBeInTheDocument(); + expect(timeAgo).toBeInTheDocument(); + await user.hover(timeAgo); + + await waitFor(() => { + expect( + screen.getByText(HUMAN_READABLE_DATETIME_REGEX) + ).toBeInTheDocument(); + }); expect(screen.getByText(/View data/)).toBeInTheDocument(); }); - it("Renders a last-updated timestamp with tooltip and link to report when a last_fetched date is present but not currently running an interval", () => { + it("Renders a last-updated timestamp with tooltip and link to report when a last_fetched date is present but not currently running an interval", async () => { const tenDaysAgo = new Date(); tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); - render( + const { user } = renderWithSetup( { hostId={4} /> ); + const timeAgo = screen.getByText(/\d+.+ago/); + expect(timeAgo).toBeInTheDocument(); + await user.hover(timeAgo); - expect(screen.getByText(HUMAN_READABLE_DATETIME_REGEX)).toBeInTheDocument(); - expect(screen.getByText(/\d+.+ago/)).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByText(HUMAN_READABLE_DATETIME_REGEX) + ).toBeInTheDocument(); + }); expect(screen.getByText(/View data/)).toBeInTheDocument(); }); }); diff --git a/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tsx b/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tsx index 73e184ad55..9ec3bf5b10 100644 --- a/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tsx +++ b/frontend/pages/hosts/details/cards/Queries/ReportUpdatedCell/ReportUpdatedCell.tsx @@ -2,10 +2,8 @@ import React from "react"; import { browserHistory } from "react-router"; import { HumanTimeDiffWithFleetLaunchCutoff } from "components/HumanTimeDiffWithDateTip"; -import { uniqueId } from "lodash"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; import Icon from "components/Icon"; +import TooltipWrapper from "components/TooltipWrapper"; import TextCell from "components/TableContainer/DataTable/TextCell"; import Button from "components/buttons/Button"; import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants"; @@ -42,30 +40,22 @@ const ReportUpdatedCell = ({ return ( { - const tooltipId = uniqueId(); - return ( - <> - - {val} - - - { - <> - Results from this report are not reported in Fleet. -
    - Data is being sent to your log destination. - - } -
    - - ); - }} + formatter={(val) => ( + + Results from this report are not reported in Fleet. +
    + Data is being sent to your log destination. + + } + position="top" + underline={false} + showArrow + > + {val} +
    + )} value="No report" /> ); @@ -73,26 +63,24 @@ const ReportUpdatedCell = ({ // Query is scheduled to run on host, but hasn't yet if (!last_fetched) { - const tipId = uniqueId(); return ( ( - <> - - {val} - - - Fleet is collecting report results. -
    - Check back later. -
    - + + Fleet is collecting report results. +
    + Check back later. + + } + position="top" + underline={false} + showArrow + > + {val} +
    )} grey italic diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tests.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tests.tsx index 29a5d40145..853657fbe2 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tests.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tests.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { screen } from "@testing-library/react"; +import { screen, waitFor } from "@testing-library/react"; import { createCustomRenderer } from "test/test-utils"; import { noop } from "lodash"; @@ -86,7 +86,7 @@ describe("Self-service - UpdateSoftwareItem component", () => { ui_status: "failed_install_update_available", }; - render( + const { user } = render( { const failedButton = screen.getByRole("button", { name: "Failed" }); expect(failedButton).toBeInTheDocument(); - expect(screen.getByText(/Software failed to install/)).toBeInTheDocument(); + await user.hover(failedButton); + + await waitFor(() => { + expect( + screen.getByText(/Software failed to install/) + ).toBeInTheDocument(); + }); }); it("renders 'Updated' state when ui_status is 'recently_updated'", () => { diff --git a/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx b/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx index 12ef9a34ca..3ed2314c63 100644 --- a/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx +++ b/frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useRef } from "react"; -import ReactTooltip from "react-tooltip"; import { IAppLastInstall, @@ -90,13 +89,12 @@ const InstallerInfo = ({ software }: IInstallerInfoProps) => { ); }; -type IInstallerStatusProps = Pick & { +type IInstallerStatusProps = Pick & { last_install: ISoftwareLastInstall | IAppLastInstall | null; onShowInstallerDetails: (uuid?: InstallOrCommandUuid) => void; }; const InstallerStatus = ({ - id, status, last_install, onShowInstallerDetails, @@ -109,43 +107,36 @@ const InstallerStatus = ({ return (
    -
    - {displayConfig.iconName === "pending-outline" ? ( - - ) : ( - - )} - {last_install && displayConfig.displayText === "Failed" && ( - - - - )} -
    - - - {displayConfig.tooltip({ - lastInstalledAt: last_install?.installed_at, - })} - - +
    + {displayConfig.iconName === "pending-outline" && ( + + )} + {last_install && displayConfig.displayText === "Failed" && ( + + + + )} +
    +
    ); }; @@ -220,7 +211,6 @@ const InstallerStatusAction = ({ {showFailedInstallStatus && (
    { }, }); - const { container, user } = render( + const { user } = render( { expect(screen.getByRole("button", { name: "Save" })).toBeDisabled(); expect(screen.getByRole("button", { name: "Run" })).toBeDisabled(); + await user.hover(screen.getByRole("button", { name: "Save" })); await waitFor(() => { - waitFor(() => { - user.hover(screen.getByRole("button", { name: "Save" })); - }); - expect( - container.querySelector("#save-policy-button") - ).toHaveTextContent(/to save or run the policy/i); + screen.getByText(/to save or run the policy/i) + ).toBeInTheDocument(); }); }); diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx index ca6ec5c0cd..b480e96006 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx +++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/PolicyForm.tsx @@ -4,11 +4,9 @@ import React, { useState, useContext, useEffect, KeyboardEvent } from "react"; import { useQuery, useQueryClient } from "react-query"; import { Ace } from "ace-builds"; -import ReactTooltip from "react-tooltip"; import { useDebouncedCallback } from "use-debounce"; import { size } from "lodash"; import classnames from "classnames"; -import { COLORS } from "styles/var/colors"; import { addGravatarUrlToResource } from "utilities/helpers"; import { AppContext } from "context/app"; @@ -884,16 +882,22 @@ const PolicyForm = ({ {hasSavePermissions && ( ( - // TODO - update to use TooltipWrapper - <> - + + Select the platforms this +
    + policy will be checked on +
    + to save or run the policy. + + } + tooltipClass={`${baseClass}__button-wrap--tooltip`} + position="top" + disableTooltip={!isExistingPolicy || isAnyPlatformSelected} + underline={false} + > + - - - {disabledLiveQuery ? ( - <>Live reports are disabled in organization settings - ) : ( - <> - Select the platforms this
    - policy will be checked on
    - to save or run the policy. - - )} -
    + + + +
    {isSaveNewPolicyModalOpen && ( diff --git a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss b/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss index b5614f9f43..d37bdd13c1 100644 --- a/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss +++ b/frontend/pages/policies/PolicyPage/components/PolicyForm/_styles.scss @@ -131,7 +131,7 @@ } &__policy-name { - font-size:$large; + font-size: $large; &.input-field--error { border: 1px solid $core-vibrant-red; @@ -142,21 +142,9 @@ display: flex; justify-content: space-between; align-items: center; - - .autofill-tooltip-wrapper { - display: flex; // Required for vertical centering - } - - .autofill-button-tooltip { - font-weight: $regular; - } } &__button-wrap { - &--tooltip { - display: flex; - } - .policy-form__run { min-width: 64px; } diff --git a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx index 97fd55f9c2..e3ed5f4c90 100644 --- a/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx +++ b/frontend/pages/policies/PolicyPage/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx @@ -20,8 +20,6 @@ import Button from "components/buttons/Button"; import Modal from "components/Modal"; import TargetLabelSelector from "components/TargetLabelSelector"; import Icon from "components/Icon"; -import ReactTooltip from "react-tooltip"; -import { COLORS } from "styles/var/colors"; export interface ISaveNewPolicyModalProps { baseClass: string; @@ -171,14 +169,23 @@ const SaveNewPolicyModal = ({ (labelName === "Resolution" && isFetchingAutofillResolution); return ( - <> -
    + + Policy queries (SQL) will be sent to a
    + large language model (LLM). Fleet
    + doesn't use this data to train models. + + ) + } + position="top" + disableTooltip={disableForm} + underline={false} + > +
    - - {aiFeaturesDisabled ? ( - "AI features are disabled in organization settings" - ) : ( - <> - Policy queries (SQL) will be sent to a
    - large language model (LLM). Fleet
    - doesn't use this data to train models. - - )} -
    - +
    ); }, [isFetchingAutofillDescription, isFetchingAutofillResolution, disableForm] @@ -325,35 +314,35 @@ const SaveNewPolicyModal = ({
    )}
    - + Select the platforms this +
    + policy will be checked on +
    + to save the policy. + + } + tooltipClass={`${baseClass}__button--modal-save-tooltip`} + position="top" + disableTooltip={!disableSave} + underline={false} + showArrow + tipOffset={8} > - - - Select the platforms this -
    - policy will be checked on -
    - to save the policy. -
    -
    + + + + -
    - - Live reports are disabled in organization settings - +
    + +
    +
    )} {canEditQuery && ( diff --git a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx index 1ae2e67451..fcedfcc00a 100644 --- a/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx +++ b/frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx @@ -653,7 +653,6 @@ const EditQueryForm = ({ isAnyTeamObserverPlus) && (
    )} + Search hosts by name, user email, hostname, +
    + UUID, serial number, or private IP address. + +); -export const VULNERABILITIES_SEARCH_BOX_TOOLTIP = - 'To search for an exact CVE, surround the string in double quotes (e.g. "CVE-2024-1234")'; +export const VULNERABILITIES_SEARCH_BOX_TOOLTIP = ( + <> + To search for an exact CVE, surround the string +
    + in double quotes (e.g. "CVE-2024-1234"). + +); // Keys from API export const MDM_STATUS_TOOLTIP: Record< diff --git a/frontend/utilities/helpers.tsx b/frontend/utilities/helpers.tsx index 8207cd558b..b744969913 100644 --- a/frontend/utilities/helpers.tsx +++ b/frontend/utilities/helpers.tsx @@ -730,9 +730,9 @@ export const getPerformanceImpactIndicatorTooltip = ( case PerformanceImpactIndicatorValue.UNDETERMINED: return ( <> - Performance impact will be available when{" "} - {isHostSpecific ? "the" : "this"}
    - report runs{isHostSpecific && " on this host"}. + Performance impact will be available +
    when {isHostSpecific ? "the" : "this"} report runs + {isHostSpecific && " on this host"}. ); default: