From e45debed589f5f710d1a7f685f0c5b3b1240b98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Mon, 3 May 2021 19:02:58 +0100 Subject: [PATCH] fix(ui): enable mobx strict mode in tests --- ui/src/App.test.tsx | 2 +- ui/src/Components/AlertAck/index.test.tsx | 107 +++++++++++++----- ui/src/Components/FaviconBadge/index.test.tsx | 2 +- ui/src/Components/Fetcher/index.test.tsx | 2 +- .../AlertGroup/Alert/AlertMenu.test.tsx | 98 ++++++++-------- .../AlertGrid/AlertGroup/Alert/index.test.tsx | 16 +-- .../AlertGroup/GroupFooter/index.test.tsx | 36 +++--- .../AlertGroup/GroupHeader/GroupMenu.test.tsx | 94 +++++++-------- .../AlertGrid/AlertGroup/Silences.test.tsx | 6 +- .../Grid/AlertGrid/AlertGroup/index.test.tsx | 18 ++- .../Grid/AlertGrid/GridLabelSelect.test.tsx | 2 +- .../Components/Grid/AlertGrid/index.test.tsx | 8 +- ui/src/Components/Grid/index.test.tsx | 12 +- .../Labels/FilterInputLabel/index.test.tsx | 10 +- ui/src/Components/Labels/Utils.test.ts | 20 +++- .../AlertGroupCollapseConfiguration.test.tsx | 3 +- .../AlertGroupConfiguration.test.tsx | 2 +- .../AlertGroupSortConfiguration.test.tsx | 2 +- .../AlertGroupWidthConfiguration.test.tsx | 2 +- .../AlertGroupWidthConfiguration.tsx | 2 +- .../MultiGridConfiguration.test.tsx | 6 +- .../MainModal/Configuration/SortLabelName.tsx | 11 +- .../MainModal/MainModalContent.test.tsx | 3 +- ui/src/Components/MainModal/index.stories.tsx | 3 +- .../ManagedSilence/DeleteSilence.test.tsx | 53 +++++---- .../ManagedSilence/SilenceDetails.test.tsx | 44 +++---- .../NavBar/FilterInput/History.test.tsx | 26 +++-- .../Components/NavBar/FilterInput/History.tsx | 34 ++++-- ui/src/Components/NavBar/index.stories.tsx | 4 +- .../OverviewModalContent.test.tsx | 10 +- .../AlertManagerInput/index.test.tsx | 27 +++-- .../SilenceModal/SilenceForm.test.tsx | 48 ++++---- .../Components/SilenceModal/SilenceForm.tsx | 8 +- .../SilenceModal/SilenceModalContent.test.tsx | 69 +++++------ .../SilenceModal/SilencePreview/index.tsx | 2 +- .../SilenceSubmitController.test.tsx | 26 ++--- .../SilenceSubmitProgress.test.tsx | 71 ++++++------ .../SilenceSubmit/SilenceSubmitProgress.tsx | 19 ++-- .../Components/SilenceModal/index.stories.tsx | 5 +- ui/src/Components/SilenceModal/index.test.tsx | 2 +- ui/src/Components/SilenceModal/index.tsx | 2 +- ui/src/Components/Toast/AppToasts.test.tsx | 8 +- .../Components/Toast/ToastMessages.test.tsx | 8 +- ui/src/Components/Toast/ToastMessages.tsx | 4 +- ui/src/Components/Toast/index.stories.tsx | 2 +- ui/src/Stores/AlertStore.test.ts | 14 +-- ui/src/Stores/AlertStore.ts | 69 ++++++++--- ui/src/Stores/Settings.ts | 4 + ui/src/Stores/SilenceFormStore.test.ts | 14 +-- ui/src/Stores/SilenceFormStore.ts | 34 +++++- ui/src/__fixtures__/Stories.ts | 23 ++-- ui/src/setupTests.ts | 9 ++ 52 files changed, 673 insertions(+), 433 deletions(-) diff --git a/ui/src/App.test.tsx b/ui/src/App.test.tsx index a6b2607eb..ec53451ba 100644 --- a/ui/src/App.test.tsx +++ b/ui/src/App.test.tsx @@ -151,7 +151,7 @@ describe("", () => { m2.values = [StringToOption("foo"), StringToOption("baz")]; const store = new SilenceFormStore(); store.data.setMatchers([m1, m2]); - store.data.comment = "base64"; + store.data.setComment("base64"); const m = store.data.toBase64; global.window.location = { diff --git a/ui/src/Components/AlertAck/index.test.tsx b/ui/src/Components/AlertAck/index.test.tsx index c4438d894..e67c8ddbb 100644 --- a/ui/src/Components/AlertAck/index.test.tsx +++ b/ui/src/Components/AlertAck/index.test.tsx @@ -26,12 +26,18 @@ beforeEach(() => { alertStore = new AlertStore([]); silenceFormStore = new SilenceFormStore(); - alertStore.settings.values.alertAcknowledgement = { - enabled: true, - durationSeconds: 123, - author: "default author", - comment: "COMMENT", - }; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 123, + author: "default author", + comment: "COMMENT", + }, + }, + }); + alertStore.data.setUpstreams({ counters: { total: 1, healthy: 1, failed: 0 }, clusters: { default: ["default"] }, @@ -100,7 +106,17 @@ const MountAndClick = async () => { describe("", () => { it("is null when acks are disabled", () => { - alertStore.settings.values.alertAcknowledgement.enabled = false; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: false, + durationSeconds: 123, + author: "default author", + comment: "COMMENT", + }, + }, + }); const tree = MountedAlertAck(); expect(tree.html()).toBe(""); }); @@ -295,9 +311,17 @@ describe("", () => { }); it("uses settings when generating payload", async () => { - alertStore.settings.values.alertAcknowledgement.durationSeconds = 237; - alertStore.settings.values.alertAcknowledgement.author = "me"; - alertStore.settings.values.alertAcknowledgement.comment = "comment"; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 237, + author: "me", + comment: "comment", + }, + }, + }); await MountAndClick(); expect(JSON.parse((fetchMock.lastOptions() as any).body)).toEqual({ comment: "comment", @@ -312,10 +336,17 @@ describe("", () => { }); it("injects timestamp when configured", async () => { - alertStore.settings.values.alertAcknowledgement.durationSeconds = 237; - alertStore.settings.values.alertAcknowledgement.author = "me"; - alertStore.settings.values.alertAcknowledgement.comment = - "ACK! This alert was acknowledged using karma on %NOW%"; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 237, + author: "me", + comment: "ACK! This alert was acknowledged using karma on %NOW%", + }, + }, + }); await MountAndClick(); expect(JSON.parse((fetchMock.lastOptions() as any).body)).toEqual({ comment: @@ -331,11 +362,18 @@ describe("", () => { }); it("uses author from authentication info when auth is enabled", async () => { - alertStore.info.authentication.enabled = true; - alertStore.info.authentication.username = "auth@example.com"; - alertStore.settings.values.alertAcknowledgement.durationSeconds = 222; - alertStore.settings.values.alertAcknowledgement.author = "me"; - alertStore.settings.values.alertAcknowledgement.comment = "FOO: bar"; + alertStore.info.setAuthentication(true, "auth@example.com"); + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 222, + author: "me", + comment: "FOO: bar", + }, + }, + }); await MountAndClick(); expect(JSON.parse((fetchMock.lastOptions() as any).body)).toEqual({ comment: "FOO: bar", @@ -350,11 +388,18 @@ describe("", () => { }); it("uses author from silenceFormStore if authentication is disabled", async () => { - alertStore.info.authentication.enabled = false; - alertStore.info.authentication.username = "wrong"; - alertStore.settings.values.alertAcknowledgement.durationSeconds = 222; - alertStore.settings.values.alertAcknowledgement.author = "me"; - alertStore.settings.values.alertAcknowledgement.comment = "FOO: bar"; + alertStore.info.setAuthentication(false, "wrong"); + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 222, + author: "me", + comment: "FOO: bar", + }, + }, + }); silenceFormStore.data.setAuthor("bob@example.com"); await MountAndClick(); expect(JSON.parse((fetchMock.lastOptions() as any).body)).toEqual({ @@ -370,9 +415,17 @@ describe("", () => { }); it("uses default author as fallback", async () => { - alertStore.settings.values.alertAcknowledgement.durationSeconds = 222; - alertStore.settings.values.alertAcknowledgement.author = "me"; - alertStore.settings.values.alertAcknowledgement.comment = "FOO: bar"; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ + alertAcknowledgement: { + enabled: true, + durationSeconds: 222, + author: "me", + comment: "FOO: bar", + }, + }, + }); silenceFormStore.data.setAuthor(""); await MountAndClick(); expect(JSON.parse((fetchMock.lastOptions() as any).body)).toEqual({ diff --git a/ui/src/Components/FaviconBadge/index.test.tsx b/ui/src/Components/FaviconBadge/index.test.tsx index fdbec041c..e417ad8bd 100644 --- a/ui/src/Components/FaviconBadge/index.test.tsx +++ b/ui/src/Components/FaviconBadge/index.test.tsx @@ -36,7 +36,7 @@ describe("", () => { }); it("badge is updated when alertStore.status.error changes", () => { - alertStore.status.error = "foo"; + alertStore.status.setError("foo"); MountedFaviconBadge(); expect(Favico.badge).toHaveBeenCalledTimes(1); expect(Favico.badge).toHaveBeenCalledWith("?"); diff --git a/ui/src/Components/Fetcher/index.test.tsx b/ui/src/Components/Fetcher/index.test.tsx index f51c01de6..8b2fe1152 100644 --- a/ui/src/Components/Fetcher/index.test.tsx +++ b/ui/src/Components/Fetcher/index.test.tsx @@ -321,7 +321,7 @@ describe(" children", () => { it("doesn't render any children when upgrade is needed", () => { act(() => { - alertStore.info.upgradeNeeded = true; + alertStore.info.setUpgradeNeeded(true); }); const tree = mount( diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx index 7bb5a12d9..e708f0af9 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.tsx @@ -3,7 +3,11 @@ import { act } from "react-dom/test-utils"; import { mount } from "enzyme"; import { MockAlertGroup, MockAlert } from "__fixtures__/Alerts"; -import { APIAlertGroupT, APIAlertT } from "Models/APITypes"; +import { + APIAlertGroupT, + APIAlertT, + APIAlertsResponseUpstreamsT, +} from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { AlertMenu, MenuContent } from "./AlertMenu"; @@ -16,6 +20,49 @@ let group: APIAlertGroupT; let MockAfterClick: () => void; let MockSetIsMenuOpen: () => void; +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { total: 1, healthy: 1, failed: 0 }, + clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, + instances: [ + { + name: "am1", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "default", + clusterMembers: ["am1"], + }, + { + name: "ro", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: true, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "ro", + clusterMembers: ["ro"], + }, + { + name: "am2", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "am2", + clusterMembers: ["am2"], + }, + ], +}); + beforeEach(() => { jest.useFakeTimers(); @@ -28,48 +75,7 @@ beforeEach(() => { alert = MockAlert([], { foo: "bar" }, "active"); group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {}); - alertStore.data.setUpstreams({ - counters: { total: 1, healthy: 1, failed: 0 }, - clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, - instances: [ - { - name: "am1", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: false, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "default", - clusterMembers: ["am1"], - }, - { - name: "ro", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: true, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "ro", - clusterMembers: ["ro"], - }, - { - name: "am2", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: false, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "am2", - clusterMembers: ["am2"], - }, - ], - }); + alertStore.data.setUpstreams(generateUpstreams()); }); const MountedAlertMenu = (group: APIAlertGroupT) => { @@ -171,8 +177,10 @@ describe("", () => { }); it("'Silence' menu entry is disabled when all Alertmanager instances are read-only", () => { - alertStore.data.upstreams.instances[0].readonly = true; - alertStore.data.upstreams.instances[2].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + upstreams.instances[2].readonly = true; + alertStore.data.setUpstreams(upstreams); const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(1); expect(button.hasClass("disabled")).toBe(true); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.tsx index d2dd9a913..909a5697e 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.tsx @@ -173,11 +173,11 @@ describe("", () => { it("renders a silence if alert is silenced", () => { const alert = MockedAlert(); alert.alertmanager[0].silencedBy = ["silence123456789"]; - alertStore.data.silences = { + alertStore.data.setSilences({ default: { silence123456789: MockSilence(), }, - }; + }); const group = MockAlertGroup({}, [alert], [], {}, { default: [] }); const tree = MountedAlert(alert, group, false, false, false); const silence = tree.find("ManagedSilence"); @@ -188,11 +188,11 @@ describe("", () => { it("renders a fallback silence if the silence is not found in alertStore", () => { const alert = MockedAlert(); alert.alertmanager[0].silencedBy = ["silence123456789"]; - alertStore.data.silences = { + alertStore.data.setSilences({ default: { "123": MockSilence(), }, - }; + }); const group = MockAlertGroup({}, [alert], [], {}, { default: [] }); const tree = MountedAlert(alert, group, false, false, false); const silence = tree.find("FallbackSilenceDesciption"); @@ -203,11 +203,11 @@ describe("", () => { it("renders a fallback silence if the cluster is not found in alertStore", () => { const alert = MockedAlert(); alert.alertmanager[0].silencedBy = ["silence123456789"]; - alertStore.data.silences = { + alertStore.data.setSilences({ foo: { "123": MockSilence(), }, - }; + }); const group = MockAlertGroup({}, [alert], [], {}, { default: [] }); const tree = MountedAlert(alert, group, false, false, false); const silence = tree.find("FallbackSilenceDesciption"); @@ -239,11 +239,11 @@ describe("", () => { inhibitedBy: [], }, ]; - alertStore.data.silences = { + alertStore.data.setSilences({ ha: { silence123456789: MockSilence(), }, - }; + }); const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false, false); const silence = tree.find("ManagedSilence"); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.tsx index 7bd2ea512..915b76201 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.tsx @@ -54,7 +54,7 @@ beforeEach(() => { group = MockGroup(); advanceTo(new Date(Date.UTC(2000, 0, 1, 15, 0, 0))); - alertStore.data.receivers = ["by-cluster-service", "by-name"]; + alertStore.data.setReceivers(["by-cluster-service", "by-name"]); }); afterEach(() => { @@ -90,11 +90,11 @@ describe("", () => { group.alerts[index].alertmanager[0].silencedBy = ["123456789"]; } group.shared.silences = { default: ["123456789"] }; - alertStore.data.silences = { + alertStore.data.setSilences({ default: { "123456789": MockSilence(), }, - }; + }); const tree = MountedGroupFooter().find("GroupFooter"); expect(tree.find("ManagedSilence")).toHaveLength(1); @@ -105,9 +105,9 @@ describe("", () => { group.alerts[index].alertmanager[0].silencedBy = ["123456789"]; } group.shared.silences = { default: ["123456789"] }; - alertStore.data.silences = { + alertStore.data.setSilences({ default: {}, - }; + }); const tree = MountedGroupFooter().find("GroupFooter"); expect(tree.find("FallbackSilenceDesciption")).toHaveLength(1); @@ -118,9 +118,9 @@ describe("", () => { group.alerts[index].alertmanager[0].silencedBy = ["123456789"]; } group.shared.silences = { default: ["123456789"] }; - alertStore.data.silences = { + alertStore.data.setSilences({ foo: {}, - }; + }); const tree = MountedGroupFooter().find("GroupFooter"); expect(tree.find("FallbackSilenceDesciption")).toHaveLength(1); @@ -132,25 +132,26 @@ describe("", () => { } group.shared.silences = { default: ["123456789"] }; - alertStore.data.silences = { + const silence = MockSilence(); + silence.id = "123456789"; + alertStore.data.setSilences({ default: { - "123456789": MockSilence(), + "123456789": silence, }, - }; - alertStore.data.silences["default"]["123456789"].id = "123456789"; + }); const tree = MountedGroupFooter().find("GroupFooter"); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("renders @receiver label when alertStore.data.receivers.length > 1", () => { - alertStore.data.receivers = ["foo", "bar"]; + alertStore.data.setReceivers(["foo", "bar"]); const tree = MountedGroupFooter(); expect(toDiffableHtml(tree.html())).toMatch(/@receiver:/); }); it("doesn't render @receiver label when alertStore.data.receivers.length == 0", () => { - alertStore.data.receivers = []; + alertStore.data.setReceivers([]); const tree = MountedGroupFooter(); expect(toDiffableHtml(tree.html())).not.toMatch(/@receiver:/); }); @@ -161,12 +162,13 @@ describe("", () => { } group.shared.silences = { default: ["123456789"] }; - alertStore.data.silences = { + const silence = MockSilence(); + silence.id = "123456789"; + alertStore.data.setSilences({ default: { - "123456789": MockSilence(), + "123456789": silence, }, - }; - alertStore.data.silences["default"]["123456789"].id = "123456789"; + }); const tree = mount( void; let MockSetIsMenuOpen: () => void; +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { total: 3, healthy: 3, failed: 0 }, + clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, + instances: [ + { + name: "am1", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "default", + clusterMembers: ["am1"], + }, + { + name: "ro", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: true, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "ro", + clusterMembers: ["ro"], + }, + { + name: "am2", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "am2", + clusterMembers: ["am2"], + }, + ], +}); + beforeEach(() => { alertStore = new AlertStore([]); silenceFormStore = new SilenceFormStore(); @@ -25,48 +68,7 @@ beforeEach(() => { MockAfterClick = jest.fn(); MockSetIsMenuOpen = jest.fn(); - alertStore.data.setUpstreams({ - counters: { total: 3, healthy: 3, failed: 0 }, - clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, - instances: [ - { - name: "am1", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: false, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "default", - clusterMembers: ["am1"], - }, - { - name: "ro", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: true, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "ro", - clusterMembers: ["ro"], - }, - { - name: "am2", - uri: "http://localhost:8080", - publicURI: "http://example.com", - readonly: false, - headers: {}, - corsCredentials: "include", - error: "", - version: "0.17.0", - cluster: "am2", - clusterMembers: ["am2"], - }, - ], - }); + alertStore.data.setUpstreams(generateUpstreams()); }); const MountedGroupMenu = (group: APIAlertGroupT, themed: boolean) => { @@ -180,8 +182,10 @@ describe("", () => { }); it("'Silence' menu entry is disabled when all Alertmanager instances are read-only", () => { - alertStore.data.upstreams.instances[0].readonly = true; - alertStore.data.upstreams.instances[2].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + upstreams.instances[2].readonly = true; + alertStore.data.setUpstreams(upstreams); const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedMenuContent(group); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.test.tsx index 6de197506..e70603a33 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silences.test.tsx @@ -33,7 +33,7 @@ describe("", () => { it("renders ManagedSilence if silence is present in AlertStore", () => { const silence = MockSilence(); - alertStore.data.silences = { fakeCluster: { [silence.id]: silence } }; + alertStore.data.setSilences({ fakeCluster: { [silence.id]: silence } }); const tree = mount( ", () => { it("re-render when silence was removed AlertStore is a no-op", () => { const silence = MockSilence(); - alertStore.data.silences = { fakeCluster: { [silence.id]: silence } }; + alertStore.data.setSilences({ fakeCluster: { [silence.id]: silence } }); const tree = mount( ", () => { expect(tree.find("ManagedSilence")).toHaveLength(1); const snapshot = toDiffableHtml(tree.html()); - alertStore.data.silences = {}; + alertStore.data.setSilences({}); tree.setProps({}); expect(tree.find("ManagedSilence")).toHaveLength(1); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.tsx b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.tsx index f86cfbaa3..dcbe33950 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.tsx @@ -42,7 +42,7 @@ beforeEach(() => { silenceFormStore = new SilenceFormStore(); group = MockGroup("fakeGroup"); - alertStore.data.receivers = ["by-cluster-service", "by-name"]; + alertStore.data.setReceivers(["by-cluster-service", "by-name"]); }); afterEach(() => { @@ -91,7 +91,7 @@ const ValidateCollapse = ( ) => { global.innerWidth = innerWidth; - settingsStore.alertGroupConfig.config.defaultCollapseState = defaultCollapseState; + settingsStore.alertGroupConfig.setDefaultCollapseState(defaultCollapseState); MockAlerts(3); const tree = MountedAlertGroup(jest.fn(), false); @@ -242,14 +242,14 @@ describe("", () => { }); it("renders @receiver label when alertStore.data.receivers.length > 1", () => { - alertStore.data.receivers = ["foo", "bar"]; + alertStore.data.setReceivers(["foo", "bar"]); MockAlerts(10); const tree = MountedAlertGroup(jest.fn(), false); expect(tree.html()).toMatch(/@receiver:/); }); it("doesn't render @receiver label when alertStore.data.receivers.length == 0", () => { - alertStore.data.receivers = []; + alertStore.data.setReceivers([]); MockAlerts(10); const tree = MountedAlertGroup(jest.fn(), false); expect(tree.html()).not.toMatch(/@receiver:/); @@ -426,14 +426,20 @@ describe(" card theme", () => { }); it("renders AlertHistory when enabled", () => { - alertStore.settings.values.historyEnabled = true; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ historyEnabled: true }, + }); group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 }; const tree = MountedAlertGroup(jest.fn(), false); expect(tree.find("AlertHistory")).toHaveLength(1); }); it("doesn't render AlertHistory when disabled", () => { - alertStore.settings.values.historyEnabled = false; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ historyEnabled: false }, + }); group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 }; const tree = MountedAlertGroup(jest.fn(), false); expect(tree.find("AlertHistory")).toHaveLength(0); diff --git a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.test.tsx b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.test.tsx index 504e71370..08266c39f 100644 --- a/ui/src/Components/Grid/AlertGrid/GridLabelSelect.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/GridLabelSelect.test.tsx @@ -73,7 +73,7 @@ describe("", () => { toggle.simulate("click"); expect(tree.find("div.components-grid-label-select-menu")).toHaveLength(1); - settingsStore.multiGridConfig.config.gridLabel = "foo"; + settingsStore.multiGridConfig.setGridLabel("foo"); const options = tree.find("div.react-select__option"); options.at(5).simulate("click"); expect(settingsStore.multiGridConfig.config.gridLabel).toBe("cluster"); diff --git a/ui/src/Components/Grid/AlertGrid/index.test.tsx b/ui/src/Components/Grid/AlertGrid/index.test.tsx index 211c63637..38ee39ab3 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.tsx +++ b/ui/src/Components/Grid/AlertGrid/index.test.tsx @@ -437,7 +437,7 @@ describe("", () => { { canvas: 4000, columns: 10 }, ].map((t) => it(`renders ${t.columns} column(s) on ${t.canvas} breakpoint`, () => { - settingsStore.gridConfig.config.groupWidth = 400; + settingsStore.gridConfig.setGroupWidth(400); VerifyColumnCount(t.canvas - 1, t.canvas - 1, Math.max(1, t.columns - 1)); VerifyColumnCount(t.canvas, t.canvas, t.columns); VerifyColumnCount(t.canvas + 1, t.canvas + 1, t.columns); @@ -459,7 +459,7 @@ describe("", () => { { canvas: 3840, columns: 9 }, ].map((t) => it(`renders ${t.columns} column(s) with ${t.canvas} resolution`, () => { - settingsStore.gridConfig.config.groupWidth = 400; + settingsStore.gridConfig.setGroupWidth(400); VerifyColumnCount(t.canvas, t.canvas, t.columns); }) ); @@ -522,7 +522,7 @@ describe("", () => { }); it("scrollbar render doesn't resize alert groups", () => { - settingsStore.gridConfig.config.groupWidth = 400; + settingsStore.gridConfig.setGroupWidth(400); MockGroupList(20, 1); // set initial width @@ -551,7 +551,7 @@ describe("", () => { }); it("viewport resize doesn't allow loops", () => { - settingsStore.gridConfig.config.groupWidth = 400; + settingsStore.gridConfig.setGroupWidth(400); MockGroupList(10, 1); diff --git a/ui/src/Components/Grid/index.test.tsx b/ui/src/Components/Grid/index.test.tsx index 90c0addd4..b116929a6 100644 --- a/ui/src/Components/Grid/index.test.tsx +++ b/ui/src/Components/Grid/index.test.tsx @@ -86,7 +86,7 @@ describe("", () => { }); it("renders only FatalError on failed fetch", () => { - alertStore.status.error = "error"; + alertStore.status.setError("error"); alertStore.data.setUpstreams({ counters: { total: 0, healthy: 0, failed: 1 }, instances: [ @@ -110,33 +110,33 @@ describe("", () => { }); it("renders UpgradeNeeded when alertStore.info.upgradeNeeded=true", () => { - alertStore.info.upgradeNeeded = true; + alertStore.info.setUpgradeNeeded(true); const tree = ShallowGrid(); expect(tree.text()).toBe(""); }); it("renders ReloadNeeded when alertStore.info.reloadNeeded=true", () => { - alertStore.info.reloadNeeded = true; + alertStore.info.setReloadNeeded(true); const tree = ShallowGrid(); expect(tree.text()).toBe(""); }); it("renders AlertGrid before any fetch finished when totalAlerts is 0", () => { - alertStore.info.version = "unknown"; + alertStore.info.setVersion("unknown"); alertStore.info.setTotalAlerts(0); const tree = ShallowGrid(); expect(tree.find("AlertGrid")).toHaveLength(1); }); it("renders EmptyGrid after first fetch when totalAlerts is 0", () => { - alertStore.info.version = "1.2.3"; + alertStore.info.setVersion("1.2.3"); alertStore.info.setTotalAlerts(0); const tree = ShallowGrid(); expect(tree.text()).toBe(""); }); it("renders AlertGrid after first fetch finished when totalAlerts is >0", () => { - alertStore.info.version = "unknown"; + alertStore.info.setVersion("unknown"); alertStore.info.setTotalAlerts(1); const tree = ShallowGrid(); expect(tree.find("AlertGrid")).toHaveLength(1); diff --git a/ui/src/Components/Labels/FilterInputLabel/index.test.tsx b/ui/src/Components/Labels/FilterInputLabel/index.test.tsx index c68f0783b..4445a393b 100644 --- a/ui/src/Components/Labels/FilterInputLabel/index.test.tsx +++ b/ui/src/Components/Labels/FilterInputLabel/index.test.tsx @@ -84,12 +84,18 @@ describe(" className", () => { }); it("applied filter included in staticColorLabels with '=' matcher should use 'btn-info' class", () => { - alertStore.settings.values.staticColorLabels = ["foo"]; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: ["foo"] }, + }); ValidateClass("=", true, "btn-info"); }); it("applied filter included in staticColorLabels with any matcher other than '=' should use 'btn-default' class", () => { - alertStore.settings.values.staticColorLabels = ["foo"]; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: ["foo"] }, + }); for (const matcher of NonEqualMatchers) { ValidateClass(matcher, true, "btn-default"); } diff --git a/ui/src/Components/Labels/Utils.test.ts b/ui/src/Components/Labels/Utils.test.ts index c1092b3c3..88be232ce 100644 --- a/ui/src/Components/Labels/Utils.test.ts +++ b/ui/src/Components/Labels/Utils.test.ts @@ -15,14 +15,20 @@ beforeEach(() => { describe("", () => { it("static label uses StaticColorLabelClassMap.badge", () => { - alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: ["foo", "job", "bar"] }, + }); const cs = GetClassAndStyle(alertStore, "foo", "bar"); expect(cs.colorClassNames).toContain(StaticColorLabelClassMap.badge); }); Object.entries(StaticColorLabelClassMap).map(([key, _]) => it(`non-static label doesn't use StaticColorLabelClassMap.${key}`, () => { - alertStore.settings.values.staticColorLabels = []; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: [] }, + }); const cs = GetClassAndStyle(alertStore, "foo", "bar"); expect(cs.colorClassNames).not.toContain(StaticColorLabelClassMap.badge); }) @@ -63,13 +69,19 @@ describe("", () => { }); it("style prop on a label included in staticColorLabels should be empty", () => { - alertStore.settings.values.staticColorLabels = ["foo", "job", "bar"]; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: ["foo", "job", "bar"] }, + }); const cs = GetClassAndStyle(alertStore, "foo", "bar"); expect(cs.style).toEqual({}); }); it("style prop on a label without any color information should be empty", () => { - alertStore.settings.values.staticColorLabels = []; + alertStore.settings.setValues({ + ...alertStore.settings.values, + ...{ staticColorLabels: [] }, + }); const cs = GetClassAndStyle(alertStore, "foo", "bar"); expect(cs.style).toEqual({}); }); diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.test.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.test.tsx index 2fb3ed078..e4c626802 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.test.tsx +++ b/ui/src/Components/MainModal/Configuration/AlertGroupCollapseConfiguration.test.tsx @@ -30,8 +30,7 @@ describe("", () => { }); it("resets stored config to defaults if it is invalid", (done) => { - (settingsStore.alertGroupConfig.config - .defaultCollapseState as string) = "foo"; + settingsStore.alertGroupConfig.setDefaultCollapseState("foo" as any); const tree = FakeConfiguration(); const select = tree.find("div.react-select__value-container"); expect(select.text()).toBe( diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupConfiguration.test.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupConfiguration.test.tsx index 331e8cfb7..8410c172a 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupConfiguration.test.tsx +++ b/ui/src/Components/MainModal/Configuration/AlertGroupConfiguration.test.tsx @@ -41,7 +41,7 @@ describe("", () => { }); it("custom interval value is rendered correctly", () => { - settingsStore.alertGroupConfig.config.defaultRenderCount = 4; + settingsStore.alertGroupConfig.setDefaultRenderCount(4); const component = FakeConfiguration(); expect(component.find("Range").props().values).toContain(4); }); diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.tsx index 88667b798..67602ff01 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.tsx +++ b/ui/src/Components/MainModal/Configuration/AlertGroupSortConfiguration.test.tsx @@ -43,7 +43,7 @@ describe("", () => { }); it("invalid sortOrder value is reset on mount", () => { - (settingsStore.gridConfig.config.sortOrder as string) = "badValue"; + settingsStore.gridConfig.setSortOrder("badValue" as any); FakeConfiguration(); expect(settingsStore.gridConfig.config.sortOrder).toBe( settingsStore.gridConfig.options.default.value diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.test.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.test.tsx index d1c9ab5ac..ad51d275b 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.test.tsx +++ b/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.test.tsx @@ -41,7 +41,7 @@ describe("", () => { }); it("custom interval value is rendered correctly", () => { - settingsStore.gridConfig.config.groupWidth = 460; + settingsStore.gridConfig.setGroupWidth(460); const component = FakeConfiguration(); expect(component.find("Range").props().values).toContain(460); }); diff --git a/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.tsx b/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.tsx index 7e71685f3..bdbea4847 100644 --- a/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.tsx +++ b/ui/src/Components/MainModal/Configuration/AlertGroupWidthConfiguration.tsx @@ -14,7 +14,7 @@ const AlertGroupWidthConfiguration: FC<{ ]); const onChangeComplete = debounce((value: number) => { - settingsStore.gridConfig.config.groupWidth = value as number; + settingsStore.gridConfig.setGroupWidth(value); }, 200); return ( diff --git a/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.tsx b/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.tsx index 406edb847..0bbac2aaa 100644 --- a/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.tsx +++ b/ui/src/Components/MainModal/Configuration/MultiGridConfiguration.test.tsx @@ -49,19 +49,19 @@ describe("", () => { }); it("correctly renders default option when multi-grid is disabled", () => { - settingsStore.multiGridConfig.config.gridLabel = ""; + settingsStore.multiGridConfig.setGridLabel(""); const tree = FakeConfiguration(); expect(tree.find("Creatable").text()).toBe("Disable multi-grid"); }); it("correctly renders default option when multi-grid is set to @auto", () => { - settingsStore.multiGridConfig.config.gridLabel = "@auto"; + settingsStore.multiGridConfig.setGridLabel("@auto"); const tree = FakeConfiguration(); expect(tree.find("Creatable").text()).toBe("Automatic selection"); }); it("correctly renders default option when multi-grid is enabled", () => { - settingsStore.multiGridConfig.config.gridLabel = "cluster"; + settingsStore.multiGridConfig.setGridLabel("cluster"); const tree = FakeConfiguration(); expect(tree.find("Creatable").text()).toBe("cluster"); }); diff --git a/ui/src/Components/MainModal/Configuration/SortLabelName.tsx b/ui/src/Components/MainModal/Configuration/SortLabelName.tsx index c1fc5a560..6057d1495 100644 --- a/ui/src/Components/MainModal/Configuration/SortLabelName.tsx +++ b/ui/src/Components/MainModal/Configuration/SortLabelName.tsx @@ -19,7 +19,7 @@ const SortLabelName: FC<{ ); if (!settingsStore.gridConfig.config.sortLabel) { - settingsStore.gridConfig.config.sortLabel = StaticLabels.AlertName; + settingsStore.gridConfig.setSortLabel(StaticLabels.AlertName); } const context = React.useContext(ThemeContext); @@ -30,13 +30,16 @@ const SortLabelName: FC<{ classNamePrefix="react-select" instanceId="configuration-sort-label" formatCreateLabel={NewLabelName} - defaultValue={StringToOption(settingsStore.gridConfig.config.sortLabel)} + defaultValue={StringToOption( + settingsStore.gridConfig.config.sortLabel as string + )} options={ response ? response.map((value: string) => StringToOption(value)) : [] } onChange={(option) => { - settingsStore.gridConfig.config.sortLabel = (option as OptionT) - .value as string; + settingsStore.gridConfig.setSortLabel( + (option as OptionT).value as string + ); }} components={{ Menu: AnimatedMenu }} /> diff --git a/ui/src/Components/MainModal/MainModalContent.test.tsx b/ui/src/Components/MainModal/MainModalContent.test.tsx index e8eb9edf4..eeae7a2b2 100644 --- a/ui/src/Components/MainModal/MainModalContent.test.tsx +++ b/ui/src/Components/MainModal/MainModalContent.test.tsx @@ -93,8 +93,7 @@ describe("", () => { }); it("shows username when alertStore.info.authentication.enabled=true", () => { - alertStore.info.authentication.enabled = true; - alertStore.info.authentication.username = "me@example.com"; + alertStore.info.setAuthentication(true, "me@example.com"); const tree = mount( {Wrapped( diff --git a/ui/src/Components/MainModal/index.stories.tsx b/ui/src/Components/MainModal/index.stories.tsx index 517065dd8..4328a5f68 100644 --- a/ui/src/Components/MainModal/index.stories.tsx +++ b/ui/src/Components/MainModal/index.stories.tsx @@ -34,8 +34,7 @@ storiesOf("MainModal", module) } ); - alertStore.info.authentication.enabled = true; - alertStore.info.authentication.username = "me@example.com"; + alertStore.info.setAuthentication(true, "me@example.com"); return ( ({ + counters: { total: 1, healthy: 1, failed: 0 }, + instances: [ + { + name: "am1", + cluster: "am", + uri: "http://localhost:9093", + publicURI: "http://localhost:9093", + readonly: false, + error: "", + version: "0.17.0", + headers: {}, + corsCredentials: "include", + clusterMembers: ["am1"], + }, + ], + clusters: { am: ["am1"] }, +}); + beforeEach(() => { advanceTo(new Date(Date.UTC(2000, 0, 1, 0, 30, 0))); jest.useFakeTimers(); @@ -28,24 +47,7 @@ beforeEach(() => { cluster = "am"; silence = MockSilence(); - alertStore.data.setUpstreams({ - counters: { total: 1, healthy: 1, failed: 0 }, - instances: [ - { - name: "am1", - cluster: "am", - uri: "http://localhost:9093", - publicURI: "http://localhost:9093", - readonly: false, - error: "", - version: "0.17.0", - headers: {}, - corsCredentials: "include", - clusterMembers: ["am1"], - }, - ], - clusters: { am: ["am1"] }, - }); + alertStore.data.setUpstreams(generateUpstreams()); }); afterEach(() => { @@ -137,7 +139,10 @@ describe("", () => { }); it("button is disabled when all alertmanager instances are read-only", () => { - alertStore.data.upstreams.instances[0].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + alertStore.data.setUpstreams(upstreams); + const tree = mount( ", () => { typeof useFetchDelete >).mockReturnValue({ response: "success", error: null, isDeleting: false }); - alertStore.data.upstreams.instances[0].headers = { + const upstreams = generateUpstreams(); + upstreams.instances[0].headers = { Authorization: "Basic ***", }; + alertStore.data.setUpstreams(upstreams); const tree = MountedDeleteSilenceModalContent(); tree.find(".btn-danger").simulate("click"); @@ -224,7 +231,9 @@ describe("", () => { typeof useFetchDelete >).mockReturnValue({ response: "success", error: null, isDeleting: false }); - alertStore.data.upstreams.instances[0].corsCredentials = "omit"; + const upstreams = generateUpstreams(); + upstreams.instances[0].corsCredentials = "omit"; + alertStore.data.setUpstreams(upstreams); const tree = MountedDeleteSilenceModalContent(); tree.find(".btn-danger").simulate("click"); diff --git a/ui/src/Components/ManagedSilence/SilenceDetails.test.tsx b/ui/src/Components/ManagedSilence/SilenceDetails.test.tsx index 6877eccd1..00f6be2b6 100644 --- a/ui/src/Components/ManagedSilence/SilenceDetails.test.tsx +++ b/ui/src/Components/ManagedSilence/SilenceDetails.test.tsx @@ -7,7 +7,7 @@ import copy from "copy-to-clipboard"; import { advanceTo, clear } from "jest-date-mock"; import { MockSilence } from "__fixtures__/Alerts"; -import { APISilenceT } from "Models/APITypes"; +import { APIAlertsResponseUpstreamsT, APISilenceT } from "Models/APITypes"; import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceDetails } from "./SilenceDetails"; @@ -19,30 +19,32 @@ let silence: APISilenceT; const MockEditSilence = jest.fn(); +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { total: 1, healthy: 1, failed: 0 }, + instances: [ + { + name: "am1", + cluster: "am", + clusterMembers: ["am"], + uri: "http://localhost:9093", + publicURI: "http://example.com", + readonly: false, + error: "", + version: "0.17.0", + headers: {}, + corsCredentials: "include", + }, + ], + clusters: { am: ["am1"] }, +}); + beforeEach(() => { alertStore = new AlertStore([]); silenceFormStore = new SilenceFormStore(); cluster = "am"; silence = MockSilence(); - alertStore.data.setUpstreams({ - counters: { total: 1, healthy: 1, failed: 0 }, - instances: [ - { - name: "am1", - cluster: "am", - clusterMembers: ["am"], - uri: "http://localhost:9093", - publicURI: "http://example.com", - readonly: false, - error: "", - version: "0.17.0", - headers: {}, - corsCredentials: "include", - }, - ], - clusters: { am: ["am1"] }, - }); + alertStore.data.setUpstreams(generateUpstreams()); jest.restoreAllMocks(); }); @@ -97,7 +99,9 @@ describe("", () => { }); it("Edit silence button is disabled when all alertmanager instances are read-only", () => { - alertStore.data.upstreams.instances[0].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + alertStore.data.setUpstreams(upstreams); const tree = MountedSilenceDetails(); expect(tree.find("button").prop("disabled")).toBe(true); diff --git a/ui/src/Components/NavBar/FilterInput/History.test.tsx b/ui/src/Components/NavBar/FilterInput/History.test.tsx index 568393726..e6c3ed516 100644 --- a/ui/src/Components/NavBar/FilterInput/History.test.tsx +++ b/ui/src/Components/NavBar/FilterInput/History.test.tsx @@ -138,7 +138,9 @@ describe("", () => { it("renders correctly when rendered with a filter in history", async () => { const promise = Promise.resolve(); const tree = MountedHistory(); - PopulateHistory(tree, 1); + act(() => { + PopulateHistory(tree, 1); + }); tree.find("button.cursor-pointer").simulate("click"); expect(tree.text()).toBe( @@ -155,7 +157,9 @@ describe("", () => { it("clicking on a filter set in history populates alertStore", async () => { const promise = Promise.resolve(); const tree = MountedHistory(); - PopulateHistory(tree, 1); + act(() => { + PopulateHistory(tree, 1); + }); tree.find("button.cursor-pointer").simulate("click"); const button = tree.find("button.dropdown-item").at(0); @@ -179,7 +183,9 @@ describe("", () => { const promise = Promise.resolve(); const tree = MountedHistory(); - PopulateHistory(tree, 16); + act(() => { + PopulateHistory(tree, 16); + }); tree.find("button.cursor-pointer").simulate("click"); expect(tree.find("button.dropdown-item")).toHaveLength(8); @@ -205,7 +211,9 @@ describe("", () => { const promise = Promise.resolve(); const tree = MountedHistory(); - PopulateHistory(tree, 16); + act(() => { + PopulateHistory(tree, 16); + }); tree.find("button.cursor-pointer").simulate("click"); expect(tree.find("button.dropdown-item")).toHaveLength(4); @@ -247,7 +255,7 @@ describe("", () => { }); it("clicking on 'Reset filters' clears current filter set in Settings", () => { - settingsStore.savedFilters.config.filters = ["foo=bar"]; + settingsStore.savedFilters.save(["foo=bar"]); const tree = MountedHistory(); tree.find("button.cursor-pointer").simulate("click"); @@ -260,9 +268,12 @@ describe("", () => { expect(settingsStore.savedFilters.config.filters).toHaveLength(0); }); - it("clicking on 'Clear history' clears the history", () => { + it("clicking on 'Clear history' clears the history", async () => { + const promise = Promise.resolve(); const tree = MountedHistory(); - PopulateHistory(tree, 5); + act(() => { + PopulateHistory(tree, 5); + }); tree.find("button.cursor-pointer").simulate("click"); expect(tree.find("button.dropdown-item")).toHaveLength(5); @@ -274,5 +285,6 @@ describe("", () => { }); tree.update(); expect(tree.find("button.dropdown-item")).toHaveLength(0); + await act(() => promise); }); }); diff --git a/ui/src/Components/NavBar/FilterInput/History.tsx b/ui/src/Components/NavBar/FilterInput/History.tsx index c83518166..5ebb65fc6 100644 --- a/ui/src/Components/NavBar/FilterInput/History.tsx +++ b/ui/src/Components/NavBar/FilterInput/History.tsx @@ -9,6 +9,7 @@ import { ReactNode, } from "react"; +import { action } from "mobx"; import { observer } from "mobx-react-lite"; import { localStored } from "mobx-stored"; @@ -161,21 +162,28 @@ interface HistoryStorageT { filters: ReduceFilterT[][]; } -const History: FC<{ - alertStore: AlertStore; - settingsStore: Settings; -}> = observer(({ alertStore, settingsStore }) => { - // this will be dumped to local storage via mobx-stored - const history: HistoryStorageT = localStored( - "history.filters", +class HistoryStorage { + config: HistoryStorageT = localStored( + "filters", { - filters: [], + filters: [] as ReduceFilterT[][], }, { delay: 100, } ); + setFilters = action((newFilters: ReduceFilterT[][]) => { + this.config.filters = newFilters; + }); +} + +const History: FC<{ + alertStore: AlertStore; + settingsStore: Settings; +}> = observer(({ alertStore, settingsStore }) => { + // this will be dumped to local storage via mobx-stored + const [history] = useState(new HistoryStorage()); const [isVisible, setIsVisible] = useState(false); const hide = useCallback(() => setIsVisible(false), []); const toggle = useCallback(() => setIsVisible(!isVisible), [isVisible]); @@ -203,9 +211,11 @@ const History: FC<{ // it up if user selects a filter set that was already in history const newHistory = [ ...[validAppliedFilters], - ...history.filters.filter((f) => JSON.stringify(f) !== filtersJSON), + ...history.config.filters.filter( + (f) => JSON.stringify(f) !== filtersJSON + ), ].slice(0, 8); - history.filters = newHistory; + history.setFilters(newHistory); } else { mountRef.current = true; } @@ -251,9 +261,9 @@ const History: FC<{ popperPlacement={placement} popperRef={ref} popperStyle={style} - filters={history.filters} + filters={history.config.filters} onClear={() => { - history.filters = []; + history.setFilters([]); }} alertStore={alertStore} settingsStore={settingsStore} diff --git a/ui/src/Components/NavBar/index.stories.tsx b/ui/src/Components/NavBar/index.stories.tsx index 264cfaffd..005fb940b 100644 --- a/ui/src/Components/NavBar/index.stories.tsx +++ b/ui/src/Components/NavBar/index.stories.tsx @@ -39,7 +39,7 @@ storiesOf("NavBar", module).add("NavBar", () => { const silenceFormStore = new SilenceFormStore(); alertStore.info.setTotalAlerts(197); - alertStore.data.colors = { + alertStore.data.setColors({ cluster: { staging: { brightness: 205, @@ -52,7 +52,7 @@ storiesOf("NavBar", module).add("NavBar", () => { background: "rgba(115,101,152,255)", }, }, - }; + }); alertStore.filters.setFilterValues([ NewFilter("cluster=staging", "cluster", "=", "staging", true, true, 15), diff --git a/ui/src/Components/OverviewModal/OverviewModalContent.test.tsx b/ui/src/Components/OverviewModal/OverviewModalContent.test.tsx index 268d488c4..4b6a45a4d 100644 --- a/ui/src/Components/OverviewModal/OverviewModalContent.test.tsx +++ b/ui/src/Components/OverviewModal/OverviewModalContent.test.tsx @@ -33,7 +33,7 @@ describe("", () => { NewUnappliedFilter("abc=xyz"), NewUnappliedFilter("foo=bar"), ]); - alertStore.data.counters = [ + alertStore.data.setCounters([ { name: "foo", hits: 16, @@ -67,21 +67,21 @@ describe("", () => { }, ], }, - ]; + ]); const tree = MountedOverviewModalContent(); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("matches snapshot with no labels to show", () => { - alertStore.data.counters = []; + alertStore.data.setCounters([]); const tree = MountedOverviewModalContent(); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); it("renders all labels after expand button click", () => { alertStore.info.setTotalAlerts(5); - alertStore.data.counters = [ + alertStore.data.setCounters([ { name: "foo", hits: 5, @@ -102,7 +102,7 @@ describe("", () => { }, ], }, - ]; + ]); const tree = MountedOverviewModalContent(); expect(tree.find("span.components-label")).toHaveLength(2 + 1); // +1 for toggle icon diff --git a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.tsx b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.tsx index 2669fa282..8eea9d896 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput/index.test.tsx +++ b/ui/src/Components/SilenceModal/AlertManagerInput/index.test.tsx @@ -7,17 +7,14 @@ import { AlertStore } from "Stores/AlertStore"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { ThemeContext } from "Components/Theme"; import { AlertManagerInput } from "."; +import { APIAlertsResponseUpstreamsT } from "Models/APITypes"; let alertStore: AlertStore; let silenceFormStore: SilenceFormStore; -beforeEach(() => { - alertStore = new AlertStore([]); - alertStore.data.upstreams.clusters = { - HA: ["am1", "am2"], - am3: ["am3"], - }; - alertStore.data.setInstances([ +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { total: 3, healthy: 3, failed: 0 }, + instances: [ { name: "am1", uri: "http://am1.example.com", @@ -54,7 +51,13 @@ beforeEach(() => { cluster: "am3", clusterMembers: ["am3"], }, - ]); + ], + clusters: { HA: ["am1", "am2"], am3: ["am3"] }, +}); + +beforeEach(() => { + alertStore = new AlertStore([]); + alertStore.data.setUpstreams(generateUpstreams()); silenceFormStore = new SilenceFormStore(); }); @@ -166,7 +169,7 @@ describe("", () => { }); it("is enabled when silenceFormStore.data.silenceID is null", () => { - silenceFormStore.data.silenceID = null; + silenceFormStore.data.setSilenceID(null); const tree = MountedAlertManagerInput(); const select = tree.find("StateManager"); expect((select.props() as any).isDisabled).toBeFalsy(); @@ -192,9 +195,11 @@ describe("", () => { }); it("doesn't include readonly instances", () => { - alertStore.data.upstreams.instances[0].readonly = true; - alertStore.data.upstreams.instances[2].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + upstreams.instances[2].readonly = true; MountedAlertManagerInput(); + alertStore.data.setUpstreams(upstreams); expect(silenceFormStore.data.alertmanagers).toHaveLength(1); expect(silenceFormStore.data.alertmanagers).toContainEqual({ label: "am2", diff --git a/ui/src/Components/SilenceModal/SilenceForm.test.tsx b/ui/src/Components/SilenceModal/SilenceForm.test.tsx index 1a465c402..5093b5979 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.test.tsx +++ b/ui/src/Components/SilenceModal/SilenceForm.test.tsx @@ -9,20 +9,19 @@ import { SilenceFormStore, NewEmptyMatcher } from "Stores/SilenceFormStore"; import { QueryOperators, StaticLabels } from "Common/Query"; import { ThemeContext } from "Components/Theme"; import SilenceForm from "./SilenceForm"; +import { APIAlertsResponseUpstreamsT } from "Models/APITypes"; let alertStore: AlertStore; let settingsStore: Settings; let silenceFormStore: SilenceFormStore; -beforeEach(() => { - alertStore = new AlertStore([]); - settingsStore = new Settings(null); - silenceFormStore = new SilenceFormStore(); - - alertStore.data.upstreams.clusters = { - am1: ["am1"], - }; - alertStore.data.setInstances([ +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { + healthy: 1, + failed: 0, + total: 1, + }, + instances: [ { name: "am1", uri: "http://am1.example.com", @@ -35,7 +34,17 @@ beforeEach(() => { cluster: "am1", clusterMembers: ["am1"], }, - ]); + ], + clusters: { + am1: ["am1"], + }, +}); + +beforeEach(() => { + alertStore = new AlertStore([]); + settingsStore = new Settings(null); + silenceFormStore = new SilenceFormStore(); + alertStore.data.setUpstreams(generateUpstreams()); }); const MountedSilenceForm = () => { @@ -84,7 +93,7 @@ describe(" matchers", () => { ...filterCombos("cluster"), ...filterCombos("foo"), ]); - silenceFormStore.data.autofillMatchers = true; + silenceFormStore.data.setAutofillMatchers(true); const tree = MountedSilenceForm(); const matchers = tree.find("SilenceMatch"); expect(matchers).toHaveLength(6); @@ -174,7 +183,7 @@ describe(" matchers", () => { ...filterCombos("cluster"), ...filterCombos("foo"), ]); - silenceFormStore.data.autofillMatchers = false; + silenceFormStore.data.setAutofillMatchers(false); const tree = MountedSilenceForm(); const matchers = tree.find("SilenceMatch"); expect(matchers).toHaveLength(1); @@ -204,7 +213,7 @@ describe(" matchers", () => { }); it("trash icon is visible when there are two matchers", () => { - silenceFormStore.data.autofillMatchers = false; + silenceFormStore.data.setAutofillMatchers(false); silenceFormStore.data.addEmptyMatcher(); silenceFormStore.data.addEmptyMatcher(); const tree = MountedSilenceForm(); @@ -216,7 +225,7 @@ describe(" matchers", () => { }); it("clicking trash icon on a matcher select removes it", () => { - silenceFormStore.data.autofillMatchers = false; + silenceFormStore.data.setAutofillMatchers(false); silenceFormStore.data.addEmptyMatcher(); silenceFormStore.data.addEmptyMatcher(); silenceFormStore.data.addEmptyMatcher(); @@ -261,7 +270,7 @@ describe(" preview", () => { silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]); silenceFormStore.data.setAuthor("me@example.com"); silenceFormStore.data.setComment("fake silence"); - silenceFormStore.data.autofillMatchers = false; + silenceFormStore.data.setAutofillMatchers(false); const tree = MountedSilenceForm(); tree.find(".badge.cursor-pointer.text-muted").simulate("click"); @@ -280,7 +289,7 @@ describe(" preview", () => { silenceFormStore.data.setAlertmanagers([{ label: "am1", value: ["am1"] }]); silenceFormStore.data.setAuthor("me@example.com"); silenceFormStore.data.setComment("fake silence"); - silenceFormStore.data.autofillMatchers = false; + silenceFormStore.data.setAutofillMatchers(false); const tree = MountedSilenceForm(); tree.find(".badge.cursor-pointer.text-muted").simulate("click"); @@ -296,8 +305,7 @@ describe(" preview", () => { describe(" inputs", () => { it("author is read-only when info.authentication.enabled is true", () => { - alertStore.info.authentication.enabled = true; - alertStore.info.authentication.username = "auth@example.com"; + alertStore.info.setAuthentication(true, "auth@example.com"); const tree = MountedSilenceForm(); const input = tree.find("input[placeholder='Author']"); expect(input.props().readOnly).toBe(true); @@ -306,7 +314,7 @@ describe(" inputs", () => { }); it("default author value comes from Settings store", () => { - settingsStore.silenceFormConfig.config.author = "foo@example.com"; + settingsStore.silenceFormConfig.saveAuthor("foo@example.com"); const tree = MountedSilenceForm(); const input = tree.find("input[placeholder='Author']"); expect(input.props().value).toBe("foo@example.com"); @@ -314,7 +322,7 @@ describe(" inputs", () => { }); it("default author value is empty if nothing is stored in Settings", () => { - settingsStore.silenceFormConfig.config.author = ""; + settingsStore.silenceFormConfig.saveAuthor(""); const tree = MountedSilenceForm(); const input = tree.find("input[placeholder='Author']"); expect(input.text()).toBe(""); diff --git a/ui/src/Components/SilenceModal/SilenceForm.tsx b/ui/src/Components/SilenceModal/SilenceForm.tsx index 4fa78e0a4..653b4c1d4 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.tsx +++ b/ui/src/Components/SilenceModal/SilenceForm.tsx @@ -102,7 +102,7 @@ const SilenceForm: FC<{ } // reset cluster request state - silenceFormStore.data.requestsByCluster = {}; + silenceFormStore.data.setRequestsByCluster({}); if (silenceFormStore.data.autofillMatchers) { silenceFormStore.data.setMatchers([]); @@ -124,7 +124,7 @@ const SilenceForm: FC<{ } else { matcher.values = [StringToOption(f.value)]; } - silenceFormStore.data.matchers.push(matcher); + silenceFormStore.data.addMatcherWithID(matcher); }); } } @@ -168,12 +168,12 @@ const SilenceForm: FC<{ silenceFormStore.data.alertmanagers.forEach((am) => { rbc[am.label] = NewClusterRequest(am.label, am.value); }); - silenceFormStore.data.requestsByCluster = rbc; + silenceFormStore.data.setRequestsByCluster(rbc); settingsStore.silenceFormConfig.saveAuthor(silenceFormStore.data.author); if (silenceFormStore.data.isValid) - silenceFormStore.data.currentStage = "preview"; + silenceFormStore.data.setStage("preview"); silenceFormStore.data.setWasValidated(true); }; diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.test.tsx b/ui/src/Components/SilenceModal/SilenceModalContent.test.tsx index 5b8690fa6..6a605bb43 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.test.tsx +++ b/ui/src/Components/SilenceModal/SilenceModalContent.test.tsx @@ -8,36 +8,39 @@ import { Settings } from "Stores/Settings"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { ThemeContext } from "Components/Theme"; import { SilenceModalContent } from "./SilenceModalContent"; +import { APIAlertsResponseUpstreamsT } from "Models/APITypes"; let alertStore: AlertStore; let settingsStore: Settings; let silenceFormStore: SilenceFormStore; +const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({ + counters: { total: 1, healthy: 1, failed: 0 }, + instances: [ + { + name: "am1", + cluster: "am", + clusterMembers: ["am1"], + uri: "http://localhost:9093", + publicURI: "http://localhost:9093", + readonly: false, + error: "", + version: "0.17.0", + headers: {}, + corsCredentials: "include", + }, + ], + clusters: { am: ["am1"] }, +}); + beforeEach(() => { alertStore = new AlertStore([]); settingsStore = new Settings(null); silenceFormStore = new SilenceFormStore(); - alertStore.data.setUpstreams({ - counters: { total: 1, healthy: 1, failed: 0 }, - instances: [ - { - name: "am1", - cluster: "am", - clusterMembers: ["am1"], - uri: "http://localhost:9093", - publicURI: "http://localhost:9093", - readonly: false, - error: "", - version: "0.17.0", - headers: {}, - corsCredentials: "include", - }, - ], - clusters: { am: ["am1"] }, - }); + alertStore.data.setUpstreams(generateUpstreams()); - silenceFormStore.tab.current = "editor"; + silenceFormStore.tab.setTab("editor"); }); afterEach(() => { @@ -63,7 +66,9 @@ const MountedSilenceModalContent = () => { describe("", () => { it("Renders ReadOnlyPlaceholder when there are no writable Alertmanager upstreams", () => { - alertStore.data.upstreams.instances[0].readonly = true; + const upstreams = generateUpstreams(); + upstreams.instances[0].readonly = true; + alertStore.data.setUpstreams(upstreams); const tree = MountedSilenceModalContent(); const placeholder = tree.find("ReadOnlyPlaceholder"); expect(placeholder).toHaveLength(1); @@ -79,7 +84,7 @@ describe("", () => { }); it("Clicking on the Editor tab changes content", () => { - silenceFormStore.tab.current = "browser"; + silenceFormStore.tab.setTab("browser"); const tree = MountedSilenceModalContent(); const tabs = tree.find("Tab"); tabs.at(0).simulate("click"); @@ -88,13 +93,13 @@ describe("", () => { }); it("Content is not blurred when silenceFormStore.toggle.blurred is false", () => { - silenceFormStore.toggle.blurred = false; + silenceFormStore.toggle.setBlur(false); const tree = MountedSilenceModalContent(); expect(tree.find("div.modal-body.modal-content-blur")).toHaveLength(0); }); it("Content is blurred when silenceFormStore.toggle.blurred is true", () => { - silenceFormStore.toggle.blurred = true; + silenceFormStore.toggle.setBlur(true); const tree = MountedSilenceModalContent(); expect(tree.find("div.modal-body.modal-content-blur")).toHaveLength(1); }); @@ -102,28 +107,28 @@ describe("", () => { describe(" Editor", () => { it("title is 'New silence' when creating new silence", () => { - silenceFormStore.data.currentStage = "form"; - silenceFormStore.data.silenceID = null; + silenceFormStore.data.setStage("form"); + silenceFormStore.data.setSilenceID(null); const tree = MountedSilenceModalContent(); const tab = tree.find("Tab").at(0); expect(tab.props().title).toBe("New silence"); }); it("title is 'Editing silence' when editing exiting silence", () => { - silenceFormStore.data.currentStage = "form"; + silenceFormStore.data.setStage("form"); silenceFormStore.data.setSilenceID("1234"); const tree = MountedSilenceModalContent(); const tab = tree.find("Tab").at(0); expect(tab.props().title).toBe("Editing silence"); }); it("title is 'Preview silenced alerts' when previewing silenced alerts", () => { - silenceFormStore.data.currentStage = "preview"; + silenceFormStore.data.setStage("preview"); silenceFormStore.data.setSilenceID("1234"); const tree = MountedSilenceModalContent(); const tab = tree.find("Tab").at(0); expect(tab.props().title).toBe("Preview silenced alerts"); }); it("title is 'Silence submitted' after sending silence to Alertmanager", () => { - silenceFormStore.data.currentStage = "submit"; + silenceFormStore.data.setStage("submit"); silenceFormStore.data.setSilenceID("1234"); const tree = MountedSilenceModalContent(); const tab = tree.find("Tab").at(0); @@ -131,21 +136,21 @@ describe(" Editor", () => { }); it("renders SilenceForm when silenceFormStore.data.currentStage is 'UserInput'", () => { - silenceFormStore.data.currentStage = "form"; + silenceFormStore.data.setStage("form"); const tree = MountedSilenceModalContent(); const form = tree.find("SilenceForm"); expect(form).toHaveLength(1); }); it("renders SilencePreview when silenceFormStore.data.currentStage is 'Preview'", () => { - silenceFormStore.data.currentStage = "preview"; + silenceFormStore.data.setStage("preview"); const tree = MountedSilenceModalContent(); const ctrl = tree.find("SilencePreview"); expect(ctrl).toHaveLength(1); }); it("renders SilenceSubmitController when silenceFormStore.data.currentStage is 'Submit'", () => { - silenceFormStore.data.currentStage = "submit"; + silenceFormStore.data.setStage("submit"); const tree = MountedSilenceModalContent(); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); @@ -153,7 +158,7 @@ describe(" Editor", () => { describe(" Browser", () => { it("renders silence browser when tab is set to Browser", () => { - silenceFormStore.tab.current = "browser"; + silenceFormStore.tab.setTab("browser"); const tree = MountedSilenceModalContent(); const form = tree.find("Browser"); expect(form).toHaveLength(1); diff --git a/ui/src/Components/SilenceModal/SilencePreview/index.tsx b/ui/src/Components/SilenceModal/SilencePreview/index.tsx index c2b025f13..c1b1b9c86 100644 --- a/ui/src/Components/SilenceModal/SilencePreview/index.tsx +++ b/ui/src/Components/SilenceModal/SilencePreview/index.tsx @@ -31,7 +31,7 @@ const SilencePreview: FC<{