From c982746673907323551cd1c6f4a965d914e0a492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Tue, 23 Jun 2020 14:45:12 +0100 Subject: [PATCH] fix(ui): avoid updates on silence components re-rerenders Fixes #1878 --- .../SilenceModal/SilenceModalContent.test.js | 3 +- .../SilenceSubmit/SilenceSubmitController.js | 6 +- .../SilenceSubmitController.test.js | 3 +- .../SilenceSubmit/SilenceSubmitProgress.js | 139 +++++++++--------- .../SilenceModalContent.test.js.snap | 3 + ui/src/Components/SilenceModal/index.js | 94 ++++++------ 6 files changed, 126 insertions(+), 122 deletions(-) create mode 100644 ui/src/Components/SilenceModal/__snapshots__/SilenceModalContent.test.js.snap diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.test.js b/ui/src/Components/SilenceModal/SilenceModalContent.test.js index 4f65b97dc..5f1136be0 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.test.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.test.js @@ -145,8 +145,7 @@ describe(" 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(); }); }); diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js index 73d183dca..07024dd65 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { memo } from "react"; import PropTypes from "prop-types"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -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 (
@@ -34,7 +34,7 @@ const SilenceSubmitController = ({ silenceFormStore, alertStore }) => {
); -}; +}); SilenceSubmitController.propTypes = { alertStore: PropTypes.instanceOf(AlertStore).isRequired, silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.test.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.test.js index 08068f3b8..a8ddb66f8 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.test.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitController.test.js @@ -31,8 +31,7 @@ describe("", () => { 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", () => { diff --git a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js index bbf3e3f95..7ac5c81c9 100644 --- a/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js +++ b/ui/src/Components/SilenceModal/SilenceSubmit/SilenceSubmitProgress.js @@ -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"; @@ -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 ( -
-
- {inProgress ? ( - - ) : error ? ( - - ) : ( - - )} -
-
- {cluster} + return ( +
+
+ {inProgress ? ( + + ) : error ? ( + + ) : ( + + )} +
+
+ {cluster} +
+
+ {error ? ( + error + ) : response && responseURI ? ( + + {response.silenceID} + + ) : null} +
-
- {error ? ( - error - ) : response && responseURI ? ( - - {response.silenceID} - - ) : null} -
-
- ); -}; + ); + } +); SilenceSubmitProgress.propTypes = { cluster: PropTypes.string.isRequired, members: PropTypes.arrayOf(PropTypes.string).isRequired, diff --git a/ui/src/Components/SilenceModal/__snapshots__/SilenceModalContent.test.js.snap b/ui/src/Components/SilenceModal/__snapshots__/SilenceModalContent.test.js.snap new file mode 100644 index 000000000..55ffd5d02 --- /dev/null +++ b/ui/src/Components/SilenceModal/__snapshots__/SilenceModalContent.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` Editor renders SilenceSubmitController when silenceFormStore.data.currentStage is 'Submit' 1`] = `"
"`; diff --git a/ui/src/Components/SilenceModal/index.js b/ui/src/Components/SilenceModal/index.js index dee05df69..5ee68d670 100644 --- a/ui/src/Components/SilenceModal/index.js +++ b/ui/src/Components/SilenceModal/index.js @@ -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"; @@ -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 ( - -
  • - - - - - -
  • - - - - - } + return useObserver(() => ( + +
  • + + - - - - - ); - } -); + + + +
  • + + + + + } + > + + + +
    + )); +}; SilenceModal.propTypes = { alertStore: PropTypes.instanceOf(AlertStore).isRequired, silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,