Fleet UI: [small released bug] Fix time cell with tooltip component to not have a date restriction, add unit tests for edge cases (#13824)
This commit is contained in:
@@ -0,0 +1 @@
|
||||
- Fleet UI: Fix software vulnerabilities time ago column for old CVEs
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { renderWithSetup } from "test/test-utils";
|
||||
|
||||
import HumanTimeDiffWithDateTip from "./HumanTimeDiffWithDateTip";
|
||||
|
||||
const EMPTY_STRING = "Unavailable";
|
||||
const INVALID_STRING = "Invalid date";
|
||||
|
||||
describe("HumanTimeDiffWithDateTip - component", () => {
|
||||
it("renders tooltip on hover", async () => {
|
||||
const { user } = renderWithSetup(
|
||||
<HumanTimeDiffWithDateTip timeString="2015-12-06T10:30:00Z" />
|
||||
);
|
||||
|
||||
// Note: number of years varies over time
|
||||
await user.hover(screen.getByText(/years ago/i));
|
||||
|
||||
// Note: hour of day varies for timezones
|
||||
expect(screen.getByText(/12\/6\/2015/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles empty string error", async () => {
|
||||
render(<HumanTimeDiffWithDateTip timeString="" />);
|
||||
|
||||
const emptyStringText = screen.getByText(EMPTY_STRING);
|
||||
expect(emptyStringText).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("handles invalid string error", async () => {
|
||||
render(<HumanTimeDiffWithDateTip timeString="foobar" />);
|
||||
|
||||
const invalidStringText = screen.getByText(INVALID_STRING);
|
||||
expect(invalidStringText).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -1,40 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
import { uniqueId } from "lodash";
|
||||
import { humanHostLastSeen } from "utilities/helpers";
|
||||
import { humanLastSeen, internationalTimeFormat } from "utilities/helpers";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import intlFormat from "date-fns/intlFormat";
|
||||
|
||||
export default ({ timeString }: { timeString: string }): JSX.Element => {
|
||||
interface IHumanTimeDiffWithDateTip {
|
||||
timeString: string;
|
||||
}
|
||||
|
||||
/** Returns "Unavailable" if date is empty string or "Unavailable"
|
||||
* Returns "Invalid date" if date is invalid */
|
||||
export default ({ timeString }: IHumanTimeDiffWithDateTip): JSX.Element => {
|
||||
const id = uniqueId();
|
||||
return timeString === "Unavailable" ? (
|
||||
<span>Unavailable</span>
|
||||
) : (
|
||||
<>
|
||||
<span className={"date-tooltip"} data-tip data-for={`tooltip-${id}`}>
|
||||
{humanHostLastSeen(timeString)}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="date-tooltip-text"
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id={`tooltip-${id}`}
|
||||
backgroundColor="#3e4771"
|
||||
>
|
||||
{intlFormat(
|
||||
new Date(timeString),
|
||||
{
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
},
|
||||
{ locale: window.navigator.languages[0] }
|
||||
)}
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
|
||||
if (timeString === "Unavailable" || timeString === "") {
|
||||
return <span>Unavailable</span>;
|
||||
}
|
||||
|
||||
try {
|
||||
return (
|
||||
<>
|
||||
<span className={"date-tooltip"} data-tip data-for={`tooltip-${id}`}>
|
||||
{humanLastSeen(timeString)}
|
||||
</span>
|
||||
<ReactTooltip
|
||||
className="date-tooltip-text"
|
||||
place="top"
|
||||
type="dark"
|
||||
effect="solid"
|
||||
id={`tooltip-${id}`}
|
||||
backgroundColor="#3e4771"
|
||||
>
|
||||
{internationalTimeFormat(new Date(timeString))}
|
||||
</ReactTooltip>
|
||||
</>
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof RangeError) {
|
||||
return <span>Invalid date</span>;
|
||||
}
|
||||
return <span>Unavailable</span>;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
import React from "react";
|
||||
import { find, lowerCase, noop } from "lodash";
|
||||
import { intlFormat, formatDistanceToNowStrict } from "date-fns";
|
||||
import { formatDistanceToNowStrict } from "date-fns";
|
||||
|
||||
import { ActivityType, IActivity, IActivityDetails } from "interfaces/activity";
|
||||
import { addGravatarUrlToResource } from "utilities/helpers";
|
||||
import {
|
||||
addGravatarUrlToResource,
|
||||
internationalTimeFormat,
|
||||
} from "utilities/helpers";
|
||||
import { DEFAULT_GRAVATAR_LINK } from "utilities/constants";
|
||||
import Avatar from "components/Avatar";
|
||||
import Button from "components/buttons/Button";
|
||||
import Icon from "components/Icon";
|
||||
import ReactTooltip from "react-tooltip";
|
||||
import PremiumFeatureIconWithTooltip from "components/PremiumFeatureIconWithTooltip";
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
const baseClass = "activity-item";
|
||||
|
||||
@@ -705,18 +707,7 @@ const ActivityItem = ({
|
||||
id={`activity-${activity.id}`}
|
||||
backgroundColor="#3e4771"
|
||||
>
|
||||
{intlFormat(
|
||||
activityCreatedAt,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
},
|
||||
{ locale: window.navigator.languages[0] }
|
||||
)}
|
||||
{internationalTimeFormat(activityCreatedAt)}
|
||||
</ReactTooltip>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
-1
@@ -50,7 +50,6 @@ const Vulnerabilities = ({
|
||||
return (
|
||||
<div className="section section--vulnerabilities">
|
||||
<p className="section__header">Vulnerabilities</p>
|
||||
|
||||
{software?.vulnerabilities?.length ? (
|
||||
<>
|
||||
{software && (
|
||||
|
||||
@@ -39,6 +39,9 @@ export const FREQUENCY_DROPDOWN_OPTIONS = [
|
||||
export const GITHUB_NEW_ISSUE_LINK =
|
||||
"https://github.com/fleetdm/fleet/issues/new?assignees=&labels=bug%2C%3Areproduce&template=bug-report.md";
|
||||
|
||||
/** July 28, 2016 is the date of the initial commit to fleet/fleet. */
|
||||
export const INITIAL_FLEET_DATE = "2016-07-28T00:00:00Z";
|
||||
|
||||
export const LOGGING_TYPE_OPTIONS = [
|
||||
{ label: "Snapshot", value: "snapshot" },
|
||||
{ label: "Differential", value: "differential" },
|
||||
|
||||
@@ -15,9 +15,10 @@ import { buildQueryStringFromParams } from "utilities/url";
|
||||
import md5 from "js-md5";
|
||||
import {
|
||||
formatDistanceToNow,
|
||||
isAfter,
|
||||
intervalToDuration,
|
||||
formatDuration,
|
||||
intlFormat,
|
||||
intervalToDuration,
|
||||
isAfter,
|
||||
} from "date-fns";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
@@ -44,6 +45,7 @@ import {
|
||||
DEFAULT_GRAVATAR_LINK_FALLBACK,
|
||||
DEFAULT_GRAVATAR_LINK_DARK,
|
||||
DEFAULT_GRAVATAR_LINK_DARK_FALLBACK,
|
||||
INITIAL_FLEET_DATE,
|
||||
PLATFORM_LABEL_DISPLAY_TYPES,
|
||||
} from "utilities/constants";
|
||||
import { IScheduledQueryStats } from "interfaces/scheduled_query_stats";
|
||||
@@ -586,7 +588,7 @@ export const humanHostLastRestart = (
|
||||
!detailUpdatedAt ||
|
||||
!uptime ||
|
||||
detailUpdatedAt === DEFAULT_EMPTY_CELL_VALUE ||
|
||||
detailUpdatedAt < "2016-07-28T00:00:00Z" ||
|
||||
detailUpdatedAt < INITIAL_FLEET_DATE ||
|
||||
typeof uptime !== "number"
|
||||
) {
|
||||
return "Unavailable";
|
||||
@@ -613,7 +615,7 @@ export const humanHostLastRestart = (
|
||||
};
|
||||
|
||||
export const humanHostLastSeen = (lastSeen: string): string => {
|
||||
if (!lastSeen || lastSeen < "2016-07-28T00:00:00Z") {
|
||||
if (!lastSeen || lastSeen < INITIAL_FLEET_DATE) {
|
||||
return "Never";
|
||||
}
|
||||
if (lastSeen === "Unavailable") {
|
||||
@@ -623,7 +625,7 @@ export const humanHostLastSeen = (lastSeen: string): string => {
|
||||
};
|
||||
|
||||
export const humanHostEnrolled = (enrolled: string): string => {
|
||||
if (!enrolled || enrolled < "2016-07-28T00:00:00Z") {
|
||||
if (!enrolled || enrolled < INITIAL_FLEET_DATE) {
|
||||
return "Never";
|
||||
}
|
||||
return formatDistanceToNow(new Date(enrolled), { addSuffix: true });
|
||||
@@ -636,8 +638,7 @@ export const humanHostMemory = (bytes: number): string => {
|
||||
export const humanHostDetailUpdated = (detailUpdated?: string): string => {
|
||||
// Handles the case when a host has checked in to Fleet but
|
||||
// its details haven't been updated.
|
||||
// July 28, 2016 is the date of the initial commit to fleet/fleet.
|
||||
if (!detailUpdated || detailUpdated < "2016-07-28T00:00:00Z") {
|
||||
if (!detailUpdated || detailUpdated < INITIAL_FLEET_DATE) {
|
||||
return "unavailable";
|
||||
}
|
||||
try {
|
||||
@@ -647,6 +648,33 @@ export const humanHostDetailUpdated = (detailUpdated?: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
/** Unlike humanHost helper functions, there are no Fleet-related date restrictions */
|
||||
export const humanLastSeen = (lastSeen: string): string => {
|
||||
if (!lastSeen) {
|
||||
return "Never";
|
||||
}
|
||||
if (lastSeen === "Unavailable") {
|
||||
return "Unavailable";
|
||||
}
|
||||
|
||||
return formatDistanceToNow(new Date(lastSeen), { addSuffix: true });
|
||||
};
|
||||
|
||||
export const internationalTimeFormat = (date: number | Date): string => {
|
||||
return intlFormat(
|
||||
date,
|
||||
{
|
||||
year: "numeric",
|
||||
month: "numeric",
|
||||
day: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
second: "numeric",
|
||||
},
|
||||
{ locale: window.navigator.languages[0] }
|
||||
);
|
||||
};
|
||||
|
||||
const MAC_WINDOWS_DISK_ENCRYPTION_MESSAGES = {
|
||||
darwin: {
|
||||
enabled:
|
||||
@@ -687,8 +715,7 @@ export const hostTeamName = (teamName: string | null): string => {
|
||||
|
||||
export const humanQueryLastRun = (lastRun: string): string => {
|
||||
// Handles the case when a query has never been ran.
|
||||
// July 28, 2016 is the date of the initial commit to fleet/fleet.
|
||||
if (!lastRun || lastRun < "2016-07-28T00:00:00Z") {
|
||||
if (!lastRun || lastRun < INITIAL_FLEET_DATE) {
|
||||
return "Has not run";
|
||||
}
|
||||
|
||||
@@ -893,6 +920,8 @@ export default {
|
||||
humanHostEnrolled,
|
||||
humanHostMemory,
|
||||
humanHostDetailUpdated,
|
||||
humanLastSeen,
|
||||
internationalTimeFormat,
|
||||
getHostDiskEncryptionTooltipMessage,
|
||||
hostTeamName,
|
||||
humanQueryLastRun,
|
||||
|
||||
Reference in New Issue
Block a user