Fleet UI: Dashboard summary tiles clickable with new component (#7826)
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
button {
|
||||
color: $core-vibrant-blue;
|
||||
font-weight: $bold;
|
||||
padding: 6px;
|
||||
|
||||
.fleeticon-chevronleft {
|
||||
@@ -35,7 +36,7 @@
|
||||
}
|
||||
|
||||
button:hover,
|
||||
button.focus-visible {
|
||||
button:focus {
|
||||
background-color: $ui-vibrant-blue-10;
|
||||
}
|
||||
|
||||
|
||||
@@ -294,15 +294,12 @@ $base-class: "button";
|
||||
padding: 0;
|
||||
height: auto;
|
||||
line-height: normal;
|
||||
font-weight: normal;
|
||||
|
||||
&:active {
|
||||
box-shadow: none;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--unstyled-modal-query {
|
||||
|
||||
@@ -67,6 +67,10 @@
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
|
||||
.button {
|
||||
font-weight: $bold;
|
||||
}
|
||||
}
|
||||
|
||||
&__load-activities-button {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import paths from "router/paths";
|
||||
import SummaryTile from "../HostsSummary/SummaryTile";
|
||||
|
||||
const baseClass = "hosts-status";
|
||||
|
||||
interface IHostSummaryProps {
|
||||
@@ -23,26 +26,20 @@ const HostsStatus = ({
|
||||
|
||||
return (
|
||||
<div className={baseClass} style={opacity}>
|
||||
<div className={`${baseClass}__tile online-tile`}>
|
||||
<div>
|
||||
<div
|
||||
className={`${baseClass}__tile-count ${baseClass}__tile-count--online`}
|
||||
>
|
||||
{onlineCount}
|
||||
</div>
|
||||
<div className={`${baseClass}__tile-description`}>Online hosts</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${baseClass}__tile offline-tile`}>
|
||||
<div>
|
||||
<div
|
||||
className={`${baseClass}__tile-count ${baseClass}__tile-count--offline`}
|
||||
>
|
||||
{offlineCount}
|
||||
</div>
|
||||
<div className={`${baseClass}__tile-description`}>Offline hosts</div>
|
||||
</div>
|
||||
</div>
|
||||
<SummaryTile
|
||||
count={onlineCount}
|
||||
isLoading={isLoadingHosts}
|
||||
showUI={showHostsUI}
|
||||
title="Online hosts"
|
||||
path={paths.MANAGE_HOSTS_ONLINE}
|
||||
/>
|
||||
<SummaryTile
|
||||
count={offlineCount}
|
||||
isLoading={isLoadingHosts}
|
||||
showUI={showHostsUI}
|
||||
title="Offline hosts"
|
||||
path={paths.MANAGE_HOSTS_OFFLINE}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,33 +3,4 @@
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
font-size: $x-small;
|
||||
|
||||
&__tile {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__tile-count {
|
||||
font-size: $large;
|
||||
|
||||
&:before {
|
||||
border-radius: 100%;
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
margin-right: $pad-small;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&--online:before {
|
||||
background-color: $ui-success;
|
||||
}
|
||||
|
||||
&--offline:before {
|
||||
background-color: $ui-offline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ILabelSummary } from "interfaces/label";
|
||||
import { PLATFORM_NAME_TO_LABEL_NAME } from "utilities/constants";
|
||||
|
||||
import DataError from "components/DataError";
|
||||
import SummaryTile from "./SummaryTile";
|
||||
|
||||
import WindowsIcon from "../../../../../assets/images/icon-windows-48x48@2x.png";
|
||||
import LinuxIcon from "../../../../../assets/images/icon-linux-48x48@2x.png";
|
||||
@@ -76,43 +77,36 @@ const HostsSummary = ({
|
||||
}
|
||||
|
||||
const renderMacCount = () => (
|
||||
<div className={`${baseClass}__tile mac-tile`}>
|
||||
<div className={`${baseClass}__tile-icon`}>
|
||||
<img src={MacIcon} alt="mac icon" id="mac-icon" />
|
||||
</div>
|
||||
<div>
|
||||
<div className={`${baseClass}__tile-count mac-count`}>{macCount}</div>
|
||||
<div className={`${baseClass}__tile-description`}>macOS hosts</div>
|
||||
</div>
|
||||
</div>
|
||||
<SummaryTile
|
||||
icon={MacIcon}
|
||||
count={macCount}
|
||||
isLoading={isLoadingHostsSummary}
|
||||
showUI={showHostsUI}
|
||||
title="macOS hosts"
|
||||
path={paths.MANAGE_HOSTS_LABEL(7)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderWindowsCount = () => (
|
||||
<div className={`${baseClass}__tile windows-tile`}>
|
||||
<div className={`${baseClass}__tile-icon`}>
|
||||
<img src={WindowsIcon} alt="windows icon" id="windows-icon" />
|
||||
</div>
|
||||
<div>
|
||||
<div className={`${baseClass}__tile-count windows-count`}>
|
||||
{windowsCount}
|
||||
</div>
|
||||
<div className={`${baseClass}__tile-description`}>Windows hosts</div>
|
||||
</div>
|
||||
</div>
|
||||
<SummaryTile
|
||||
icon={WindowsIcon}
|
||||
count={windowsCount}
|
||||
isLoading={isLoadingHostsSummary}
|
||||
showUI={showHostsUI}
|
||||
title="Windows hosts"
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderLinuxCount = () => (
|
||||
<div className={`${baseClass}__tile linux-tile`}>
|
||||
<div className={`${baseClass}__tile-icon`}>
|
||||
<img src={LinuxIcon} alt="linux icon" id="linux-icon" />
|
||||
</div>
|
||||
<div>
|
||||
<div className={`${baseClass}__tile-count linux-count`}>
|
||||
{linuxCount}
|
||||
</div>
|
||||
<div className={`${baseClass}__tile-description`}>Linux hosts</div>
|
||||
</div>
|
||||
</div>
|
||||
<SummaryTile
|
||||
icon={LinuxIcon}
|
||||
count={linuxCount}
|
||||
isLoading={isLoadingHostsSummary}
|
||||
showUI={showHostsUI}
|
||||
title="Linux hosts"
|
||||
path={paths.MANAGE_HOSTS_LABEL(12)}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderCounts = () => {
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
import React from "react";
|
||||
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
|
||||
import paths from "router/paths";
|
||||
|
||||
import SummaryTile from "./SummaryTile";
|
||||
|
||||
import TestIcon from "../../../../../../assets/images/icon-windows-black-24x24@2x.png";
|
||||
|
||||
const INITIAL_OPACITY = 0;
|
||||
|
||||
const LOADING_OPACITY = 0.4;
|
||||
|
||||
describe("SummaryTile - component", () => {
|
||||
it("summary tile is hidden when showUI is false on first load", () => {
|
||||
render(
|
||||
<SummaryTile
|
||||
count={200}
|
||||
isLoading={false}
|
||||
showUI={false} // being tested
|
||||
title={"Windows hosts"}
|
||||
icon={TestIcon}
|
||||
tooltip={"Hosts on any Windows device"}
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
const tile = screen.getByTestId("tile");
|
||||
|
||||
expect(tile).toHaveStyle(`opacity: ${INITIAL_OPACITY}`);
|
||||
});
|
||||
|
||||
it("renders loading state", () => {
|
||||
render(
|
||||
<SummaryTile
|
||||
count={200}
|
||||
isLoading // tested
|
||||
showUI
|
||||
title={"Windows hosts"}
|
||||
icon={TestIcon}
|
||||
tooltip={"Hosts on any Windows device"}
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
const tile = screen.getByTestId("tile");
|
||||
|
||||
expect(tile).toHaveStyle(`opacity: ${LOADING_OPACITY}`);
|
||||
});
|
||||
|
||||
it("renders title, count, and image based on the information and data passed in", () => {
|
||||
render(
|
||||
<SummaryTile
|
||||
count={200} // tested
|
||||
isLoading={false}
|
||||
showUI
|
||||
title={"Windows hosts"} // tested
|
||||
icon={TestIcon} // tested
|
||||
tooltip={"Hosts on any Windows device"}
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
const title = screen.getByText("Windows hosts");
|
||||
|
||||
const count = screen.getByText("200");
|
||||
|
||||
const icon = screen.getByRole("img");
|
||||
|
||||
expect(title).toBeInTheDocument();
|
||||
|
||||
expect(count).toBeInTheDocument();
|
||||
|
||||
expect(icon).toHaveAttribute("src", "test-file-stub");
|
||||
});
|
||||
|
||||
it("renders tooltip on title hover", async () => {
|
||||
render(
|
||||
<SummaryTile
|
||||
count={200}
|
||||
isLoading={false}
|
||||
showUI
|
||||
title={"Windows hosts"}
|
||||
icon={TestIcon}
|
||||
tooltip={"Hosts on any Windows device"} // tested
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.mouseOver(screen.getByText("Windows hosts"));
|
||||
|
||||
expect(
|
||||
await screen.findByText("Hosts on any Windows device")
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders manage host page on click", async () => {
|
||||
render(
|
||||
<SummaryTile
|
||||
count={200}
|
||||
isLoading={false}
|
||||
showUI
|
||||
title={"Windows hosts"}
|
||||
icon={TestIcon}
|
||||
tooltip={"Hosts on any Windows device"} // tested
|
||||
path={paths.MANAGE_HOSTS_LABEL(10)}
|
||||
/>
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText("Windows hosts"));
|
||||
|
||||
expect(window.location.pathname).toBe("/hosts/manage/labels/10");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import React from "react";
|
||||
import { browserHistory } from "react-router";
|
||||
import Button from "components/buttons/Button";
|
||||
import { kebabCase } from "lodash";
|
||||
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
interface ISummaryTileProps {
|
||||
count: number;
|
||||
isLoading: boolean;
|
||||
showUI: boolean;
|
||||
title: string;
|
||||
icon?: any;
|
||||
tooltip?: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const baseClass = "summary-tile";
|
||||
|
||||
const SummaryTile = ({
|
||||
count,
|
||||
isLoading,
|
||||
showUI, // false on first load only
|
||||
title,
|
||||
icon,
|
||||
tooltip,
|
||||
path,
|
||||
}: ISummaryTileProps): JSX.Element => {
|
||||
const numberWithCommas = (x: number): string => {
|
||||
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
};
|
||||
// Renders opaque information as host information is loading
|
||||
let opacity = { opacity: 0 };
|
||||
if (showUI) {
|
||||
opacity = isLoading ? { opacity: 0.4 } : { opacity: 1 };
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
browserHistory.push(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={baseClass} style={opacity} data-testid="tile">
|
||||
<Button
|
||||
className={`${baseClass}__tile ${kebabCase(title)}-tile`}
|
||||
variant="unstyled"
|
||||
onClick={() => handleClick()}
|
||||
>
|
||||
<>
|
||||
{icon && (
|
||||
<div className={`${baseClass}__icon-wrapper`}>
|
||||
<img
|
||||
src={icon}
|
||||
alt={title}
|
||||
className={`${baseClass}__icon`}
|
||||
id={`${kebabCase(title)}-icon`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div
|
||||
className={`${baseClass}__count ${baseClass}__count--${kebabCase(
|
||||
title
|
||||
)}`}
|
||||
>
|
||||
{numberWithCommas(count)}
|
||||
</div>
|
||||
<div className={`${baseClass}__description`}>
|
||||
{tooltip ? (
|
||||
<TooltipWrapper tipContent={tooltip}>{title}</TooltipWrapper>
|
||||
) : (
|
||||
title
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummaryTile;
|
||||
@@ -0,0 +1,78 @@
|
||||
.summary-tile {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
|
||||
&__tile {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: $pad-medium;
|
||||
height: fit-content;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon-wrapper {
|
||||
margin-right: px-to-rem(12);
|
||||
white-space: nowrap;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
float: right;
|
||||
}
|
||||
|
||||
// Scale without dead space around icons
|
||||
#mac-os-hosts-icon,
|
||||
#windows-hosts-icon,
|
||||
#linux-hosts-icon {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
#missing-hosts-icon {
|
||||
width: 28px;
|
||||
}
|
||||
|
||||
#low-disk-space-hosts-icon {
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&__count {
|
||||
font-size: $large;
|
||||
white-space: nowrap;
|
||||
|
||||
&--online-hosts:before {
|
||||
border-radius: 100%;
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
margin-right: $pad-small;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
margin-bottom: 5px;
|
||||
background-color: $ui-success;
|
||||
}
|
||||
|
||||
&--offline-hosts:before {
|
||||
border-radius: 100%;
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
margin-right: $pad-small;
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
margin-bottom: 5px;
|
||||
background-color: $ui-offline;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
white-space: nowrap;
|
||||
|
||||
.component__tooltip-wrapper__tip-text {
|
||||
width: 200px;
|
||||
white-space: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./SummaryTile";
|
||||
@@ -9,6 +9,9 @@
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
&:focus-visible {
|
||||
outline: 1px solid $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
.data-table__wrapper {
|
||||
overflow-x: auto;
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
color: $core-vibrant-blue;
|
||||
font-weight: $bold;
|
||||
text-decoration: none !important;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 1px solid $core-vibrant-blue;
|
||||
}
|
||||
}
|
||||
&__action-button-text {
|
||||
text-align: right;
|
||||
|
||||
@@ -45,6 +45,8 @@ export default {
|
||||
MANAGE_HOSTS_LABEL: (labelId: number | string): string => {
|
||||
return `${URL_PREFIX}/hosts/manage/labels/${labelId}`;
|
||||
},
|
||||
MANAGE_HOSTS_ONLINE: `${URL_PREFIX}/hosts/manage/online`,
|
||||
MANAGE_HOSTS_OFFLINE: `${URL_PREFIX}/hosts/manage/offline`,
|
||||
HOST_DETAILS: (host: IHost): string => {
|
||||
return `${URL_PREFIX}/hosts/${host.id}`;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user