Frontend: Unit tests /components directory, convert to svg icons (#8716)

This commit is contained in:
RachelElysia
2022-12-07 09:59:38 -08:00
committed by GitHub
parent 10b3179b63
commit fa8ee026ff
46 changed files with 529 additions and 158 deletions
@@ -184,10 +184,13 @@ describe("Policies flow (empty)", () => {
i: number,
expected: boolean[]
) => {
const check = expected[i] ? "compatible" : "incompatible";
const check = expected[i]
? "compatible-platform"
: "incompatible-platform";
const compatibility = expected[i] ? "compatible" : "incompatible";
assert(
el.children("img").attr("alt") === check,
`expected policy to be ${platforms[i]} ${check}`
el.children("div").attr("class").includes(check),
`expected policy to be ${platforms[i]} ${compatibility}`
);
};
@@ -327,7 +327,7 @@ const PlatformWrapper = ({
</p>
</div>
<RevealButton
baseClass={baseClass}
className={baseClass}
isShowing={showPlainOsquery}
hideText={"Plain osquery"}
showText={"Plain osquery"}
@@ -7,7 +7,7 @@ describe("BackLink - component", () => {
render(<BackLink text="Back to software" />);
const text = screen.getByText("Back to software");
const icon = screen.getByTestId("Icon");
const icon = screen.getByTestId("icon");
expect(text).toBeInTheDocument();
expect(icon).toBeInTheDocument();
@@ -32,7 +32,7 @@ describe("CustomLink - component", () => {
/>
);
const icon = screen.getByTestId("Icon");
const icon = screen.getByTestId("icon");
expect(icon).toBeInTheDocument();
expect(icon.closest("a")).toHaveAttribute("target", "_blank");
@@ -0,0 +1,30 @@
import React from "react";
import { screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import EnrollSecretRow from "./EnrollSecretRow";
const TEAM_SECRET = {
secret: "super-secret-secret",
created_at: "",
team_id: 2,
};
describe("Enroll secret row", () => {
it("Hides secret by default and shows secret on click of eye icon", async () => {
const { user, container } = renderWithSetup(
<EnrollSecretRow secret={TEAM_SECRET} />
);
// Secret hidden by default
const secretHidden = container.querySelector("input");
expect(secretHidden?.type === "password").toBeTruthy();
// Click eye icon
const eyeIcon = screen.getByTestId("eye-icon");
await user.click(eyeIcon);
// Secret shown
const secretShown = container.querySelector("input");
expect(secretShown?.type === "text").toBeTruthy();
});
});
@@ -93,7 +93,11 @@ const EnrollSecretRow = ({
};
return (
<div className={`${baseClass}__secret`} key={uniqueId()}>
<div
className={`${baseClass}__secret`}
key={uniqueId()}
data-testid="osquery-secret"
>
<InputField
disabled
inputWrapperClass={`${baseClass}__secret-input`}
+1 -1
View File
@@ -35,7 +35,7 @@ const Icon = ({ name, color, direction, className, size }: IIconProps) => {
const IconComponent = ICON_MAP[name];
return (
<div className={classNames} data-testid="Icon">
<div className={classNames} data-testid="icon">
<IconComponent {...props} />
</div>
);
@@ -0,0 +1,38 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import LastUpdatedText from "./LastUpdatedText";
describe("Last updated text", () => {
it("renders updated text", () => {
const currentDate = new Date();
currentDate.setDate(currentDate.getDate() - 2);
const twoDaysAgo = currentDate.toISOString();
render(
<LastUpdatedText whatToRetrieve="software" lastUpdatedAt={twoDaysAgo} />
);
const text = screen.getByText("Updated 2 days ago");
expect(text).toBeInTheDocument();
});
it("renders never if missing timestamp", () => {
render(<LastUpdatedText whatToRetrieve="software" />);
const text = screen.getByText("Updated never");
expect(text).toBeInTheDocument();
});
it("renders tooltip on hover", async () => {
const { user } = renderWithSetup(
<LastUpdatedText whatToRetrieve="software" />
);
await user.hover(screen.getByText("Updated never"));
expect(screen.getByText(/to retrieve software/i)).toBeInTheDocument();
});
});
@@ -7,7 +7,7 @@ import TooltipWrapper from "components/TooltipWrapper";
const baseClass = "component__last-updated-text";
interface ILastUpdatedTextProps {
lastUpdatedAt: string;
lastUpdatedAt?: string;
whatToRetrieve: string;
}
const LastUpdatedText = ({
@@ -0,0 +1,50 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import PlatformCompatibility from "./PlatformCompatibility";
describe("Platform compatibility", () => {
it("renders compatible platforms", () => {
render(
<PlatformCompatibility
compatiblePlatforms={["macOS", "Windows"]}
error={null}
/>
);
const macCompatibility = screen.getByText("macOS").firstElementChild;
const windowsCompatibility = screen.getByText("Windows").firstElementChild;
const linuxCompatibility = screen.getByText("Linux").firstElementChild;
expect(macCompatibility).toHaveAttribute(
"class",
"icon compatible-platform"
);
expect(windowsCompatibility).toHaveAttribute(
"class",
"icon compatible-platform"
);
expect(linuxCompatibility).toHaveAttribute(
"class",
"icon incompatible-platform"
);
});
it("renders empty state", () => {
render(<PlatformCompatibility compatiblePlatforms={[]} error={null} />);
const text = screen.getByText(/No platforms/i);
expect(text).toBeInTheDocument();
});
it("renders error state", () => {
render(
<PlatformCompatibility
compatiblePlatforms={["macOS"]}
error={{ name: "Error", message: "The resource was not found." }}
/>
);
const text = screen.getByText(/possible syntax error/i);
expect(text).toBeInTheDocument();
});
});
@@ -4,8 +4,7 @@ import { IOsqueryPlatform } from "interfaces/platform";
import { PLATFORM_DISPLAY_NAMES } from "utilities/constants";
import TooltipWrapper from "components/TooltipWrapper";
import CompatibleIcon from "../../../assets/images/icon-compatible-green-16x16@2x.png";
import IncompatibleIcon from "../../../assets/images/icon-incompatible-red-16x16@2x.png";
import Icon from "components/Icon";
interface IPlatformCompatibilityProps {
compatiblePlatforms: IOsqueryPlatform[] | null;
@@ -51,7 +50,10 @@ const PlatformCompatibility = ({
return (
<span className={baseClass}>
<b>
<TooltipWrapper tipContent="Estimated compatiblity based on <br /> the tables used in the query.">
<TooltipWrapper
tipContent="Estimated compatiblity based on <br /> the tables used in the query."
isDelayed
>
Compatible with:
</TooltipWrapper>
</b>
@@ -79,9 +81,12 @@ const PlatformCompatibility = ({
key={`platform-compatibility__${platform}`}
className="platform"
>
<img
alt={isCompatible ? "compatible" : "incompatible"}
src={isCompatible ? CompatibleIcon : IncompatibleIcon}
<Icon
name={isCompatible ? "check" : "ex"}
className={
isCompatible ? "compatible-platform" : "incompatible-platform"
}
color={isCompatible ? "status-success" : "status-error"}
/>
{platform}
</span>
@@ -5,7 +5,7 @@
padding-top: $pad-medium;
b,
img,
svg,
span {
display: flex;
align-items: center;
@@ -17,12 +17,10 @@
.platform {
padding-left: 0px;
}
img {
height: 16px;
width: 16px;
padding-left: 12px;
padding-right: $pad-xsmall;
.icon {
padding-left: 12px;
padding-right: $pad-xsmall;
}
}
}
@@ -0,0 +1,33 @@
import React from "react";
import { screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import DropdownCell from "./DropdownCell";
const DROPDOWN_OPTIONS = [
{ disabled: false, label: "Edit", value: "edit-query" },
{ disabled: false, label: "Show query", value: "show-query" },
{ disabled: true, label: "Delete", value: "delete-query" },
];
const PLACEHOLDER = "Actions";
const ON_CHANGE = (value: string) => {
console.log(value);
};
describe("Dropdown cell", () => {
it("renders dropdown placeholder and options", async () => {
const { user } = renderWithSetup(
<DropdownCell
options={DROPDOWN_OPTIONS}
placeholder={PLACEHOLDER}
onChange={ON_CHANGE}
/>
);
await user.click(screen.getByText("Actions"));
expect(screen.getByText(/edit/i)).toBeInTheDocument();
expect(screen.getByText(/show query/i)).toBeInTheDocument();
expect(screen.getByText(/delete/i)).toBeInTheDocument();
});
});
@@ -0,0 +1,28 @@
import React from "react";
import { screen } from "@testing-library/react";
import { createCustomRenderer } from "test/test-utils";
import IssueCell from "./IssueCell";
describe("Issue cell", () => {
it("renders icon, total issues, and failing policies tooltip", async () => {
const render = createCustomRenderer({});
const { user } = render(
<IssueCell
issues={{
total_issues_count: 4,
failing_policies_count: 2,
}}
rowId={1}
/>
);
const icon = screen.queryByTestId("icon");
await user.hover(screen.getByText("4"));
expect(screen.getByText(/failing policies/i)).toBeInTheDocument();
expect(icon).toBeInTheDocument();
});
});
@@ -2,6 +2,8 @@ import React from "react";
import ReactTooltip from "react-tooltip";
import { isEmpty } from "lodash";
import Icon from "components/Icon";
import IssueIcon from "../../../../../assets/images/icon-issue-fleet-black-50-16x16@2x.png";
interface IIssueCellProps<T> {
@@ -25,7 +27,7 @@ const IssueCell = ({ issues, rowId }: IIssueCellProps<any>): JSX.Element => {
data-for={`host-issue__${rowId.toString()}`}
data-tip-disable={false}
>
<img alt="host issue" src={IssueIcon} />
<Icon name="issue" />
</span>
<ReactTooltip
place="top"
@@ -0,0 +1,19 @@
import React from "react";
import { screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import PATHS from "router/paths";
import LinkCell from "./LinkCell";
const VALUE = "40 hosts";
describe("Link cell", () => {
it("renders text and path", async () => {
const { user } = renderWithSetup(
<LinkCell value={VALUE} path={PATHS.MANAGE_HOSTS} />
);
await user.click(screen.getByText("40 hosts"));
expect(window.location.pathname).toContain("/hosts");
});
});
@@ -0,0 +1,19 @@
import React from "react";
import { screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import PillCell from "./PillCell";
const PERFORMANCE_IMPACT = { indicator: "Minimal", id: 3 };
describe("Pill cell", () => {
it("renders pill text and tooltip on hover", async () => {
const { user } = renderWithSetup(
<PillCell value={PERFORMANCE_IMPACT} hostDetails />
);
await user.hover(screen.getByText("Minimal"));
expect(screen.getByText(/little to no impact/i)).toBeInTheDocument();
});
});
@@ -1,11 +1,11 @@
import React from "react";
import classnames from "classnames";
import { v4 as uuidv4 } from "uuid";
import { uniqueId } from "lodash";
import ReactTooltip from "react-tooltip";
interface IPillCellProps {
value: [string, number];
value: { indicator: string; id: number };
customIdPrefix?: string;
hostDetails?: boolean;
}
@@ -19,16 +19,15 @@ const PillCell = ({
customIdPrefix,
hostDetails,
}: IPillCellProps): JSX.Element => {
const [pillText, id] = value;
const { indicator, id } = value;
const pillClassName = classnames(
"data-table__pill",
`data-table__pill--${generateClassTag(pillText)}`,
`data-table__pill--${generateClassTag(indicator || "")}`,
"tooltip"
);
const disable = () => {
switch (pillText) {
switch (indicator) {
case "Minimal":
return false;
case "Considerable":
@@ -43,7 +42,7 @@ const PillCell = ({
};
const tooltipText = () => {
switch (pillText) {
switch (indicator) {
case "Minimal":
return (
<>
@@ -85,25 +84,30 @@ const PillCell = ({
return null;
}
};
const tooltipId = uniqueId();
return (
<>
<span
data-tip
data-for={`${customIdPrefix || "pill"}__${id?.toString() || uuidv4()}`}
data-for={`${customIdPrefix || "pill"}__${id?.toString() || tooltipId}`}
data-tip-disable={disable()}
>
<span className={pillClassName}>{pillText}</span>
<span className={pillClassName}>{indicator}</span>
</span>
<ReactTooltip
place="bottom"
// offset={getTooltipOffset(pillText)}
effect="solid"
backgroundColor="#3e4771"
id={`${customIdPrefix || "pill"}__${id?.toString() || uuidv4()}`}
id={`${customIdPrefix || "pill"}__${id?.toString() || tooltipId}`}
data-html
>
<span className={`tooltip ${generateClassTag(pillText)}__tooltip-text`}>
<span
className={`tooltip ${generateClassTag(
indicator || ""
)}__tooltip-text`}
>
{tooltipText()}
</span>
</ReactTooltip>
@@ -0,0 +1,31 @@
import React from "react";
import { getByTestId, render, screen, within } from "@testing-library/react";
import PlatformCell from "./PlatformCell";
const PLATFORMS = ["windows", "darwin", "linux"];
describe("Platform cell", () => {
it("renders platform icons in correct order", () => {
render(<PlatformCell value={PLATFORMS} />);
const icons = screen.queryAllByTestId("icon");
const appleIcon = screen.queryByTestId("apple-icon");
const linuxIcon = screen.queryByTestId("linux-icon");
const windowsIcon = screen.queryByTestId("windows-icon");
expect(icons).toHaveLength(3);
expect(icons[0].firstChild).toBe(appleIcon);
expect(icons[1].firstChild).toBe(linuxIcon);
expect(icons[2].firstChild).toBe(windowsIcon);
});
it("renders empty state", () => {
render(<PlatformCell value={[]} />);
const icons = screen.queryAllByTestId("icon");
const emptyText = screen.queryByText("---");
expect(icons).toHaveLength(0);
expect(emptyText).toBeInTheDocument();
});
});
@@ -36,6 +36,7 @@ const PlatformCell = ({
className={`${baseClass}__icon`}
name={ICONS[platform]}
size="small"
key={ICONS[platform]}
/>
) : null;
})
@@ -1,5 +1,5 @@
import React, { useState, useRef, useLayoutEffect } from "react";
import { v4 as uuidv4 } from "uuid";
import { uniqueId } from "lodash";
import ReactTooltip from "react-tooltip";
@@ -26,7 +26,7 @@ const TruncatedTextCell = ({
}
}, []);
const id = uuidv4();
const tooltipId = uniqueId();
const tooltipDisabled = offsetWidth === scrollWidth;
return (
@@ -34,7 +34,7 @@ const TruncatedTextCell = ({
<div
className={"data-table__truncated-text"}
data-tip
data-for={id}
data-for={tooltipId}
data-tip-disable={tooltipDisabled}
>
<span
@@ -49,7 +49,7 @@ const TruncatedTextCell = ({
place="bottom"
effect="solid"
backgroundColor="#3e4771"
id={id}
id={tooltipId}
data-html
className={"truncated-tooltip"} // responsive widths
>
@@ -7,7 +7,7 @@ describe("ViewAllHostsLink - component", () => {
render(<ViewAllHostsLink />);
const text = screen.getByText("View all hosts");
const icon = screen.getByTestId("Icon");
const icon = screen.getByTestId("icon");
expect(text).toBeInTheDocument();
expect(icon).toBeInTheDocument();
@@ -0,0 +1,92 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { renderWithSetup } from "test/testingUtils";
import RevealButton from "./RevealButton";
const SHOW_TEXT = "Show advanced options";
const HIDE_TEXT = "Hide advanced options";
const TOOLTIP_HTML = "Customize logging type and platforms";
describe("Reveal button", () => {
it("renders show text", async () => {
render(
<RevealButton
isShowing={false}
hideText={HIDE_TEXT}
showText={SHOW_TEXT}
/>
);
const showText = screen.getByText(SHOW_TEXT);
expect(showText).toBeInTheDocument();
});
it("renders hide text", async () => {
render(
<RevealButton isShowing hideText={HIDE_TEXT} showText={SHOW_TEXT} />
);
const hideText = screen.getByText(HIDE_TEXT);
expect(hideText).toBeInTheDocument();
});
it("hides caret by default", async () => {
render(
<RevealButton
isShowing={false}
hideText={HIDE_TEXT}
showText={SHOW_TEXT}
/>
);
const icon = screen.queryByTestId("icon");
expect(icon).toBeNull();
});
it("renders caret on left", async () => {
render(
<RevealButton
isShowing={false}
hideText={HIDE_TEXT}
showText={SHOW_TEXT}
caretPosition={"before"}
/>
);
const icon = screen.queryByTestId("icon");
expect(icon?.nextSibling).toHaveTextContent(SHOW_TEXT);
});
it("renders caret on right", async () => {
render(
<RevealButton
isShowing={false}
hideText={HIDE_TEXT}
showText={SHOW_TEXT}
caretPosition={"after"}
/>
);
const icon = screen.queryByTestId("icon");
expect(icon?.previousSibling).toHaveTextContent(SHOW_TEXT);
});
it("renders tooltip on hover if provided", async () => {
const { user } = renderWithSetup(
<RevealButton
isShowing={false}
hideText={HIDE_TEXT}
showText={SHOW_TEXT}
caretPosition={"before"}
tooltipHtml={TOOLTIP_HTML}
/>
);
await user.hover(screen.getByText(SHOW_TEXT));
expect(screen.getByText(TOOLTIP_HTML)).toBeInTheDocument();
});
});
@@ -1,10 +1,12 @@
import React from "react";
import classnames from "classnames";
import Button from "components/buttons/Button";
import TooltipWrapper from "components/TooltipWrapper";
import Icon from "components/Icon";
export interface IRevealButtonProps {
isShowing: boolean;
baseClass: string;
className?: string;
hideText: string;
showText: string;
caretPosition?: "before" | "after";
@@ -16,8 +18,11 @@ export interface IRevealButtonProps {
| ((evt: React.MouseEvent<HTMLButtonElement>) => void);
}
const baseClass = "reveal-button";
const RevealButton = ({
isShowing,
className,
hideText,
showText,
caretPosition,
@@ -26,31 +31,47 @@ const RevealButton = ({
tooltipHtml,
onClick,
}: IRevealButtonProps): JSX.Element => {
const classNameGenerator = () => {
if (caretPosition === "before") {
return isShowing ? "reveal upcaretbefore" : "reveal rightcaretbefore";
}
if (caretPosition === "after") {
return isShowing ? "reveal upcaretafter" : "reveal downcaretafter";
}
return "reveal";
};
const classNames = classnames(baseClass, className);
const buttonText = isShowing ? hideText : showText;
const buttonContent = () => {
const text = isShowing ? hideText : showText;
const buttonText = tooltipHtml ? (
<TooltipWrapper tipContent={tooltipHtml}>{text}</TooltipWrapper>
) : (
text
);
return (
<>
{caretPosition === "before" && (
<Icon
name="chevron"
direction={isShowing ? "right" : "down"}
color="core-fleet-blue"
/>
)}
{buttonText}
{caretPosition === "after" && (
<Icon
name="chevron"
direction={isShowing ? "up" : "down"}
color="core-fleet-blue"
/>
)}
</>
);
};
return (
<Button
variant="unstyled"
className={`reveal-button ${classNameGenerator()}`}
variant="text-icon"
className={classNames}
onClick={onClick}
autofocus={autofocus}
disabled={disabled}
>
{tooltipHtml ? (
<TooltipWrapper tipContent={tooltipHtml}>{buttonText}</TooltipWrapper>
) : (
buttonText
)}
{buttonContent()}
</Button>
);
};
@@ -1,54 +1,6 @@
.reveal {
margin: $pad-medium 0 $pad-large;
color: $core-vibrant-blue;
font-weight: $bold;
font-size: $x-small;
}
.rightcaretbefore {
&::before {
content: url("../assets/images/icon-chevron-blue-16x16@2x.png");
transform: scale(0.5) rotate(-90deg);
width: 16px;
padding: 0px;
padding-right: 10px;
margin-top: 5px;
}
}
.upcaretbefore {
&::before {
content: url("../assets/images/icon-chevron-blue-16x16@2x.png");
transform: scale(0.5) rotate(180deg);
width: 16px;
padding: 0px;
padding-right: 2px;
margin-right: $pad-small;
margin-top: 5px;
position: relative;
top: -4px;
left: 6px;
}
}
.downcaretafter {
&::after {
content: url("../assets/images/icon-chevron-blue-16x16@2x.png");
transform: scale(0.5);
width: 16px;
padding: 0px;
padding-left: 2px;
margin-bottom: 2px;
}
}
.upcaretafter {
&::after {
content: url("../assets/images/icon-chevron-blue-16x16@2x.png");
transform: scale(0.5) rotate(180deg);
width: 16px;
padding: 0px;
margin-bottom: 2px;
margin-left: 11px;
}
.reveal-button .children-wrapper {
display: inline-flex;
align-items: center;
padding: $pad-small $pad-xxsmall; // larger clickable area
gap: $pad-xsmall;
}
@@ -1,9 +1,8 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { difference, isEqual } from "lodash";
import { difference, isEqual, uniqueId } from "lodash";
import Select from "react-select";
import "react-select/dist/react-select.css";
import { v4 as uuidv4 } from "uuid";
import debounce from "utilities/debounce";
import targetInterface from "interfaces/target";
@@ -43,7 +42,7 @@ class SelectTargetsInput extends Component {
// must have unique key to select correctly
const uuidTargets = targets.map((target) => ({
...target,
uuid: uuidv4(),
uuid: uniqueId(),
}));
this.setState({ uuidTargets });
@@ -53,7 +52,7 @@ class SelectTargetsInput extends Component {
// must have unique key to deselect correctly
const uuidSelectedTargets = selectedTargets.map((target) => ({
...target,
uuid: uuidv4(),
uuid: uniqueId(),
}));
this.setState({ uuidSelectedTargets });
+1
View File
@@ -23,6 +23,7 @@ const Apple = ({
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
data-testid="apple-icon"
>
<path
fillRule="evenodd"
+4 -3
View File
@@ -1,10 +1,11 @@
import React from "react";
import { COLORS, Colors } from "styles/var/colors";
interface ICheckProps {
color?: string;
color?: Colors;
}
const Check = ({ color = "#6a67fe" }: ICheckProps) => {
const Check = ({ color = "core-fleet-blue" }: ICheckProps) => {
return (
<svg
width="16"
@@ -18,7 +19,7 @@ const Check = ({ color = "#6a67fe" }: ICheckProps) => {
fillRule="evenodd"
clipRule="evenodd"
d="M2.917 8.684c-.02 0-.042 0-.083.02a1.035 1.035 0 0 1-.23-.083c.063-.041.167-.021.313.063Zm10.56-5.603c-.543-.292-1.147.27-1.5.604-.812.791-1.5 1.708-2.27 2.54-.855.917-1.646 1.834-2.52 2.73-.5.5-1.042 1.04-1.375 1.666-.75-.73-1.396-1.52-2.228-2.166C2.98 7.996 1.98 7.663 2 8.767c.042 1.437 1.313 2.978 2.25 3.957.395.417.916.854 1.52.874.73.042 1.479-.833 1.916-1.312.77-.832 1.396-1.77 2.104-2.623.916-1.125 1.854-2.23 2.748-3.374.563-.709 2.333-2.458.938-3.208Z"
fill={color}
fill={COLORS[color]}
/>
</g>
<defs>
+1
View File
@@ -8,6 +8,7 @@ const Eye = () => {
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
data-testid="eye-icon"
>
<g clipPath="url(#a)" fill="#6a67fe">
<path d="M7.996 14C3.654 14 .246 8.6.102 8.37A.708.708 0 0 1 0 8c0-.133.036-.262.102-.37C.246 7.4 3.654 2 7.996 2c4.342 0 7.758 5.4 7.902 5.63A.708.708 0 0 1 16 8a.708.708 0 0 1-.102.37C15.754 8.6 12.346 14 7.996 14ZM2.198 8c.85 1.17 2.594 4 5.798 4 3.203 0 4.948-2.83 5.797-4-.85-1.17-2.602-4-5.797-4S3.022 6.83 2.198 8Z" />
+28
View File
@@ -0,0 +1,28 @@
import React from "react";
import { COLORS, Colors } from "styles/var/colors";
interface IErrorProps {
color?: Colors;
}
const Issue = ({ color = "ui-fleet-black-50" }: IErrorProps) => {
return (
<svg
width="16"
height="16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 14A6 6 0 1 0 8 2a6 6 0 0 0 0 12Zm0 2A8 8 0 1 0 8 0a8 8 0 0 0 0 16ZM8 4a1 1 0 0 1 1 1v3a1 1 0 1 1-2 0V5a1 1 0 0 1 1-1Zm0 8a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
fill={COLORS[color]}
/>
</svg>
);
};
export default Issue;
+1
View File
@@ -23,6 +23,7 @@ const Linux = ({
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
data-testid="linux-icon"
>
<path
d="M15.992 10.96c-.717-2.833-2.224-4.3-3.069-4.927l-.136-.18c.136-.413.211-.853.211-1.306C12.998 2.033 10.76 0 7.992 0 5.226 0 2.98 2.04 2.98 4.547c0 .453.075.893.211 1.306-.06.074-.113.147-.166.22C2.172 6.72.717 8.187.008 10.96c-.03.12.023.253.129.333.113.08.263.094.392.047.392-.16.912-.4 1.394-.733A5.99 5.99 0 0 0 4.23 14.4h-.904c-.498 0-.905.36-.905.8 0 .44.407.8.905.8h9.288c.498 0 .905-.36.905-.8 0-.44-.407-.8-.905-.8h-.875a5.987 5.987 0 0 0 2.307-3.813c.49.34 1.018.586 1.418.746a.412.412 0 0 0 .392-.046.302.302 0 0 0 .136-.327Zm-8.007 2.86c-2.36 0-4.275-1.88-4.275-4.193 0-1.187.505-2.687 1.312-3.86-.422-.327-.686-.807-.686-1.334 0-.986.905-1.78 2.013-1.78.67 0 1.259.287 1.628.734.37-.447.958-.734 1.629-.734 1.116 0 2.013.8 2.013 1.78 0 .534-.264 1.007-.686 1.334.806 1.173 1.312 2.666 1.312 3.86.015 2.32-1.9 4.193-4.26 4.193Z"
+1
View File
@@ -23,6 +23,7 @@ const Windows = ({
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
data-testid="windows-icon"
>
<path
d="m1.092 13.142 5.192 1.038V8.32H1.092v4.822ZM1.092 7.665h5.192V1.836L1.092 2.874v4.79ZM7.11 7.665h8.382V0L7.11 1.677v5.988ZM7.11 14.34 15.491 16V8.32H7.11v6.02Z"
+2 -1
View File
@@ -3,8 +3,8 @@ import CalendarCheck from "./CalendarCheck";
import Check from "./Check";
import Chevron from "./Chevron";
import Ex from "./Ex";
import ExternalLink from "./ExternalLink";
import Issue from "./Issue";
import Plus from "./Plus";
import LowDiskSpaceHosts from "./LowDiskSpaceHosts";
@@ -40,6 +40,7 @@ export const ICON_MAP = {
"external-link": ExternalLink,
"low-disk-space-hosts": LowDiskSpaceHosts,
"missing-hosts": MissingHosts,
issue: Issue,
plus: Plus,
clipboard: Clipboard,
eye: Eye,
@@ -47,7 +47,7 @@ interface ICellProps extends IRowProps {
interface IPillCellProps extends IRowProps {
cell: {
value: [string, number];
value: { indicator: string; id: number };
};
}
+8 -6
View File
@@ -1,6 +1,5 @@
import { useQuery, UseQueryResult } from "react-query";
import { filter } from "lodash";
import { v4 as uuidv4 } from "uuid";
import { filter, uniqueId } from "lodash";
import { IHost } from "interfaces/host";
import { ILabel } from "interfaces/label";
@@ -53,20 +52,23 @@ const getTargets = async (
const all = filter(
labels,
({ display_text: text }) => text === "All Hosts"
).map((label) => ({ ...label, uuid: uuidv4() }));
).map((label) => ({ ...label, uuid: uniqueId() }));
const platforms = filter(
labels,
({ display_text: text }) =>
text === "macOS" || text === "MS Windows" || text === "All Linux"
).map((label) => ({ ...label, uuid: uuidv4() }));
).map((label) => ({ ...label, uuid: uniqueId() }));
const other = filter(
labels,
({ label_type: type }) => type === "regular"
).map((label) => ({ ...label, uuid: uuidv4() }));
).map((label) => ({ ...label, uuid: uniqueId() }));
const teams = targets.teams.map((team) => ({ ...team, uuid: uuidv4() }));
const teams = targets.teams.map((team) => ({
...team,
uuid: uniqueId(),
}));
const labelCount =
all.length + platforms.length + other.length + teams.length;
@@ -60,12 +60,11 @@ describe("SummaryTile - component", () => {
const title = screen.getByText("Windows hosts");
const count = screen.getByText("200");
// TOOD: Fix icon assertion
// const icon = screen.getByRole("svg");
const icon = screen.queryByTestId("icon");
expect(title).toBeInTheDocument();
expect(count).toBeInTheDocument();
// expect(icon).toBeInTheDocument();
expect(icon).toBeInTheDocument();
});
it("does not render icon if not provided", () => {
@@ -80,7 +79,7 @@ describe("SummaryTile - component", () => {
/>
);
const icon = screen.queryByRole("img");
const icon = screen.queryByRole("svg");
expect(icon).toBeNull();
});
@@ -187,11 +187,7 @@
display: flex;
align-items: center;
.host-issue {
img {
width: 16px;
height: 16px;
vertical-align: sub;
}
height: 16px;
}
}
@@ -33,7 +33,7 @@ interface ICellProps extends IRowProps {
interface IPillCellProps extends IRowProps {
cell: {
value: [string, number];
value: { indicator: string; id: number };
};
}
@@ -51,7 +51,7 @@ interface IDataColumn {
interface IPackTable extends Partial<IQueryStats> {
frequency: string;
last_run: string;
performance: (string | number)[];
performance: { indicator: string; id: number };
}
// NOTE: cellProps come from react-table
@@ -123,10 +123,10 @@ const enhancePackData = (query_stats: IQueryStats[]): IPackTable[] => {
last_executed: query.last_executed,
frequency: secondsToHms(query.interval),
last_run: humanQueryLastRun(query.last_executed),
performance: [
performanceIndicator(scheduledQueryPerformance),
query.scheduled_query_id || uniqueId(),
],
performance: {
indicator: performanceIndicator(scheduledQueryPerformance),
id: query.scheduled_query_id || parseInt(uniqueId(), 10),
},
};
});
};
@@ -1,5 +1,4 @@
import React from "react";
import { uniqueId } from "lodash";
import { IQueryStats } from "interfaces/query_stats";
import { performanceIndicator, secondsToDhms } from "utilities/helpers";
@@ -29,7 +28,10 @@ interface ICellProps extends IRowProps {
interface IPillCellProps extends IRowProps {
cell: {
value: [string, number];
value: {
indicator: string;
id: number;
};
};
}
@@ -46,7 +48,7 @@ interface IDataColumn {
interface IScheduleTable extends Partial<IQueryStats> {
frequency: string;
performance: (string | number)[];
performance: { indicator: string; id: number };
}
// NOTE: cellProps come from react-table
@@ -102,10 +104,10 @@ const enhanceScheduleData = (query_stats: IQueryStats[]): IScheduleTable[] => {
return {
query_name: query.query_name,
frequency: secondsToDhms(query.interval),
performance: [
performanceIndicator(scheduledQueryPerformance),
query.scheduled_query_id || uniqueId(),
],
performance: {
indicator: performanceIndicator(scheduledQueryPerformance),
id: query.scheduled_query_id,
},
};
});
};
@@ -461,7 +461,7 @@ const ManagePolicyPage = ({
{showInheritedPoliciesButton && globalPolicies && (
<RevealButton
isShowing={showInheritedPolicies}
baseClass={baseClass}
className={baseClass}
hideText={inheritedPoliciesButtonText(
showInheritedPolicies,
globalPolicies.length
@@ -422,7 +422,7 @@ const PolicyForm = ({
</div>
<RevealButton
isShowing={showQueryEditor}
baseClass={baseClass}
className={baseClass}
hideText="Hide SQL"
showText="Show SQL"
onClick={() => setShowQueryEditor(!showQueryEditor)}
@@ -156,7 +156,12 @@ const generateTableHeaders = (currentUser: IUser): IDataColumn[] => {
disableSortBy: true,
accessor: "performance",
Cell: (cellProps: ICellProps) => (
<PillCell value={[cellProps.cell.value, cellProps.row.original.id]} />
<PillCell
value={{
indicator: cellProps.cell.value,
id: cellProps.row.original.id,
}}
/>
),
},
{
@@ -404,7 +404,7 @@ const QueryForm = ({
</div>
<RevealButton
isShowing={showQueryEditor}
baseClass={baseClass}
className={baseClass}
hideText="Hide SQL"
showText="Show SQL"
onClick={() => setShowQueryEditor(!showQueryEditor)}
@@ -536,7 +536,7 @@ const ManageSchedulePage = ({
inheritedScheduledQueriesList.length > 0 ? (
<RevealButton
isShowing={showInheritedQueries}
baseClass={baseClass}
className={baseClass}
hideText={`Hide ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`}
showText={`Show ${inheritedScheduledQueriesList.length} inherited ${inheritedQueryOrQueries}`}
caretPosition={"before"}
@@ -275,7 +275,7 @@ const ScheduleEditorModal = ({
<div>
<RevealButton
isShowing={showAdvancedOptions}
baseClass={baseClass}
className={baseClass}
hideText={"Hide advanced options"}
showText={"Show advanced options"}
caretPosition={"after"}
@@ -54,7 +54,7 @@ interface INumberCellProps extends IRowProps {
interface IPillCellProps extends IRowProps {
cell: {
value: [string, number];
value: { indicator: string; id: number };
};
}
@@ -236,6 +236,10 @@ const enhanceAllScheduledQueryData = (
system_time_p50: scheduledQuery.stats?.system_time_p50,
total_executions: scheduledQuery.stats?.total_executions,
};
console.log(
"performanceIndicator(scheduledQueryPerformance)",
performanceIndicator(scheduledQueryPerformance)
);
return {
name: scheduledQuery.name,
query_name: scheduledQuery.query_name,
@@ -250,10 +254,10 @@ const enhanceAllScheduledQueryData = (
version: scheduledQuery.version,
shard: scheduledQuery.shard,
type: teamId ? "team_scheduled_query" : "global_scheduled_query",
performance: [
performanceIndicator(scheduledQueryPerformance),
scheduledQuery.id,
],
performance: {
indicator: performanceIndicator(scheduledQueryPerformance),
id: scheduledQuery.id,
},
};
});
};