diff --git a/ui/src/Components/FetchPauser/index.js b/ui/src/Components/FetchPauser/index.js
new file mode 100644
index 000000000..78be3973a
--- /dev/null
+++ b/ui/src/Components/FetchPauser/index.js
@@ -0,0 +1,29 @@
+import { Component } from "react";
+import PropTypes from "prop-types";
+
+import { inject } from "mobx-react";
+
+const FetchPauser = inject("alertStore")(
+ class FetchPauser extends Component {
+ static propTypes = {
+ children: PropTypes.any,
+ alertStore: PropTypes.object.isRequired
+ };
+
+ componentDidMount() {
+ const { alertStore } = this.props;
+ alertStore.status.pause();
+ }
+
+ componentWillUnmount() {
+ const { alertStore } = this.props;
+ alertStore.status.resume();
+ }
+
+ render() {
+ return this.props.children;
+ }
+ }
+);
+
+export { FetchPauser };
diff --git a/ui/src/Components/FetchPauser/index.test.js b/ui/src/Components/FetchPauser/index.test.js
new file mode 100644
index 000000000..bc9dfa634
--- /dev/null
+++ b/ui/src/Components/FetchPauser/index.test.js
@@ -0,0 +1,37 @@
+import React from "react";
+
+import { Provider } from "mobx-react";
+
+import { mount } from "enzyme";
+
+import { AlertStore } from "Stores/AlertStore";
+import { FetchPauser } from ".";
+
+let alertStore;
+
+beforeEach(() => {
+ alertStore = new AlertStore([]);
+});
+
+const MountedFetchPauser = () => {
+ return mount(
+
+
+
+
+
+ );
+};
+
+describe("", () => {
+ it("mounting FetchPauser pauses alertStore", () => {
+ MountedFetchPauser();
+ expect(alertStore.status.paused).toBe(true);
+ });
+
+ it("unmounting FetchPauser resumes alertStore", () => {
+ const tree = MountedFetchPauser();
+ tree.unmount();
+ expect(alertStore.status.paused).toBe(false);
+ });
+});
diff --git a/ui/src/Components/Fetcher/index.js b/ui/src/Components/Fetcher/index.js
index 1361825a5..53e0e1896 100644
--- a/ui/src/Components/Fetcher/index.js
+++ b/ui/src/Components/Fetcher/index.js
@@ -43,7 +43,7 @@ const Fetcher = observer(
status === AlertStoreStatuses.Fetching.toString() ||
status === AlertStoreStatuses.Processing.toString();
- if (pastDeadline && !updateInProgress) {
+ if (pastDeadline && !updateInProgress && !alertStore.status.paused) {
this.lastTick.update();
alertStore.fetchWithThrottle();
}
@@ -61,8 +61,10 @@ const Fetcher = observer(
componentDidUpdate() {
const { alertStore } = this.props;
- this.lastTick.update();
- alertStore.fetchWithThrottle();
+ if (!alertStore.status.paused) {
+ this.lastTick.update();
+ alertStore.fetchWithThrottle();
+ }
}
componentWillUnmount() {
diff --git a/ui/src/Components/Fetcher/index.test.js b/ui/src/Components/Fetcher/index.test.js
index 2f4679602..1f3610e5d 100644
--- a/ui/src/Components/Fetcher/index.test.js
+++ b/ui/src/Components/Fetcher/index.test.js
@@ -147,4 +147,34 @@ describe("", () => {
instance.componentWillUnmount();
expect(instance.timer).toBeNull();
});
+
+ it("doesn't fetch on mount when paused", () => {
+ alertStore.status.pause();
+ MountedFetcher();
+ expect(fetchSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it("doesn't fetch on update when paused", () => {
+ alertStore.status.pause();
+ const tree = MountedFetcher();
+ tree.instance().componentDidUpdate();
+ expect(fetchSpy).toHaveBeenCalledTimes(0);
+ });
+
+ it("fetches on update when resumed", () => {
+ alertStore.status.pause();
+ const tree = MountedFetcher();
+ alertStore.status.resume();
+ tree.instance().componentDidUpdate();
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ });
+
+ it("fetches on resume", () => {
+ alertStore.status.pause();
+ MountedFetcher();
+ alertStore.status.resume();
+ advanceBy(2 * 1000);
+ jest.runOnlyPendingTimers();
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
+ });
});
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
index 7ac4e5446..4e521bc85 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
@@ -15,6 +15,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCaretDown } from "@fortawesome/free-solid-svg-icons/faCaretDown";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
+import { FetchPauser } from "Components/FetchPauser";
+
const onSilenceClick = (silenceFormStore, group, alert) => {
silenceFormStore.data.resetProgress();
silenceFormStore.data.fillMatchersFromGroup(group, [alert]);
@@ -32,33 +34,35 @@ const MenuContent = onClickOutside(
silenceFormStore
}) => {
return (
-
-
Alert source links:
- {alert.alertmanager.map(am => (
-
- {am.name}
-
- ))}
-
+
onSilenceClick(silenceFormStore, group, alert)}
+ className="dropdown-menu d-block"
+ ref={popperRef}
+ style={popperStyle}
+ data-placement={popperPlacement}
>
-
Silence this alert
+
Alert source links:
+ {alert.alertmanager.map(am => (
+
+ {am.name}
+
+ ))}
+
+
onSilenceClick(silenceFormStore, group, alert)}
+ >
+ Silence this alert
+
-
+
);
}
);
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js
index d8bca5f3d..ecc328666 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js
@@ -1,16 +1,21 @@
import React from "react";
+import { Provider } from "mobx-react";
+
import { mount } from "enzyme";
import { MockAlertGroup, MockAlert } from "__mocks__/Alerts.js";
+import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { AlertMenu, MenuContent } from "./AlertMenu";
+let alertStore;
let silenceFormStore;
let alert;
let group;
beforeEach(() => {
+ alertStore = new AlertStore([]);
silenceFormStore = new SilenceFormStore();
alert = MockAlert([], { foo: "bar" }, "active");
group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {});
@@ -20,12 +25,14 @@ const MockAfterClick = jest.fn();
const MountedAlertMenu = group => {
return mount(
-
- );
+
+
+
+ ).find("AlertMenu");
};
describe("", () => {
@@ -55,15 +62,17 @@ describe("", () => {
const MountedMenuContent = group => {
return mount(
-
+
+
+
);
};
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
index b337576f8..81244998c 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
@@ -16,6 +16,7 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { FormatAPIFilterQuery } from "Stores/AlertStore";
import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query";
+import { FetchPauser } from "Components/FetchPauser";
const onSilenceClick = (silenceFormStore, group) => {
silenceFormStore.data.resetProgress();
@@ -43,28 +44,30 @@ const MenuContent = onClickOutside(
)}`;
return (
-
+
{
- copy(groupLink);
- afterClick();
- }}
+ className="dropdown-menu d-block"
+ ref={popperRef}
+ style={popperStyle}
+ data-placement={popperPlacement}
>
-
Copy link to this group
+
{
+ copy(groupLink);
+ afterClick();
+ }}
+ >
+ Copy link to this group
+
+
onSilenceClick(silenceFormStore, group)}
+ >
+ Silence this group
+
- onSilenceClick(silenceFormStore, group)}
- >
- Silence this group
-
-
+
);
}
);
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js
index eb8c3a7ff..80b20234c 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js
@@ -1,23 +1,32 @@
import React from "react";
+import { Provider } from "mobx-react";
+
import { mount } from "enzyme";
import copy from "copy-to-clipboard";
import { MockAlertGroup } from "__mocks__/Alerts.js";
+import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { GroupMenu, MenuContent } from "./GroupMenu";
+let alertStore;
let silenceFormStore;
beforeEach(() => {
+ alertStore = new AlertStore([]);
silenceFormStore = new SilenceFormStore();
});
const MockAfterClick = jest.fn();
const MountedGroupMenu = group => {
- return mount();
+ return mount(
+
+
+
+ ).find("GroupMenu");
};
describe("", () => {
@@ -51,14 +60,16 @@ describe("", () => {
const MountedMenuContent = group => {
return mount(
-
+
+
+
);
};
diff --git a/ui/src/Components/NavBar/FetchIndicator/__snapshots__/index.test.js.snap b/ui/src/Components/NavBar/FetchIndicator/__snapshots__/index.test.js.snap
index 19ea854d1..6c380c8aa 100644
--- a/ui/src/Components/NavBar/FetchIndicator/__snapshots__/index.test.js.snap
+++ b/ui/src/Components/NavBar/FetchIndicator/__snapshots__/index.test.js.snap
@@ -24,7 +24,7 @@ exports[` matches snapshot when idle 1`] = `
matches snapshot when idle 1`] = `
"
`;
+exports[` matches snapshot when paused 1`] = `
+"
+
+"
+`;
+
exports[` matches snapshot when response is processed 1`] = `
"