From 42a9998f14b7dc7d6f9bc1633e4f6755c69dec67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Thu, 25 Jul 2019 16:27:42 +0100 Subject: [PATCH] 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. --- ui/package-lock.json | 13 ++ ui/package.json | 1 + .../__snapshots__/index.test.js.snap | 149 ++++++++++++++++ .../Grid/AlertGrid/AlertGroup/index.js | 166 +++++++++++++----- .../Grid/AlertGrid/AlertGroup/index.test.js | 55 ++++++ 5 files changed, 336 insertions(+), 48 deletions(-) create mode 100644 ui/src/Components/Grid/AlertGrid/AlertGroup/__snapshots__/index.test.js.snap diff --git a/ui/package-lock.json b/ui/package-lock.json index fa8ecd6d8..eed86c407 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -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", diff --git a/ui/package.json b/ui/package.json index 5ff8c80bb..5c031f15c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -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", diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/__snapshots__/index.test.js.snap new file mode 100644 index 000000000..f116851e6 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/__snapshots__/index.test.js.snap @@ -0,0 +1,149 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` lazy rendering matches snapshot when invisible 1`] = ` +" +
+
+
+ + + + + + + + + +
+ + + alertname: + + + Fake Alert + + +
+
+ + + groupName: + + + fakeGroup + + +
+
+ +
+ + 1 + +
+ +
+ + + + +
+
+
+
+
+
    +
  • +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
  • +
  • +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
  • +
+
+
+
+" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 3e0cc34a1..333792e8b 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -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 + }) => ( +
+
    + {group.alerts.slice(0, renderConfig.alertsToRender).map(alert => ( + + ))} + {showLoadButtons ? ( +
  • + + + {Math.min(renderConfig.alertsToRender, group.alerts.length)} + {" of "} + {group.alerts.length} + + +
  • + ) : null} +
+
+ ) +); + +const FakeLabel = ({ width, color }) => ( +
+ {" "} +
+); + +const AlertGroupPlaceholder = () => ( +
+
    +
  • + + + + + +
  • +
  • + + + + + +
  • +
+
+); + const AlertGroup = observer( class AlertGroup extends Component { static propTypes = { @@ -224,56 +307,43 @@ const AlertGroup = observer( setIsMenuOpen={this.renderConfig.setIsMenuOpen} /> {this.collapse.value ? null : ( -
-
    - {group.alerts - .slice(0, this.renderConfig.alertsToRender) - .map(alert => ( - - ))} - {group.alerts.length > this.defaultRenderCount ? ( -
  • - - - {Math.min( - this.renderConfig.alertsToRender, - group.alerts.length - )} - {" of "} - {group.alerts.length} - - -
  • - ) : null} -
-
+ this.defaultRenderCount + } + loadLess={this.loadLess} + loadMore={this.loadMore} + /> + } + placeholder={} + /> )} {this.collapse.value === false && group.alerts.length > 1 ? ( - + } + placeholder={null} /> ) : null} diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js index c79616719..1f092d8c2 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js @@ -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(" card theme", () => { expect(tree.find("GroupHeader").props().themedCounters).toBe(false); }); }); + +describe(" 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(); + }); +});