Fleet UI: Some of many tooltip updates to react-tooltip 5.x (#42830)
This commit is contained in:
@@ -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 (
|
||||
<div className={classNames}>
|
||||
<div className={`${baseClass}__avatar-wrapper`}>
|
||||
@@ -73,25 +70,16 @@ const FeedListItem = ({
|
||||
<span>{children}</span>
|
||||
</span>
|
||||
<br />
|
||||
<span
|
||||
<TooltipWrapper
|
||||
className={`${baseClass}__details-bottomline`}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
position="top"
|
||||
disableTooltip={!createdAt}
|
||||
tipContent={createdAt ? internationalTimeFormat(createdAt) : ""}
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
{createdAt && dateAgo(createdAt)}
|
||||
</span>
|
||||
{createdAt && (
|
||||
<ReactTooltip
|
||||
className="date-tooltip"
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id={tooltipId}
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
>
|
||||
{internationalTimeFormat(createdAt)}
|
||||
</ReactTooltip>
|
||||
)}
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
<div className={`${baseClass}__details-actions`}>
|
||||
{allowShowDetails && (
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<span
|
||||
className={`${baseClass} tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className="tooltip__tooltip-text">
|
||||
The vulnerability has been actively exploited in the <br />
|
||||
wild. This data is reported by the Cybersecurity <br />
|
||||
and Infrastructure Security Agency (CISA).
|
||||
</span>
|
||||
}
|
||||
position={tooltipPosition}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<span className={`${baseClass} tooltip tooltip__tooltip-icon`}>
|
||||
<Icon name="error" size="small" color="status-error" />
|
||||
</span>
|
||||
|
||||
<ReactTooltip
|
||||
place={tooltipPosition}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipId}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
<>
|
||||
The vulnerability has been actively exploited in the wild. This
|
||||
data is reported by the Cybersecurity and Infrastructure Security
|
||||
Agency (CISA).
|
||||
</>
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+14
-23
@@ -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 = ({
|
||||
<span className={`${baseClass}`}>
|
||||
{value}
|
||||
{mdm?.dep_profile_error && (
|
||||
<>
|
||||
<span
|
||||
className={`tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`host-mdm-status__${id}`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<Icon name="error-outline" color="status-error" size="medium" />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`host-mdm-status__${id}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
Fleet hit Apple’s API rate limit when preparing the macOS Setup
|
||||
Assistant for this host. Fleet will try again every hour.
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className="tooltip__tooltip-text">
|
||||
Fleet hit Apple's API rate limit when preparing the macOS
|
||||
Setup Assistant for this host. Fleet will try again every hour.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<Icon name="error-outline" color="status-error" size="medium" />
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -3,8 +3,5 @@
|
||||
flex-direction: row;
|
||||
text-wrap: nowrap;
|
||||
gap: $pad-small;
|
||||
|
||||
.tooltip__tooltip-text {
|
||||
text-wrap: wrap;
|
||||
}
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
+28
-33
@@ -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<T> {
|
||||
displayName: string;
|
||||
@@ -24,39 +23,35 @@ const LiveQueryIssueCell = ({
|
||||
return (
|
||||
<>
|
||||
{displayName}{" "}
|
||||
<span
|
||||
className={`host-issue tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={`host-issue__${rowId.toString()}`}
|
||||
data-tip-disable={false}
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className="tooltip__tooltip-text">
|
||||
{status === "offline" ? (
|
||||
<>
|
||||
Offline hosts will not <br />
|
||||
respond to a live report.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This host might take up to
|
||||
<br /> {distributedInterval} seconds to respond.
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<Icon
|
||||
name="error-outline"
|
||||
size="small"
|
||||
color={status === "offline" ? "status-error" : "status-warning"}
|
||||
/>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`host-issue__${rowId.toString()}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
{status === "offline" ? (
|
||||
<>
|
||||
Offline hosts will not <br />
|
||||
respond to a live report.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
This host might take up to
|
||||
<br /> {distributedInterval} seconds to respond.
|
||||
</>
|
||||
)}
|
||||
<span className="host-issue tooltip tooltip__tooltip-icon">
|
||||
<Icon
|
||||
name="error-outline"
|
||||
size="small"
|
||||
color={status === "offline" ? "status-error" : "status-warning"}
|
||||
/>
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</TooltipWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+4
-2
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+23
-30
@@ -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 (
|
||||
<span className={`${baseClass}`}>
|
||||
<span
|
||||
data-tip
|
||||
data-for={`${customIdPrefix || "pill"}__${id?.toString() || tooltipId}`}
|
||||
data-tip-disable={disableTooltip}
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span
|
||||
className={`tooltip ${generateClassTag(
|
||||
indicator || ""
|
||||
)}__tooltip-text`}
|
||||
>
|
||||
{getPerformanceImpactIndicatorTooltip(
|
||||
indicatorValue,
|
||||
isHostSpecific
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
position="top"
|
||||
disableTooltip={disableTooltip}
|
||||
underline={false}
|
||||
showArrow
|
||||
// Pills require more gap from text to tooltip
|
||||
tipOffset={indicatorValue === "Undetermined" ? 8 : 12}
|
||||
>
|
||||
<span className={pillClassName}>{indicatorValue}</span>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`${customIdPrefix || "pill"}__${id?.toString() || tooltipId}`}
|
||||
data-html
|
||||
>
|
||||
<span
|
||||
className={`tooltip ${generateClassTag(
|
||||
indicatorValue || ""
|
||||
)}__tooltip-text`}
|
||||
>
|
||||
{getPerformanceImpactIndicatorTooltip(indicatorValue, isHostSpecific)}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<span data-tip data-for={tooltipId}>
|
||||
{DEFAULT_EMPTY_CELL_VALUE}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipId}
|
||||
>
|
||||
{emptyCellTooltipText}
|
||||
</ReactTooltip>
|
||||
</>
|
||||
<TooltipWrapper
|
||||
tipContent={emptyCellTooltipText}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
<span>{DEFAULT_EMPTY_CELL_VALUE}</span>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
return DEFAULT_EMPTY_CELL_VALUE;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<T = any> {
|
||||
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 = <T,>({
|
||||
{customControl && customControl()}
|
||||
{searchable && !wideSearch && (
|
||||
<div className={`${baseClass}__search`}>
|
||||
<div
|
||||
className={`${baseClass}__search-input`}
|
||||
data-tip
|
||||
data-for="search-tooltip"
|
||||
data-tip-disable={!searchToolTipText}
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className={`tooltip ${baseClass}__tooltip-text`}>
|
||||
{searchToolTipText}
|
||||
</span>
|
||||
}
|
||||
disableTooltip={!searchToolTipText}
|
||||
underline={false}
|
||||
position="top"
|
||||
tipOffset={8}
|
||||
showArrow
|
||||
>
|
||||
<SearchField
|
||||
placeholder={inputPlaceHolder}
|
||||
defaultValue={searchQuery}
|
||||
onChange={onSearchQueryChange}
|
||||
/>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="search-tooltip"
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip ${baseClass}__tooltip-text`}>
|
||||
{searchToolTipText}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<div className={`${baseClass}__search-input`}>
|
||||
<SearchField
|
||||
placeholder={inputPlaceHolder}
|
||||
defaultValue={searchQuery}
|
||||
onChange={onSearchQueryChange}
|
||||
/>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
{customFiltersButton && customFiltersButton()}
|
||||
@@ -457,30 +452,30 @@ const TableContainer = <T,>({
|
||||
{/* Render search bar only if not empty component */}
|
||||
{searchable && !wideSearch && (
|
||||
<div className={`${baseClass}__search`}>
|
||||
<div
|
||||
className={`${baseClass}__search-input ${
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
data-tip
|
||||
data-for="search-tooltip"
|
||||
data-tip-disable={!searchToolTipText}
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className={`tooltip ${baseClass}__tooltip-text`}>
|
||||
{searchToolTipText}
|
||||
</span>
|
||||
}
|
||||
disableTooltip={!searchToolTipText}
|
||||
underline={false}
|
||||
position="top"
|
||||
tipOffset={8}
|
||||
showArrow
|
||||
>
|
||||
<SearchField
|
||||
placeholder={inputPlaceHolder}
|
||||
defaultValue={searchQuery}
|
||||
onChange={onSearchQueryChange}
|
||||
/>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="search-tooltip"
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip ${baseClass}__tooltip-text`}>
|
||||
{searchToolTipText}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<div
|
||||
className={`${baseClass}__search-input ${
|
||||
stackControls ? "stack-table-controls" : ""
|
||||
}`}
|
||||
>
|
||||
<SearchField
|
||||
placeholder={inputPlaceHolder}
|
||||
defaultValue={searchQuery}
|
||||
onChange={onSearchQueryChange}
|
||||
/>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<div className={baseClass}>
|
||||
<span
|
||||
className={`${baseClass}__icon tooltip tooltip__tooltip-icon`}
|
||||
data-tip
|
||||
data-for={tooltipID}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<Icon name={iconName} />
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipID}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
{/* TODO: enhance to be dynmaic */}
|
||||
Software can be installed on Host details page.
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IconCell;
|
||||
@@ -1,3 +0,0 @@
|
||||
.icon-cell {
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./IconCell";
|
||||
+29
-37
@@ -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 <TextCell value={text} italic={italicize} />;
|
||||
};
|
||||
|
||||
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 (
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={tooltipId}
|
||||
data-html
|
||||
>
|
||||
<ul className={`${baseClass}__vulnerability-list`}>
|
||||
{condensedVulnerabilities.map((vulnerability) => {
|
||||
const key =
|
||||
typeof vulnerability === "string" ? vulnerability : uniqueId();
|
||||
return <li key={key}>{vulnerability}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</ReactTooltip>
|
||||
<ul className={`${baseClass}__vulnerability-list`}>
|
||||
{condensedVulnerabilities.map((vulnerability) => (
|
||||
<li key={vulnerability}>{vulnerability}</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
className={`${baseClass}__vulnerability-text-with-tooltip`}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={vulnerabilityTooltip}
|
||||
position={tooltipPosition}
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
<div className={`${baseClass}__vulnerability-text-with-tooltip`}>
|
||||
{cell}
|
||||
</div>
|
||||
{vulnerabilityTooltip}
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
.vulnerabilities-cell {
|
||||
&__vulnerability-text-with-tooltip {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&__vulnerability-list {
|
||||
|
||||
+18
-27
@@ -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 (
|
||||
<>
|
||||
<div
|
||||
data-tip
|
||||
data-for="add-integration-button"
|
||||
data-tip-disable={!formInvalid || disableChildren}
|
||||
className="tooltip"
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={formInvalid || disableChildren}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className="add-integration-tooltip"
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="add-integration-button"
|
||||
data-html
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Complete all fields to save <br /> the integration.
|
||||
</>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
}
|
||||
tooltipClass="add-integration-tooltip"
|
||||
position="top"
|
||||
disableTooltip={!formInvalid || disableChildren}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={formInvalid || disableChildren}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
+19
-41
@@ -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 (
|
||||
<ReactTooltip
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={tooltipId}
|
||||
data-html
|
||||
>
|
||||
<ul className={`${baseClass}__team-list`}>
|
||||
{condensedTeams.map((teamName) => {
|
||||
return <li key={teamName}>{teamName}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</ReactTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
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 <TextCell value={teams} />;
|
||||
}
|
||||
@@ -92,19 +64,25 @@ const TeamsCell = ({ teams, className }: ITeamsCellProps) => {
|
||||
}
|
||||
|
||||
const cell = generateCell(teams);
|
||||
const tooltip = generateTooltip(teams, tooltipId);
|
||||
const condensedTeams = condenseTeams(teams);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${baseClass}__team-text-with-tooltip`}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
>
|
||||
{cell}
|
||||
</div>
|
||||
{tooltip}
|
||||
</>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<ul className={`${baseClass}__team-list`}>
|
||||
{condensedTeams.map((teamName) => {
|
||||
return <li key={teamName}>{teamName}</li>;
|
||||
})}
|
||||
</ul>
|
||||
}
|
||||
underline={false}
|
||||
showArrow
|
||||
position="top"
|
||||
className={`${baseClass}__team-text-with-tooltip`}
|
||||
tipOffset={8}
|
||||
>
|
||||
{cell}
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ export const generateTableConfig = (
|
||||
Header: "Fleets",
|
||||
disableSortBy: true,
|
||||
Cell: (cellProps: ITeamsCellProps) => (
|
||||
<TeamsCell teams={cellProps.cell.value} className="vpp-teams-cell" />
|
||||
<TeamsCell teams={cellProps.cell.value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 = (
|
||||
<>
|
||||
<span
|
||||
className="host-issue tooltip tooltip__tooltip-icon"
|
||||
data-tip
|
||||
data-for={`host-issue-count-${rowId}`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<Icon name="error-outline" color="ui-fleet-black-50" />{" "}
|
||||
{totalIssuesCount}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place={tooltipPosition}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`host-issue-count-${rowId}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`tooltip__tooltip-text`}>
|
||||
{!!criticalVulnerabilitiesCount &&
|
||||
`Critical vulnerabilities (${criticalVulnerabilitiesCount})`}
|
||||
{!!criticalVulnerabilitiesCount && !!failingPoliciesCount && <br />}
|
||||
{!!failingPoliciesCount &&
|
||||
`Failing policies (${failingPoliciesCount})`}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
{!!criticalVulnerabilitiesCount &&
|
||||
`Critical vulnerabilities (${criticalVulnerabilitiesCount})`}
|
||||
{!!criticalVulnerabilitiesCount && !!failingPoliciesCount && <br />}
|
||||
{!!failingPoliciesCount && `Failing policies (${failingPoliciesCount})`}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<TooltipWrapper
|
||||
tipContent={tipContent}
|
||||
position={tooltipPosition}
|
||||
underline={false}
|
||||
showArrow
|
||||
className={classNames}
|
||||
tipOffset={8}
|
||||
>
|
||||
<Icon name="error-outline" color="ui-fleet-black-50" /> {totalIssuesCount}
|
||||
</TooltipWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default IssuesIndicator;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.issues-indicator {
|
||||
.component__tooltip-wrapper__element {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: $pad-small;
|
||||
}
|
||||
}
|
||||
@@ -1218,21 +1218,21 @@ const HostDetailsPage = ({
|
||||
{isBYODAccountDrivenUserEnrollment(host.mdm.enrollment_status) ||
|
||||
isAndroidHost ? (
|
||||
<EmptyTable
|
||||
header="Software library is currently not supported on this host."
|
||||
info={
|
||||
<>
|
||||
Software install is coming soon.{" "}
|
||||
<CustomLink
|
||||
newTab
|
||||
text="Learn more"
|
||||
url={
|
||||
isAndroidHost
|
||||
? ANDROID_SW_INSTALL_LEARN_MORE_LINK
|
||||
: BYOD_SW_INSTALL_LEARN_MORE_LINK
|
||||
}
|
||||
newTab
|
||||
/>
|
||||
</>
|
||||
}
|
||||
header="Software library is currently not supported on this host."
|
||||
/>
|
||||
) : (
|
||||
<SoftwareLibraryCard
|
||||
|
||||
+29
-18
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { createCustomRenderer } from "test/test-utils";
|
||||
import {
|
||||
FLEET_ANDROID_CERTIFICATE_TEMPLATE_PROFILE_ID,
|
||||
@@ -40,8 +40,9 @@ describe("OS setting status cell", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+11
-22
@@ -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 (
|
||||
<span className={baseClass}>
|
||||
<Icon name={iconName} />
|
||||
{tooltip ? (
|
||||
<>
|
||||
<span
|
||||
className={`${baseClass}__status-text`}
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
{statusText}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipId}
|
||||
data-html
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<span className="tooltip__tooltip-text">
|
||||
{status !== "action_required" ? (
|
||||
<TooltipContent
|
||||
@@ -160,8 +143,14 @@ const OSSettingStatusCell = ({
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<span className={`${baseClass}__status-text`}>{statusText}</span>
|
||||
</TooltipWrapper>
|
||||
) : (
|
||||
<span className={`${baseClass}__status-text`}>{statusText}</span>
|
||||
)}
|
||||
|
||||
@@ -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(
|
||||
<ProfileStatusIndicator
|
||||
indicatorText={indicatorText}
|
||||
iconName="success"
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<ProfileStatusIndicator
|
||||
indicatorText={indicatorText}
|
||||
iconName="success"
|
||||
tooltip={{ tooltipText }}
|
||||
/>
|
||||
);
|
||||
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(
|
||||
<ProfileStatusIndicator
|
||||
indicatorText={indicatorText}
|
||||
iconName="success"
|
||||
onClick={() => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
@@ -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 (
|
||||
<>
|
||||
<span
|
||||
className="tooltip tooltip__tooltip-icon"
|
||||
data-tip
|
||||
data-for={`${indicatorText}-tooltip`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="text-link"
|
||||
className={`${baseClass}__button`}
|
||||
>
|
||||
{indicatorText}
|
||||
</Button>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place={tooltip.position ?? "bottom"}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`${indicatorText}-tooltip`}
|
||||
data-html
|
||||
>
|
||||
<span className="tooltip__tooltip-text">{tooltip.tooltipText}</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// onclick without tooltip
|
||||
if (onClick) {
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
variant="text-link"
|
||||
className={`${baseClass}__button`}
|
||||
>
|
||||
{indicatorText}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
// tooltip without onclick
|
||||
if (tooltip?.tooltipText) {
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
className="tooltip tooltip__tooltip-icon"
|
||||
data-tip
|
||||
data-for={`${indicatorText}-tooltip`}
|
||||
data-tip-disable={false}
|
||||
>
|
||||
{indicatorText}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place={tooltip.position ?? "bottom"}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`${indicatorText}-tooltip`}
|
||||
data-html
|
||||
>
|
||||
<span className="tooltip__tooltip-text">{tooltip.tooltipText}</span>
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// no tooltip, no onclick
|
||||
return indicatorText;
|
||||
};
|
||||
|
||||
return (
|
||||
<span className={`${baseClass} info-flex__data`}>
|
||||
<Icon name={iconName} />
|
||||
{getIndicatorTextWrapped()}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileStatusIndicator;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./ProfileStatusIndicator";
|
||||
+43
-20
@@ -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(
|
||||
<ReportUpdatedCell
|
||||
interval={1000}
|
||||
discard_data
|
||||
@@ -18,14 +19,18 @@ describe("ReportUpdatedCell component", () => {
|
||||
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(
|
||||
<ReportUpdatedCell
|
||||
interval={1000}
|
||||
discard_data={false}
|
||||
@@ -36,17 +41,22 @@ describe("ReportUpdatedCell component", () => {
|
||||
/>
|
||||
);
|
||||
|
||||
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(
|
||||
<ReportUpdatedCell
|
||||
interval={1000}
|
||||
discard_data={false}
|
||||
@@ -57,15 +67,22 @@ describe("ReportUpdatedCell component", () => {
|
||||
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(
|
||||
<ReportUpdatedCell
|
||||
interval={0}
|
||||
discard_data={false}
|
||||
@@ -76,9 +93,15 @@ describe("ReportUpdatedCell component", () => {
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
+31
-43
@@ -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 (
|
||||
<TextCell
|
||||
className={`${baseClass}__value no-report`}
|
||||
formatter={(val) => {
|
||||
const tooltipId = uniqueId();
|
||||
return (
|
||||
<>
|
||||
<span data-tip data-for={tooltipId}>
|
||||
{val}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={tooltipId}
|
||||
>
|
||||
{
|
||||
<>
|
||||
Results from this report are not reported in Fleet.
|
||||
<br />
|
||||
Data is being sent to your log destination.
|
||||
</>
|
||||
}
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
formatter={(val) => (
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Results from this report are not reported in Fleet.
|
||||
<br />
|
||||
Data is being sent to your log destination.
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
<span>{val}</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
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 (
|
||||
<TextCell
|
||||
value={DEFAULT_EMPTY_CELL_VALUE}
|
||||
formatter={(val) => (
|
||||
<>
|
||||
<span data-tip data-for={tipId}>
|
||||
{val}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
id={tipId}
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
place="top"
|
||||
>
|
||||
Fleet is collecting report results.
|
||||
<br />
|
||||
Check back later.
|
||||
</ReactTooltip>
|
||||
</>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Fleet is collecting report results.
|
||||
<br />
|
||||
Check back later.
|
||||
</>
|
||||
}
|
||||
position="top"
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
<span>{val}</span>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
grey
|
||||
italic
|
||||
|
||||
+9
-3
@@ -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(
|
||||
<UpdateSoftwareItem
|
||||
software={software}
|
||||
onClickUpdateAction={noop}
|
||||
@@ -97,7 +97,13 @@ describe("Self-service - UpdateSoftwareItem component", () => {
|
||||
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'", () => {
|
||||
|
||||
+30
-40
@@ -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<IHostSoftware, "id" | "status"> & {
|
||||
type IInstallerStatusProps = Pick<IHostSoftware, "status"> & {
|
||||
last_install: ISoftwareLastInstall | IAppLastInstall | null;
|
||||
onShowInstallerDetails: (uuid?: InstallOrCommandUuid) => void;
|
||||
};
|
||||
|
||||
const InstallerStatus = ({
|
||||
id,
|
||||
status,
|
||||
last_install,
|
||||
onShowInstallerDetails,
|
||||
@@ -109,43 +107,36 @@ const InstallerStatus = ({
|
||||
|
||||
return (
|
||||
<div className={`${baseClass}__status-content`}>
|
||||
<div
|
||||
className={`${baseClass}__status-with-tooltip`}
|
||||
data-tip
|
||||
data-for={`install-tooltip__${id}`}
|
||||
<TooltipWrapper
|
||||
tipContent={displayConfig.tooltip({
|
||||
lastInstalledAt: last_install?.installed_at,
|
||||
})}
|
||||
underline={false}
|
||||
showArrow
|
||||
position="top"
|
||||
tipOffset={8}
|
||||
>
|
||||
{displayConfig.iconName === "pending-outline" ? (
|
||||
<Spinner size="x-small" includeContainer={false} centered={false} />
|
||||
) : (
|
||||
<Icon name={displayConfig.iconName || "install"} />
|
||||
)}
|
||||
{last_install && displayConfig.displayText === "Failed" && (
|
||||
<span data-testid={`${baseClass}__status--test`}>
|
||||
<Button
|
||||
className={`${baseClass}__item-status-button`}
|
||||
variant="inverse"
|
||||
onClick={() => {
|
||||
onShowInstallerDetails();
|
||||
}}
|
||||
>
|
||||
{displayConfig.displayText}
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className={`${baseClass}__status-tooltip`}
|
||||
effect="solid"
|
||||
backgroundColor="#3e4771"
|
||||
id={`install-tooltip__${id}`}
|
||||
data-html
|
||||
>
|
||||
<span className={`${baseClass}__status-tooltip-text`}>
|
||||
{displayConfig.tooltip({
|
||||
lastInstalledAt: last_install?.installed_at,
|
||||
})}
|
||||
</span>
|
||||
</ReactTooltip>
|
||||
<div className={`${baseClass}__status-with-tooltip`}>
|
||||
{displayConfig.iconName === "pending-outline" && (
|
||||
<Spinner size="x-small" includeContainer={false} centered={false} />
|
||||
)}
|
||||
{last_install && displayConfig.displayText === "Failed" && (
|
||||
<span data-testid={`${baseClass}__status--test`}>
|
||||
<Button
|
||||
className={`${baseClass}__item-status-button`}
|
||||
variant="inverse"
|
||||
onClick={() => {
|
||||
onShowInstallerDetails();
|
||||
}}
|
||||
size="small"
|
||||
>
|
||||
<Icon name={displayConfig.iconName || "install"} />
|
||||
{displayConfig.displayText}
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -220,7 +211,6 @@ const InstallerStatusAction = ({
|
||||
{showFailedInstallStatus && (
|
||||
<div className={`${baseClass}__item-status`}>
|
||||
<InstallerStatus
|
||||
id={id}
|
||||
status={status}
|
||||
last_install={lastInstall}
|
||||
onShowInstallerDetails={onShowInstallerDetails}
|
||||
|
||||
@@ -188,7 +188,7 @@ describe("PolicyForm - component", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { container, user } = render(
|
||||
const { user } = render(
|
||||
<PolicyForm
|
||||
policyIdForEdit={mockPolicy.id}
|
||||
showOpenSchemaActionText={false}
|
||||
@@ -214,15 +214,12 @@ describe("PolicyForm - component", () => {
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
<GitOpsModeTooltipWrapper
|
||||
renderChildren={(disableChildren) => (
|
||||
// TODO - update to use TooltipWrapper
|
||||
<>
|
||||
<span
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
data-tip
|
||||
data-for="save-policy-button"
|
||||
data-tip-disable={
|
||||
!isExistingPolicy || isAnyPlatformSelected
|
||||
}
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Select the platforms this
|
||||
<br />
|
||||
policy will be checked on
|
||||
<br />
|
||||
to save or run the policy.
|
||||
</>
|
||||
}
|
||||
tooltipClass={`${baseClass}__button-wrap--tooltip`}
|
||||
position="top"
|
||||
disableTooltip={!isExistingPolicy || isAnyPlatformSelected}
|
||||
underline={false}
|
||||
>
|
||||
<span className={`${baseClass}__button-wrap--tooltip`}>
|
||||
<Button
|
||||
onClick={promptSavePolicy()}
|
||||
disabled={disableSaveFormErrors || disableChildren}
|
||||
@@ -903,63 +907,47 @@ const PolicyForm = ({
|
||||
Save
|
||||
</Button>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
id="save-policy-button"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
>
|
||||
Select the platforms this
|
||||
<br />
|
||||
policy will be checked on
|
||||
<br />
|
||||
to save or run the policy.
|
||||
</ReactTooltip>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{/* TODO - update to use TooltipWrapper */}
|
||||
<span
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
data-tip
|
||||
data-for="run-policy-button"
|
||||
data-tip-disable={
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
disabledLiveQuery ? (
|
||||
<>
|
||||
Live reports are disabled <br />
|
||||
in organization settings.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Select the platforms this <br />
|
||||
policy will be checked on <br />
|
||||
to save or run the policy.
|
||||
</>
|
||||
)
|
||||
}
|
||||
disableTooltip={
|
||||
(!isExistingPolicy || isAnyPlatformSelected) &&
|
||||
!disabledLiveQuery
|
||||
}
|
||||
underline={false}
|
||||
showArrow
|
||||
position="top"
|
||||
>
|
||||
<Button
|
||||
onClick={goToSelectTargets}
|
||||
disabled={
|
||||
isAddingAutomation ||
|
||||
(isExistingPolicy && !isAnyPlatformSelected) ||
|
||||
disabledLiveQuery
|
||||
}
|
||||
variant="inverse"
|
||||
>
|
||||
Run <Icon name="run" />
|
||||
</Button>
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className={`${baseClass}__button-wrap--tooltip`}
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
id="run-policy-button"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
data-html
|
||||
>
|
||||
{disabledLiveQuery ? (
|
||||
<>Live reports are disabled in organization settings</>
|
||||
) : (
|
||||
<>
|
||||
Select the platforms this <br />
|
||||
policy will be checked on <br />
|
||||
to save or run the policy.
|
||||
</>
|
||||
)}
|
||||
</ReactTooltip>
|
||||
<span className={`${baseClass}__button-wrap--tooltip`}>
|
||||
<Button
|
||||
onClick={goToSelectTargets}
|
||||
disabled={
|
||||
isAddingAutomation ||
|
||||
(isExistingPolicy && !isAnyPlatformSelected) ||
|
||||
disabledLiveQuery
|
||||
}
|
||||
variant="inverse"
|
||||
>
|
||||
Run <Icon name="run" />
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
</form>
|
||||
{isSaveNewPolicyModalOpen && (
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+46
-57
@@ -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 (
|
||||
<>
|
||||
<div
|
||||
data-tip
|
||||
data-for={`autofill-button-${labelName}`}
|
||||
// Tooltip shows except when fetching AI autofill
|
||||
data-tip-disable={disableForm}
|
||||
className="autofill-tooltip-wrapper"
|
||||
>
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
aiFeaturesDisabled ? (
|
||||
"AI features are disabled in organization settings"
|
||||
) : (
|
||||
<>
|
||||
Policy queries (SQL) will be sent to a <br />
|
||||
large language model (LLM). Fleet <br />
|
||||
doesn't use this data to train models.
|
||||
</>
|
||||
)
|
||||
}
|
||||
position="top"
|
||||
disableTooltip={disableForm}
|
||||
underline={false}
|
||||
>
|
||||
<div className="autofill-tooltip-wrapper">
|
||||
<Button
|
||||
variant="inverse"
|
||||
disabled={aiFeaturesDisabled || disableForm}
|
||||
@@ -198,25 +205,7 @@ const SaveNewPolicyModal = ({
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className="autofill-button-tooltip"
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id={`autofill-button-${labelName}`}
|
||||
data-html
|
||||
>
|
||||
{aiFeaturesDisabled ? (
|
||||
"AI features are disabled in organization settings"
|
||||
) : (
|
||||
<>
|
||||
Policy queries (SQL) will be sent to a <br />
|
||||
large language model (LLM). Fleet <br />
|
||||
doesn't use this data to train models.
|
||||
</>
|
||||
)}
|
||||
</ReactTooltip>
|
||||
</>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
},
|
||||
[isFetchingAutofillDescription, isFetchingAutofillResolution, disableForm]
|
||||
@@ -325,35 +314,35 @@ const SaveNewPolicyModal = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="modal-cta-wrap">
|
||||
<span
|
||||
className={`${baseClass}__button-wrap--modal-save`}
|
||||
data-tip
|
||||
data-for={`${baseClass}__button--modal-save-tooltip`}
|
||||
data-tip-disable={!disableSave}
|
||||
<TooltipWrapper
|
||||
tipContent={
|
||||
<>
|
||||
Select the platforms this
|
||||
<br />
|
||||
policy will be checked on
|
||||
<br />
|
||||
to save the policy.
|
||||
</>
|
||||
}
|
||||
tooltipClass={`${baseClass}__button--modal-save-tooltip`}
|
||||
position="top"
|
||||
disableTooltip={!disableSave}
|
||||
underline={false}
|
||||
showArrow
|
||||
tipOffset={8}
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={handleSavePolicy}
|
||||
disabled={disableSave}
|
||||
className="save-policy-loading"
|
||||
isLoading={isUpdatingPolicy}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<ReactTooltip
|
||||
className={`${baseClass}__button--modal-save-tooltip`}
|
||||
place="bottom"
|
||||
effect="solid"
|
||||
id={`${baseClass}__button--modal-save-tooltip`}
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
>
|
||||
Select the platforms this
|
||||
<br />
|
||||
policy will be checked on
|
||||
<br />
|
||||
to save the policy.
|
||||
</ReactTooltip>
|
||||
</span>
|
||||
<span className={`${baseClass}__button-wrap--modal-save`}>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={handleSavePolicy}
|
||||
disabled={disableSave}
|
||||
className="save-policy-loading"
|
||||
isLoading={isUpdatingPolicy}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
<Button
|
||||
className={`${baseClass}__button--modal-cancel`}
|
||||
onClick={() => setIsSaveNewPolicyModalOpen(false)}
|
||||
|
||||
-1
@@ -242,7 +242,6 @@ const generateColumnConfigs = ({
|
||||
<PerformanceImpactCell
|
||||
value={{
|
||||
indicator: cellProps.cell.value,
|
||||
id: cellProps.row.original.id,
|
||||
}}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -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 = ({
|
||||
<div
|
||||
className={`button-wrap ${baseClass}__button-wrap--new-query`}
|
||||
>
|
||||
<div
|
||||
data-tip
|
||||
data-for="live-query-button"
|
||||
// Tooltip shows when live queries are globally disabled
|
||||
data-tip-disable={!isLiveQueryDisabled}
|
||||
<TooltipWrapper
|
||||
tipContent="Live reports are disabled in organization settings."
|
||||
position="top"
|
||||
disableTooltip={!isLiveQueryDisabled}
|
||||
underline={false}
|
||||
showArrow
|
||||
>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="inverse"
|
||||
onClick={() => {
|
||||
queryId &&
|
||||
router.push(
|
||||
getPathWithQueryParams(
|
||||
PATHS.LIVE_REPORT(queryId),
|
||||
{
|
||||
host_id: hostId,
|
||||
fleet_id: currentTeamId,
|
||||
}
|
||||
)
|
||||
);
|
||||
}}
|
||||
disabled={isLiveQueryDisabled}
|
||||
>
|
||||
Live report <Icon name="run" />
|
||||
</Button>
|
||||
</div>
|
||||
<ReactTooltip
|
||||
className="live-query-button-tooltip"
|
||||
place="top"
|
||||
effect="solid"
|
||||
backgroundColor={COLORS["tooltip-bg"]}
|
||||
id="live-query-button"
|
||||
data-html
|
||||
>
|
||||
Live reports are disabled in organization settings
|
||||
</ReactTooltip>
|
||||
<div>
|
||||
<Button
|
||||
className={`${baseClass}__run`}
|
||||
variant="inverse"
|
||||
onClick={() => {
|
||||
queryId &&
|
||||
router.push(
|
||||
getPathWithQueryParams(
|
||||
PATHS.LIVE_REPORT(queryId),
|
||||
{
|
||||
host_id: hostId,
|
||||
fleet_id: currentTeamId,
|
||||
}
|
||||
)
|
||||
);
|
||||
}}
|
||||
disabled={isLiveQueryDisabled}
|
||||
>
|
||||
Live report <Icon name="run" />
|
||||
</Button>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
</div>
|
||||
)}
|
||||
{canEditQuery && (
|
||||
|
||||
@@ -653,7 +653,6 @@ const EditQueryForm = ({
|
||||
isAnyTeamObserverPlus) && (
|
||||
<div className={`button-wrap ${baseClass}__button-wrap--new-query`}>
|
||||
<TooltipWrapper
|
||||
className="live-query-button-tooltip"
|
||||
tipContent="Live reports are disabled in organization settings"
|
||||
disableTooltip={!disabledLiveQuery}
|
||||
position="top"
|
||||
@@ -932,7 +931,6 @@ const EditQueryForm = ({
|
||||
</>
|
||||
)}
|
||||
<TooltipWrapper
|
||||
className="live-query-button-tooltip"
|
||||
tipContent="Live reports are disabled in organization settings"
|
||||
disableTooltip={!disabledLiveQuery}
|
||||
position="top"
|
||||
|
||||
@@ -339,11 +339,21 @@ export const SCHEDULE_PLATFORM_DROPDOWN_OPTIONS = [
|
||||
export const HOSTS_SEARCH_BOX_PLACEHOLDER =
|
||||
"Search name, user email, hostname, UUID, serial number, or private IP address";
|
||||
|
||||
export const HOSTS_SEARCH_BOX_TOOLTIP =
|
||||
"Search hosts by name, user email, hostname, UUID, serial number, or private IP address";
|
||||
export const HOSTS_SEARCH_BOX_TOOLTIP = (
|
||||
<>
|
||||
Search hosts by name, user email, hostname,
|
||||
<br />
|
||||
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
|
||||
<br />
|
||||
in double quotes (e.g. "CVE-2024-1234").
|
||||
</>
|
||||
);
|
||||
|
||||
// Keys from API
|
||||
export const MDM_STATUS_TOOLTIP: Record<
|
||||
|
||||
@@ -730,9 +730,9 @@ export const getPerformanceImpactIndicatorTooltip = (
|
||||
case PerformanceImpactIndicatorValue.UNDETERMINED:
|
||||
return (
|
||||
<>
|
||||
Performance impact will be available when{" "}
|
||||
{isHostSpecific ? "the" : "this"} <br />
|
||||
report runs{isHostSpecific && " on this host"}.
|
||||
Performance impact will be available
|
||||
<br /> when {isHostSpecific ? "the" : "this"} report runs
|
||||
{isHostSpecific && " on this host"}.
|
||||
</>
|
||||
);
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user