Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ui): avoid updates on silence components re-rerenders #1880

Merged
merged 1 commit into from
Jun 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions ui/src/Components/SilenceModal/SilenceModalContent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ describe("<SilenceModalContent /> Editor", () => {
it("renders SilenceSubmitController when silenceFormStore.data.currentStage is 'Submit'", () => {
silenceFormStore.data.currentStage = SilenceFormStage.Submit;
const tree = MountedSilenceModalContent();
const ctrl = tree.find("SilenceSubmitController");
expect(ctrl).toHaveLength(1);
expect(tree.html()).toMatchSnapshot();
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from "react";
import React, { memo } from "react";
import PropTypes from "prop-types";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
Expand All @@ -8,7 +8,7 @@ import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { SilenceSubmitProgress } from "./SilenceSubmitProgress";

const SilenceSubmitController = ({ silenceFormStore, alertStore }) => {
const SilenceSubmitController = memo(({ silenceFormStore, alertStore }) => {
return (
<React.Fragment>
<div>
Expand All @@ -34,7 +34,7 @@ const SilenceSubmitController = ({ silenceFormStore, alertStore }) => {
</div>
</React.Fragment>
);
};
});
SilenceSubmitController.propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ describe("<SilenceSubmitController />", () => {
value: ["am2", "am3"],
});
const tree = ShallowSilenceSubmitController();
const alertmanagers = tree.find("SilenceSubmitProgress");
expect(alertmanagers).toHaveLength(2);
expect(tree.find("div").at(0).children()).toHaveLength(2);
});

it("resets the form on 'Back' button click", () => {
Expand Down
139 changes: 72 additions & 67 deletions ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, memo } from "react";
import PropTypes from "prop-types";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
Expand All @@ -10,79 +10,84 @@ import { APISilenceMatcher } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { useFetchAny } from "Hooks/useFetchAny";

const SilenceSubmitProgress = ({ alertStore, cluster, members, payload }) => {
const [upstreams, setUpstreams] = useState([]);
const { response, error, inProgress, responseURI } = useFetchAny(upstreams);
const [publicURIs, setPublicURIs] = useState({});
const SilenceSubmitProgress = memo(
({ alertStore, 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`);
} else {
const am = alertStore.data.getAlertmanagerByName(member);
if (am === undefined) {
console.error(`Alertmanager instance "${member}" not found`);
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 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 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,
},
},
},
});
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}
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>
<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>
);
};
);
}
);
SilenceSubmitProgress.propTypes = {
cluster: PropTypes.string.isRequired,
members: PropTypes.arrayOf(PropTypes.string).isRequired,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +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>"`;
94 changes: 46 additions & 48 deletions ui/src/Components/SilenceModal/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from "react";
import PropTypes from "prop-types";

import { observer } from "mobx-react-lite";
import { useObserver } from "mobx-react-lite";

import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
Expand All @@ -20,55 +20,53 @@ const SilenceModalContent = React.lazy(() =>
}))
);

const SilenceModal = observer(
({ alertStore, silenceFormStore, settingsStore }) => {
const onDeleteModalClose = React.useCallback(() => {
const event = new CustomEvent("remountModal");
window.dispatchEvent(event);
}, []);
const SilenceModal = ({ alertStore, silenceFormStore, settingsStore }) => {
const onDeleteModalClose = React.useCallback(() => {
const event = new CustomEvent("remountModal");
window.dispatchEvent(event);
}, []);

return (
<React.Fragment>
<li
className={`nav-item components-navbar-button ${
silenceFormStore.toggle.visible ? "border-info" : ""
}`}
>
<TooltipWrapper title="New silence">
<span
id="components-new-silence"
className="nav-link cursor-pointer"
onClick={silenceFormStore.toggle.toggle}
>
<FontAwesomeIcon icon={faBellSlash} />
</span>
</TooltipWrapper>
</li>
<Modal
isOpen={silenceFormStore.toggle.visible}
toggleOpen={silenceFormStore.toggle.toggle}
onExited={silenceFormStore.data.resetProgress}
>
<React.Suspense
fallback={
<h1 className="display-1 text-placeholder p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}
return useObserver(() => (
<React.Fragment>
<li
className={`nav-item components-navbar-button ${
silenceFormStore.toggle.visible ? "border-info" : ""
}`}
>
<TooltipWrapper title="New silence">
<span
id="components-new-silence"
className="nav-link cursor-pointer"
onClick={silenceFormStore.toggle.toggle}
>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={silenceFormStore.toggle.hide}
onDeleteModalClose={onDeleteModalClose}
/>
</React.Suspense>
</Modal>
</React.Fragment>
);
}
);
<FontAwesomeIcon icon={faBellSlash} />
</span>
</TooltipWrapper>
</li>
<Modal
isOpen={silenceFormStore.toggle.visible}
toggleOpen={silenceFormStore.toggle.toggle}
onExited={silenceFormStore.data.resetProgress}
>
<React.Suspense
fallback={
<h1 className="display-1 text-placeholder p-5 m-auto">
<FontAwesomeIcon icon={faSpinner} size="lg" spin />
</h1>
}
>
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={silenceFormStore.toggle.hide}
onDeleteModalClose={onDeleteModalClose}
/>
</React.Suspense>
</Modal>
</React.Fragment>
));
};
SilenceModal.propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
Expand Down