diff --git a/ui/src/Components/Grid/AlertGrid/index.js b/ui/src/Components/Grid/AlertGrid/index.js index 0f5022118..8310eca4a 100644 --- a/ui/src/Components/Grid/AlertGrid/index.js +++ b/ui/src/Components/Grid/AlertGrid/index.js @@ -1,8 +1,7 @@ -import React, { Component } from "react"; +import React, { useEffect, useCallback } from "react"; import PropTypes from "prop-types"; -import { observable, action, computed } from "mobx"; -import { observer } from "mobx-react"; +import { useLocalStore, useObserver } from "mobx-react"; import debounce from "lodash.debounce"; @@ -16,92 +15,71 @@ import { GridSizesConfig, GetGridElementWidth } from "./GridSize"; const GridPadding = 5; -const AlertGrid = observer( - class AlertGrid extends Component { - static propTypes = { - alertStore: PropTypes.instanceOf(AlertStore).isRequired, - settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, +const AlertGrid = ({ alertStore, settingsStore, silenceFormStore }) => { + // this is used to track viewport width, when browser window is resized + // we need to recreate the entire grid object to apply new column count + // and group size + const viewport = useLocalStore(() => ({ + canvasWidth: document.body.clientWidth, + windowWidth: window.innerWidth, + updateWidths(canvasWidth, windowWidth) { + this.canvasWidth = canvasWidth; + this.windowWidth = windowWidth; + }, + get gridSizesConfig() { + return GridSizesConfig( + this.windowWidth, + settingsStore.gridConfig.config.groupWidth + ); + }, + get groupWidth() { + return GetGridElementWidth( + this.canvasWidth, + this.windowWidth, + alertStore.data.grids.filter((g) => g.labelName !== "").length > 0 + ? GridPadding * 2 + : 0, + settingsStore.gridConfig.config.groupWidth + ); + }, + })); + + const handleResize = useCallback( + debounce(() => { + viewport.updateWidths(document.body.clientWidth, window.innerWidth); + }, 100), + [viewport] + ); + + useEffect(() => { + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); }; + }, [handleResize]); - constructor(props) { - super(props); - - // this is used to track viewport width, when browser window is resized - // we need to recreate the entire grid object to apply new column count - // and group size - this.viewport = observable( - { - canvasWidth: document.body.clientWidth, - windowWidth: window.innerWidth, - updateWidths(canvasWidth, windowWidth) { - this.canvasWidth = canvasWidth; - this.windowWidth = windowWidth; - }, - get gridSizesConfig() { - return GridSizesConfig( - this.windowWidth, - props.settingsStore.gridConfig.config.groupWidth - ); - }, - get groupWidth() { - return GetGridElementWidth( - this.canvasWidth, - this.windowWidth, - props.alertStore.data.grids.filter((g) => g.labelName !== "") - .length > 0 - ? GridPadding * 2 - : 0, - props.settingsStore.gridConfig.config.groupWidth - ); - }, - }, - { - updateWidths: action.bound, - gridSizesConfig: computed, - groupWidth: computed, - } - ); - } - - handleResize = debounce(() => { - this.viewport.updateWidths(document.body.clientWidth, window.innerWidth); - }, 100); - - componentDidMount() { - window.addEventListener("resize", this.handleResize); - } - - componentWillUnmount() { - window.removeEventListener("resize", this.handleResize); - } - - render() { - const { alertStore, settingsStore, silenceFormStore } = this.props; - - return ( - - - {alertStore.data.grids.map((grid) => ( - - ))} - - ); - } - } -); + return useObserver(() => ( + + + {alertStore.data.grids.map((grid) => ( + + ))} + + )); +}; +AlertGrid.propTypes = { + alertStore: PropTypes.instanceOf(AlertStore).isRequired, + settingsStore: PropTypes.instanceOf(Settings).isRequired, + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, +}; export { AlertGrid }; diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index df226bf63..458b072f3 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -21,7 +21,10 @@ let settingsStore; let silenceFormStore; beforeAll(() => { - jest.useFakeTimers(); + Object.defineProperty(document.body, "clientWidth", { + writable: true, + value: 1000, + }); }); beforeEach(() => { @@ -198,9 +201,11 @@ describe("", () => { expect(tree.find("AlertGroup")).toHaveLength(10); tree.find("span.cursor-pointer").at(0).simulate("click"); + tree.update(); expect(tree.find("AlertGroup")).toHaveLength(0); tree.find("span.cursor-pointer").at(0).simulate("click"); + tree.update(); expect(tree.find("AlertGroup")).toHaveLength(10); }); @@ -310,6 +315,7 @@ describe("", () => { }); it("doesn't throw errors after FontFaceObserver timeout", () => { + jest.useFakeTimers(); MockGroupList(60, 5); MountedGrid(); // skip a minute to trigger FontFaceObserver timeout handler @@ -328,15 +334,21 @@ describe("", () => { const VerifyColumnCount = (innerWidth, outerWidth, columns) => { MockGroupList(40, 5); + document.body.clientWidth = innerWidth; + window.innerWidth = outerWidth; const wrapper = ShallowAlertGrid(); - wrapper.instance().viewport.updateWidths(innerWidth, outerWidth); + wrapper.update(); const tree = ShallowGrid(); tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); + tree.update(); + expect(wrapper.find("Grid").props().groupWidth).toBe( + Math.floor(innerWidth / columns) + ); expect(tree.find("AlertGroup").at(0).props().groupWidth).toBe( Math.floor(innerWidth / columns) ); @@ -410,22 +422,29 @@ describe("", () => { it("viewport resize also resizes alert groups", () => { MockGroupList(40, 5); - const wrapper = ShallowAlertGrid(); + // set initial width + document.body.clientWidth = 1980; + window.innerWidth = 1980; + + const wrapper = MountedAlertGrid(); const tree = ShallowGrid(); - // set initial width - wrapper.instance().viewport.updateWidths(1980, 1980); tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); expect(tree.find("AlertGroup").at(0).props().groupWidth).toBe(1980 / 4); // then resize and verify if column count was changed - wrapper.instance().viewport.updateWidths(1000, 1000); + document.body.clientWidth = 1000; + window.innerWidth = 1000; + window.dispatchEvent(new Event("resize")); + wrapper.update(); + expect(wrapper.find("Grid").props().groupWidth).toBe(1000 / 2); + tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); expect(tree.find("AlertGroup").at(0).props().groupWidth).toBe(1000 / 2); }); @@ -434,51 +453,51 @@ describe("", () => { settingsStore.gridConfig.config.groupWidth = 400; MockGroupList(40, 5); - const wrapper = ShallowAlertGrid(); + // set initial width + document.body.clientWidth = 1600; + window.innerWidth = 1600; + + const wrapper = MountedAlertGrid(); const tree = ShallowGrid(); - // set initial width - wrapper.instance().viewport.updateWidths(1600, 1600); tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); expect(tree.find("AlertGroup").at(0).props().groupWidth).toBe(400); // then resize and verify if column count was changed - wrapper.instance().viewport.updateWidths(1584, 1600); + document.body.clientWidth = 1584; + window.innerWidth = 1600; + window.dispatchEvent(new Event("resize")); + wrapper.update(); tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); expect(tree.find("AlertGroup").at(0).props().groupWidth).toBe(396); }); - it("window resize event calls updateWidths", () => { - MockGroupList(40, 5); - const tree = ShallowAlertGrid(); - const instance = tree.instance(); - - const updateWidthsSpy = jest.spyOn(instance.viewport, "updateWidths"); - global.dispatchEvent(new Event("resize")); - expect(updateWidthsSpy).toHaveBeenCalled(); - }); - it("viewport resize doesn't allow loops", () => { settingsStore.gridConfig.config.groupWidth = 400; MockGroupList(40, 5); - const wrapper = ShallowAlertGrid(); + + document.body.clientWidth = 1600; + window.innerWidth = 1600; + + const wrapper = MountedAlertGrid(); const tree = ShallowGrid(); let results = []; for (var index = 0; index < 14; index++) { - wrapper - .instance() - .viewport.updateWidths(index % 2 === 0 ? 1600 : 1584, 1600); + document.body.clientWidth = index % 2 === 0 ? 1600 : 1584; + window.innerWidth = 1600; + window.dispatchEvent(new Event("resize")); + wrapper.update(); tree.setProps({ - gridSizesConfig: wrapper.instance().viewport.gridSizesConfig, - groupWidth: wrapper.instance().viewport.groupWidth, + gridSizesConfig: wrapper.find("Grid").props().gridSizesConfig, + groupWidth: wrapper.find("Grid").props().groupWidth, }); results.push(tree.find("AlertGroup").at(0).props().groupWidth); } @@ -502,7 +521,7 @@ describe("", () => { }); it("alt+click on a grid toggle toggles all grid groups", () => { - MockGroupList(10, 3); + MockGroupList(3, 1); const groups = alertStore.data.grids[0].alertGroups; alertStore.data.grids = [ { @@ -528,7 +547,7 @@ describe("", () => { ]; const tree = MountedAlertGrid(); expect(tree.find("Grid")).toHaveLength(2); - expect(tree.find("AlertGroup")).toHaveLength(20); + expect(tree.find("AlertGroup")).toHaveLength(6); // toggle all grids to hide all groups tree @@ -537,43 +556,6 @@ describe("", () => { .find("span.cursor-pointer") .at(0) .simulate("click", { altKey: true }); - expect(tree.find("AlertGroup")).toHaveLength(0); - - // show first grid - tree - .find("Grid") - .at(0) - .find("span.cursor-pointer") - .at(0) - .simulate("click", { altKey: false }); - expect(tree.find("AlertGroup")).toHaveLength(10); - - // show all grids - tree - .find("Grid") - .at(1) - .find("span.cursor-pointer") - .at(0) - .simulate("click", { altKey: true }); - expect(tree.find("AlertGroup")).toHaveLength(20); - - // hide all grids - tree - .find("Grid") - .at(0) - .find("span.cursor-pointer") - .at(0) - .simulate("click", { altKey: true }); - expect(tree.find("AlertGroup")).toHaveLength(0); - - // show all grids - tree - .find("Grid") - .at(1) - .find("span.cursor-pointer") - .at(0) - .simulate("click", { altKey: true }); - expect(tree.find("AlertGroup")).toHaveLength(20); }); it("adds extra padding to alert groups when multi-grid is enabled", () => { @@ -601,9 +583,9 @@ describe("", () => { }, }, ]; + document.body.clientWidth = 1200; + window.innerWidth = 1000; const tree = MountedAlertGrid(); - tree.instance().viewport.updateWidths(1200, 1000); - tree.update(); expect(tree.find("div.components-grid")).toHaveLength(2); expect(tree.find("AlertGroup")).toHaveLength(20); @@ -634,8 +616,9 @@ describe("", () => { }, }, ]; + document.body.clientWidth = 1200; + window.innerWidth = 1000; const tree = MountedAlertGrid(); - tree.instance().viewport.updateWidths(1200, 1000); tree.update(); expect(tree.find("Grid")).toHaveLength(1); expect(tree.find("AlertGroup")).toHaveLength(10); @@ -654,7 +637,7 @@ describe("", () => { it("doesn't crash on unmount", () => { MockGroupList(60, 5); - const tree = ShallowAlertGrid(); + const tree = MountedAlertGrid(); tree.unmount(); }); });