Merge pull request #843 from prymitive/lazy-render

feat(ui): lazy render alert group content
This commit is contained in:
Łukasz Mierzwa
2019-07-27 09:11:49 +01:00
committed by GitHub
5 changed files with 336 additions and 48 deletions

13
ui/package-lock.json generated
View File

@@ -12452,6 +12452,14 @@
"prop-types": "^15.6.2"
}
},
"react-lazily-render": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-lazily-render/-/react-lazily-render-1.2.0.tgz",
"integrity": "sha512-7G3w4m187V1VXs2LmF5e++ZPj3BqZ0lSsD63Tnz1L+MqsPCW55qqsqf1cM/uTHAR8gRK0tARosx0o9mJUyBeAg==",
"requires": {
"scrollparent": "^2.0.1"
}
},
"react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
@@ -13471,6 +13479,11 @@
"ajv-keywords": "^3.1.0"
}
},
"scrollparent": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/scrollparent/-/scrollparent-2.0.1.tgz",
"integrity": "sha1-cV1bnMV3YPsivczDvvtb/gaxoxc="
},
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",

View File

@@ -38,6 +38,7 @@
"react-input-range": "1.3.0",
"react-js-pagination": "3.0.2",
"react-json-pretty": "2.1.0",
"react-lazily-render": "1.2.0",
"react-linkify": "0.2.2",
"react-masonry-infinite": "1.2.2",
"react-moment": "0.9.2",

View File

