mirror of
https://github.com/prymitive/karma
synced 2026-08-19 11:36:24 +00:00
fix(ui): add notifications icon when upstreams are failing
This commit is contained in:
committed by
Łukasz Mierzwa
parent
41c63e4470
commit
0539b5b1d0
@@ -8,6 +8,10 @@
|
||||
- Easily change multi-grid source label via quick access dropdown on the grid
|
||||
header.
|
||||
|
||||
### Changed
|
||||
|
||||
- Reworked how notifications are displayed.
|
||||
|
||||
## v0.79
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -28,7 +28,6 @@ import "Styles/App.scss";
|
||||
const Grid = React.lazy(() => import("Components/Grid"));
|
||||
const NavBar = React.lazy(() => import("Components/NavBar"));
|
||||
const FaviconBadge = React.lazy(() => import("Components/FaviconBadge"));
|
||||
const AppToasts = React.lazy(() => import("Components/Toast/AppToasts"));
|
||||
|
||||
interface AppProps {
|
||||
defaultFilters: Array<string>;
|
||||
@@ -131,7 +130,6 @@ const App: FunctionComponent<AppProps> = observer(
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
<AppToasts alertStore={alertStore} />
|
||||
<FaviconBadge alertStore={alertStore} />
|
||||
</React.Suspense>
|
||||
</ThemeContext.Provider>
|
||||
|
||||
@@ -29,7 +29,7 @@ const MainModal: FC<{
|
||||
<li
|
||||
className={`nav-item components-navbar-button ${
|
||||
isVisible ? "border-info" : ""
|
||||
}`}
|
||||
} ml-auto`}
|
||||
>
|
||||
<TooltipWrapper title="Settings">
|
||||
<span
|
||||
@@ -37,7 +37,7 @@ const MainModal: FC<{
|
||||
className="nav-link cursor-pointer"
|
||||
onClick={toggle}
|
||||
>
|
||||
<FontAwesomeIcon icon={faCog} />
|
||||
<FontAwesomeIcon icon={faCog} fixedWidth />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
</li>
|
||||
|
||||
@@ -147,7 +147,11 @@ const FilterInput: FC<{
|
||||
return (
|
||||
// data-filters is there to register filters for observation in mobx
|
||||
// in order to re-render input component
|
||||
<form className="form-inline mw-100" onSubmit={onSubmit}>
|
||||
<form
|
||||
className="form-inline flex-grow-1 flex-shrink-1 mr-auto"
|
||||
style={{ minWidth: "0px" }}
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<div
|
||||
ref={formRef}
|
||||
className={`input-group w-100 mr-2 components-filterinput-outer ${
|
||||
|
||||
@@ -59,15 +59,6 @@ const MountedNavbar = (fixedTop?: boolean) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ValidateNavClass = (totalFilters: number, expectedClass: string) => {
|
||||
for (let i = 0; i < totalFilters; i++) {
|
||||
alertStore.filters.addFilter(`foo=${i}`);
|
||||
}
|
||||
const tree = MountedNavbar();
|
||||
const nav = tree.find("ul.navbar-nav");
|
||||
expect((nav.props().className as string).split(" ")).toContain(expectedClass);
|
||||
};
|
||||
|
||||
describe("<NavBar />", () => {
|
||||
it("navbar-brand shows 15 alerts with totalAlerts=15", () => {
|
||||
alertStore.info.setTotalAlerts(15);
|
||||
@@ -76,22 +67,6 @@ describe("<NavBar />", () => {
|
||||
expect(brand.text()).toBe("15");
|
||||
});
|
||||
|
||||
it("navbar-nav includes 'flex-row' class with 0 filters", () => {
|
||||
ValidateNavClass(0, "flex-row");
|
||||
});
|
||||
|
||||
it("navbar-nav includes 'flex-row' class with 1 filter", () => {
|
||||
ValidateNavClass(1, "flex-column");
|
||||
});
|
||||
|
||||
it("navbar-nav includes 'flex-column' class with 2 filters", () => {
|
||||
ValidateNavClass(2, "flex-column");
|
||||
});
|
||||
|
||||
it("navbar-nav includes 'flex-column' class with 3 filters", () => {
|
||||
ValidateNavClass(3, "flex-column");
|
||||
});
|
||||
|
||||
it("navbar includes 'fixed-top' class by default", () => {
|
||||
const tree = MountedNavbar();
|
||||
const nav = tree.find(".navbar");
|
||||
|
||||
@@ -16,6 +16,7 @@ import { IsMobile } from "Common/Device";
|
||||
import { OverviewModal } from "Components/OverviewModal";
|
||||
import { MainModal } from "Components/MainModal";
|
||||
import SilenceModal from "Components/SilenceModal";
|
||||
import AppToasts from "Components/Toast/AppToasts";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { Fetcher } from "Components/Fetcher";
|
||||
import { FilterInput } from "./FilterInput";
|
||||
@@ -102,23 +103,19 @@ const NavBar: FC<{
|
||||
>
|
||||
<nav
|
||||
ref={ref}
|
||||
className={`navbar navbar-expand navbar-dark p-1 bg-primary-transparent d-inline-block ${
|
||||
className={`navbar navbar-expand navbar-dark p-1 bg-primary-transparent d-flex ${
|
||||
fixedTop ? "fixed-top" : "w-100"
|
||||
}`}
|
||||
} align-items-start`}
|
||||
>
|
||||
<span className="navbar-nav float-left d-flex flex-row">
|
||||
<span className="navbar-nav d-flex flex-row">
|
||||
<span className="navbar-brand p-0 my-0 mx-2 h1 d-none d-sm-block">
|
||||
<OverviewModal alertStore={alertStore} />
|
||||
</span>
|
||||
<Fetcher alertStore={alertStore} settingsStore={settingsStore} />
|
||||
</span>
|
||||
<ul
|
||||
className={`navbar-nav float-right d-flex ${
|
||||
alertStore.filters.values.length >= 1
|
||||
? "flex-column flex-sm-row flex-md-row flex-lg-row flex-xl-row"
|
||||
: "flex-row"
|
||||
}`}
|
||||
>
|
||||
<FilterInput alertStore={alertStore} settingsStore={settingsStore} />
|
||||
<ul className="navbar-nav flex-wrap flex-shrink-1">
|
||||
<AppToasts alertStore={alertStore} />
|
||||
<SilenceModal
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
@@ -126,7 +123,6 @@ const NavBar: FC<{
|
||||
/>
|
||||
<MainModal alertStore={alertStore} settingsStore={settingsStore} />
|
||||
</ul>
|
||||
<FilterInput alertStore={alertStore} settingsStore={settingsStore} />
|
||||
</nav>
|
||||
</CSSTransition>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@ const SilenceModal: FC<{
|
||||
<li
|
||||
className={`nav-item components-navbar-button ${
|
||||
silenceFormStore.toggle.visible ? "border-info" : ""
|
||||
}`}
|
||||
} ml-auto`}
|
||||
>
|
||||
<TooltipWrapper title="New silence">
|
||||
<span
|
||||
@@ -37,7 +37,7 @@ const SilenceModal: FC<{
|
||||
className="nav-link cursor-pointer"
|
||||
onClick={silenceFormStore.toggle.toggle}
|
||||
>
|
||||
<FontAwesomeIcon icon={faBellSlash} />
|
||||
<FontAwesomeIcon icon={faBellSlash} fixedWidth />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
</li>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
@@ -13,6 +15,51 @@ beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
const makeErrors = () => {
|
||||
alertStore.data.setUpstreams({
|
||||
counters: { total: 3, healthy: 1, failed: 2 },
|
||||
instances: [
|
||||
{
|
||||
name: "am1",
|
||||
cluster: "am",
|
||||
clusterMembers: ["am1"],
|
||||
uri: "http://am1",
|
||||
publicURI: "http://am1",
|
||||
error: "error 1",
|
||||
version: "0.21.0",
|
||||
readonly: false,
|
||||
corsCredentials: "include",
|
||||
headers: {},
|
||||
},
|
||||
{
|
||||
name: "am2",
|
||||
cluster: "am",
|
||||
clusterMembers: ["am2"],
|
||||
uri: "file:///mock",
|
||||
publicURI: "file:///mock",
|
||||
error: "",
|
||||
version: "0.21.0",
|
||||
readonly: false,
|
||||
corsCredentials: "include",
|
||||
headers: {},
|
||||
},
|
||||
{
|
||||
name: "am3",
|
||||
cluster: "am",
|
||||
clusterMembers: ["am3"],
|
||||
uri: "http://am3",
|
||||
publicURI: "http://am3",
|
||||
error: "error 2",
|
||||
version: "0.21.0",
|
||||
readonly: false,
|
||||
corsCredentials: "include",
|
||||
headers: {},
|
||||
},
|
||||
],
|
||||
clusters: { am1: ["am1"], am2: ["am2"], am3: ["am3"] },
|
||||
});
|
||||
};
|
||||
|
||||
describe("<AppToasts />", () => {
|
||||
it("doesn't render anything when alertStore.info.upgradeNeeded=true", () => {
|
||||
alertStore.info.upgradeNeeded = true;
|
||||
@@ -20,9 +67,25 @@ describe("<AppToasts />", () => {
|
||||
expect(tree.html()).toBe("");
|
||||
});
|
||||
|
||||
it("doesn't render anything when there are no notifications to show", () => {
|
||||
const tree = mount(<AppToasts alertStore={alertStore} />);
|
||||
expect(tree.html()).toBe("");
|
||||
});
|
||||
|
||||
it("renders upstream error toasts for each unhealthy upstream", () => {
|
||||
makeErrors();
|
||||
const tree = mount(<AppToasts alertStore={alertStore} />);
|
||||
expect(tree.find("Toast")).toHaveLength(2);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("removes notifications when upstream recovers", () => {
|
||||
makeErrors();
|
||||
const tree = mount(<AppToasts alertStore={alertStore} />);
|
||||
expect(tree.find("Toast")).toHaveLength(2);
|
||||
|
||||
alertStore.data.setUpstreams({
|
||||
counters: { total: 3, healthy: 1, failed: 2 },
|
||||
counters: { total: 3, healthy: 3, failed: 0 },
|
||||
instances: [
|
||||
{
|
||||
name: "am1",
|
||||
@@ -30,7 +93,7 @@ describe("<AppToasts />", () => {
|
||||
clusterMembers: ["am1"],
|
||||
uri: "http://am1",
|
||||
publicURI: "http://am1",
|
||||
error: "error 1",
|
||||
error: "",
|
||||
version: "0.21.0",
|
||||
readonly: false,
|
||||
corsCredentials: "include",
|
||||
@@ -54,7 +117,7 @@ describe("<AppToasts />", () => {
|
||||
clusterMembers: ["am3"],
|
||||
uri: "http://am3",
|
||||
publicURI: "http://am3",
|
||||
error: "error 2",
|
||||
error: "",
|
||||
version: "0.21.0",
|
||||
readonly: false,
|
||||
corsCredentials: "include",
|
||||
@@ -63,9 +126,24 @@ describe("<AppToasts />", () => {
|
||||
],
|
||||
clusters: { am1: ["am1"], am2: ["am2"], am3: ["am3"] },
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("Toast")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("clicking navbar icon toggles all notifications", () => {
|
||||
makeErrors();
|
||||
alertStore.info.upgradeNeeded = false;
|
||||
alertStore.info.upgradeReady = false;
|
||||
const tree = mount(<AppToasts alertStore={alertStore} />);
|
||||
expect(tree.find("Toast")).toHaveLength(2);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
expect(tree.find("div.bg-toast")).toHaveLength(2);
|
||||
expect(tree.find("span.badge.cursor-pointer.with-click")).toHaveLength(2);
|
||||
|
||||
tree.find("span.badge.cursor-pointer.with-click").at(1).simulate("click");
|
||||
expect(tree.find("div.bg-toast")).toHaveLength(1);
|
||||
|
||||
tree.find("span#components-notifications").simulate("click");
|
||||
tree.update();
|
||||
expect(tree.find("div.bg-toast")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders UpgradeToastMessage when alertStore.info.upgradeReady=true", () => {
|
||||
|
||||
@@ -1,22 +1,51 @@
|
||||
import React, { FC } from "react";
|
||||
import React, { FC, Fragment, useCallback } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowUp } from "@fortawesome/free-solid-svg-icons/faArrowUp";
|
||||
import { faExclamation } from "@fortawesome/free-solid-svg-icons/faExclamation";
|
||||
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToastContainer, Toast } from ".";
|
||||
import { ToastMessage, UpgradeToastMessage } from "./ToastMessages";
|
||||
|
||||
const AppToasts: FC<{
|
||||
alertStore: AlertStore;
|
||||
}> = ({ alertStore }) => {
|
||||
return alertStore.info.upgradeNeeded ? null : (
|
||||
<ToastContainer>
|
||||
{alertStore.data.upstreams.instances
|
||||
.filter((upstream) => upstream.error !== "")
|
||||
.map((upstream) => (
|
||||
const show = useCallback(() => {
|
||||
const e = new CustomEvent("showNotifications");
|
||||
window.dispatchEvent(e);
|
||||
}, []);
|
||||
|
||||
if (alertStore.info.upgradeNeeded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
alertStore.data.upstreamsWithErrors.length === 0 &&
|
||||
alertStore.info.upgradeReady === false
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<li className="nav-item components-navbar-button ml-auto">
|
||||
<TooltipWrapper title="Show all notifications">
|
||||
<span
|
||||
id="components-notifications"
|
||||
className="nav-link cursor-pointer"
|
||||
onClick={show}
|
||||
>
|
||||
<FontAwesomeIcon icon={faInfoCircle} fixedWidth />
|
||||
</span>
|
||||
</TooltipWrapper>
|
||||
</li>
|
||||
<ToastContainer>
|
||||
{alertStore.data.upstreamsWithErrors.map((upstream) => (
|
||||
<Toast
|
||||
key={upstream.name}
|
||||
icon={faExclamation}
|
||||
@@ -29,15 +58,16 @@ const AppToasts: FC<{
|
||||
}
|
||||
/>
|
||||
))}
|
||||
{alertStore.info.upgradeReady ? (
|
||||
<Toast
|
||||
key="upgrade"
|
||||
icon={faArrowUp}
|
||||
iconClass="text-success"
|
||||
message={<UpgradeToastMessage alertStore={alertStore} />}
|
||||
/>
|
||||
) : null}
|
||||
</ToastContainer>
|
||||
{alertStore.info.upgradeReady ? (
|
||||
<Toast
|
||||
key="upgrade"
|
||||
icon={faArrowUp}
|
||||
iconClass="text-success"
|
||||
message={<UpgradeToastMessage alertStore={alertStore} />}
|
||||
/>
|
||||
) : null}
|
||||
</ToastContainer>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,28 @@
|
||||
|
||||
exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeReady=true 1`] = `
|
||||
"
|
||||
<li class=\\"nav-item components-navbar-button ml-auto\\">
|
||||
<div class=\\" tooltip-trigger\\">
|
||||
<span id=\\"components-notifications\\"
|
||||
class=\\"nav-link cursor-pointer\\"
|
||||
>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"info-circle\\"
|
||||
class=\\"svg-inline--fa fa-info-circle fa-w-16 fa-fw \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<div class=\\"toast-container d-flex flex-column\\">
|
||||
<div class=\\"m-1 bg-toast text-white rounded shadow components-animation-fade-appear components-animation-fade-appear-active\\">
|
||||
<div class=\\"d-flex flex-row p-1\\">
|
||||
@@ -78,19 +100,18 @@ exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeR
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
|
||||
<span class=\\"badge components-label with-click with-click-dark\\">
|
||||
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"chevron-down\\"
|
||||
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
|
||||
data-icon=\\"times\\"
|
||||
class=\\"svg-inline--fa fa-times fa-w-11 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 448 512\\"
|
||||
style=\\"transition: transform 0.25s ease-in-out;\\"
|
||||
viewbox=\\"0 0 352 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
|
||||
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
@@ -104,6 +125,28 @@ exports[`<AppToasts /> renders UpgradeToastMessage when alertStore.info.upgradeR
|
||||
|
||||
exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream 1`] = `
|
||||
"
|
||||
<li class=\\"nav-item components-navbar-button ml-auto\\">
|
||||
<div class=\\" tooltip-trigger\\">
|
||||
<span id=\\"components-notifications\\"
|
||||
class=\\"nav-link cursor-pointer\\"
|
||||
>
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"info-circle\\"
|
||||
class=\\"svg-inline--fa fa-info-circle fa-w-16 fa-fw \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 512 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
<div class=\\"toast-container d-flex flex-column\\">
|
||||
<div class=\\"m-1 bg-toast text-white rounded shadow components-animation-fade-appear components-animation-fade-appear-active\\">
|
||||
<div class=\\"d-flex flex-row p-1\\">
|
||||
@@ -150,19 +193,18 @@ exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
|
||||
<span class=\\"badge components-label with-click with-click-dark\\">
|
||||
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"chevron-down\\"
|
||||
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
|
||||
data-icon=\\"times\\"
|
||||
class=\\"svg-inline--fa fa-times fa-w-11 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 448 512\\"
|
||||
style=\\"transition: transform 0.25s ease-in-out;\\"
|
||||
viewbox=\\"0 0 352 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
|
||||
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
@@ -215,19 +257,18 @@ exports[`<AppToasts /> renders upstream error toasts for each unhealthy upstream
|
||||
</div>
|
||||
</div>
|
||||
<div class=\\"flex-shrink-0 flex-grow-0 align-self-top\\">
|
||||
<span class=\\"badge components-label with-click with-click-dark\\">
|
||||
<span class=\\"badge components-label cursor-pointer with-click with-click-dark\\">
|
||||
<svg aria-hidden=\\"true\\"
|
||||
focusable=\\"false\\"
|
||||
data-prefix=\\"fas\\"
|
||||
data-icon=\\"chevron-down\\"
|
||||
class=\\"svg-inline--fa fa-chevron-down fa-w-14 cursor-pointer m-2\\"
|
||||
data-icon=\\"times\\"
|
||||
class=\\"svg-inline--fa fa-times fa-w-11 \\"
|
||||
role=\\"img\\"
|
||||
xmlns=\\"http://www.w3.org/2000/svg\\"
|
||||
viewbox=\\"0 0 448 512\\"
|
||||
style=\\"transition: transform 0.25s ease-in-out;\\"
|
||||
viewbox=\\"0 0 352 512\\"
|
||||
>
|
||||
<path fill=\\"currentColor\\"
|
||||
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
|
||||
d=\\"M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z\\"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
import { act } from "react-dom/test-utils";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
@@ -20,7 +22,7 @@ describe("<Toast />", () => {
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
|
||||
});
|
||||
|
||||
it("toggles body on toggle icon click", () => {
|
||||
it("hides body on close icon click", () => {
|
||||
const tree = mount(
|
||||
<Toast
|
||||
icon={faExclamation}
|
||||
@@ -30,10 +32,39 @@ describe("<Toast />", () => {
|
||||
);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
|
||||
|
||||
tree.find("svg.cursor-pointer").simulate("click");
|
||||
tree.find("span.badge.cursor-pointer").simulate("click");
|
||||
expect(toDiffableHtml(tree.html())).not.toMatch(/fake error/);
|
||||
});
|
||||
|
||||
it("shows hidden body on showNotifications event", () => {
|
||||
const tree = mount(
|
||||
<Toast
|
||||
icon={faExclamation}
|
||||
iconClass="text-danger"
|
||||
message="fake error"
|
||||
/>
|
||||
);
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
|
||||
|
||||
tree.find("span.badge.cursor-pointer").simulate("click");
|
||||
expect(toDiffableHtml(tree.html())).not.toMatch(/fake error/);
|
||||
|
||||
tree.find("svg.cursor-pointer").simulate("click");
|
||||
const e = new CustomEvent("showNotifications");
|
||||
act(() => {
|
||||
window.dispatchEvent(e);
|
||||
});
|
||||
tree.update();
|
||||
expect(toDiffableHtml(tree.html())).toMatch(/fake error/);
|
||||
});
|
||||
|
||||
it("unmounts cleanly", () => {
|
||||
const tree = mount(
|
||||
<Toast
|
||||
icon={faExclamation}
|
||||
iconClass="text-danger"
|
||||
message="fake error"
|
||||
/>
|
||||
);
|
||||
tree.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactNode, useState } from "react";
|
||||
import React, { FC, ReactNode, useState, useEffect } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import TransitionGroup from "react-transition-group/TransitionGroup";
|
||||
@@ -7,9 +7,9 @@ import { CSSTransition } from "react-transition-group";
|
||||
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCircle } from "@fortawesome/free-solid-svg-icons/faCircle";
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes";
|
||||
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
|
||||
const Toast: FC<{
|
||||
icon: IconDefinition;
|
||||
@@ -18,6 +18,16 @@ const Toast: FC<{
|
||||
}> = ({ icon, iconClass, message }) => {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const show = () => setIsOpen(true);
|
||||
window.addEventListener("showNotifications", show);
|
||||
return () => {
|
||||
window.removeEventListener("showNotifications", show);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="m-1 bg-toast text-white rounded shadow">
|
||||
<div className="d-flex flex-row p-1">
|
||||
@@ -29,15 +39,14 @@ const Toast: FC<{
|
||||
<FontAwesomeIcon icon={icon} className="fa-stack-1x text-white" />
|
||||
</div>
|
||||
<div className="flex-shrink-1 flex-grow-1 align-self-center ml-1 mr-3 text-break text-wrap">
|
||||
{isOpen ? message : null}
|
||||
{message}
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex-grow-0 align-self-top">
|
||||
<span className="badge components-label with-click with-click-dark">
|
||||
<ToggleIcon
|
||||
isOpen={isOpen}
|
||||
className={`cursor-pointer ${isOpen ? "m-2" : "mr-2"}`}
|
||||
onClick={() => setIsOpen((v) => !v)}
|
||||
/>
|
||||
<span
|
||||
className="badge components-label cursor-pointer with-click with-click-dark"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTimes} />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -152,6 +152,7 @@ interface AlertStoreDataT {
|
||||
setUpstreams: (u: APIAlertsResponseUpstreamsT) => void;
|
||||
setInstances: (i: APIAlertmanagerUpstreamT[]) => void;
|
||||
setClusters: (c: APIAlertsResponseUpstreamsClusterMapT) => void;
|
||||
readonly upstreamsWithErrors: APIAlertmanagerUpstreamT[];
|
||||
}
|
||||
|
||||
interface AlertStoreInfoT {
|
||||
@@ -343,6 +344,11 @@ class AlertStore {
|
||||
setClusters(c: APIAlertsResponseUpstreamsClusterMapT) {
|
||||
this.upstreams.clusters = c;
|
||||
},
|
||||
get upstreamsWithErrors(): APIAlertmanagerUpstreamT[] {
|
||||
return this.upstreams.instances.filter(
|
||||
(upstream) => upstream.error !== ""
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
gridPadding: computed,
|
||||
|
||||
Reference in New Issue
Block a user