diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
index 213e96b5a..e5478689b 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js
@@ -1,8 +1,7 @@
-import React, { Component } from "react";
+import React from "react";
import PropTypes from "prop-types";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
+import { useObserver, useLocalStore } from "mobx-react";
import hash from "object-hash";
@@ -96,94 +95,79 @@ MenuContent.propTypes = {
afterClick: PropTypes.func.isRequired,
};
-const AlertMenu = observer(
- class AlertMenu extends Component {
- static propTypes = {
- group: APIGroup.isRequired,
- alert: APIAlert.isRequired,
- alertStore: PropTypes.instanceOf(AlertStore).isRequired,
- silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
- setIsMenuOpen: PropTypes.func.isRequired,
- };
+const AlertMenu = ({
+ group,
+ alert,
+ alertStore,
+ silenceFormStore,
+ setIsMenuOpen,
+}) => {
+ const collapse = useLocalStore(() => ({
+ value: true,
+ toggle() {
+ this.value = !this.value;
+ setIsMenuOpen(!this.value);
+ },
+ hide() {
+ this.value = true;
+ setIsMenuOpen(!this.value);
+ },
+ }));
- constructor(props) {
- super(props);
+ const uniqueClass = `components-grid-alert-${group.id}-${hash(alert.labels)}`;
- this.collapse = observable(
- {
- value: true,
- toggle() {
- this.value = !this.value;
- props.setIsMenuOpen(!this.value);
- },
- hide() {
- this.value = true;
- props.setIsMenuOpen(!this.value);
- },
- },
- { toggle: action.bound, hide: action.bound },
- { name: "Alert menu toggle" }
- );
- }
-
- handleClickOutside = action((event) => {
- this.collapse.hide();
- });
-
- render() {
- const { group, alert, alertStore, silenceFormStore } = this.props;
-
- const uniqueClass = `components-grid-alert-${group.id}-${hash(
- alert.labels
- )}`;
-
- return (
-
-
- {({ ref }) => (
-
-
- {alert.startsAt}
-
- )}
-
-
-
- {({ placement, ref, style }) => (
-
- )}
-
-
-
- );
- }
- }
-);
+ return useObserver(() => (
+
+
+ {({ ref }) => (
+
+
+ {alert.startsAt}
+
+ )}
+
+
+
+ {({ placement, ref, style }) => (
+
+ )}
+
+
+
+ ));
+};
+AlertMenu.propTypes = {
+ group: APIGroup.isRequired,
+ alert: APIAlert.isRequired,
+ alertStore: PropTypes.instanceOf(AlertStore).isRequired,
+ silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
+ setIsMenuOpen: PropTypes.func.isRequired,
+};
export { AlertMenu, MenuContent };
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 87126bdee..d35a8e9f5 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js
@@ -13,9 +13,20 @@ let silenceFormStore;
let alert;
let group;
+let MockAfterClick;
+let MockSetIsMenuOpen;
+
+beforeAll(() => {
+ jest.useFakeTimers();
+});
+
beforeEach(() => {
alertStore = new AlertStore([]);
silenceFormStore = new SilenceFormStore();
+
+ MockAfterClick = jest.fn();
+ MockSetIsMenuOpen = jest.fn();
+
alert = MockAlert([], { foo: "bar" }, "active");
group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {});
@@ -38,9 +49,6 @@ beforeEach(() => {
};
});
-const MockAfterClick = jest.fn();
-const MockSetIsMenuOpen = jest.fn();
-
const MountedAlertMenu = (group) => {
return mount(
{
};
describe("", () => {
- it("is collapsed by default", () => {
+ it("menu content is hidden by default", () => {
const tree = MountedAlertMenu(group);
- expect(tree.instance().collapse.value).toBe(true);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
+ expect(MockSetIsMenuOpen).not.toHaveBeenCalled();
});
- it("clicking toggle sets collapse value to 'false'", async () => {
+ it("clicking toggle renders menu content", async () => {
const promise = Promise.resolve();
const tree = MountedAlertMenu(group);
- const toggle = tree.find(".cursor-pointer");
+ const toggle = tree.find("span.cursor-pointer");
toggle.simulate("click");
- expect(tree.instance().collapse.value).toBe(false);
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
await act(() => promise);
});
- it("handleClickOutside() call sets collapse value to 'true'", async () => {
+ it("clicking toggle twice hides menu content", async () => {
const promise = Promise.resolve();
const tree = MountedAlertMenu(group);
- const toggle = tree.find(".cursor-pointer");
+ const toggle = tree.find("span.cursor-pointer");
+
toggle.simulate("click");
- expect(tree.instance().collapse.value).toBe(false);
+ jest.runOnlyPendingTimers();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
- tree.instance().handleClickOutside();
+ toggle.simulate("click");
+ jest.runOnlyPendingTimers();
+ tree.update();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
+ await act(() => promise);
+ });
- expect(tree.instance().collapse.value).toBe(true);
+ it("clicking menu item hides menu content", async () => {
+ const promise = Promise.resolve();
+ const tree = MountedAlertMenu(group);
+ const toggle = tree.find("span.cursor-pointer");
+
+ toggle.simulate("click");
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
+
+ tree.find("a.dropdown-item").at(0).simulate("click");
+ jest.runOnlyPendingTimers();
+ tree.update();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
await act(() => promise);
});
});
diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
index e85237a11..88c1bfc4d 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js
@@ -1,8 +1,7 @@
-import React, { Component } from "react";
+import React from "react";
import PropTypes from "prop-types";
-import { action, observable } from "mobx";
-import { observer } from "mobx-react";
+import { useObserver, useLocalStore } from "mobx-react";
import copy from "copy-to-clipboard";
@@ -99,88 +98,75 @@ MenuContent.propTypes = {
afterClick: PropTypes.func.isRequired,
};
-const GroupMenu = observer(
- class GroupMenu extends Component {
- static propTypes = {
- group: APIGroup.isRequired,
- alertStore: PropTypes.instanceOf(AlertStore).isRequired,
- silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
- themed: PropTypes.bool.isRequired,
- setIsMenuOpen: PropTypes.func.isRequired,
- };
+const GroupMenu = ({
+ group,
+ alertStore,
+ silenceFormStore,
+ themed,
+ setIsMenuOpen,
+}) => {
+ const collapse = useLocalStore(() => ({
+ isHidden: true,
+ toggle() {
+ this.isHidden = !this.isHidden;
+ setIsMenuOpen(!this.isHidden);
+ },
+ hide() {
+ this.isHidden = true;
+ setIsMenuOpen(!this.isHidden);
+ },
+ }));
- constructor(props) {
- super(props);
-
- this.collapse = observable(
- {
- value: true,
- toggle() {
- this.value = !this.value;
- props.setIsMenuOpen(!this.value);
- },
- hide() {
- this.value = true;
- props.setIsMenuOpen(!this.value);
- },
- },
- { toggle: action.bound, hide: action.bound },
- { name: "Alert group menu toggle" }
- );
- }
-
- handleClickOutside = action((event) => {
- this.collapse.hide();
- });
-
- render() {
- const { group, alertStore, silenceFormStore, themed } = this.props;
-
- return (
-
-
- {({ ref }) => (
-
-
-
- )}
-
-
-
- {({ placement, ref, style }) => (
-
- )}
-
-
-
- );
- }
- }
-);
+ return useObserver(() => (
+
+
+ {({ ref }) => (
+
+
+
+ )}
+
+
+
+ {({ placement, ref, style }) => (
+
+ )}
+
+
+
+ ));
+};
+GroupMenu.propTypes = {
+ group: APIGroup.isRequired,
+ alertStore: PropTypes.instanceOf(AlertStore).isRequired,
+ silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
+ themed: PropTypes.bool.isRequired,
+ setIsMenuOpen: PropTypes.func.isRequired,
+};
export { GroupMenu, MenuContent };
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 abd9bef3e..05d5450da 100644
--- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js
+++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js
@@ -13,10 +13,21 @@ import { GroupMenu, MenuContent } from "./GroupMenu";
let alertStore;
let silenceFormStore;
+let MockAfterClick;
+let MockSetIsMenuOpen;
+
+beforeAll(() => {
+ jest.useFakeTimers();
+});
+
beforeEach(() => {
alertStore = new AlertStore([]);
silenceFormStore = new SilenceFormStore();
+ jest.clearAllMocks();
+ MockAfterClick = jest.fn();
+ MockSetIsMenuOpen = jest.fn();
+
alertStore.data.upstreams = {
clusters: { default: ["am1"] },
instances: [
@@ -36,9 +47,6 @@ beforeEach(() => {
};
});
-const MockAfterClick = jest.fn();
-const MockSetIsMenuOpen = jest.fn();
-
const MountedGroupMenu = (group, themed) => {
return mount(
{
themed={themed}
setIsMenuOpen={MockSetIsMenuOpen}
/>
- ).find("GroupMenu");
+ );
};
describe("", () => {
- it("is collapsed by default", () => {
+ it("menu content is hidden by default", () => {
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
const tree = MountedGroupMenu(group, true);
- expect(tree.instance().collapse.value).toBe(true);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
+ expect(MockSetIsMenuOpen).not.toHaveBeenCalled();
});
- it("clicking toggle sets collapse value to 'false'", async () => {
+ it("clicking toggle renders menu content", async () => {
const promise = Promise.resolve();
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
const tree = MountedGroupMenu(group, true);
- const toggle = tree.find(".cursor-pointer");
+ const toggle = tree.find("span.cursor-pointer");
toggle.simulate("click");
- expect(tree.instance().collapse.value).toBe(false);
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
await act(() => promise);
});
- it("handleClickOutside() call sets collapse value to 'true'", async () => {
+ it("clicking toggle twice hides menu content", async () => {
const promise = Promise.resolve();
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
const tree = MountedGroupMenu(group, true);
- const toggle = tree.find(".cursor-pointer");
+ const toggle = tree.find("span.cursor-pointer");
toggle.simulate("click");
- expect(tree.instance().collapse.value).toBe(false);
+ jest.runOnlyPendingTimers();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
- tree.instance().handleClickOutside();
+ toggle.simulate("click");
+ jest.runOnlyPendingTimers();
+ tree.update();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
+ await act(() => promise);
+ });
- expect(tree.instance().collapse.value).toBe(true);
+ it("clicking menu item hides menu content", async () => {
+ const promise = Promise.resolve();
+ const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
+ const tree = MountedGroupMenu(group, true);
+ const toggle = tree.find("span.cursor-pointer");
+
+ toggle.simulate("click");
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(1);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(1);
+
+ tree.find("div.dropdown-item").at(0).simulate("click");
+ jest.runOnlyPendingTimers();
+ tree.update();
+ expect(MockSetIsMenuOpen).toHaveBeenCalledTimes(2);
+ expect(tree.find("div.dropdown-menu")).toHaveLength(0);
await act(() => promise);
});
});