fix(ui): enable mobx strict mode in tests

This commit is contained in:
Łukasz Mierzwa
2021-05-03 19:29:05 +01:00
committed by Łukasz Mierzwa
parent b47089cbe8
commit e45debed58
52 changed files with 673 additions and 433 deletions
+1 -1
View File
@@ -151,7 +151,7 @@ describe("<App />", () => {
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 = {
+80 -27
View File
@@ -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("<AlertAck />", () => {
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("<AlertAck />", () => {
});
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("<AlertAck />", () => {
});
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("<AlertAck />", () => {
});
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("<AlertAck />", () => {
});
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("<AlertAck />", () => {
});
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({
@@ -36,7 +36,7 @@ describe("<FaviconBadge />", () => {
});
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("?");
+1 -1
View File
@@ -321,7 +321,7 @@ describe("<Fetcher /> children", () => {
it("doesn't render any children when upgrade is needed", () => {
act(() => {
alertStore.info.upgradeNeeded = true;
alertStore.info.setUpgradeNeeded(true);
});
const tree = mount(
<Fetcher alertStore={alertStore} settingsStore={settingsStore} />
@@ -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("<MenuContent />", () => {
});
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);
@@ -173,11 +173,11 @@ describe("<Alert />", () => {
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("<Alert />", () => {
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("<Alert />", () => {
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("<Alert />", () => {
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");
@@ -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("<GroupFooter />", () => {
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("<GroupFooter />", () => {
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("<GroupFooter />", () => {
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("<GroupFooter />", () => {
}
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("<GroupFooter />", () => {
}
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(
<GroupFooter
@@ -5,7 +5,7 @@ import { mount } from "enzyme";
import copy from "copy-to-clipboard";
import { MockAlertGroup } from "__fixtures__/Alerts";
import { APIAlertGroupT } from "Models/APITypes";
import { APIAlertGroupT, APIAlertsResponseUpstreamsT } from "Models/APITypes";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { GroupMenu, MenuContent } from "./GroupMenu";
@@ -16,6 +16,49 @@ let silenceFormStore: SilenceFormStore;
let MockAfterClick: () => 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("<MenuContent />", () => {
});
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);
@@ -33,7 +33,7 @@ describe("<RenderSilence />", () => {
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(
<RenderSilence
@@ -52,7 +52,7 @@ describe("<RenderSilence />", () => {
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(
<RenderSilence
@@ -66,7 +66,7 @@ describe("<RenderSilence />", () => {
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);
@@ -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("<AlertGroup />", () => {
});
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("<AlertGroup /> 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);
@@ -73,7 +73,7 @@ describe("<GridLabelSelect />", () => {
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");
@@ -437,7 +437,7 @@ describe("<AlertGrid />", () => {
{ 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("<AlertGrid />", () => {
{ 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("<AlertGrid />", () => {
});
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("<AlertGrid />", () => {
});
it("viewport resize doesn't allow loops", () => {
settingsStore.gridConfig.config.groupWidth = 400;
settingsStore.gridConfig.setGroupWidth(400);
MockGroupList(10, 1);
+6 -6
View File
@@ -86,7 +86,7 @@ describe("<Grid />", () => {
});
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("<Grid />", () => {
});
it("renders UpgradeNeeded when alertStore.info.upgradeNeeded=true", () => {
alertStore.info.upgradeNeeded = true;
alertStore.info.setUpgradeNeeded(true);
const tree = ShallowGrid();
expect(tree.text()).toBe("<UpgradeNeeded />");
});
it("renders ReloadNeeded when alertStore.info.reloadNeeded=true", () => {
alertStore.info.reloadNeeded = true;
alertStore.info.setReloadNeeded(true);
const tree = ShallowGrid();
expect(tree.text()).toBe("<ReloadNeeded />");
});
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("<EmptyGrid />");
});
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);
@@ -84,12 +84,18 @@ describe("<FilterInputLabel /> 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");
}
+16 -4
View File
@@ -15,14 +15,20 @@ beforeEach(() => {
describe("<GetClassAndStyle />", () => {
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("<GetClassAndStyle />", () => {
});
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({});
});
@@ -30,8 +30,7 @@ describe("<AlertGroupCollapseConfiguration />", () => {
});
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(
@@ -41,7 +41,7 @@ describe("<AlertGroupConfiguration />", () => {
});
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);
});
@@ -43,7 +43,7 @@ describe("<AlertGroupSortConfiguration />", () => {
});
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
@@ -41,7 +41,7 @@ describe("<AlertGroupWidthConfiguration />", () => {
});
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);
});
@@ -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 (
@@ -49,19 +49,19 @@ describe("<MultiGridConfiguration />", () => {
});
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");
});
@@ -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 }}
/>
@@ -93,8 +93,7 @@ describe("<MainModalContent />", () => {
});
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(
<span>
{Wrapped(
@@ -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 (
<MainModalContent
alertStore={alertStore}
@@ -7,7 +7,7 @@ import { advanceTo, clear } from "jest-date-mock";
import { MockSilence } from "__fixtures__/Alerts";
import { PressKey } from "__fixtures__/PressKey";
import { useFetchDelete } from "Hooks/useFetchDelete";
import { APISilenceT } from "Models/APITypes";
import { APISilenceT, APIAlertsResponseUpstreamsT } from "Models/APITypes";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { DeleteSilence, DeleteSilenceModalContent } from "./DeleteSilence";
@@ -19,6 +19,25 @@ let silenceFormStore: SilenceFormStore;
let cluster: string;
let silence: APISilenceT;
const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({
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("<DeleteSilence />", () => {
});
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(
<DeleteSilence
alertStore={alertStore}
@@ -197,9 +202,11 @@ describe("<DeleteSilenceModalContent />", () => {
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("<DeleteSilenceModalContent />", () => {
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");
@@ -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("<SilenceDetails />", () => {
});
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);
@@ -138,7 +138,9 @@ describe("<HistoryMenu />", () => {
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("<HistoryMenu />", () => {
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("<HistoryMenu />", () => {
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("<HistoryMenu />", () => {
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("<HistoryMenu />", () => {
});
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("<HistoryMenu />", () => {
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("<HistoryMenu />", () => {
});
tree.update();
expect(tree.find("button.dropdown-item")).toHaveLength(0);
await act(() => promise);
});
});
@@ -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<HistoryStorage>(new HistoryStorage());
const [isVisible, setIsVisible] = useState<boolean>(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}
+2 -2
View File
@@ -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),
@@ -33,7 +33,7 @@ describe("<OverviewModalContent />", () => {
NewUnappliedFilter("abc=xyz"),
NewUnappliedFilter("foo=bar"),
]);
alertStore.data.counters = [
alertStore.data.setCounters([
{
name: "foo",
hits: 16,
@@ -67,21 +67,21 @@ describe("<OverviewModalContent />", () => {
},
],
},
];
]);
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("<OverviewModalContent />", () => {
},
],
},
];
]);
const tree = MountedOverviewModalContent();
expect(tree.find("span.components-label")).toHaveLength(2 + 1); // +1 for toggle icon
@@ -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("<AlertManagerInput />", () => {
});
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("<AlertManagerInput />", () => {
});
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",
@@ -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("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> preview", () => {
describe("<SilenceForm /> 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("<SilenceForm /> 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("<SilenceForm /> 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("");
@@ -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);
};
@@ -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("<SilenceModalContent />", () => {
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("<SilenceModalContent />", () => {
});
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("<SilenceModalContent />", () => {
});
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("<SilenceModalContent />", () => {
describe("<SilenceModalContent /> 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("<SilenceModalContent /> 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("<SilenceModalContent /> Editor", () => {
describe("<SilenceModalContent /> 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);
@@ -31,7 +31,7 @@ const SilencePreview: FC<{
<button
type="button"
className="btn btn-primary"
onClick={silenceFormStore.data.setStageSubmit}
onClick={() => silenceFormStore.data.setStage("submit")}
>
<FontAwesomeIcon icon={faCheckCircle} className="pr-1" />
Submit
@@ -59,10 +59,10 @@ beforeEach(() => {
describe("<SilenceSubmitController />", () => {
it("renders MultiClusterStatus when multiple clusters are used", () => {
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
single: NewClusterRequest("single", ["single"]),
};
});
const tree = shallow(
<SilenceSubmitController
@@ -75,9 +75,9 @@ describe("<SilenceSubmitController />", () => {
});
it("renders SingleClusterStatus when multiple clusters are used", () => {
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
const tree = shallow(
<SilenceSubmitController
@@ -90,7 +90,7 @@ describe("<SilenceSubmitController />", () => {
});
it("resets the form on 'Back' button click", () => {
silenceFormStore.data.currentStage = "submit";
silenceFormStore.data.setStage("submit");
const tree = shallow(
<SilenceSubmitController
alertStore={alertStore}
@@ -105,10 +105,10 @@ describe("<SilenceSubmitController />", () => {
describe("<MultiClusterStatus />", () => {
it("renders all passed SilenceSubmitProgress", () => {
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
single: NewClusterRequest("single", ["single"]),
};
});
const tree = shallow(
<MultiClusterStatus
alertStore={alertStore}
@@ -120,7 +120,7 @@ describe("<MultiClusterStatus />", () => {
it("renders spinner for pending requests", () => {
const single = NewClusterRequest("single", ["single"]);
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<MultiClusterStatus
alertStore={alertStore}
@@ -137,7 +137,7 @@ describe("<MultiClusterStatus />", () => {
const single = NewClusterRequest("single", ["single"]);
single.isDone = true;
single.error = "fake error";
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<MultiClusterStatus
alertStore={alertStore}
@@ -155,7 +155,7 @@ describe("<MultiClusterStatus />", () => {
single.isDone = true;
single.silenceID = "123456789";
single.silenceLink = "http://localhost";
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<MultiClusterStatus
alertStore={alertStore}
@@ -175,7 +175,7 @@ describe("<MultiClusterStatus />", () => {
describe("<SingleClusterStatus />", () => {
it("renders spinner for pending requests", () => {
const single = NewClusterRequest("single", ["single"]);
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<SingleClusterStatus
alertStore={alertStore}
@@ -191,7 +191,7 @@ describe("<SingleClusterStatus />", () => {
const single = NewClusterRequest("single", ["single"]);
single.isDone = true;
single.error = "fake error";
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<SingleClusterStatus
alertStore={alertStore}
@@ -210,7 +210,7 @@ describe("<SingleClusterStatus />", () => {
single.isDone = true;
single.silenceID = "123456789";
single.silenceLink = "http://localhost";
silenceFormStore.data.requestsByCluster = { single: single };
silenceFormStore.data.setRequestsByCluster({ single: single });
const tree = shallow(
<SingleClusterStatus
alertStore={alertStore}
@@ -7,38 +7,41 @@ import fetchMock from "fetch-mock";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore, NewClusterRequest } from "Stores/SilenceFormStore";
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
import { APIAlertsResponseUpstreamsT } from "Models/APITypes";
let alertStore: AlertStore;
let silenceFormStore: SilenceFormStore;
const generateUpstreams = (): APIAlertsResponseUpstreamsT => ({
counters: { total: 1, healthy: 1, failed: 0 },
clusters: { mockAlertmanager: ["mockAlertmanager"] },
instances: [
{
name: "mockAlertmanager",
uri: "http://localhost",
publicURI: "http://example.com",
readonly: false,
headers: { foo: "bar" },
corsCredentials: "include",
error: "",
version: "0.17.0",
cluster: "mockAlertmanager",
clusterMembers: ["mockAlertmanager"],
},
],
});
beforeEach(() => {
alertStore = new AlertStore([]);
silenceFormStore = new SilenceFormStore();
alertStore.data.setUpstreams({
counters: { total: 1, healthy: 1, failed: 0 },
clusters: { mockAlertmanager: ["mockAlertmanager"] },
instances: [
{
name: "mockAlertmanager",
uri: "http://localhost",
publicURI: "http://example.com",
readonly: false,
headers: { foo: "bar" },
corsCredentials: "include",
error: "",
version: "0.17.0",
cluster: "mockAlertmanager",
clusterMembers: ["mockAlertmanager"],
},
],
});
alertStore.data.setUpstreams(generateUpstreams());
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
mockAlertmanager: NewClusterRequest("mockAlertmanager", [
"mockAlertmanager",
]),
};
});
fetchMock.resetHistory();
fetchMock.mock(
@@ -114,7 +117,9 @@ describe("<SilenceSubmitProgress />", () => {
});
it("uses CORS credentials from alertmanager config", async () => {
alertStore.data.upstreams.instances[0].corsCredentials = "same-origin";
const upstreams = generateUpstreams();
upstreams.instances[0].corsCredentials = "same-origin";
alertStore.data.setUpstreams(upstreams);
MountedSilenceSubmitProgress();
await act(async () => {
await fetchMock.flush(true);
@@ -165,9 +170,9 @@ describe("<SilenceSubmitProgress />", () => {
},
],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
mount(
<SilenceSubmitProgress
@@ -234,9 +239,9 @@ describe("<SilenceSubmitProgress />", () => {
},
],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
const tree = mount(
<SilenceSubmitProgress
@@ -288,9 +293,9 @@ describe("<SilenceSubmitProgress />", () => {
},
],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
mount(
<SilenceSubmitProgress
@@ -327,9 +332,9 @@ describe("<SilenceSubmitProgress />", () => {
clusters: { ha: ["am1", "am2"] },
instances: [],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
mount(
<SilenceSubmitProgress
@@ -393,9 +398,9 @@ describe("<SilenceSubmitProgress />", () => {
},
],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
mount(
<SilenceSubmitProgress
@@ -463,9 +468,9 @@ describe("<SilenceSubmitProgress />", () => {
},
],
});
silenceFormStore.data.requestsByCluster = {
silenceFormStore.data.setRequestsByCluster({
ha: NewClusterRequest("ha", ["am1", "am2"]),
};
});
mount(
<SilenceSubmitProgress
@@ -64,15 +64,18 @@ const SilenceSubmitProgress: FC<{
useEffect(() => {
if (!inProgress && error !== null) {
silenceFormStore.data.requestsByCluster[cluster].isDone = true;
silenceFormStore.data.requestsByCluster[cluster].error = error;
silenceFormStore.data.setRequestsByClusterUpdate(cluster, {
isDone: true,
error: error,
});
} else if (!inProgress && response !== null) {
silenceFormStore.data.requestsByCluster[cluster].isDone = true;
silenceFormStore.data.requestsByCluster[cluster].silenceID =
response.silenceID;
silenceFormStore.data.requestsByCluster[cluster].silenceLink = `${
publicURIs[responseURI as string]
}/#/silences/${response.silenceID}`;
silenceFormStore.data.setRequestsByClusterUpdate(cluster, {
isDone: true,
silenceID: response.silenceID,
silenceLink: `${publicURIs[responseURI as string]}/#/silences/${
response.silenceID
}`,
});
}
}, [cluster, error, inProgress, publicURIs, response, responseURI]); // eslint-disable-line react-hooks/exhaustive-deps
@@ -47,8 +47,7 @@ storiesOf("SilenceModal", module)
const settingsStore = new Settings(null);
const silenceFormStore = new SilenceFormStore();
alertStore.info.authentication.enabled = true;
alertStore.info.authentication.username = "me@example.com";
alertStore.info.setAuthentication(true, "me@example.com");
alertStore.data.setUpstreams({
counters: { healthy: 3, failed: 0, total: 3 },
@@ -94,7 +93,7 @@ storiesOf("SilenceModal", module)
});
silenceFormStore.toggle.visible = true;
silenceFormStore.data.autofillMatchers = false;
silenceFormStore.data.setAutofillMatchers(false);
silenceFormStore.data.setMatchers([
MockMatcher("cluster", ["prod"], false),
MockMatcher("instance", ["server1", "server3"], true),
@@ -143,7 +143,7 @@ describe("<SilenceModal />", () => {
// mark form as dirty, resetProgress() should change this value to false
silenceFormStore.data.setWasValidated(true);
// disable autofill, closing modal should re-enable it
silenceFormStore.data.autofillMatchers = false;
silenceFormStore.data.setAutofillMatchers(false);
// click to hide
toggle.simulate("click");
+1 -1
View File
@@ -46,7 +46,7 @@ const SilenceModal: FC<{
toggleOpen={silenceFormStore.toggle.toggle}
onExited={() => {
silenceFormStore.data.resetProgress();
silenceFormStore.data.autofillMatchers = true;
silenceFormStore.data.setAutofillMatchers(true);
}}
>
<React.Suspense
+4 -4
View File
@@ -58,7 +58,7 @@ const makeErrors = () => {
describe("<AppToasts />", () => {
it("doesn't render anything when alertStore.info.upgradeNeeded=true", () => {
alertStore.info.upgradeNeeded = true;
alertStore.info.setUpgradeNeeded(true);
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.html()).toBe("");
});
@@ -128,8 +128,8 @@ describe("<AppToasts />", () => {
it("clicking navbar icon toggles all notifications", () => {
makeErrors();
alertStore.info.upgradeNeeded = false;
alertStore.info.upgradeReady = false;
alertStore.info.setUpgradeNeeded(false);
alertStore.info.setUpgradeReady(false);
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.find("div.bg-toast")).toHaveLength(2);
expect(tree.find("span.badge.cursor-pointer.with-click")).toHaveLength(2);
@@ -143,7 +143,7 @@ describe("<AppToasts />", () => {
});
it("renders UpgradeToastMessage when alertStore.info.upgradeReady=true", () => {
alertStore.info.upgradeReady = true;
alertStore.info.setUpgradeReady(true);
const tree = mount(<AppToasts alertStore={alertStore} />);
expect(tree.find("UpgradeToastMessage")).toHaveLength(1);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
@@ -9,7 +9,7 @@ let alertStore: AlertStore;
beforeEach(() => {
alertStore = new AlertStore([]);
alertStore.info.version = "1.2.3";
alertStore.info.setVersion("1.2.3");
});
describe("<ToastMessage />", () => {
@@ -46,4 +46,10 @@ describe("<UpgradeToastMessage />", () => {
tree.find("button").simulate("click");
expect(alertStore.info.upgradeNeeded).toBe(true);
});
it("upgradeNeeded=true after onAnimationEnd is called", () => {
const tree = mount(<UpgradeToastMessage alertStore={alertStore} />);
tree.find("div.toast-upgrade-progressbar").simulate("animationend");
expect(alertStore.info.upgradeNeeded).toBe(true);
});
});
+2 -2
View File
@@ -27,7 +27,7 @@ const UpgradeToastMessage: FC<{
const setPause = useCallback(() => {
if (isPaused) {
alertStore.info.setUpgradeNeeded();
alertStore.info.setUpgradeNeeded(true);
} else {
setIsPaused(true);
}
@@ -56,7 +56,7 @@ const UpgradeToastMessage: FC<{
className={`progress-bar bg-white ${
isPaused ? "" : "toast-upgrade-progressbar"
}`}
onAnimationEnd={alertStore.info.setUpgradeNeeded}
onAnimationEnd={() => alertStore.info.setUpgradeNeeded(true)}
role="progressbar"
style={{ width: 100 }}
></div>
+1 -1
View File
@@ -11,7 +11,7 @@ import "Styles/Percy.scss";
storiesOf("AppToasts", module).add("AppToasts", () => {
const alertStore = new AlertStore([]);
alertStore.info.version = "999.99.0";
alertStore.info.setVersion("999.99.0");
return (
<div className="d-flex flex-column">
+7 -7
View File
@@ -177,9 +177,9 @@ describe("AlertStore.info", () => {
it("setUpgradeNeeded() sets upgradeNeeded to true", () => {
const store = new AlertStore([]);
expect(store.info.upgradeNeeded).toBe(false);
store.info.setUpgradeNeeded();
store.info.setUpgradeNeeded(true);
expect(store.info.upgradeNeeded).toBe(true);
store.info.setUpgradeNeeded();
store.info.setUpgradeNeeded(true);
expect(store.info.upgradeNeeded).toBe(true);
});
});
@@ -575,8 +575,8 @@ describe("AlertStore.fetch", () => {
});
it("unapplied filters are marked as applied on fetch error", async () => {
const store = new AlertStore(["foo"]);
store.filters.values[0].applied = false;
const store = new AlertStore([]);
store.filters.setFilterValues([NewUnappliedFilter("foo")]);
jest.spyOn(console, "trace").mockImplementation(() => {});
fetchMock.reset();
@@ -600,7 +600,7 @@ describe("AlertStore.fetch", () => {
const store = new AlertStore(["label=value"]);
// initial fetch, should update settings
store.settings.values = { foo: "bar" } as any;
store.settings.setValues({ foo: "bar" } as any);
await expect(store.fetch("", false, "", "", "")).resolves.toBeUndefined();
expect(store.settings.values).toMatchObject({
staticColorLabels: ["job"],
@@ -664,14 +664,14 @@ describe("AlertStore.fetch", () => {
const g1 = MockGroup("group1", 1, 1, 0);
const g2 = MockGroup("group2", 1, 1, 0);
const g3 = MockGroup("group3", 1, 1, 0);
store.data.grids = [
store.data.setGrids([
{
labelName: "",
labelValue: "",
alertGroups: [g1, g2, g3],
stateCount: { unprocessed: 0, active: 3, suppressed: 0 },
},
];
]);
expect(store.data.grids).toHaveLength(1);
expect(Object.keys(store.data.grids[0].alertGroups)).toHaveLength(3);
+55 -14
View File
@@ -150,8 +150,11 @@ interface AlertStoreDataT {
getColorData: (name: string, value: string) => APILabelColorT | undefined;
setGrids: (g: APIGridT[]) => void;
setUpstreams: (u: APIAlertsResponseUpstreamsT) => void;
setInstances: (i: APIAlertmanagerUpstreamT[]) => void;
setClusters: (c: APIAlertsResponseUpstreamsClusterMapT) => void;
setSilences: (s: APIAlertsResponseSilenceMapT) => void;
setCounters: (c: APILabelCounterT[]) => void;
setReceivers: (r: string[]) => void;
setColors: (c: APIAlertsResponseColorsT) => void;
readonly upstreamsWithErrors: APIAlertmanagerUpstreamT[];
}
@@ -168,13 +171,17 @@ interface AlertStoreInfoT {
reloadNeeded: boolean;
setIsRetrying: () => void;
clearIsRetrying: () => void;
setUpgradeNeeded: () => void;
setReloadNeeded: () => void;
setUpgradeNeeded: (v: boolean) => void;
setUpgradeReady: (v: boolean) => void;
setReloadNeeded: (v: boolean) => void;
setTotalAlerts: (n: number) => void;
setAuthentication: (enabled: boolean, username: string) => void;
setVersion: (v: string) => void;
}
interface AlertStoreSettingsT {
values: APISettingsT;
setValues: (v: APISettingsT) => void;
}
interface AlertStoreStatusT {
@@ -191,6 +198,7 @@ interface AlertStoreStatusT {
resume: () => void;
togglePause: () => void;
stop: () => void;
setError: (e: null | string) => void;
}
interface AlertStoreUIT {
@@ -338,12 +346,21 @@ class AlertStore {
setUpstreams(u: APIAlertsResponseUpstreamsT) {
this.upstreams = u;
},
setInstances(i: APIAlertmanagerUpstreamT[]) {
this.upstreams.instances = i;
},
setClusters(c: APIAlertsResponseUpstreamsClusterMapT) {
this.upstreams.clusters = c;
},
setSilences(s: APIAlertsResponseSilenceMapT) {
this.silences = s;
},
setCounters(c: APILabelCounterT[]) {
this.counters = c;
},
setReceivers(r: string[]) {
this.receivers = r;
},
setColors(c: APIAlertsResponseColorsT) {
this.colors = c;
},
get upstreamsWithErrors(): APIAlertmanagerUpstreamT[] {
return this.upstreams.instances.filter(
(upstream) => upstream.error !== ""
@@ -357,8 +374,11 @@ class AlertStore {
clustersWithoutReadOnly: computed,
setGrids: action.bound,
setUpstreams: action.bound,
setInstances: action.bound,
setClusters: action.bound,
setSilences: action.bound,
setCounters: action.bound,
setReceivers: action.bound,
setColors: action.bound,
},
{ name: "API Response data" }
);
@@ -381,15 +401,25 @@ class AlertStore {
clearIsRetrying() {
this.isRetrying = false;
},
setUpgradeNeeded() {
this.upgradeNeeded = true;
setUpgradeNeeded(v: boolean) {
this.upgradeNeeded = v;
},
setReloadNeeded() {
this.reloadNeeded = true;
setUpgradeReady(v: boolean) {
this.upgradeReady = v;
},
setReloadNeeded(v: boolean) {
this.reloadNeeded = v;
},
setTotalAlerts(n: number) {
this.totalAlerts = n;
},
setAuthentication(enabled: boolean, username: string) {
this.authentication.enabled = enabled;
this.authentication.username = username;
},
setVersion(v: string) {
this.version = v;
},
},
{
setIsRetrying: action.bound,
@@ -397,6 +427,8 @@ class AlertStore {
setReloadNeeded: action.bound,
setUpgradeNeeded: action.bound,
setTotalAlerts: action.bound,
setAuthentication: action.bound,
setVersion: action.bound,
},
{ name: "API response info" }
);
@@ -429,9 +461,14 @@ class AlertStore {
comment: "ACK! This alert was acknowledged using karma",
},
historyEnabled: true,
} as APISettingsT,
setValues(v: APISettingsT) {
this.values = v;
},
},
{},
{
setValues: action.bound,
},
{
name: "Global settings",
}
@@ -474,6 +511,9 @@ class AlertStore {
this.paused = true;
this.stopped = true;
},
setError(e: null | string) {
this.error = e;
},
},
{
setIdle: action,
@@ -484,6 +524,7 @@ class AlertStore {
resume: action.bound,
togglePause: action.bound,
stop: action.bound,
setError: action.bound,
},
{ name: "Store status" }
);
@@ -532,7 +573,7 @@ class AlertStore {
// if that request comes back as type=opaque then we might be getting
// redirected by an auth proxy
if (result.type === "opaque") {
this.info.setReloadNeeded();
this.info.setReloadNeeded(true);
}
this.info.clearIsRetrying();
this.status.setProcessing();
@@ -607,7 +648,7 @@ class AlertStore {
this.info.version !== "unknown" &&
this.info.version !== result.version
) {
this.info.upgradeReady = true;
this.info.setUpgradeReady(true);
this.status.stop();
}
// update extra root level keys that are stored under 'info'
+4
View File
@@ -138,6 +138,7 @@ class GridConfig {
setSortOrder: (o: SortOrderT) => void;
setSortLabel: (l: string) => void;
setSortReverse: (v: boolean | null) => void;
setGroupWidth: (w: number) => void;
constructor(groupWidth: number) {
this.config = localStored(
@@ -160,6 +161,9 @@ class GridConfig {
this.setSortReverse = action((v: boolean | null) => {
this.config.reverseSort = v;
});
this.setGroupWidth = action((w: number) => {
this.config.groupWidth = w;
});
}
}
+7 -7
View File
@@ -70,7 +70,7 @@ describe("SilenceFormStore.toggle", () => {
it("hide() set 'visible' to false", () => {
expect(store.toggle.visible).toBe(false);
store.toggle.visible = true;
store.toggle.show();
expect(store.toggle.visible).toBe(true);
store.toggle.hide();
expect(store.toggle.visible).toBe(false);
@@ -89,7 +89,7 @@ describe("SilenceFormStore.data", () => {
});
it("resetProgress() sets currentStage to UserInput", () => {
store.data.currentStage = "submit";
store.data.setStage("submit");
store.data.resetProgress();
expect(store.data.currentStage).toBe("form");
});
@@ -292,7 +292,7 @@ describe("SilenceFormStore.data", () => {
});
it("fillMatchersFromGroup() resets silenceID if set", () => {
store.data.silenceID = "12345";
store.data.setSilenceID("12345");
const group = MockGroup();
store.data.fillMatchersFromGroup(group, [], [], [group.alerts[0]]);
expect(store.data.silenceID).toBeNull();
@@ -401,14 +401,14 @@ describe("SilenceFormStore.data", () => {
}
it("toAlertmanagerPayload constains id when store.data.silenceID is set", () => {
store.data.silenceID = "12345";
store.data.setSilenceID("12345");
expect(store.data.toAlertmanagerPayload).toMatchObject({
id: "12345",
});
});
it("toAlertmanagerPayload doesn't contain id when store.data.silenceID is null", () => {
store.data.silenceID = null;
store.data.setSilenceID(null);
expect(store.data.toAlertmanagerPayload.id).toBeUndefined();
});
@@ -437,7 +437,7 @@ describe("SilenceFormStore.data", () => {
const b64 = store.data.toBase64;
store.data.setMatchers([]);
store.data.comment = "";
store.data.setComment("");
store.data.fromBase64(b64);
expect(store.data.matchers).toMatchObject([
@@ -550,7 +550,7 @@ describe("SilenceFormStore.data.isValid", () => {
MockMatcher("foo", [{ label: "bar", value: "bar" }]),
]);
store.data.setAuthor("me@example.com");
store.data.comment = "fake silence";
store.data.setComment("fake silence");
expect(store.data.isValid).toBe(true);
});
});
+28 -6
View File
@@ -237,13 +237,14 @@ interface SilenceFormStoreDataT {
resetStartEnd: () => void;
resetProgress: () => void;
resetSilenceID: () => void;
setSilenceID: (id: string) => void;
setSilenceID: (id: string | null) => void;
setAlertmanagers: (val: MultiValueOptionT[]) => void;
setAutofillMatchers: (v: boolean) => void;
setResetInputs: (v: boolean) => void;
setStageSubmit: () => void;
setStage: (val: SilenceFormStageT) => void;
setMatchers: (m: MatcherWithIDT[]) => void;
addEmptyMatcher: () => void;
addMatcherWithID: (m: MatcherWithIDT) => void;
deleteMatcher: (id: string) => void;
fillMatchersFromGroup: (
group: APIAlertGroupT,
@@ -265,6 +266,11 @@ interface SilenceFormStoreDataT {
incEnd: (minutes: number) => void;
decEnd: (minutes: number) => void;
setWasValidated: (v: boolean) => void;
setRequestsByCluster: (val: { [key: string]: ClusterRequestT }) => void;
setRequestsByClusterUpdate: (
key: string,
v: Partial<ClusterRequestT>
) => void;
readonly toAlertmanagerPayload: AlertmanagerSilencePayloadT;
readonly toDuration: DurationT;
}
@@ -411,7 +417,7 @@ class SilenceFormStore {
this.silenceID = null;
},
setSilenceID(id: string) {
setSilenceID(id: string | null) {
this.silenceID = id;
},
@@ -426,8 +432,8 @@ class SilenceFormStore {
this.resetInputs = v;
},
setStageSubmit() {
this.currentStage = "submit";
setStage(val: SilenceFormStageT) {
this.currentStage = val;
},
setMatchers(m: MatcherWithIDT[]) {
@@ -438,6 +444,9 @@ class SilenceFormStore {
addEmptyMatcher() {
this.matchers.push(NewEmptyMatcher());
},
addMatcherWithID(m: MatcherWithIDT) {
this.matchers.push(m);
},
deleteMatcher(id: string) {
// only delete matchers if we have more than 1
@@ -541,6 +550,16 @@ class SilenceFormStore {
this.wasValidated = v;
},
setRequestsByCluster(val: { [key: string]: ClusterRequestT }) {
this.requestsByCluster = val;
},
setRequestsByClusterUpdate(key: string, v: Partial<ClusterRequestT>) {
this.requestsByCluster[key] = {
...this.requestsByCluster[key],
...v,
};
},
get toAlertmanagerPayload() {
const startsAt = new Date(this.startsAt);
startsAt.setSeconds(0);
@@ -577,9 +596,10 @@ class SilenceFormStore {
setAlertmanagers: action.bound,
setAutofillMatchers: action.bound,
setResetInputs: action.bound,
setStageSubmit: action.bound,
setStage: action.bound,
setMatchers: action.bound,
addEmptyMatcher: action.bound,
addMatcherWithID: action.bound,
deleteMatcher: action.bound,
fillMatchersFromGroup: action.bound,
fillFormFromSilence: action.bound,
@@ -594,6 +614,8 @@ class SilenceFormStore {
decEnd: action.bound,
isValid: computed,
setWasValidated: action.bound,
setRequestsByCluster: action.bound,
setRequestsByClusterUpdate: action.bound,
toAlertmanagerPayload: computed,
toDuration: computed,
},
+15 -8
View File
@@ -81,16 +81,23 @@ const MockGroup = (
};
const MockGrid = (alertStore: AlertStore): void => {
alertStore.settings.values.alertAcknowledgement.enabled = true;
alertStore.data.receivers = ["by-cluster-service", "by-name"];
alertStore.settings.setValues({
...alertStore.settings.values,
...{
alertAcknowledgement: {
...alertStore.settings.values.alertAcknowledgement,
enabled: true,
},
},
});
alertStore.data.setReceivers(["by-cluster-service", "by-name"]);
const silence = MockSilence();
silence.startsAt = "2018-08-14T12:00:00Z";
silence.endsAt = "2018-08-14T18:00:00Z";
alertStore.data.silences = { prod: {} };
alertStore.data.silences["prod"][silence.id] = silence;
alertStore.data.setSilences({ prod: { [silence.id]: silence } });
alertStore.data.colors = {
alertStore.data.setColors({
group: {
group1: {
brightness: 50,
@@ -123,9 +130,9 @@ const MockGrid = (alertStore: AlertStore): void => {
background: "rgba(66,250,123,255)",
},
},
};
});
alertStore.data.counters = [
alertStore.data.setCounters([
{
name: "@receiver",
hits: 2,
@@ -193,7 +200,7 @@ const MockGrid = (alertStore: AlertStore): void => {
},
],
},
];
]);
const groups = [];
for (let i = 1; i <= 10; i++) {
+9
View File
@@ -8,11 +8,20 @@ import { useInView } from "react-intersection-observer";
// react-idle-timer >= 4.6.0
import "regenerator-runtime/runtime";
import { configure } from "mobx";
import { FetchRetryConfig } from "Common/Fetch";
import { useFetchGetMock } from "__fixtures__/useFetchGet";
import { useFetchGet } from "Hooks/useFetchGet";
configure({
enforceActions: "always",
//computedRequiresReaction: true,
//reactionRequiresObservable: true,
//observableRequiresReaction: true,
});
jest.mock("Hooks/useFetchGet");
jest.mock("react-intersection-observer");