Fleet UI: Show tooltip for truncated vulnerabilities list in Update details modal (#49236)

This commit is contained in:
RachelElysia
2026-07-13 15:37:07 -07:00
committed by GitHub
parent aba0c2dd2b
commit ca1acc2467
5 changed files with 29 additions and 51 deletions
+1
View File
@@ -0,0 +1 @@
- Fixed an issue where the truncated vulnerabilities list in the Update details modal did not show a tooltip listing the remaining CVEs.
@@ -119,10 +119,6 @@ const renderVisibleRow = ({
const isTruncatedFirst = visibleCount === 0;
const content = isTruncatedFirst ? truncatedFirstContent : standardContent;
const rowStyle: React.CSSProperties = {
whiteSpace: "nowrap",
overflow: "hidden",
};
const rowClass = classnames(`${baseClass}__visible`, {
[`${baseClass}__visible--truncated`]: isTruncatedFirst,
});
@@ -130,16 +126,12 @@ const renderVisibleRow = ({
if (onClick) {
return (
<Button variant="link" className={rowClass} onClick={onClick}>
<span style={rowStyle}>{content}</span>
<span>{content}</span>
</Button>
);
}
return (
<span className={rowClass} style={rowStyle}>
{content}
</span>
);
return <span className={rowClass}>{content}</span>;
};
const TruncatedTextList = ({
@@ -207,24 +199,9 @@ const TruncatedTextList = ({
const hidden = items.slice(visibleCount);
return (
<div
ref={containerRef}
className={classnames(baseClass, className)}
style={{ position: "relative", minWidth: 0 }}
>
<div ref={containerRef} className={classnames(baseClass, className)}>
{/* Hidden measurement layer — same font/size as the visible row */}
<div
className={`${baseClass}__measure`}
aria-hidden
style={{
position: "absolute",
top: 0,
left: 0,
visibility: "hidden",
pointerEvents: "none",
whiteSpace: "nowrap",
}}
>
<div className={`${baseClass}__measure`} aria-hidden>
{items.map((item, i) => (
<span
// eslint-disable-next-line react/no-array-index-key
@@ -14,20 +14,7 @@ import {
import Card from "components/Card";
import DataSet from "components/DataSet";
import TooltipWrapper from "components/TooltipWrapper";
const generateVulnerabilitiesValue = (vulnerabilities: string[]) => {
const first3 = vulnerabilities.slice(0, 3);
const rest = vulnerabilities.slice(3);
const first3Text = first3.join(", ");
const restText = `, +${rest.length} more`;
return (
<>
<span>{`${first3Text}${rest.length > 0 ? restText : ""}`}</span>
</>
);
};
import TruncatedTextList from "components/TruncatedTextList";
const baseClass = "inventory-versions";
@@ -97,15 +84,14 @@ const InventoryVersion = ({
textOnly
/>
)}
</div>
{vulnerabilities && vulnerabilities.length !== 0 && (
<div className={`${baseClass}__row`}>
{vulnerabilities && vulnerabilities.length !== 0 && (
<DataSet
className={`${baseClass}__vulnerabilities`}
title="Vulnerabilities"
value={generateVulnerabilitiesValue(vulnerabilities)}
value={<TruncatedTextList items={vulnerabilities} />}
/>
</div>
)}
)}
</div>
{!!installedPaths?.length &&
installedPaths.map((path) => {
// Find the signature info for this path
@@ -3,8 +3,8 @@
flex-direction: column;
gap: $pad-small;
.data-set dd {
white-space: initial;
dt {
white-space: nowrap;
}
&__versions {
@@ -20,9 +20,22 @@
}
&__row {
display: flex;
flex-wrap: wrap;
gap: $pad-xxlarge;
}
// Fill the row so TruncatedTextList has a bounded width to measure against.
&__vulnerabilities {
flex: 1;
min-width: 0;
// dd is display:flex, so TruncatedTextList's outer div would size to its
// content by default and truncate too early. Stretch it to fill.
.truncated-text-list {
min-width: 100%;
}
}
&__sig-info {
display: flex;
flex-direction: column;
@@ -39,8 +39,9 @@ describe("SoftwareDetailsModal", () => {
expect(screen.getByText("Hash:")).toBeVisible();
expect(screen.getByText("mockhashhere")).toBeVisible();
// Vulnerabilities
expect(screen.getByText(/CVE-2020-0001/)).toBeVisible();
// Vulnerabilities — TruncatedTextList renders items in both a hidden
// measure layer and the visible row, so match all occurrences.
expect(screen.getAllByText(/CVE-2020-0001/).length).toBeGreaterThan(0);
// Close button
expect(screen.getByRole("button", { name: "Close" })).toBeVisible();