diff --git a/ui/package.json b/ui/package.json index b99671d38..bfc8b4038 100644 --- a/ui/package.json +++ b/ui/package.json @@ -50,7 +50,6 @@ "react-tippy": "1.2.3", "react-transition-group": "2.9.0", "react-truncate": "2.4.0", - "screen-orientation": "1.0.3", "whatwg-fetch": "3.0.0" }, "scripts": { diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 9c32e770a..1e085a8df 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -55,7 +55,8 @@ const AlertGroup = observer( group: APIGroup.isRequired, showAlertmanagers: PropTypes.bool.isRequired, settingsStore: PropTypes.instanceOf(Settings).isRequired, - silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, + style: PropTypes.object }; constructor(props) { @@ -154,7 +155,8 @@ const AlertGroup = observer( group, showAlertmanagers, afterUpdate, - silenceFormStore + silenceFormStore, + style } = this.props; let footerAlertmanagers = []; @@ -176,7 +178,10 @@ const AlertGroup = observer( return ( -
+
+ Math.floor( + canvasWidth < 800 + ? canvasWidth + : canvasWidth < 1400 + ? canvasWidth / 2 + : canvasWidth < 2100 + ? canvasWidth / 3 + : canvasWidth < 2800 + ? canvasWidth / 4 + : canvasWidth < 3500 + ? canvasWidth / 5 + : canvasWidth < 4200 + ? canvasWidth / 6 + : canvasWidth < 5600 + ? canvasWidth / 7 + : canvasWidth / 8 + ); + +export { GridSizesConfig, GetGridElementWidth }; diff --git a/ui/src/Components/Grid/AlertGrid/index.css b/ui/src/Components/Grid/AlertGrid/index.css deleted file mode 100644 index 56dc6a42e..000000000 --- a/ui/src/Components/Grid/AlertGrid/index.css +++ /dev/null @@ -1,51 +0,0 @@ -.components-grid-alertgrid-alertgroup { - width: 100%; -} - -@media screen and (min-width: 800px) and (max-width: 1399px) { - .components-grid-alertgrid-alertgroup { - width: 50%; - } -} - -@media screen and (min-width: 1400px) and (max-width: 2099px) { - .components-grid-alertgrid-alertgroup { - width: 33.333%; - } -} - -@media screen and (min-width: 2100px) and (max-width: 2799px) { - .components-grid-alertgrid-alertgroup { - width: 25%; - } -} - -@media screen and (min-width: 2800px) and (max-width: 3499px) { - .components-grid-alertgrid-alertgroup { - width: 20%; - } -} - -@media screen and (min-width: 3500px) and (max-width: 4199px) { - .components-grid-alertgrid-alertgroup { - width: 16.666%; - } -} - -@media screen and (min-width: 4200px) and (max-width: 4899px) { - .components-grid-alertgrid-alertgroup { - width: 14.285%; - } -} - -@media screen and (min-width: 4900px) and (max-width: 5599px) { - .components-grid-alertgrid-alertgroup { - width: 14.285%; - } -} - -@media screen and (min-width: 5600px) { - .components-grid-alertgrid-alertgroup { - width: 12.5%; - } -} diff --git a/ui/src/Components/Grid/AlertGrid/index.js b/ui/src/Components/Grid/AlertGrid/index.js index 7827e0b44..bb37d42cf 100644 --- a/ui/src/Components/Grid/AlertGrid/index.js +++ b/ui/src/Components/Grid/AlertGrid/index.js @@ -8,6 +8,10 @@ import FontFaceObserver from "fontfaceobserver"; import moment from "moment"; +import { debounce } from "lodash"; + +import ReactResizeDetector from "react-resize-detector"; + import MasonryInfiniteScroller from "react-masonry-infinite"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; @@ -17,9 +21,7 @@ import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { SilenceFormStore } from "Stores/SilenceFormStore"; import { AlertGroup } from "./AlertGroup"; -import { GridSizesConfig } from "./Constants"; - -import "./index.css"; +import { GridSizesConfig, GetGridElementWidth } from "./Constants"; const AlertGrid = observer( class AlertGrid extends Component { @@ -29,6 +31,25 @@ const AlertGrid = observer( silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired }; + 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( + { + width: document.body.clientWidth, + update() { + this.width = document.body.clientWidth; + } + }, + { + update: action.bound + } + ); + } + // store reference to generated masonry component so we can call it // to repack the grid after any component was re-rendered, which could // alter its size breaking grid layout @@ -174,6 +195,10 @@ const AlertGrid = observer( return ( + ))} diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index fad39df8a..7564f533f 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -13,9 +13,15 @@ import { AlertGrid } from "."; let alertStore; let settingsStore; let silenceFormStore; +let bodyWidth; beforeAll(() => { jest.useFakeTimers(); + Object.defineProperty(document.body, "clientWidth", { + get: () => { + return bodyWidth; + } + }); }); beforeEach(() => { @@ -74,6 +80,18 @@ const MockGroupList = (count, alertPerGroup) => { alertStore.data.groups = groups; }; +const VerifyColumnCount = (innerWidth, columns) => { + bodyWidth = innerWidth; + MockGroupList(60, 5); + const tree = ShallowAlertGrid(); + expect( + tree + .find("AlertGroup") + .at(0) + .props().style.width + ).toBe(Math.floor(innerWidth / columns)); +}; + describe("", () => { it("renders only first 50 alert groups", () => { MockGroupList(60, 5); @@ -322,4 +340,89 @@ describe("", () => { advanceBy(60 * 1000); jest.runOnlyPendingTimers(); }); + + it("renders 1 column with document.body.clientWidth=799", () => { + VerifyColumnCount(799, 1); + }); + + it("renders 2 columns with document.body.clientWidth=800", () => { + VerifyColumnCount(800, 2); + }); + + it("renders 2 columns with document.body.clientWidth=1399", () => { + VerifyColumnCount(1399, 2); + }); + + it("renders 3 columns with document.body.clientWidth=1400", () => { + VerifyColumnCount(1400, 3); + }); + + it("renders 3 columns with document.body.clientWidth=2099", () => { + VerifyColumnCount(2099, 3); + }); + + it("renders 4 columns with document.body.clientWidth=2100", () => { + VerifyColumnCount(2100, 4); + }); + + it("renders 4 columns with document.body.clientWidth=2799", () => { + VerifyColumnCount(2799, 4); + }); + + it("renders 5 columns with document.body.clientWidth=2800", () => { + VerifyColumnCount(2800, 5); + }); + + it("renders 5 columns with document.body.clientWidth=3499", () => { + VerifyColumnCount(3499, 5); + }); + + it("renders 6 columns with document.body.clientWidth=1399", () => { + VerifyColumnCount(3500, 6); + }); + + it("renders 6 columns with document.body.clientWidth=4199", () => { + VerifyColumnCount(4199, 6); + }); + + it("renders 7 columns with document.body.clientWidth=1399", () => { + VerifyColumnCount(4200, 7); + }); + + it("renders 7 columns with document.body.clientWidth=5599", () => { + VerifyColumnCount(5599, 7); + }); + + it("renders 8 columns with document.body.clientWidth=5600", () => { + VerifyColumnCount(5600, 8); + }); + + it("viewport resize also resizes alert groups", () => { + bodyWidth = 1980; + MockGroupList(60, 5); + const tree = ShallowAlertGrid(); + expect( + tree + .find("AlertGroup") + .at(0) + .props().style.width + ).toBe(1980 / 3); + + bodyWidth = 1000; + // not sure how to force ReactResizeDetector to detect width change, so + // we directly call viewport update here + tree.instance().viewport.update(); + expect( + tree + .find("AlertGroup") + .at(0) + .props().style.width + ).toBe(1000 / 2); + }); + + it("doesn't crash on unmount", () => { + MockGroupList(60, 5); + const tree = ShallowAlertGrid(); + tree.unmount(); + }); }); diff --git a/ui/src/Components/Grid/index.js b/ui/src/Components/Grid/index.js index 226521b59..67f71e16f 100644 --- a/ui/src/Components/Grid/index.js +++ b/ui/src/Components/Grid/index.js @@ -1,11 +1,8 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import { observable, action } from "mobx"; import { observer } from "mobx-react"; -import screenOrientation from "screen-orientation"; - import { AlertStore } from "Stores/AlertStore"; import { Settings } from "Stores/Settings"; import { SilenceFormStore } from "Stores/SilenceFormStore"; @@ -22,36 +19,6 @@ const Grid = observer( silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired }; - 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( - { - width: window.innerWidth, - orientation: screenOrientation().direction, - update() { - this.width = window.innerWidth; - this.orientation = screenOrientation().direction; - } - }, - { - update: action.bound - } - ); - } - - componentDidMount() { - this.viewport.update(); - window.addEventListener("resize", this.viewport.update); - } - - componentWillUnmount() { - window.removeEventListener("resize", this.viewport.update); - } - render() { const { alertStore, settingsStore, silenceFormStore } = this.props; @@ -88,7 +55,6 @@ const Grid = observer( ))} ", () => { expect(tree.text()).toBe(""); }); - it("re-creates AlertGrid after viewport resize", () => { - // Different columns are positioned using css via fixed offsets, so - // it's hard to tell how many columns we have just by looking at the - // generated css - // This test only checks if we force re-render of the AlertGrid component - // by updating its key prop - - global.innerWidth = 1980; - global.innerHeight = 1080; - const tree = ShallowGrid(); - expect(tree.find("AlertGrid").key()).toBe("1980-landscape"); - - global.innerWidth = 500; - global.innerHeight = 1000; - global.dispatchEvent(new Event("resize")); - expect(tree.find("AlertGrid").key()).toBe("500-portrait"); - }); - it("unmounts without crashes", () => { const tree = ShallowGrid(); tree.unmount(); diff --git a/ui/yarn.lock b/ui/yarn.lock index af5f33b10..ba406ef99 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -3829,11 +3829,6 @@ dom-serializer@0, dom-serializer@~0.1.1: domelementtype "^1.3.0" entities "^1.1.1" -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - integrity sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg= - domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" @@ -5077,14 +5072,6 @@ global-prefix@^3.0.0: kind-of "^6.0.2" which "^1.3.1" -global@~4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" - integrity sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8= - dependencies: - min-document "^2.19.0" - process "~0.5.1" - globals@^11.1.0, globals@^11.7.0: version "11.11.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" @@ -7365,13 +7352,6 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - integrity sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU= - dependencies: - dom-walk "^0.1.0" - mini-css-extract-plugin@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" @@ -9101,11 +9081,6 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - integrity sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8= - progress@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -10212,14 +10187,6 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -screen-orientation@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/screen-orientation/-/screen-orientation-1.0.3.tgz#a390951b13504417fa19293d5b7e1fb93e8766c4" - integrity sha1-o5CVGxNQRBf6GSk9W34fuT6HZsQ= - dependencies: - global "~4.3.0" - view-size "~1.0.1" - scss-tokenizer@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" @@ -11550,13 +11517,6 @@ vfile@^4.0.0: unist-util-stringify-position "^2.0.0" vfile-message "^2.0.0" -view-size@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/view-size/-/view-size-1.0.1.tgz#747d8285fab00896ba83e4c0dcf333e1e028ab11" - integrity sha1-dH2ChfqwCJa6g+TA3PMz4eAoqxE= - dependencies: - global "~4.3.0" - vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"