mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
committed by
Łukasz Mierzwa
parent
0c65bc7821
commit
d8a5b8dff1
+2
-1
@@ -67,7 +67,8 @@
|
||||
"jest": {
|
||||
"collectCoverageFrom": [
|
||||
"src/**/*.{js,ts,tsx}",
|
||||
"!src/**/*.stories.{js,ts,tsx}"
|
||||
"!src/**/*.stories.{js,ts,tsx}",
|
||||
"!src/react-app-env.d.ts"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
SilenceFormStage,
|
||||
NewEmptyMatcher,
|
||||
MatcherValueToObject,
|
||||
NewClusterRequest,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { QueryOperators } from "Common/Query";
|
||||
@@ -43,6 +44,9 @@ const SilenceForm = ({
|
||||
silenceFormStore.data.verifyStarEnd();
|
||||
}
|
||||
|
||||
// reset cluster request state
|
||||
silenceFormStore.data.requestsByCluster = {};
|
||||
|
||||
if (
|
||||
silenceFormStore.data.matchers.filter(
|
||||
(m) => m.name !== "" || m.values.length
|
||||
@@ -100,6 +104,12 @@ const SilenceForm = ({
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
let rbc = {};
|
||||
silenceFormStore.data.alertmanagers.forEach((am) => {
|
||||
rbc[am.label] = NewClusterRequest(am.label, am.value);
|
||||
});
|
||||
silenceFormStore.data.requestsByCluster = rbc;
|
||||
|
||||
settingsStore.silenceFormConfig.saveAuthor(silenceFormStore.data.author);
|
||||
|
||||
if (silenceFormStore.data.isValid)
|
||||
|
||||
@@ -1,26 +1,79 @@
|
||||
import React, { memo } from "react";
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useObserver } from "mobx-react-lite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons/faCheckCircle";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
const SilenceSubmitController = memo(({ silenceFormStore, alertStore }) => {
|
||||
return (
|
||||
const SilenceSubmitController = ({ silenceFormStore, alertStore }) => {
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
<div>
|
||||
{silenceFormStore.data.alertmanagers.map((am) => (
|
||||
<SilenceSubmitProgress
|
||||
key={am.label}
|
||||
cluster={am.label}
|
||||
members={am.value}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
))}
|
||||
<div className="table-responsive">
|
||||
<table className="table table-borderless">
|
||||
<tbody>
|
||||
{Object.values(silenceFormStore.data.requestsByCluster).map(
|
||||
(clusterRequest) => (
|
||||
<tr key={clusterRequest.cluster}>
|
||||
<td className="align-middle" style={{ width: "1%" }}>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faCheckCircle}
|
||||
className="text-success"
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<SilenceSubmitProgress
|
||||
key={clusterRequest.cluster}
|
||||
cluster={clusterRequest.cluster}
|
||||
members={clusterRequest.members}
|
||||
payload={silenceFormStore.data.toAlertmanagerPayload}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className="align-middle">{clusterRequest.cluster}</td>
|
||||
<td>
|
||||
<div
|
||||
className={`rounded text-center ${
|
||||
clusterRequest.isDone && clusterRequest.error
|
||||
? "bg-light"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{clusterRequest.isDone ? (
|
||||
clusterRequest.error ? (
|
||||
clusterRequest.error
|
||||
) : (
|
||||
<a
|
||||
href={clusterRequest.silenceLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{clusterRequest.silenceID}
|
||||
</a>
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className="d-flex flex-row-reverse">
|
||||
<button
|
||||
@@ -33,8 +86,8 @@ const SilenceSubmitController = memo(({ silenceFormStore, alertStore }) => {
|
||||
</button>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
));
|
||||
};
|
||||
SilenceSubmitController.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
|
||||
@@ -3,7 +3,11 @@ import React from "react";
|
||||
import { shallow } from "enzyme";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore, SilenceFormStage } from "Stores/SilenceFormStore";
|
||||
import {
|
||||
SilenceFormStore,
|
||||
SilenceFormStage,
|
||||
NewClusterRequest,
|
||||
} from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitController } from "./SilenceSubmitController";
|
||||
|
||||
let alertStore;
|
||||
@@ -12,31 +16,126 @@ let silenceFormStore;
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
});
|
||||
|
||||
const ShallowSilenceSubmitController = () => {
|
||||
return shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
alertStore.data.upstreams = {
|
||||
clusters: { ha: ["am1", "am2"], single: "single" },
|
||||
instances: [
|
||||
{
|
||||
name: "am1",
|
||||
uri: "http://am1.example.com",
|
||||
publicURI: "http://am1.example.com",
|
||||
readonly: false,
|
||||
headers: {},
|
||||
corsCredentials: "include",
|
||||
error: "",
|
||||
version: "0.17.0",
|
||||
cluster: "ha",
|
||||
clusterMembers: ["am1", "am2"],
|
||||
},
|
||||
{
|
||||
name: "am2",
|
||||
uri: "http://am2.example.com",
|
||||
publicURI: "http://am2.example.com",
|
||||
readonly: false,
|
||||
headers: {},
|
||||
corsCredentials: "include",
|
||||
error: "",
|
||||
version: "0.17.0",
|
||||
cluster: "ha",
|
||||
clusterMembers: ["am1", "am2"],
|
||||
},
|
||||
{
|
||||
name: "single",
|
||||
uri: "http://single.example.com",
|
||||
publicURI: "http://single.example.com",
|
||||
readonly: false,
|
||||
headers: {},
|
||||
corsCredentials: "include",
|
||||
error: "",
|
||||
version: "0.17.0",
|
||||
cluster: "ha",
|
||||
clusterMembers: ["single"],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
describe("<SilenceSubmitController />", () => {
|
||||
it("renders all passed SilenceSubmitProgress", () => {
|
||||
silenceFormStore.data.alertmanagers.push({ label: "am1", value: ["am1"] });
|
||||
silenceFormStore.data.alertmanagers.push({
|
||||
label: "ha",
|
||||
value: ["am2", "am3"],
|
||||
});
|
||||
const tree = ShallowSilenceSubmitController();
|
||||
expect(tree.find("div").at(0).children()).toHaveLength(2);
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
single: NewClusterRequest("single", ["single"]),
|
||||
};
|
||||
const tree = shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
expect(tree.find("tr")).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("renders spinner for pending requests", () => {
|
||||
const single = NewClusterRequest("single", ["single"]);
|
||||
silenceFormStore.data.requestsByCluster = { single: single };
|
||||
const tree = shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
expect(tree.find("tr")).toHaveLength(1);
|
||||
expect(tree.find("td").at(0).html()).toMatch(/fa-circle-notch/);
|
||||
expect(tree.find("td").at(1).text()).toBe("single");
|
||||
expect(tree.find("td").at(2).text()).toBe("");
|
||||
});
|
||||
|
||||
it("renders error for failed requests", () => {
|
||||
const single = NewClusterRequest("single", ["single"]);
|
||||
single.isDone = true;
|
||||
single.error = "fake error";
|
||||
silenceFormStore.data.requestsByCluster = { single: single };
|
||||
const tree = shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
expect(tree.find("tr")).toHaveLength(1);
|
||||
expect(tree.find("td").at(0).html()).toMatch(/fa-exclamation-circle/);
|
||||
expect(tree.find("td").at(1).text()).toBe("single");
|
||||
expect(tree.find("td").at(2).text()).toBe("fake error");
|
||||
});
|
||||
|
||||
it("renders silence link for completed requests", () => {
|
||||
const single = NewClusterRequest("single", ["single"]);
|
||||
single.isDone = true;
|
||||
single.silenceID = "123456789";
|
||||
single.silenceLink = "http://localhost";
|
||||
silenceFormStore.data.requestsByCluster = { single: single };
|
||||
const tree = shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
expect(tree.find("tr")).toHaveLength(1);
|
||||
expect(tree.find("td").at(0).html()).toMatch(/fa-check-circle/);
|
||||
expect(tree.find("td").at(1).text()).toBe("single");
|
||||
expect(tree.find("td").at(2).text()).toBe("123456789");
|
||||
expect(
|
||||
tree.find("td").at(2).find('a[href="http://localhost"]')
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("resets the form on 'Back' button click", () => {
|
||||
silenceFormStore.data.currentStage = SilenceFormStage.Submit;
|
||||
const tree = ShallowSilenceSubmitController();
|
||||
const tree = shallow(
|
||||
<SilenceSubmitController
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
const button = tree.find("button");
|
||||
button.simulate("click");
|
||||
expect(silenceFormStore.data.currentStage).toBe(SilenceFormStage.UserInput);
|
||||
|
||||
@@ -1,93 +1,75 @@
|
||||
import React, { useEffect, useState, memo } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCircleNotch } from "@fortawesome/free-solid-svg-icons/faCircleNotch";
|
||||
import { faCheckCircle } from "@fortawesome/free-regular-svg-icons/faCheckCircle";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
|
||||
|
||||
import { APISilenceMatcher } from "Models/API";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { useFetchAny } from "Hooks/useFetchAny";
|
||||
|
||||
const SilenceSubmitProgress = memo(
|
||||
({ alertStore, cluster, members, payload }) => {
|
||||
const [upstreams, setUpstreams] = useState([]);
|
||||
const { response, error, inProgress, responseURI } = useFetchAny(upstreams);
|
||||
const [publicURIs, setPublicURIs] = useState({});
|
||||
const SilenceSubmitProgress = ({
|
||||
alertStore,
|
||||
silenceFormStore,
|
||||
cluster,
|
||||
members,
|
||||
payload,
|
||||
}) => {
|
||||
const [upstreams, setUpstreams] = useState([]);
|
||||
const { response, error, inProgress, responseURI } = useFetchAny(upstreams);
|
||||
const [publicURIs, setPublicURIs] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
let uris = {};
|
||||
let membersToTry = [];
|
||||
for (const member of members) {
|
||||
if (alertStore.data.isReadOnlyAlertmanager(member)) {
|
||||
console.error(`Alertmanager instance "${member}" is read-only`);
|
||||
useEffect(() => {
|
||||
let uris = {};
|
||||
let membersToTry = [];
|
||||
for (const member of members) {
|
||||
if (alertStore.data.isReadOnlyAlertmanager(member)) {
|
||||
console.error(`Alertmanager instance "${member}" is read-only`);
|
||||
} else {
|
||||
const am = alertStore.data.getAlertmanagerByName(member);
|
||||
if (am === undefined) {
|
||||
console.error(`Alertmanager instance "${member}" not found`);
|
||||
} else {
|
||||
const am = alertStore.data.getAlertmanagerByName(member);
|
||||
if (am === undefined) {
|
||||
console.error(`Alertmanager instance "${member}" not found`);
|
||||
} else {
|
||||
const uri = `${am.uri}/api/v2/silences`;
|
||||
membersToTry.push({
|
||||
uri: uri,
|
||||
options: {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
credentials: am.corsCredentials,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...am.headers,
|
||||
},
|
||||
const uri = `${am.uri}/api/v2/silences`;
|
||||
membersToTry.push({
|
||||
uri: uri,
|
||||
options: {
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
credentials: am.corsCredentials,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...am.headers,
|
||||
},
|
||||
});
|
||||
uris[uri] = am.publicURI;
|
||||
}
|
||||
},
|
||||
});
|
||||
uris[uri] = am.publicURI;
|
||||
}
|
||||
}
|
||||
if (membersToTry.length) {
|
||||
setPublicURIs(uris);
|
||||
setUpstreams(membersToTry);
|
||||
}
|
||||
}, [alertStore.data, members, payload]);
|
||||
}
|
||||
if (membersToTry.length) {
|
||||
setPublicURIs(uris);
|
||||
setUpstreams(membersToTry);
|
||||
}
|
||||
}, [alertStore.data, members, payload]);
|
||||
|
||||
return (
|
||||
<div className="d-flex mb-2">
|
||||
<div className="p-2 flex-fill my-auto flex-grow-0 flex-shrink-0">
|
||||
{inProgress ? (
|
||||
<FontAwesomeIcon icon={faCircleNotch} spin />
|
||||
) : error ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="text-danger"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faCheckCircle} className="text-success" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-2 mr-1 flex-fill my-auto flex-grow-0 flex-shrink-0">
|
||||
{cluster}
|
||||
</div>
|
||||
<div
|
||||
className={`p-2 flex-fill flex-grow-1 flex-shrink-1 rounded text-center ${
|
||||
error ? "bg-light" : ""
|
||||
}`}
|
||||
>
|
||||
{error ? (
|
||||
error
|
||||
) : response && responseURI ? (
|
||||
<a
|
||||
href={`${publicURIs[responseURI]}/#/silences/${response.silenceID}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{response.silenceID}
|
||||
</a>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (!inProgress && error !== null) {
|
||||
silenceFormStore.data.requestsByCluster[cluster].isDone = true;
|
||||
silenceFormStore.data.requestsByCluster[cluster].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]}/#/silences/${response.silenceID}`;
|
||||
}
|
||||
}, [cluster, error, inProgress, publicURIs, response, responseURI]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
return <FontAwesomeIcon icon={faCircleNotch} spin />;
|
||||
};
|
||||
SilenceSubmitProgress.propTypes = {
|
||||
cluster: PropTypes.string.isRequired,
|
||||
members: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
@@ -100,6 +82,7 @@ SilenceSubmitProgress.propTypes = {
|
||||
id: PropTypes.string,
|
||||
}).isRequired,
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
};
|
||||
|
||||
export { SilenceSubmitProgress };
|
||||
|
||||
@@ -6,12 +6,16 @@ import { mount } from "enzyme";
|
||||
import fetchMock from "fetch-mock";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore, NewClusterRequest } from "Stores/SilenceFormStore";
|
||||
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";
|
||||
|
||||
let alertStore;
|
||||
let silenceFormStore;
|
||||
|
||||
beforeEach(() => {
|
||||
alertStore = new AlertStore([]);
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
|
||||
alertStore.data.upstreams = {
|
||||
instances: [
|
||||
{
|
||||
@@ -29,6 +33,12 @@ beforeEach(() => {
|
||||
],
|
||||
};
|
||||
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
mockAlertmanager: NewClusterRequest("mockAlertmanager", [
|
||||
"mockAlertmanager",
|
||||
]),
|
||||
};
|
||||
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.any(
|
||||
{
|
||||
@@ -59,6 +69,7 @@ const MountedSilenceSubmitProgress = () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -151,6 +162,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -164,6 +178,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
@@ -178,7 +193,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("will render error message from last failed cluster member", async () => {
|
||||
it("will use error message from last failed cluster member", async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.mock("http://am2.example.com/api/v2/silences", {
|
||||
throws: new TypeError("failed to fetch from am2"),
|
||||
@@ -215,6 +230,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
const tree = mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -228,6 +246,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
@@ -235,8 +254,12 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
});
|
||||
tree.update();
|
||||
expect(fetchMock.calls()).toHaveLength(2);
|
||||
expect(tree.text()).toBe("hafailed to fetch from am1");
|
||||
expect(silenceFormStore.data.requestsByCluster.ha).toMatchObject({
|
||||
isDone: true,
|
||||
error: "failed to fetch from am1",
|
||||
});
|
||||
});
|
||||
|
||||
it("will log an error if Alertmanager instance is missing from instances and try the next one", async () => {
|
||||
@@ -260,6 +283,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -273,6 +299,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
@@ -294,6 +321,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
clusters: { ha: ["am1", "am2"] },
|
||||
instances: [],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -307,6 +337,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
@@ -355,6 +386,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -368,6 +402,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
await act(async () => {
|
||||
@@ -420,6 +455,9 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
},
|
||||
],
|
||||
};
|
||||
silenceFormStore.data.requestsByCluster = {
|
||||
ha: NewClusterRequest("ha", ["am1", "am2"]),
|
||||
};
|
||||
|
||||
mount(
|
||||
<SilenceSubmitProgress
|
||||
@@ -433,6 +471,7 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
comment: "fake payload",
|
||||
}}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
);
|
||||
expect(fetchMock.calls()).toHaveLength(0);
|
||||
@@ -443,47 +482,6 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
expect(consoleSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("renders returned silence ID on successful fetch", async () => {
|
||||
const tree = MountedSilenceSubmitProgress();
|
||||
await act(async () => {
|
||||
await act(async () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
});
|
||||
// force re-render
|
||||
tree.update();
|
||||
const silenceLink = tree.find("a");
|
||||
expect(silenceLink).toHaveLength(1);
|
||||
expect(silenceLink.text()).toBe("123456789");
|
||||
});
|
||||
|
||||
it("renders returned error message on failed fetch", async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.any({
|
||||
status: 500,
|
||||
body: "mock error message",
|
||||
});
|
||||
const tree = MountedSilenceSubmitProgress();
|
||||
await act(async () => {
|
||||
await act(async () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
});
|
||||
expect(tree.text()).toBe("mockAlertmanagermock error message");
|
||||
});
|
||||
|
||||
it("renders success icon on successful fetch", async () => {
|
||||
const tree = MountedSilenceSubmitProgress();
|
||||
await act(async () => {
|
||||
await act(async () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(1);
|
||||
expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("renders silence link on successful fetch", async () => {
|
||||
const tree = MountedSilenceSubmitProgress();
|
||||
await act(async () => {
|
||||
@@ -492,12 +490,17 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
});
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("a").getDOMNode().getAttribute("href")).toBe(
|
||||
"http://example.com/#/silences/123456789"
|
||||
);
|
||||
expect(
|
||||
silenceFormStore.data.requestsByCluster.mockAlertmanager
|
||||
).toMatchObject({
|
||||
isDone: true,
|
||||
error: null,
|
||||
silenceID: "123456789",
|
||||
silenceLink: "http://example.com/#/silences/123456789",
|
||||
});
|
||||
});
|
||||
|
||||
it("renders error icon on failed fetch", async () => {
|
||||
it("sets error icon on failed fetch", async () => {
|
||||
fetchMock.reset();
|
||||
fetchMock.any({
|
||||
status: 500,
|
||||
@@ -510,7 +513,11 @@ describe("<SilenceSubmitProgress />", () => {
|
||||
});
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("FontAwesomeIcon.text-success")).toHaveLength(0);
|
||||
expect(tree.find("FontAwesomeIcon.text-danger")).toHaveLength(1);
|
||||
expect(
|
||||
silenceFormStore.data.requestsByCluster.mockAlertmanager
|
||||
).toMatchObject({
|
||||
isDone: true,
|
||||
error: "error message",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`<SilenceModalContent /> Editor renders SilenceSubmitController when silenceFormStore.data.currentStage is 'Submit' 1`] = `"<div class=\\"modal-header py-2\\"><nav class=\\"nav nav-pills nav-justified w-100\\"><span class=\\"nav-item nav-link cursor-pointer mx-1 px-2 active\\">Silence submitted</span><span class=\\"nav-item nav-link cursor-pointer mx-1 px-2 components-tab-inactive\\">Browse</span><button type=\\"button\\" class=\\"close\\"><span>×</span></button></nav></div><div class=\\"modal-body \\"><div></div><div class=\\"d-flex flex-row-reverse\\"><button type=\\"button\\" class=\\"btn btn-primary\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" data-prefix=\\"fas\\" data-icon=\\"arrow-left\\" class=\\"svg-inline--fa fa-arrow-left fa-w-14 pr-1\\" role=\\"img\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 448 512\\"><path fill=\\"currentColor\\" d=\\"M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z\\"></path></svg>Back</button></div></div>"`;
|
||||
exports[`<SilenceModalContent /> Editor renders SilenceSubmitController when silenceFormStore.data.currentStage is 'Submit' 1`] = `"<div class=\\"modal-header py-2\\"><nav class=\\"nav nav-pills nav-justified w-100\\"><span class=\\"nav-item nav-link cursor-pointer mx-1 px-2 active\\">Silence submitted</span><span class=\\"nav-item nav-link cursor-pointer mx-1 px-2 components-tab-inactive\\">Browse</span><button type=\\"button\\" class=\\"close\\"><span>×</span></button></nav></div><div class=\\"modal-body \\"><div class=\\"table-responsive\\"><table class=\\"table table-borderless\\"><tbody></tbody></table></div><div class=\\"d-flex flex-row-reverse\\"><button type=\\"button\\" class=\\"btn btn-primary\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" data-prefix=\\"fas\\" data-icon=\\"arrow-left\\" class=\\"svg-inline--fa fa-arrow-left fa-w-14 pr-1\\" role=\\"img\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 448 512\\"><path fill=\\"currentColor\\" d=\\"M257.5 445.1l-22.2 22.2c-9.4 9.4-24.6 9.4-33.9 0L7 273c-9.4-9.4-9.4-24.6 0-33.9L201.4 44.7c9.4-9.4 24.6-9.4 33.9 0l22.2 22.2c9.5 9.5 9.3 25-.4 34.3L136.6 216H424c13.3 0 24 10.7 24 24v32c0 13.3-10.7 24-24 24H136.6l120.5 114.8c9.8 9.3 10 24.8.4 34.3z\\"></path></svg>Back</button></div></div>"`;
|
||||
|
||||
@@ -104,6 +104,15 @@ const MatchersFromGroup = (group, stripLabels, alerts, onlyActive) => {
|
||||
return matchers;
|
||||
};
|
||||
|
||||
const NewClusterRequest = (cluster, members) => ({
|
||||
cluster: cluster,
|
||||
members: members,
|
||||
isDone: false,
|
||||
silenceID: null,
|
||||
silenceLink: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const GenerateAlertmanagerSilenceData = (
|
||||
startsAt,
|
||||
endsAt,
|
||||
@@ -188,6 +197,7 @@ class SilenceFormStore {
|
||||
endsAt: addHours(new Date(), 1),
|
||||
comment: "",
|
||||
author: "",
|
||||
requestsByCluster: {},
|
||||
|
||||
get isValid() {
|
||||
if (this.alertmanagers.length === 0) return false;
|
||||
@@ -371,4 +381,5 @@ export {
|
||||
SilenceTabNames,
|
||||
MatchersFromGroup,
|
||||
GenerateAlertmanagerSilenceData,
|
||||
NewClusterRequest,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user