feat(ui): add a component to reload the page if needed

This commit is contained in:
Łukasz Mierzwa
2019-12-21 18:33:38 +00:00
parent 54550bacf9
commit 59470daffb
10 changed files with 156 additions and 7 deletions

View File

@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ReloadNeeded /> matches snapshot 1`] = `
"
<h1 class=\\"display-1 text-placeholder screen-center\\">
<div class=\\"container-fluid text-center\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"exclamation-circle\\"
class=\\"svg-inline--fa fa-exclamation-circle fa-w-16 screen-center-icon-big text-danger mb-4\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z\\"
>
</path>
</svg>
<p class=\\"lead text-white bg-secondary p-3 rounded text-wrap text-break\\">
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"spinner\\"
class=\\"svg-inline--fa fa-spinner fa-w-16 fa-spin mr-2\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M304 48c0 26.51-21.49 48-48 48s-48-21.49-48-48 21.49-48 48-48 48 21.49 48 48zm-48 368c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zm208-208c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.49-48-48-48zM96 256c0-26.51-21.49-48-48-48S0 229.49 0 256s21.49 48 48 48 48-21.49 48-48zm12.922 99.078c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.491-48-48-48zm294.156 0c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48c0-26.509-21.49-48-48-48zM108.922 60.922c-26.51 0-48 21.49-48 48s21.49 48 48 48 48-21.49 48-48-21.491-48-48-48z\\"
>
</path>
</svg>
All API connection attempts failed. This migth be caused by authentication middleware, will try to reload.
</p>
</div>
</h1>
"
`;

View File

@@ -0,0 +1,48 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle";
import { faSpinner } from "@fortawesome/free-solid-svg-icons/faSpinner";
import { CenteredMessage } from "Components/CenteredMessage";
class ReloadNeeded extends Component {
static propTypes = {
reloadAfter: PropTypes.number.isRequired
};
reloadApp = () => {
window.location.reload();
};
componentDidMount() {
const { reloadAfter } = this.props;
this.timer = setTimeout(this.reloadApp, reloadAfter);
}
componentWillUnmount() {
clearTimeout(this.timer);
this.timer = null;
}
render() {
return (
<CenteredMessage>
<div className="container-fluid text-center">
<FontAwesomeIcon
icon={faExclamationCircle}
className="screen-center-icon-big text-danger mb-4"
/>
<p className="lead text-white bg-secondary p-3 rounded text-wrap text-break">
<FontAwesomeIcon className="mr-2" icon={faSpinner} spin />
All API connection attempts failed. This migth be caused by
authentication middleware, will try to reload.
</p>
</div>
</CenteredMessage>
);
}
}
export { ReloadNeeded };

View File

@@ -0,0 +1,43 @@
import React from "react";
import { mount, shallow } from "enzyme";
import toDiffableHtml from "diffable-html";
import { ReloadNeeded } from ".";
beforeEach(() => {
jest.useFakeTimers();
jest.clearAllTimers();
});
afterEach(() => {
jest.clearAllTimers();
jest.restoreAllMocks();
});
describe("<ReloadNeeded />", () => {
it("matches snapshot", () => {
const tree = shallow(
<ReloadNeeded newVersion="1.2.3" reloadAfter={100000000} />
);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
it("calls window.location.reload after timer is done", () => {
const reloadSpy = jest
.spyOn(global.window.location, "reload")
.mockImplementation(() => {});
mount(<ReloadNeeded reloadAfter={100000000} />);
jest.runOnlyPendingTimers();
expect(reloadSpy).toBeCalled();
});
it("timer is cleared on unmount", () => {
const tree = mount(<ReloadNeeded reloadAfter={100000000} />);
const instance = tree.instance();
instance.componentWillUnmount();
expect(instance.timer).toBeNull();
});
});

View File

@@ -25,7 +25,7 @@ exports[`<UpgradeNeeded /> matches snapshot 1`] = `
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"spinner\\"
class=\\"svg-inline--fa fa-spinner fa-w-16 fa-spin mr-1\\"
class=\\"svg-inline--fa fa-spinner fa-w-16 fa-spin mr-2\\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 512 512\\"

View File

@@ -41,7 +41,7 @@ class UpgradeNeeded extends Component {
/>
</div>
<p className="lead text-muted">
<FontAwesomeIcon className="mr-1" icon={faSpinner} spin />
<FontAwesomeIcon className="mr-2" icon={faSpinner} spin />
Upgrading to a new version: {newVersion}
</p>
</div>

View File

@@ -10,6 +10,7 @@ import { AlertGrid } from "./AlertGrid";
import { FatalError } from "./FatalError";
import { UpstreamError } from "./UpstreamError";
import { UpgradeNeeded } from "./UpgradeNeeded";
import { ReloadNeeded } from "./ReloadNeeded";
import { EmptyGrid } from "./EmptyGrid";
const Grid = observer(
@@ -23,10 +24,6 @@ const Grid = observer(
render() {
const { alertStore, settingsStore, silenceFormStore } = this.props;
if (alertStore.status.error) {
return <FatalError message={alertStore.status.error} />;
}
if (alertStore.info.upgradeNeeded) {
return (
<UpgradeNeeded
@@ -36,6 +33,14 @@ const Grid = observer(
);
}
if (alertStore.info.reloadNeeded) {
return <ReloadNeeded reloadAfter={4000} />;
}
if (alertStore.status.error) {
return <FatalError message={alertStore.status.error} />;
}
if (
alertStore.data.upstreams.counters &&
alertStore.data.upstreams.counters.total === 1 &&

View File

@@ -8,6 +8,7 @@ import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FatalError } from "./FatalError";
import { UpgradeNeeded } from "./UpgradeNeeded";
import { ReloadNeeded } from "./ReloadNeeded";
import { EmptyGrid } from "./EmptyGrid";
import { Grid } from ".";
import { InternalError } from "../../ErrorBoundary";
@@ -32,6 +33,9 @@ storiesOf("Grid", module)
.add("UpgradeNeeded", () => {
return <UpgradeNeeded newVersion="1.2.3" reloadAfter={100000000} />;
})
.add("ReloadNeeded", () => {
return <ReloadNeeded reloadAfter={100000000} />;
})
.add("EmptyGrid", () => {
return (
<div className="text-center">

View File

@@ -94,6 +94,12 @@ describe("<Grid />", () => {
expect(tree.text()).toBe("<UpgradeNeeded />");
});
it("renders ReloadNeeded when alertStore.info.reloadNeeded=true", () => {
alertStore.info.reloadNeeded = 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.totalAlerts = 0;

View File

@@ -203,7 +203,7 @@ describe("<FilterInput Autosuggest />", () => {
tree.find("input").simulate("change", { target: { value: "bar" } });
await WaitForFetch(tree);
expect(fetch.mock.calls).toHaveLength(6);
expect(fetch.mock.calls).toHaveLength(11);
expect(fetch.mock.calls[0]).toContain("./autocomplete.json?term=bar");
expect(instance.inputStore.suggestions).toHaveLength(0);
});

View File

@@ -90,6 +90,7 @@ describe("<SilencePreview />", () => {
{
method: "GET",
credentials: "include",
mode: "cors",
redirect: "follow"
}
);
@@ -108,6 +109,7 @@ describe("<SilencePreview />", () => {
{
method: "GET",
credentials: "include",
mode: "cors",
redirect: "follow"
}
);