UI: Update tooltip/link structure & functionality in Software tables (#13893)

This commit is contained in:
Jacob Shandling
2023-09-13 10:19:56 -07:00
committed by GitHub
parent 7acd3be94b
commit 46b1db7850
8 changed files with 96 additions and 58 deletions
@@ -0,0 +1,3 @@
- Restored the ability to click on and select/copy text from software bundle tooltips while
maintaining the abilities to click the software's name to get more details and to click anywhere
else in the row to view all hosts with that software installed.
@@ -3,6 +3,7 @@ import React from "react";
import { Link } from "react-router";
import classnames from "classnames";
import TooltipWrapper from "components/TooltipWrapper";
interface ILinkCellProps {
value: string | JSX.Element;
@@ -10,7 +11,7 @@ interface ILinkCellProps {
className?: string;
customOnClick?: (e: React.MouseEvent) => void;
/** allows viewing overflow for tooltip */
withTooltip?: boolean;
tooltipContent?: string;
title?: string;
}
@@ -21,21 +22,32 @@ const LinkCell = ({
path,
className,
customOnClick,
withTooltip,
title,
tooltipContent,
}: ILinkCellProps): JSX.Element => {
const cellClasses = classnames(
baseClass,
className,
withTooltip && "link-cell-tooltip"
);
const cellClasses = classnames(baseClass, className);
const onClick = (e: React.MouseEvent): void => {
customOnClick && customOnClick(e);
};
return (
<Link className={cellClasses} to={path} onClick={onClick} title={title}>
return tooltipContent ? (
<TooltipWrapper
position="top"
className="link-cell-tooltip-wrapper"
tipContent={tooltipContent}
>
<Link className={cellClasses} to={path} onClick={onClick} title={title}>
{value}
</Link>
</TooltipWrapper>
) : (
<Link
className={cellClasses}
to={path}
onClick={customOnClick}
title={title}
>
{value}
</Link>
);
@@ -241,21 +241,32 @@ $shadow-transition-width: 10px;
}
}
// css to properly style link-cell with tooltip
.link-cell-tooltip {
.link-cell-tooltip-wrapper {
overflow: visible; // fixes tooltip overflow cut off by cell
white-space: nowrap; // single line
margin: 0; // padding applied to .link-cell for larger clickable area
.component__tooltip-wrapper {
display: block;
white-space: nowrap; // single line
margin: 0; // padding applied to .link-cell for larger clickable area
.component__tooltip-wrapper__element {
&__element {
display: block;
white-space: nowrap; // single line
text-overflow: ellipsis; // truncates text
overflow: hidden;
&__underline {
width: 100%;
.component__tooltip-wrapper__underline {
max-width: 100%; // fixes underline overflowing past truncated text
&::after {
bottom: 9px; // compensate for padding to make larger clickable area
}
}
// TODO this naming is now confusing, as this .link-cell is not the outermost layer of
// the cell it's a NameCell
.link-cell {
padding: 10px 0;
}
}
&__tip-text {
cursor: auto;
}
}
}
@@ -4,7 +4,7 @@ import React from "react";
import * as DOMPurify from "dompurify";
interface ITooltipWrapperProps {
children: string;
children: string | JSX.Element;
tipContent: string;
position?: "top" | "bottom";
isDelayed?: boolean;
@@ -33,11 +33,17 @@ const TooltipWrapper = ({
<div className={classname} data-position={position}>
<div className={`${baseClass}__element`}>
{children}
<div className={`${baseClass}__underline`} data-text={children} />
<div
className={`${baseClass}__element__underline`}
data-text={children}
/>
</div>
<div
className={tipClass}
dangerouslySetInnerHTML={{ __html: sanitizedTipContent }}
onClick={(e) => {
e.stopPropagation();
}}
/>
</div>
);
+23 -18
View File
@@ -18,26 +18,31 @@
position: static;
display: inline; // treat like a span but allow other tags as children
white-space: nowrap;
}
&__underline {
position: absolute;
top: 0;
left: 0;
bottom: 0;
&::before {
content: attr(data-text);
opacity: 0;
visibility: hidden;
}
&::after {
content: "";
width: 100%;
height: 100%;
&__underline {
position: absolute;
bottom: -2px;
top: 0;
left: 0;
border-bottom: 1px dashed $ui-fleet-black-50;
bottom: 0;
&::before {
content: attr(data-text);
opacity: 0;
visibility: hidden;
}
&::after {
content: "";
width: 100%;
height: 100%;
position: absolute;
bottom: -2px;
left: 0;
border-bottom: 1px dashed $ui-fleet-black-50;
}
}
a {
position: relative;
z-index: 99;
}
}
&__tip-text {
@@ -14,6 +14,7 @@ import TooltipWrapper from "components/TooltipWrapper";
import ViewAllHostsLink from "components/ViewAllHostsLink";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import { COLORS } from "styles/var/colors";
import { getSoftwareBundleTooltipMarkup } from "utilities/helpers";
interface IHeaderProps {
column: {
@@ -218,8 +219,10 @@ export const generateSoftwareTableHeaders = ({
<LinkCell
path={PATHS.SOFTWARE_DETAILS(id.toString())}
customOnClick={onClickSoftware}
value={bundle ? renderBundleTooltip(name, bundle) : name}
withTooltip={!!bundle}
value={name}
tooltipContent={
bundle ? getSoftwareBundleTooltipMarkup(bundle) : undefined
}
/>
);
},
@@ -6,7 +6,10 @@ import ReactTooltip from "react-tooltip";
import { formatSoftwareType, ISoftware } from "interfaces/software";
import { IVulnerability } from "interfaces/vulnerability";
import PATHS from "router/paths";
import { formatFloatAsPercentage } from "utilities/helpers";
import {
formatFloatAsPercentage,
getSoftwareBundleTooltipMarkup,
} from "utilities/helpers";
import { DEFAULT_EMPTY_CELL_VALUE } from "utilities/constants";
import HeaderCell from "components/TableContainer/DataTable/HeaderCell";
@@ -65,23 +68,6 @@ const condenseVulnerabilities = (
: condensed;
};
const renderBundleTooltip = (name: string, bundle: string) => (
<span className="name-container">
<TooltipWrapper
position="top"
tipContent={`
<span>
<b>Bundle identifier: </b>
<br />
${bundle}
</span>
`}
>
{name}
</TooltipWrapper>
</span>
);
const getMaxProbability = (vulns: IVulnerability[]) =>
vulns.reduce(
(max, { epss_probability }) => Math.max(max, epss_probability || 0),
@@ -217,8 +203,10 @@ const generateTableHeaders = (
<LinkCell
path={PATHS.SOFTWARE_DETAILS(id.toString())}
customOnClick={onClickSoftware}
value={bundle ? renderBundleTooltip(name, bundle) : name}
withTooltip={!!bundle}
value={name}
tooltipContent={
bundle ? getSoftwareBundleTooltipMarkup(bundle) : undefined
}
/>
);
},
+10
View File
@@ -900,6 +900,16 @@ export const getNextLocationPath = ({
return queryString ? `/${nextLocation}?${queryString}` : `/${nextLocation}`;
};
export const getSoftwareBundleTooltipMarkup = (bundle: string) => {
return `
<span>
<b>Bundle identifier: </b>
<br />
${bundle}
</span>
`;
};
export default {
addGravatarUrlToResource,
formatConfigDataForServer,