mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
feat(ui): lazy render alert group content
This is to avoid rendering lots of expensive components while they're not in the viewport. Should provide some performance improvements when there's plenty of alerts.
This commit is contained in:
13
ui/package-lock.json
generated
13
ui/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
"
|
||||
`;
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user