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} ;
- })}
-
-
+
+ {condensedVulnerabilities.map((vulnerability) => (
+ {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 (
- <>
-
-
- Add
-
-
-
+
Complete all fields to save the integration.
>
-
- >
+ }
+ tooltipClass="add-integration-tooltip"
+ position="top"
+ disableTooltip={!formInvalid || disableChildren}
+ underline={false}
+ showArrow
+ tipOffset={8}
+ >
+
+ Add
+
+
);
}}
/>
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 (
- <>
-
-
- {indicatorText}
-
-
-
- {tooltip.tooltipText}
-
- >
- );
- }
-
- // onclick without tooltip
- if (onClick) {
- return (
-
- {indicatorText}
-
- );
- }
-
- // 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" && (
-
- {
- onShowInstallerDetails();
- }}
- >
- {displayConfig.displayText}
-
-
- )}
-
-
-
- {displayConfig.tooltip({
- lastInstalledAt: last_install?.installed_at,
- })}
-
-
+
+ {displayConfig.iconName === "pending-outline" && (
+
+ )}
+ {last_install && displayConfig.displayText === "Failed" && (
+
+ {
+ onShowInstallerDetails();
+ }}
+ size="small"
+ >
+
+ {displayConfig.displayText}
+
+
+ )}
+
+
);
};
@@ -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}
+ >
+
-
- Select the platforms this
-
- policy will be checked on
-
- to save or run the policy.
-
- >
+
)}
/>
)}
- {/* TODO - update to use TooltipWrapper */}
-
+ Live reports are disabled
+ in organization settings.
+ >
+ ) : (
+ <>
+ Select the platforms this
+ policy will be checked on
+ to save or run the policy.
+ >
+ )
+ }
+ disableTooltip={
(!isExistingPolicy || isAnyPlatformSelected) &&
!disabledLiveQuery
}
+ underline={false}
+ showArrow
+ position="top"
>
-
- Run
-
-
-
- {disabledLiveQuery ? (
- <>Live reports are disabled in organization settings>
- ) : (
- <>
- Select the platforms this
- policy will be checked on
- to save or run the policy.
- >
- )}
-
+
+
+ Run
+
+
+
{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}
>
-
- Save
-
-
- Select the platforms this
-
- policy will be checked on
-
- to save the policy.
-
-
+
+
+ Save
+
+
+
setIsSaveNewPolicyModalOpen(false)}
diff --git a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
index ba91aeb504..60b0d4d76e 100644
--- a/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
+++ b/frontend/pages/queries/ManageQueriesPage/components/QueriesTable/QueriesTableConfig.tsx
@@ -242,7 +242,6 @@ const generateColumnConfigs = ({
),
diff --git a/frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx b/frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx
index 44debd81f5..a50cfb50a0 100644
--- a/frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx
+++ b/frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx
@@ -2,8 +2,6 @@ import React, { useContext, useState, useEffect } from "react";
import { useQuery } from "react-query";
import { InjectedRouter, Params } from "react-router/lib/Router";
import { useErrorHandler } from "react-error-boundary";
-import ReactTooltip from "react-tooltip";
-import { COLORS } from "styles/var/colors";
import PATHS from "router/paths";
import { AppContext } from "context/app";
@@ -295,42 +293,35 @@ const QueryDetailsPage = ({
-
- {
- queryId &&
- router.push(
- getPathWithQueryParams(
- PATHS.LIVE_REPORT(queryId),
- {
- host_id: hostId,
- fleet_id: currentTeamId,
- }
- )
- );
- }}
- disabled={isLiveQueryDisabled}
- >
- Live report
-
-
-
- Live reports are disabled in organization settings
-
+
+ {
+ queryId &&
+ router.push(
+ getPathWithQueryParams(
+ PATHS.LIVE_REPORT(queryId),
+ {
+ host_id: hostId,
+ fleet_id: currentTeamId,
+ }
+ )
+ );
+ }}
+ disabled={isLiveQueryDisabled}
+ >
+ Live report
+
+
+
)}
{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: