mirror of
https://github.com/prymitive/karma
synced 2026-05-05 03:16:51 +00:00
@@ -53,7 +53,7 @@ labels:
|
||||
color: "#ff220c"
|
||||
log:
|
||||
config: false
|
||||
level: warning
|
||||
level: debug
|
||||
sentry:
|
||||
private: https://84a9ef37a6ed4fdb80e9ea2310d1ed26:8c6ee6f0ab02406482ff4b4e824e2c27@sentry.io/1279017
|
||||
public: https://84a9ef37a6ed4fdb80e9ea2310d1ed26@sentry.io/1279017
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<MountedLabelWithPercent /> matches snapshot 1`] = `
|
||||
"
|
||||
<div class
|
||||
style=\\"display: inline-block; max-width: 100%;\\"
|
||||
data-tooltipped
|
||||
aria-describedby=\\"tippy-tooltip-1\\"
|
||||
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
|
||||
>
|
||||
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
|
||||
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
|
||||
25
|
||||
</span>
|
||||
<span class=\\"components-label-name\\">
|
||||
foo:
|
||||
</span>
|
||||
<span class=\\"components-label-value\\">
|
||||
bar
|
||||
</span>
|
||||
</span>
|
||||
<div class=\\"progress silence-progress bg-white pr-1\\">
|
||||
<div class=\\"progress-bar bg-success\\"
|
||||
role=\\"progressbar\\"
|
||||
style=\\"width: 50%;\\"
|
||||
aria-valuenow=\\"50\\"
|
||||
aria-valuemin=\\"0\\"
|
||||
aria-valuemax=\\"100\\"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
"
|
||||
`;
|
||||
69
ui/src/Components/Labels/LabelWithPercent/index.js
Normal file
69
ui/src/Components/Labels/LabelWithPercent/index.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { BaseLabel } from "Components/Labels/BaseLabel";
|
||||
|
||||
import "./index.scss";
|
||||
|
||||
const LabelWithPercent = inject("alertStore")(
|
||||
observer(
|
||||
class FilteringLabel extends BaseLabel {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
hits: PropTypes.number.isRequired,
|
||||
percent: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { name, value, hits, percent } = this.props;
|
||||
|
||||
let cs = this.getClassAndStyle(
|
||||
name,
|
||||
value,
|
||||
"components-label-with-hover mb-0 pl-0 text-left"
|
||||
);
|
||||
|
||||
const progressBarBg =
|
||||
percent > 66
|
||||
? "bg-danger"
|
||||
: percent > 66
|
||||
? "bg-warning"
|
||||
: "bg-success";
|
||||
|
||||
return (
|
||||
<TooltipWrapper title="Click to only show alerts with this label or Alt+Click to hide them">
|
||||
<span
|
||||
className={cs.className}
|
||||
style={cs.style}
|
||||
onClick={e => this.handleClick(e)}
|
||||
>
|
||||
<span className="mr-1 px-1 bg-primary text-white components-labelWithPercent-percent">
|
||||
{hits}
|
||||
</span>
|
||||
<span className="components-label-name">{name}:</span>{" "}
|
||||
<span className="components-label-value">{value}</span>
|
||||
</span>
|
||||
<div className="progress silence-progress bg-white pr-1">
|
||||
<div
|
||||
className={`progress-bar ${progressBarBg}`}
|
||||
role="progressbar"
|
||||
style={{ width: percent + "%" }}
|
||||
aria-valuenow={percent}
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
/>
|
||||
</div>
|
||||
</TooltipWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export { LabelWithPercent };
|
||||
4
ui/src/Components/Labels/LabelWithPercent/index.scss
Normal file
4
ui/src/Components/Labels/LabelWithPercent/index.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.components-labelWithPercent-percent {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
63
ui/src/Components/Labels/LabelWithPercent/index.test.js
Normal file
63
ui/src/Components/Labels/LabelWithPercent/index.test.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore, NewUnappliedFilter } from "Stores/AlertStore";
|
||||
|
||||
import { LabelWithPercent } from ".";
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
const MountedLabelWithPercent = (name, value) => {
|
||||
return mount(
|
||||
<LabelWithPercent
|
||||
alertStore={alertStore}
|
||||
name={name}
|
||||
value={value}
|
||||
hits={25}
|
||||
percent={50}
|
||||
/>
|
||||
).find(".components-label");
|
||||
};
|
||||
|
||||
const RenderAndClick = (name, value, clickOptions) => {
|
||||
const tree = MountedLabelWithPercent(name, value);
|
||||
tree.find(".components-label").simulate("click", clickOptions || {});
|
||||
};
|
||||
|
||||
describe("<MountedLabelWithPercent />", () => {
|
||||
it("matches snapshot", () => {
|
||||
const tree = mount(
|
||||
<LabelWithPercent
|
||||
alertStore={alertStore}
|
||||
name="foo"
|
||||
value="bar"
|
||||
hits={25}
|
||||
percent={50}
|
||||
/>
|
||||
);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("calling onClick() adds a new filter 'foo=bar'", () => {
|
||||
RenderAndClick("foo", "bar");
|
||||
expect(alertStore.filters.values).toHaveLength(1);
|
||||
expect(alertStore.filters.values).toContainEqual(
|
||||
NewUnappliedFilter("foo=bar")
|
||||
);
|
||||
});
|
||||
|
||||
it("calling onClick() while holding Alt key adds a new filter 'foo!=bar'", () => {
|
||||
RenderAndClick("foo", "bar", { altKey: true });
|
||||
expect(alertStore.filters.values).toHaveLength(1);
|
||||
expect(alertStore.filters.values).toContainEqual(
|
||||
NewUnappliedFilter("foo!=bar")
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -8,13 +8,12 @@ import ReactResizeDetector from "react-resize-detector";
|
||||
|
||||
import IdleTimer from "react-idle-timer";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import { NavBarSlide } from "Components/Animations/NavBarSlide";
|
||||
import { OverviewModal } from "Components/OverviewModal";
|
||||
import { MainModal } from "Components/MainModal";
|
||||
import { SilenceModal } from "Components/SilenceModal";
|
||||
import { FetchIndicator } from "./FetchIndicator";
|
||||
@@ -141,12 +140,8 @@ const NavBar = observer(
|
||||
>
|
||||
<nav className="navbar fixed-top navbar-expand navbar-dark p-1 bg-primary-transparent d-inline-block">
|
||||
<ReactResizeDetector handleHeight onResize={this.onResize} />
|
||||
<span className="navbar-brand my-0 mx-2 h1 d-none d-sm-block float-left">
|
||||
<Flash spy={alertStore.info.totalAlerts}>
|
||||
<div className="d-inline-block">
|
||||
{alertStore.info.totalAlerts}
|
||||
</div>
|
||||
</Flash>
|
||||
<span className="navbar-brand p-0 my-0 mx-2 h1 d-none d-sm-block float-left">
|
||||
<OverviewModal alertStore={alertStore} />
|
||||
<FetchIndicator alertStore={alertStore} />
|
||||
</span>
|
||||
<ul className={`navbar-nav float-right d-flex ${flexClass}`}>
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("<NavBar />", () => {
|
||||
it("navbar-brand shows 15 alerts with totalAlerts=15", () => {
|
||||
alertStore.info.totalAlerts = 15;
|
||||
const tree = MountedNavbar();
|
||||
const brand = tree.find(".navbar-brand");
|
||||
const brand = tree.find("span.navbar-brand");
|
||||
expect(brand.text()).toBe("15");
|
||||
});
|
||||
|
||||
|
||||
81
ui/src/Components/OverviewModal/OverviewModalContent.js
Normal file
81
ui/src/Components/OverviewModal/OverviewModalContent.js
Normal file
@@ -0,0 +1,81 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { LabelWithPercent } from "Components/Labels/LabelWithPercent";
|
||||
|
||||
const LabelsTable = observer(({ alertStore }) => (
|
||||
<table
|
||||
className="table table-borderless top-labels"
|
||||
style={{ tableLayout: "fixed" }}
|
||||
>
|
||||
<tbody className="mw-100">
|
||||
{alertStore.data.counters.map(nameStats => (
|
||||
<tr key={nameStats.name}>
|
||||
<td width="25%" className="text-nowrap mw-100 p-1">
|
||||
<span className="badge badge-light components-label mx-0 my-1 pl-0 text-left">
|
||||
<span className="bg-primary text-white mr-1 px-1 components-labelWithPercent-percent">
|
||||
{nameStats.hits}
|
||||
</span>
|
||||
{nameStats.name}
|
||||
</span>
|
||||
</td>
|
||||
<td width="75%" className="mw-100 p-1">
|
||||
{nameStats.values.slice(0, 9).map(valueStats => (
|
||||
<LabelWithPercent
|
||||
key={valueStats.value}
|
||||
name={nameStats.name}
|
||||
value={valueStats.value}
|
||||
hits={valueStats.hits}
|
||||
percent={valueStats.percent}
|
||||
/>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
));
|
||||
|
||||
const NothingToShow = () => (
|
||||
<div className="jumbotron bg-white">
|
||||
<h1 className="display-5 text-secondary text-center">
|
||||
No labels to display
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
|
||||
const OverviewModalContent = observer(
|
||||
class OverviewModalContent extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
onHide: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
render() {
|
||||
const { alertStore, onHide } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div className="modal-header">
|
||||
<h5 className="modal-title">Overview</h5>
|
||||
<button type="button" className="close" onClick={onHide}>
|
||||
<span className="align-middle">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{alertStore.data.counters.length === 0 ? (
|
||||
<NothingToShow />
|
||||
) : (
|
||||
<LabelsTable alertStore={alertStore} />
|
||||
)}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export { OverviewModalContent };
|
||||
66
ui/src/Components/OverviewModal/OverviewModalContent.test.js
Normal file
66
ui/src/Components/OverviewModal/OverviewModalContent.test.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
|
||||
import { Provider } from "mobx-react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import toDiffableHtml from "diffable-html";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { OverviewModalContent } from "./OverviewModalContent";
|
||||
|
||||
let alertStore;
|
||||
const onHide = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
onHide.mockClear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("<OverviewModalContent />", () => {
|
||||
it("matches snapshot with labels to show", () => {
|
||||
alertStore.data.counters = [
|
||||
{
|
||||
name: "foo",
|
||||
hits: 16,
|
||||
values: [
|
||||
{ value: "bar1", hits: 8, percent: 50 },
|
||||
{ value: "bar2", hits: 4, percent: 25 },
|
||||
{ value: "bar3", hits: 4, percent: 25 }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// we have multiple fragments and enzyme only renders the first one
|
||||
// in html() and text(), debug() would work but it's noisy
|
||||
// https://github.com/airbnb/enzyme/issues/1213
|
||||
const tree = mount(
|
||||
<span>
|
||||
<Provider alertStore={alertStore}>
|
||||
<OverviewModalContent alertStore={alertStore} onHide={onHide} />
|
||||
</Provider>
|
||||
</span>
|
||||
);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("matches snapshot with no labels to show", () => {
|
||||
alertStore.data.counters = [];
|
||||
|
||||
// we have multiple fragments and enzyme only renders the first one
|
||||
// in html() and text(), debug() would work but it's noisy
|
||||
// https://github.com/airbnb/enzyme/issues/1213
|
||||
const tree = mount(
|
||||
<span>
|
||||
<Provider alertStore={alertStore}>
|
||||
<OverviewModalContent alertStore={alertStore} onHide={onHide} />
|
||||
</Provider>
|
||||
</span>
|
||||
);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<OverviewModalContent /> matches snapshot with labels to show 1`] = `
|
||||
"
|
||||
<span>
|
||||
<div class=\\"modal-header\\">
|
||||
<h5 class=\\"modal-title\\">
|
||||
Overview
|
||||
</h5>
|
||||
<button type=\\"button\\"
|
||||
class=\\"close\\"
|
||||
>
|
||||
<span class=\\"align-middle\\">
|
||||
×
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class=\\"modal-body\\">
|
||||
<table class=\\"table table-borderless top-labels\\"
|
||||
style=\\"table-layout: fixed;\\"
|
||||
>
|
||||
<tbody class=\\"mw-100\\">
|
||||
<tr>
|
||||
<td width=\\"25%\\"
|
||||
class=\\"text-nowrap mw-100 p-1\\"
|
||||
>
|
||||
<span class=\\"badge badge-light components-label mx-0 my-1 pl-0 text-left\\">
|
||||
<span class=\\"bg-primary text-white mr-1 px-1 components-labelWithPercent-percent\\">
|
||||
16
|
||||
</span>
|
||||
foo
|
||||
</span>
|
||||
</td>
|
||||
<td width=\\"75%\\"
|
||||
class=\\"mw-100 p-1\\"
|
||||
>
|
||||
<div class
|
||||
style=\\"display: inline-block; max-width: 100%;\\"
|
||||
data-tooltipped
|
||||
aria-describedby=\\"tippy-tooltip-1\\"
|
||||
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
|
||||
>
|
||||
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
|
||||
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
|
||||
8
|
||||
</span>
|
||||
<span class=\\"components-label-name\\">
|
||||
foo:
|
||||
</span>
|
||||
<span class=\\"components-label-value\\">
|
||||
bar1
|
||||
</span>
|
||||
</span>
|
||||
<div class=\\"progress silence-progress bg-white pr-1\\">
|
||||
<div class=\\"progress-bar bg-success\\"
|
||||
role=\\"progressbar\\"
|
||||
style=\\"width: 50%;\\"
|
||||
aria-valuenow=\\"50\\"
|
||||
aria-valuemin=\\"0\\"
|
||||
aria-valuemax=\\"100\\"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class
|
||||
style=\\"display: inline-block; max-width: 100%;\\"
|
||||
data-tooltipped
|
||||
aria-describedby=\\"tippy-tooltip-2\\"
|
||||
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
|
||||
>
|
||||
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
|
||||
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
|
||||
4
|
||||
</span>
|
||||
<span class=\\"components-label-name\\">
|
||||
foo:
|
||||
</span>
|
||||
<span class=\\"components-label-value\\">
|
||||
bar2
|
||||
</span>
|
||||
</span>
|
||||
<div class=\\"progress silence-progress bg-white pr-1\\">
|
||||
<div class=\\"progress-bar bg-success\\"
|
||||
role=\\"progressbar\\"
|
||||
style=\\"width: 25%;\\"
|
||||
aria-valuenow=\\"25\\"
|
||||
aria-valuemin=\\"0\\"
|
||||
aria-valuemax=\\"100\\"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class
|
||||
style=\\"display: inline-block; max-width: 100%;\\"
|
||||
data-tooltipped
|
||||
aria-describedby=\\"tippy-tooltip-3\\"
|
||||
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
|
||||
>
|
||||
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover mb-0 pl-0 text-left\\">
|
||||
<span class=\\"mr-1 px-1 bg-primary text-white components-labelWithPercent-percent\\">
|
||||
4
|
||||
</span>
|
||||
<span class=\\"components-label-name\\">
|
||||
foo:
|
||||
</span>
|
||||
<span class=\\"components-label-value\\">
|
||||
bar3
|
||||
</span>
|
||||
</span>
|
||||
<div class=\\"progress silence-progress bg-white pr-1\\">
|
||||
<div class=\\"progress-bar bg-success\\"
|
||||
role=\\"progressbar\\"
|
||||
style=\\"width: 25%;\\"
|
||||
aria-valuenow=\\"25\\"
|
||||
aria-valuemin=\\"0\\"
|
||||
aria-valuemax=\\"100\\"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</span>
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`<OverviewModalContent /> matches snapshot with no labels to show 1`] = `
|
||||
"
|
||||
<span>
|
||||
<div class=\\"modal-header\\">
|
||||
<h5 class=\\"modal-title\\">
|
||||
Overview
|
||||
</h5>
|
||||
<button type=\\"button\\"
|
||||
class=\\"close\\"
|
||||
>
|
||||
<span class=\\"align-middle\\">
|
||||
×
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class=\\"modal-body\\">
|
||||
<div class=\\"jumbotron bg-white\\">
|
||||
<h1 class=\\"display-5 text-secondary text-center\\">
|
||||
No labels to display
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
"
|
||||
`;
|
||||
84
ui/src/Components/OverviewModal/index.js
Normal file
84
ui/src/Components/OverviewModal/index.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React, { Component } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observer, Provider } from "mobx-react";
|
||||
import { observable, action } from "mobx";
|
||||
|
||||
import Flash from "react-reveal/Flash";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { Modal } from "Components/Modal";
|
||||
|
||||
import "./index.scss";
|
||||
|
||||
// https://github.com/facebook/react/issues/14603
|
||||
const OverviewModalContent = React.lazy(() =>
|
||||
import("./OverviewModalContent").then(module => ({
|
||||
default: module.OverviewModalContent
|
||||
}))
|
||||
);
|
||||
|
||||
const OverviewModal = observer(
|
||||
class OverviewModal extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired
|
||||
};
|
||||
|
||||
toggle = observable(
|
||||
{
|
||||
show: false,
|
||||
toggle() {
|
||||
this.show = !this.show;
|
||||
},
|
||||
hide() {
|
||||
this.show = false;
|
||||
}
|
||||
},
|
||||
{ toggle: action.bound, hide: action.bound }
|
||||
);
|
||||
|
||||
render() {
|
||||
const { alertStore } = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<TooltipWrapper title="Show alert overview">
|
||||
<Flash spy={alertStore.info.totalAlerts}>
|
||||
<div
|
||||
className={`text-center d-inline-block cursor-pointer navbar-brand m-0 ${
|
||||
this.toggle.show ? "border-bottom border-info" : ""
|
||||
}`}
|
||||
onClick={this.toggle.toggle}
|
||||
>
|
||||
{alertStore.info.totalAlerts}
|
||||
</div>
|
||||
</Flash>
|
||||
</TooltipWrapper>
|
||||
<Modal isOpen={this.toggle.show}>
|
||||
<React.Suspense
|
||||
fallback={
|
||||
<h1 className="display-1 text-secondary p-5 m-auto">
|
||||
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
|
||||
</h1>
|
||||
}
|
||||
>
|
||||
<Provider alertStore={alertStore}>
|
||||
<OverviewModalContent
|
||||
alertStore={alertStore}
|
||||
onHide={this.toggle.hide}
|
||||
isVisible={this.toggle.show}
|
||||
/>
|
||||
</Provider>
|
||||
</React.Suspense>
|
||||
</Modal>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export { OverviewModal };
|
||||
10
ui/src/Components/OverviewModal/index.scss
Normal file
10
ui/src/Components/OverviewModal/index.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
@import "~bootswatch/dist/flatly/variables";
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
@import "~bootswatch/dist/flatly/bootswatch";
|
||||
|
||||
.navbar-brand {
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $green !important;
|
||||
}
|
||||
}
|
||||
96
ui/src/Components/OverviewModal/index.test.js
Normal file
96
ui/src/Components/OverviewModal/index.test.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import React from "react";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { OverviewModal } from ".";
|
||||
|
||||
let alertStore;
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
});
|
||||
|
||||
const MountedOverviewModal = () => {
|
||||
return mount(<OverviewModal alertStore={alertStore} />);
|
||||
};
|
||||
|
||||
describe("<OverviewModal />", () => {
|
||||
it("only renders the counter when modal is not shown", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
expect(tree.text()).toBe("0");
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders a spinner placeholder while modal content is loading", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(0);
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders modal content if fallback is not used", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(1);
|
||||
expect(tree.find(".modal-content").find("svg.fa-spinner")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("hides the modal when toggle() is called twice", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(1);
|
||||
|
||||
toggle.simulate("click");
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("hides the modal when hide() is called", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
|
||||
toggle.simulate("click");
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(1);
|
||||
|
||||
const instance = tree.instance();
|
||||
instance.toggle.hide();
|
||||
jest.runOnlyPendingTimers();
|
||||
tree.update();
|
||||
expect(tree.find("OverviewModalContent")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("'modal-open' class is appended to body node when modal is visible", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
expect(document.body.className.split(" ")).toContain("modal-open");
|
||||
});
|
||||
|
||||
it("'modal-open' class is removed from body node after modal is hidden", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
toggle.simulate("click");
|
||||
expect(document.body.className.split(" ")).not.toContain("modal-open");
|
||||
});
|
||||
|
||||
it("'modal-open' class is removed from body node after modal is unmounted", () => {
|
||||
const tree = MountedOverviewModal();
|
||||
const toggle = tree.find("div.navbar-brand");
|
||||
toggle.simulate("click");
|
||||
tree.unmount();
|
||||
expect(document.body.className.split(" ")).not.toContain("modal-open");
|
||||
});
|
||||
});
|
||||
@@ -137,7 +137,7 @@ class AlertStore {
|
||||
data = observable(
|
||||
{
|
||||
colors: {},
|
||||
counters: {},
|
||||
counters: [],
|
||||
groups: {},
|
||||
silences: {},
|
||||
upstreams: { instances: [], clusters: {} },
|
||||
|
||||
Reference in New Issue
Block a user