@@ -0,0 +1,149 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<AlertGroupContent /> lazy rendering matches snapshot when invisible 1`] = `
"
<div class=\\"components-grid-alertgrid-alertgroup p-1 components-animation-fade-appear components-animation-fade-appear-active\\">
<div class=\\"card bg-light\\">
<h5 class=\\"card-header mb-0 d-flex flex-row px-2 py-1 \\">
<span class=\\"flex-shrink-0 flex-grow-0\\">
<span class=\\"text-muted cursor-pointer badge pl-0 components-label mr-0 components-grid-alertgroup-099c5ca6d1c92f615b13056b935d0c8dee70f18c\\"
data-toggle=\\"dropdown\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"ellipsis-v\\"
class=\\"svg-inline--fa fa-ellipsis-v fa-w-6 \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 192 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z\\"
>
</path>
</svg>
</span>
</span>
<span class=\\"flex-shrink-1 flex-grow-1\\"
style=\\"min-width: 0;\\"
>
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-345\\"
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
>
<span class=\\"components-label badge badge-dark components-label-dark components-label-with-hover\\">
<span class=\\"components-label-name\\">
alertname:
</span>
<span class=\\"components-label-value\\">
Fake Alert
</span>
</span>
</div>
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-346\\"
data-original-title=\\"Click to only show alerts with this label or Alt+Click to hide them\\"
>
<span class=\\"components-label badge badge-warning components-label-dark components-label-with-hover\\">
<span class=\\"components-label-name\\">
groupName:
</span>
<span class=\\"components-label-value\\">
fakeGroup
</span>
</span>
</div>
</span>
<span class=\\"flex-shrink-0 flex-grow-0 ml-auto pl-1\\">
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-347\\"
data-original-title=\\"Click to only show active alerts or Alt+Click to hide them\\"
>
<span class=\\"components-label badge badge-danger badge-pill components-label-with-hover\\">
1
</span>
</div>
<span class=\\"text-muted cursor-pointer badge px-0 components-label mr-0\\">
<div class
style=\\"display: inline-block; max-width: 100%;\\"
data-tooltipped
aria-describedby=\\"tippy-tooltip-348\\"
data-original-title=\\"Toggle group details\\"
>
<svg aria-hidden=\\"true\\"
focusable=\\"false\\"
data-prefix=\\"fas\\"
data-icon=\\"chevron-down\\"
class=\\"svg-inline--fa fa-chevron-down fa-w-14 \\"
role=\\"img\\"
xmlns=\\"http://www.w3.org/2000/svg\\"
viewbox=\\"0 0 448 512\\"
>
<path fill=\\"currentColor\\"
d=\\"M207.029 381.476L12.686 187.132c-9.373-9.373-9.373-24.569 0-33.941l22.667-22.667c9.357-9.357 24.522-9.375 33.901-.04L224 284.505l154.745-154.021c9.379-9.335 24.544-9.317 33.901.04l22.667 22.667c9.373 9.373 9.373 24.569 0 33.941L240.971 381.476c-9.373 9.372-24.569 9.372-33.942 0z\\"
>
</path>
</svg>
</div>
</span>
</span>
</h5>
<div class=\\"card-body bg-white px-2 py-1\\">
<ul class=\\"list-group\\">
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-light\\">
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 100%; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-secondary text-secondary\\"
style=\\"width: 70px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 150px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 80px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 70px; height: 1.3rem;\\"
>
</div>
</li>
<li class=\\"components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-light\\">
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 100%; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-secondary text-secondary\\"
style=\\"width: 70px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 80px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 90px; height: 1.3rem;\\"
>
</div>
<div class=\\"components-label badge badge-light text-light\\"
style=\\"width: 120px; height: 1.3rem;\\"
>
</div>
</li>
</ul>
</div>
</div>
</div>
"
`;

View File

@@ -6,6 +6,8 @@ import { observable, action, toJS } from "mobx";
import hash from "object-hash";
import LazilyRender from "react-lazily-render";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
@@ -50,6 +52,87 @@ const AllAlertsAreUsingSameAlertmanagers = alerts => {
);
};
const AlertGroupContent = observer(
({
alertStore,
silenceFormStore,
group,
showAlertmanagers,
afterUpdate,
renderConfig,
showLoadButtons,
loadLess,
loadMore
}) => (
<div className="card-body bg-white px-2 py-1">
<ul className="list-group">
{group.alerts.slice(0, renderConfig.alertsToRender).map(alert => (
<Alert
key={hash(alert.labels)}
group={group}
alert={alert}
showAlertmanagers={showAlertmanagers}
showReceiver={group.alerts.length === 1}
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
setIsMenuOpen={renderConfig.setIsMenuOpen}
/>
))}
{showLoadButtons ? (
<li className="list-group-item border-0 p-0 text-center">
<LoadButton
icon={faMinus}
action={loadLess}
tooltip="Show fewer alerts in this group"
/>
<small className="text-muted mx-2">
{Math.min(renderConfig.alertsToRender, group.alerts.length)}
{" of "}
{group.alerts.length}
</small>
<LoadButton
icon={faPlus}
action={loadMore}
tooltip="Show more alerts in this group"
/>
</li>
) : null}
</ul>
</div>
)
);
const FakeLabel = ({ width, color }) => (
<div
className={`components-label badge badge-${color} text-${color}`}
style={{ width: width, height: "1.3rem" }}
>
{" "}
</div>
);
const AlertGroupPlaceholder = () => (
<div className="card-body bg-white px-2 py-1">
<ul className="list-group">
<li className="components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-light">
<FakeLabel width="100%" color="light" />
<FakeLabel width="70px" color="secondary" />
<FakeLabel width="150px" color="light" />
<FakeLabel width="80px" color="light" />
<FakeLabel width="70px" color="light" />
</li>
<li className="components-grid-alertgrid-alertgroup-alert list-group-item pl-1 pr-0 py-0 my-1 rounded-0 border-left-1 border-right-0 border-top-0 border-bottom-0 border-light">
<FakeLabel width="100%" color="light" />
<FakeLabel width="70px" color="secondary" />
<FakeLabel width="80px" color="light" />
<FakeLabel width="90px" color="light" />
<FakeLabel width="120px" color="light" />
</li>
</ul>
</div>
);
const AlertGroup = observer(
class AlertGroup extends Component {
static propTypes = {
@@ -224,56 +307,43 @@ const AlertGroup = observer(
setIsMenuOpen={this.renderConfig.setIsMenuOpen}
/>
{this.collapse.value ? null : (
<div className="card-body bg-white px-2 py-1">
<ul className="list-group">
{group.alerts
.slice(0, this.renderConfig.alertsToRender)
.map(alert => (
<Alert
key={hash(alert.labels)}
group={group}
alert={alert}
showAlertmanagers={
showAlertmanagers && !showAlertmanagersInFooter
}
showReceiver={group.alerts.length === 1}
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
setIsMenuOpen={this.renderConfig.setIsMenuOpen}
/>
))}
{group.alerts.length > this.defaultRenderCount ? (
<li className="list-group-item border-0 p-0 text-center">
<LoadButton
icon={faMinus}
action={this.loadLess}
tooltip="Show fewer alerts in this group"
/>
<small className="text-muted mx-2">
{Math.min(
this.renderConfig.alertsToRender,
group.alerts.length
)}
{" of "}
{group.alerts.length}
</small>
<LoadButton
icon={faPlus}
action={this.loadMore}
tooltip="Show more alerts in this group"
/>
</li>
) : null}
</ul>
</div>
<LazilyRender
key={group.id}
offset={100}
onRender={afterUpdate}
content={
<AlertGroupContent
alertStore={alertStore}
silenceFormStore={silenceFormStore}
group={group}
showAlertmanagers={
showAlertmanagers && !showAlertmanagersInFooter
}
afterUpdate={afterUpdate}
renderConfig={this.renderConfig}
showLoadButtons={
group.alerts.length > this.defaultRenderCount
}
loadLess={this.loadLess}
loadMore={this.loadMore}
/>
}
placeholder={<AlertGroupPlaceholder />}
/>
)}
{this.collapse.value === false && group.alerts.length > 1 ? (
<GroupFooter
group={group}
alertmanagers={footerAlertmanagers}
afterUpdate={afterUpdate}
silenceFormStore={silenceFormStore}
<LazilyRender
offset={100}
onRender={afterUpdate}
content={
<GroupFooter
group={group}
alertmanagers={footerAlertmanagers}
afterUpdate={afterUpdate}
silenceFormStore={silenceFormStore}
/>
}
placeholder={null}
/>
) : null}
</div>

View File

@@ -6,6 +6,8 @@ import { mount } from "enzyme";
import moment from "moment";
import toDiffableHtml from "diffable-html";
import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js";
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
@@ -30,6 +32,12 @@ const MockGroup = groupName => {
let originalInnerWidth;
const MockedLazyRender = jest.fn(({ content }) => {
return content;
});
jest.mock("react-lazily-render", () => props => MockedLazyRender(props));
beforeAll(() => {
originalInnerWidth = global.innerWidth;
});
@@ -372,3 +380,50 @@ describe("<AlertGroup /> card theme", () => {
expect(tree.find("GroupHeader").props().themedCounters).toBe(false);
});
});
describe("<AlertGroupContent /> lazy rendering", () => {
// need to add 2x mockImplementationOnce per render since we use it twice
// inside each AlertGroupContent
const RenderPlaceholder = ({ placeholder }) => {
return placeholder;
};
it("renders FilteringLabel when visible", () => {
MockAlerts(5);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.find("FilteringLabel").length).toBe(8);
});
it("renders GroupFooter when visible", () => {
MockAlerts(5);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.find("GroupFooter").length).toBe(1);
});
it("renders AlertGroupPlaceholder when invisible", () => {
MockAlerts(5);
MockedLazyRender.mockImplementationOnce(
RenderPlaceholder
).mockImplementationOnce(RenderPlaceholder);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.find("AlertGroupPlaceholder").length).toBe(1);
});
it("doesn't render GroupFooter when invisible", () => {
MockAlerts(5);
MockedLazyRender.mockImplementationOnce(
RenderPlaceholder
).mockImplementationOnce(RenderPlaceholder);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.find("GroupFooter").length).toBe(0);
});
it("matches snapshot when invisible", () => {
MockAlerts(5);
MockedLazyRender.mockImplementationOnce(
RenderPlaceholder
).mockImplementationOnce(RenderPlaceholder);
const tree = MountedAlertGroup(jest.fn(), false);
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
});
});