fix(ui): workaround multiple modals conflicting with each other

This commit is contained in:
Łukasz Mierzwa
2019-10-26 23:31:17 +01:00
parent 9e8a30aa22
commit 1fe5ced71c
12 changed files with 109 additions and 15 deletions

View File

@@ -274,7 +274,8 @@ const DeleteSilence = observer(
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
cluster: PropTypes.string.isRequired,
silence: APISilence.isRequired
silence: APISilence.isRequired,
onModalExit: PropTypes.func
};
toggle = observable(
@@ -288,7 +289,13 @@ const DeleteSilence = observer(
);
render() {
const { alertStore, silenceFormStore, cluster, silence } = this.props;
const {
alertStore,
silenceFormStore,
cluster,
silence,
onModalExit
} = this.props;
return (
<React.Fragment>
@@ -306,6 +313,7 @@ const DeleteSilence = observer(
isOpen={this.toggle.visible}
isUpper={true}
toggleOpen={this.toggle.toggle}
onExited={onModalExit}
>
<DeleteSilenceModalContent
alertStore={alertStore}

View File

@@ -24,7 +24,8 @@ const SilenceDetails = ({
silenceFormStore,
silence,
cluster,
onEditSilence
onEditSilence,
onDeleteModalClose
}) => {
let isExpired = moment(silence.endsAt) < moment();
let expiresClass = "";
@@ -126,6 +127,7 @@ const SilenceDetails = ({
silenceFormStore={silenceFormStore}
cluster={cluster}
silence={silence}
onModalExit={onDeleteModalClose}
/>
)}
</div>
@@ -138,7 +140,8 @@ SilenceDetails.propTypes = {
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
cluster: PropTypes.string.isRequired,
silence: APISilence.isRequired,
onEditSilence: PropTypes.func.isRequired
onEditSilence: PropTypes.func.isRequired,
onDeleteModalClose: PropTypes.func
};
export { SilenceDetails };

View File

@@ -24,7 +24,8 @@ const ManagedSilence = observer(
silence: APISilence.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
onDidUpdate: PropTypes.func
onDidUpdate: PropTypes.func,
onDeleteModalClose: PropTypes.func
};
// store collapse state, by default only silence comment is visible
@@ -61,7 +62,13 @@ const ManagedSilence = observer(
}
render() {
const { cluster, silence, alertStore, silenceFormStore } = this.props;
const {
cluster,
silence,
alertStore,
silenceFormStore,
onDeleteModalClose
} = this.props;
return (
<MountFade in={true}>
@@ -94,6 +101,7 @@ const ManagedSilence = observer(
alertStore={alertStore}
silenceFormStore={silenceFormStore}
onEditSilence={this.onEditSilence}
onDeleteModalClose={onDeleteModalClose}
/>
</div>
)}

View File

@@ -65,7 +65,11 @@ const Modal = observer(
}
componentWillUnmount() {
this.toggleBodyClass(false);
const { isOpen } = this.props;
if (isOpen) {
this.toggleBodyClass(false);
}
}
render() {

View File

@@ -24,6 +24,12 @@ describe("<Modal />", () => {
expect(document.body.className.split(" ")).toContain("modal-open");
});
it("'modal-open' class is not removed from body node after hidden modal is unmounted", () => {
const tree = MountedModal(false);
tree.unmount();
expect(document.body.className.split(" ")).toContain("modal-open");
});
it("'modal-open' class is removed from body node after modal is unmounted", () => {
const tree = MountedModal(true);
tree.unmount();

View File

@@ -53,7 +53,8 @@ const Browser = observer(
static propTypes = {
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired
settingsStore: PropTypes.instanceOf(Settings).isRequired,
onDeleteModalClose: PropTypes.func.isRequired
};
fetchTimer = null;
@@ -149,7 +150,12 @@ const Browser = observer(
}
render() {
const { alertStore, silenceFormStore, settingsStore } = this.props;
const {
alertStore,
silenceFormStore,
settingsStore,
onDeleteModalClose
} = this.props;
return (
<React.Fragment>
@@ -226,6 +232,7 @@ const Browser = observer(
silence={silence.silence}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
onDeleteModalClose={onDeleteModalClose}
/>
))}
</Provider>

View File

@@ -54,6 +54,8 @@ afterEach(() => {
clear();
});
const MockOnDeleteModalClose = jest.fn();
const MockSilenceList = count => {
let silences = [];
for (var index = 1; index <= count; index++) {
@@ -73,6 +75,7 @@ const MountedBrowser = () => {
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onDeleteModalClose={MockOnDeleteModalClose}
/>
);
};

View File

@@ -25,7 +25,8 @@ const SilenceModalContent = observer(
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
onHide: PropTypes.func.isRequired,
previewOpen: PropTypes.bool
previewOpen: PropTypes.bool,
onDeleteModalClose: PropTypes.func.isRequired
};
static defaultProps = {
previewOpen: false
@@ -37,7 +38,8 @@ const SilenceModalContent = observer(
silenceFormStore,
settingsStore,
onHide,
previewOpen
previewOpen,
onDeleteModalClose
} = this.props;
return (
@@ -106,6 +108,7 @@ const SilenceModalContent = observer(
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onDeleteModalClose={onDeleteModalClose}
/>
) : null}
</div>

View File

@@ -32,6 +32,7 @@ const ShallowSilenceModalContent = () => {
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
onHide={MockOnHide}
onDeleteModalClose={jest.fn()}
/>
);
};

View File

@@ -28,6 +28,16 @@ const SilenceModal = observer(
settingsStore: PropTypes.instanceOf(Settings).isRequired
};
constructor(props) {
super(props);
this.modalRef = React.createRef();
}
remountModal = () => {
this.modalRef.current.toggleBodyClass(true);
};
render() {
const { alertStore, silenceFormStore, settingsStore } = this.props;
@@ -48,6 +58,7 @@ const SilenceModal = observer(
</TooltipWrapper>
</li>
<Modal
ref={this.modalRef}
isOpen={silenceFormStore.toggle.visible}
toggleOpen={silenceFormStore.toggle.toggle}
onExited={silenceFormStore.data.resetProgress}
@@ -64,6 +75,7 @@ const SilenceModal = observer(
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={silenceFormStore.toggle.hide}
onDeleteModalClose={this.remountModal}
/>
</React.Suspense>
</Modal>

View File

@@ -2,14 +2,16 @@ import React from "react";
import { storiesOf } from "@storybook/react";
import { MockSilence } from "__mocks__/Alerts";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import {
SilenceFormStore,
NewEmptyMatcher,
MatcherValueToObject
MatcherValueToObject,
SilenceTabNames
} from "Stores/SilenceFormStore";
import { SilenceModalContent, TabNames } from "./SilenceModalContent";
import { SilenceModalContent } from "./SilenceModalContent";
import "Percy.scss";
@@ -52,6 +54,8 @@ storiesOf("SilenceModal", module)
silenceFormStore.data.comment = "fake silence";
silenceFormStore.data.resetStartEnd();
silenceFormStore.tab.current = SilenceTabNames.Editor;
return (
<SilenceModalContent
alertStore={alertStore}
@@ -59,7 +63,7 @@ storiesOf("SilenceModal", module)
settingsStore={settingsStore}
onHide={() => {}}
previewOpen={true}
openTab={TabNames.Editor}
onDeleteModalClose={() => {}}
/>
);
})
@@ -68,13 +72,41 @@ storiesOf("SilenceModal", module)
const settingsStore = new Settings();
const silenceFormStore = new SilenceFormStore();
silenceFormStore.tab.current = SilenceTabNames.Browser;
alertStore.data.upstreams = {
instances: [
{
name: "am1",
cluster: "am",
clusterMembers: ["am1"],
uri: "http://localhost:9093",
publicURI: "http://example.com",
error: "",
version: "0.15.3",
headers: {}
}
],
clusters: { am: ["am1"] }
};
let silences = [];
for (var index = 1; index <= 18; index++) {
const silence = MockSilence();
silence.id = `silence${index}`;
silences.push({
cluster: "am",
silence: silence
});
}
return (
<SilenceModalContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
settingsStore={settingsStore}
onHide={() => {}}
openTab={TabNames.Browser}
onDeleteModalClose={() => {}}
/>
);
});

View File

@@ -127,4 +127,11 @@ describe("<SilenceModal />", () => {
tree.unmount();
expect(document.body.className.split(" ")).not.toContain("modal-open");
});
it("'modal-open' class is preserved on body node after remountModal is called", () => {
silenceFormStore.toggle.visible = true;
const tree = MountedSilenceModal();
tree.instance().remountModal();
expect(document.body.className.split(" ")).toContain("modal-open");
});
});