mirror of
https://github.com/prymitive/karma
synced 2026-08-19 11:36:24 +00:00
fix(ui): use custom masonry grid component
This commit is contained in:
committed by
Łukasz Mierzwa
parent
90d969a09e
commit
53796bf54e
Generated
-18
@@ -19052,14 +19052,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-idle-timer/-/react-idle-timer-4.2.12.tgz",
|
||||
"integrity": "sha512-YD/2Oe4PU5uRv/TH6zTxykKMHpRHWHPEWCUohda81o/jzsrlgyUrklfy46fd8WjgYhlNkJKsiX/GXJAQQC1hcQ=="
|
||||
},
|
||||
"react-infinite-scroller": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-infinite-scroller/-/react-infinite-scroller-1.2.4.tgz",
|
||||
"integrity": "sha512-/oOa0QhZjXPqaD6sictN2edFMsd3kkMiE19Vcz5JDgHpzEJVqYcmq+V3mkwO88087kvKGe1URNksHEOt839Ubw==",
|
||||
"requires": {
|
||||
"prop-types": "^15.5.8"
|
||||
}
|
||||
},
|
||||
"react-input-autosize": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-2.2.2.tgz",
|
||||
@@ -19119,16 +19111,6 @@
|
||||
"tlds": "^1.57.0"
|
||||
}
|
||||
},
|
||||
"react-masonry-infinite": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/react-masonry-infinite/-/react-masonry-infinite-1.2.2.tgz",
|
||||
"integrity": "sha1-IME4b5zN2pdHUnyPQrwsAt0ueVE=",
|
||||
"requires": {
|
||||
"bricks.js": "^1.7.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"react-infinite-scroller": "^1.0.12"
|
||||
}
|
||||
},
|
||||
"react-media": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/react-media/-/react-media-1.10.0.tgz",
|
||||
|
||||
+1
-1
@@ -18,6 +18,7 @@
|
||||
"body-scroll-lock": "3.0.2",
|
||||
"bootstrap": "4.5.0",
|
||||
"bootswatch": "4.5.0",
|
||||
"bricks.js": "1.8.0",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"csshake": "1.5.3",
|
||||
"fast-deep-equal": "3.1.1",
|
||||
@@ -50,7 +51,6 @@
|
||||
"react-js-pagination": "3.0.3",
|
||||
"react-json-pretty": "2.2.0",
|
||||
"react-linkify": "0.2.2",
|
||||
"react-masonry-infinite": "1.2.2",
|
||||
"react-media": "1.10.0",
|
||||
"react-moment": "0.9.7",
|
||||
"react-popper": "2.2.3",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import React, { useEffect, useState, useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { observable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import { useObserver } from "mobx-react";
|
||||
|
||||
import debounce from "lodash.debounce";
|
||||
|
||||
@@ -10,297 +9,165 @@ import { Fade } from "react-reveal";
|
||||
|
||||
import FontFaceObserver from "fontfaceobserver";
|
||||
|
||||
import MasonryInfiniteScroller from "react-masonry-infinite";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faTh } from "@fortawesome/free-solid-svg-icons/faTh";
|
||||
import { faAngleDoubleDown } from "@fortawesome/free-solid-svg-icons/faAngleDoubleDown";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { APIGrid } from "Models/API";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { useGrid } from "Hooks/useGrid";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { DefaultDetailsCollapseValue } from "./AlertGroup/DetailsToggle";
|
||||
import { AlertGroup } from "./AlertGroup";
|
||||
import { Swimlane } from "./Swimlane";
|
||||
|
||||
const Grid = observer(
|
||||
class Grid extends Component {
|
||||
static propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
gridSizesConfig: PropTypes.array.isRequired,
|
||||
groupWidth: PropTypes.number.isRequired,
|
||||
grid: APIGrid.isRequired,
|
||||
outerPadding: PropTypes.number.isRequired,
|
||||
const Grid = ({
|
||||
alertStore,
|
||||
settingsStore,
|
||||
silenceFormStore,
|
||||
gridSizesConfig,
|
||||
groupWidth,
|
||||
grid,
|
||||
outerPadding,
|
||||
}) => {
|
||||
const { ref, repack } = useGrid(gridSizesConfig);
|
||||
const debouncedRepack = debounce(repack, 10);
|
||||
|
||||
const [groupsToRender, setGroupsToRender] = useState(50);
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(
|
||||
!DefaultDetailsCollapseValue(settingsStore)
|
||||
);
|
||||
const toggleIsExpanded = useCallback(() => {
|
||||
setIsExpanded(!isExpanded);
|
||||
debouncedRepack();
|
||||
}, [debouncedRepack, isExpanded]);
|
||||
|
||||
const onCollapseClick = (event) => {
|
||||
// left click => toggle current grid
|
||||
// left click + alt => toggle all grids
|
||||
|
||||
if (event.altKey === true) {
|
||||
const toggleEvent = new CustomEvent("alertGridCollapse", {
|
||||
detail: !isExpanded,
|
||||
});
|
||||
window.dispatchEvent(toggleEvent);
|
||||
} else {
|
||||
toggleIsExpanded();
|
||||
}
|
||||
};
|
||||
|
||||
const onAlertGridCollapseEvent = useCallback(
|
||||
(event) => {
|
||||
setIsExpanded(event.detail);
|
||||
debouncedRepack();
|
||||
},
|
||||
[debouncedRepack]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// We have font-display:swap set for font assets, this means that on initial
|
||||
// render a fallback font might be used and later swapped for the final one
|
||||
// (once the final font is loaded). This means that fallback font might
|
||||
// render to a different size and the swap can result in component resize.
|
||||
// For our grid this resize might leave gaps since everything uses fixed
|
||||
// position, so we use font observer and trigger repack when fonts are loaded
|
||||
for (const fontWeight of [300, 400, 600]) {
|
||||
const font = new FontFaceObserver("Open Sans", {
|
||||
weight: fontWeight,
|
||||
});
|
||||
// wait up to 30s, run no-op function on timeout
|
||||
font.load(null, 30000).then(debouncedRepack, () => {});
|
||||
}
|
||||
|
||||
window.addEventListener("alertGridCollapse", onAlertGridCollapseEvent);
|
||||
return () => {
|
||||
window.removeEventListener("alertGridCollapse", onAlertGridCollapseEvent);
|
||||
};
|
||||
}, [debouncedRepack, onAlertGridCollapseEvent]);
|
||||
|
||||
// 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
|
||||
masonryComponentReference = observable(
|
||||
{ ref: false },
|
||||
{},
|
||||
{ name: "Masonry reference" }
|
||||
);
|
||||
// store it for later
|
||||
storeMasonryRef = action((ref) => {
|
||||
this.masonryComponentReference.ref = ref;
|
||||
});
|
||||
// used to call forcePack() which will repack all grid elements
|
||||
// (alert groups), this needs to be called if any group size changes
|
||||
masonryRepack = debounce(
|
||||
action(() => {
|
||||
if (this.masonryComponentReference.ref) {
|
||||
this.masonryComponentReference.ref.forcePack();
|
||||
}
|
||||
}),
|
||||
10
|
||||
);
|
||||
|
||||
initial = 50;
|
||||
groupsToRender = observable(
|
||||
{
|
||||
value: this.initial,
|
||||
setValue(value) {
|
||||
this.value = value;
|
||||
},
|
||||
},
|
||||
{
|
||||
setValue: action.bound,
|
||||
},
|
||||
{ name: "Groups to render" }
|
||||
);
|
||||
// how many groups add to render count when user scrolls to the bottom
|
||||
loadMoreStep = 30;
|
||||
|
||||
loadMore = action(() => {
|
||||
const { grid } = this.props;
|
||||
|
||||
this.groupsToRender.value = Math.min(
|
||||
this.groupsToRender.value + this.loadMoreStep,
|
||||
grid.alertGroups.length
|
||||
);
|
||||
});
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { settingsStore } = props;
|
||||
|
||||
this.gridToggle = observable(
|
||||
{
|
||||
show: !DefaultDetailsCollapseValue(settingsStore),
|
||||
toggle() {
|
||||
this.show = !this.show;
|
||||
},
|
||||
set(value) {
|
||||
this.show = value;
|
||||
},
|
||||
},
|
||||
{
|
||||
toggle: action.bound,
|
||||
set: action.bound,
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
if (groupsToRender > grid.alertGroups.length) {
|
||||
setGroupsToRender(Math.max(50, grid.alertGroups.length));
|
||||
}
|
||||
}, [grid.alertGroups.length, groupsToRender]);
|
||||
|
||||
onCollapseClick = (event) => {
|
||||
// left click => toggle current grid
|
||||
// left click + alt => toggle all grids
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
this.gridToggle.toggle();
|
||||
|
||||
if (event.altKey === true) {
|
||||
const toggleEvent = new CustomEvent("alertGridCollapse", {
|
||||
detail: this.gridToggle.show,
|
||||
});
|
||||
window.dispatchEvent(toggleEvent);
|
||||
}
|
||||
};
|
||||
|
||||
onAlertGridCollapseEvent = (event) => {
|
||||
this.gridToggle.set(event.detail);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
// We have font-display:swap set for font assets, this means that on initial
|
||||
// render a fallback font might be used and later swapped for the final one
|
||||
// (once the final font is loaded). This means that fallback font might
|
||||
// render to a different size and the swap can result in component resize.
|
||||
// For our grid this resize might leave gaps since everything uses fixed
|
||||
// position, so we use font observer and trigger repack when fonts are loaded
|
||||
for (const fontWeight of [300, 400, 600]) {
|
||||
const font = new FontFaceObserver("Open Sans", {
|
||||
weight: fontWeight,
|
||||
});
|
||||
// wait up to 30s, run no-op function on timeout
|
||||
font.load(null, 30000).then(this.masonryRepack, () => {});
|
||||
}
|
||||
|
||||
window.addEventListener(
|
||||
"alertGridCollapse",
|
||||
this.onAlertGridCollapseEvent
|
||||
);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
const { grid } = this.props;
|
||||
|
||||
this.masonryRepack();
|
||||
|
||||
if (this.groupsToRender.value > grid.alertGroups.length) {
|
||||
this.groupsToRender.setValue(
|
||||
Math.max(this.initial, grid.alertGroups.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener(
|
||||
"alertGridCollapse",
|
||||
this.onAlertGridCollapseEvent
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
alertStore,
|
||||
settingsStore,
|
||||
silenceFormStore,
|
||||
gridSizesConfig,
|
||||
groupWidth,
|
||||
grid,
|
||||
outerPadding,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{grid.labelName !== "" && (
|
||||
return useObserver(() => (
|
||||
<React.Fragment>
|
||||
{grid.labelName !== "" && (
|
||||
<Swimlane
|
||||
alertStore={alertStore}
|
||||
grid={grid}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={onCollapseClick}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="components-grid"
|
||||
ref={ref}
|
||||
key={settingsStore.gridConfig.config.groupWidth}
|
||||
style={{
|
||||
paddingLeft: outerPadding + "px",
|
||||
paddingRight: outerPadding + "px",
|
||||
}}
|
||||
>
|
||||
{isExpanded || grid.labelName === ""
|
||||
? grid.alertGroups.slice(0, groupsToRender).map((group) => (
|
||||
<AlertGroup
|
||||
key={group.id}
|
||||
group={group}
|
||||
showAlertmanagers={
|
||||
Object.keys(alertStore.data.upstreams.clusters).length > 1
|
||||
}
|
||||
afterUpdate={debouncedRepack}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
style={{
|
||||
width: groupWidth,
|
||||
}}
|
||||
gridLabelValue={grid.labelValue}
|
||||
/>
|
||||
))
|
||||
: []}
|
||||
</div>
|
||||
{isExpanded && grid.alertGroups.length > groupsToRender && (
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<div className="flex-shrink-1 flex-grow-1 text-center">
|
||||
<Fade
|
||||
in={this.context.animations.in}
|
||||
duration={this.context.animations.duration}
|
||||
in={context.animations.in}
|
||||
duration={context.animations.duration}
|
||||
>
|
||||
<h5 className="components-grid-swimlane d-flex flex-row justify-content-between rounded px-2 py-1 mt-2 mb-0 border border-dark">
|
||||
<span
|
||||
className="flex-shrink-1 flex-grow-1"
|
||||
style={{ minWidth: "0px" }}
|
||||
>
|
||||
<span className="badge components-label px-0 ml-1 mr-3">
|
||||
<FontAwesomeIcon icon={faTh} className="text-muted" />
|
||||
</span>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<FilteringLabel
|
||||
key={grid.labelValue}
|
||||
name={grid.labelName}
|
||||
value={grid.labelValue}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 flex-grow-0 ml-2 mr-0">
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="unprocessed"
|
||||
counter={grid.stateCount.unprocessed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="suppressed"
|
||||
counter={grid.stateCount.suppressed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="active"
|
||||
counter={grid.stateCount.active}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<span
|
||||
className="text-muted cursor-pointer badge px-0 components-label ml-2 mr-1"
|
||||
onClick={this.onCollapseClick}
|
||||
>
|
||||
<TooltipWrapper title="Click to toggle this grid details or Alt+Click to toggle all grids">
|
||||
<ToggleIcon isOpen={this.gridToggle.show} />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</span>
|
||||
</h5>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary mb-3"
|
||||
onClick={() =>
|
||||
setGroupsToRender(
|
||||
Math.min(groupsToRender + 30, grid.alertGroups.length)
|
||||
)
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon className="mr-2" icon={faAngleDoubleDown} />
|
||||
Load more
|
||||
</button>
|
||||
</Fade>
|
||||
)}
|
||||
<MasonryInfiniteScroller
|
||||
key={settingsStore.gridConfig.config.groupWidth}
|
||||
ref={this.storeMasonryRef}
|
||||
position={false}
|
||||
pack={true}
|
||||
sizes={gridSizesConfig}
|
||||
loadMore={this.loadMore}
|
||||
hasMore={false}
|
||||
style={{
|
||||
paddingLeft: `${outerPadding}px`,
|
||||
paddingRight: `${outerPadding}px`,
|
||||
}}
|
||||
>
|
||||
{this.gridToggle.show || grid.labelName === ""
|
||||
? grid.alertGroups
|
||||
.slice(0, this.groupsToRender.value)
|
||||
.map((group) => (
|
||||
<AlertGroup
|
||||
key={group.id}
|
||||
group={group}
|
||||
showAlertmanagers={
|
||||
Object.keys(alertStore.data.upstreams.clusters).length >
|
||||
1
|
||||
}
|
||||
afterUpdate={this.masonryRepack}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
style={{
|
||||
width: groupWidth,
|
||||
}}
|
||||
gridLabelValue={grid.labelValue}
|
||||
/>
|
||||
))
|
||||
: []}
|
||||
</MasonryInfiniteScroller>
|
||||
{this.gridToggle.show &&
|
||||
grid.alertGroups.length > this.groupsToRender.value && (
|
||||
<div className="d-flex flex-row justify-content-between">
|
||||
<span className="flex-shrink-1 flex-grow-1 text-center">
|
||||
<Fade
|
||||
in={this.context.animations.in}
|
||||
duration={this.context.animations.duration}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary mb-3"
|
||||
onClick={this.loadMore}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
className="mr-2"
|
||||
icon={faAngleDoubleDown}
|
||||
/>
|
||||
Load more
|
||||
</button>
|
||||
</Fade>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
Grid.contextType = ThemeContext;
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
));
|
||||
};
|
||||
Grid.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
settingsStore: PropTypes.instanceOf(Settings).isRequired,
|
||||
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
|
||||
gridSizesConfig: PropTypes.array.isRequired,
|
||||
groupWidth: PropTypes.number.isRequired,
|
||||
grid: APIGrid.isRequired,
|
||||
outerPadding: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export { Grid };
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { Fade } from "react-reveal";
|
||||
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faTh } from "@fortawesome/free-solid-svg-icons/faTh";
|
||||
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { APIGrid } from "Models/API";
|
||||
import { FilteringLabel } from "Components/Labels/FilteringLabel";
|
||||
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
|
||||
import { TooltipWrapper } from "Components/TooltipWrapper";
|
||||
import { ToggleIcon } from "Components/ToggleIcon";
|
||||
import { ThemeContext } from "Components/Theme";
|
||||
|
||||
const Swimlane = ({ alertStore, grid, isExpanded, onToggle }) => {
|
||||
const context = React.useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<Fade in={context.animations.in} duration={context.animations.duration}>
|
||||
<h5 className="components-grid-swimlane d-flex flex-row justify-content-between rounded px-2 py-1 mt-2 mb-0 border border-dark">
|
||||
<span className="flex-shrink-1 flex-grow-1" style={{ minWidth: "0px" }}>
|
||||
<span className="badge components-label px-0 ml-1 mr-3">
|
||||
<FontAwesomeIcon icon={faTh} className="text-muted" />
|
||||
</span>
|
||||
{grid.labelName !== "" && grid.labelValue !== "" && (
|
||||
<FilteringLabel
|
||||
key={grid.labelValue}
|
||||
name={grid.labelName}
|
||||
value={grid.labelValue}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
<span className="flex-shrink-0 flex-grow-0 ml-2 mr-0">
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="unprocessed"
|
||||
counter={grid.stateCount.unprocessed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="suppressed"
|
||||
counter={grid.stateCount.suppressed}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<FilteringCounterBadge
|
||||
name="@state"
|
||||
value="active"
|
||||
counter={grid.stateCount.active}
|
||||
themed={true}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
<span
|
||||
className="text-muted cursor-pointer badge px-0 components-label ml-2 mr-1"
|
||||
onClick={onToggle}
|
||||
>
|
||||
<TooltipWrapper title="Click to toggle this grid details or Alt+Click to toggle all grids">
|
||||
<ToggleIcon isOpen={isExpanded} />
|
||||
</TooltipWrapper>
|
||||
</span>
|
||||
</span>
|
||||
</h5>
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
Swimlane.propTypes = {
|
||||
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
|
||||
grid: APIGrid.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
onToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export { Swimlane };
|
||||
@@ -30,6 +30,8 @@ beforeEach(() => {
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
|
||||
window.matchMedia = mockMatchMedia({});
|
||||
|
||||
jest.spyOn(React, "useContext").mockImplementation(() => MockThemeContext);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -166,66 +168,6 @@ describe("<Grid />", () => {
|
||||
expect(alertGroups).toHaveLength(80);
|
||||
});
|
||||
|
||||
it("resets groupsToRender.value back to 50 if current value is more than group alerts", () => {
|
||||
MockGroupList(100, 5);
|
||||
const tree = MountedGrid();
|
||||
expect(tree.find("AlertGroup")).toHaveLength(50);
|
||||
expect(tree.instance().groupsToRender.value).toBe(50);
|
||||
|
||||
tree.find("button").simulate("click");
|
||||
expect(tree.find("AlertGroup")).toHaveLength(80);
|
||||
expect(tree.instance().groupsToRender.value).toBe(80);
|
||||
|
||||
MockGroupList(10, 5);
|
||||
tree.setProps({ grid: MockGrid() });
|
||||
expect(tree.find("AlertGroup")).toHaveLength(10);
|
||||
expect(tree.instance().groupsToRender.value).toBe(50);
|
||||
|
||||
MockGroupList(100, 5);
|
||||
tree.setProps({ grid: MockGrid() });
|
||||
expect(tree.find("AlertGroup")).toHaveLength(50);
|
||||
expect(tree.instance().groupsToRender.value).toBe(50);
|
||||
});
|
||||
|
||||
it("calling masonryRepack() calls forcePack() on Masonry instance`", () => {
|
||||
const tree = ShallowGrid();
|
||||
const instance = tree.instance();
|
||||
// it's a shallow render so we don't really have masonry mounted, fake it
|
||||
instance.masonryComponentReference.ref = {
|
||||
forcePack: jest.fn(),
|
||||
};
|
||||
instance.masonryRepack();
|
||||
expect(instance.masonryComponentReference.ref.forcePack).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("masonryRepack() doesn't crash when masonryComponentReference.ref=false`", () => {
|
||||
const tree = ShallowGrid();
|
||||
const instance = tree.instance();
|
||||
instance.masonryComponentReference.ref = false;
|
||||
instance.masonryRepack();
|
||||
});
|
||||
|
||||
it("masonryRepack() doesn't crash when masonryComponentReference.ref=null`", () => {
|
||||
const tree = ShallowGrid();
|
||||
const instance = tree.instance();
|
||||
instance.masonryComponentReference.ref = null;
|
||||
instance.masonryRepack();
|
||||
});
|
||||
|
||||
it("masonryRepack() doesn't crash when masonryComponentReference.ref=undefined`", () => {
|
||||
const tree = ShallowGrid();
|
||||
const instance = tree.instance();
|
||||
instance.masonryComponentReference.ref = undefined;
|
||||
instance.masonryRepack();
|
||||
});
|
||||
|
||||
it("calling storeMasonryRef() saves the ref in local store", () => {
|
||||
const tree = ShallowGrid();
|
||||
const instance = tree.instance();
|
||||
instance.storeMasonryRef("foo");
|
||||
expect(instance.masonryComponentReference.ref).toEqual("foo");
|
||||
});
|
||||
|
||||
it("doesn't sort groups when sorting is set to 'disabled'", () => {
|
||||
settingsStore.gridConfig.config.sortOrder =
|
||||
settingsStore.gridConfig.options.disabled.value;
|
||||
@@ -662,14 +604,10 @@ describe("<AlertGrid />", () => {
|
||||
const tree = MountedAlertGrid();
|
||||
tree.instance().viewport.updateWidths(1200, 1000);
|
||||
tree.update();
|
||||
expect(tree.find("Grid")).toHaveLength(2);
|
||||
expect(tree.find("div.components-grid")).toHaveLength(2);
|
||||
expect(tree.find("AlertGroup")).toHaveLength(20);
|
||||
|
||||
expect(tree.find("Grid").at(0).prop("outerPadding")).toBe(5);
|
||||
expect(tree.find("Grid").at(1).prop("outerPadding")).toBe(5);
|
||||
expect(
|
||||
tree.find("Grid").at(0).find("div").at(3).prop("style")
|
||||
).toMatchObject({
|
||||
expect(tree.find("div.components-grid").at(0).prop("style")).toMatchObject({
|
||||
paddingLeft: "5px",
|
||||
paddingRight: "5px",
|
||||
});
|
||||
@@ -702,10 +640,10 @@ describe("<AlertGrid />", () => {
|
||||
expect(tree.find("Grid")).toHaveLength(1);
|
||||
expect(tree.find("AlertGroup")).toHaveLength(10);
|
||||
|
||||
expect(tree.find("Grid").at(0).prop("outerPadding")).toBe(0);
|
||||
expect(
|
||||
tree.find("Grid").at(0).find("div").at(1).prop("style")
|
||||
).toMatchObject({ paddingLeft: "0px", paddingRight: "0px" });
|
||||
expect(tree.find("div.components-grid").at(0).prop("style")).toMatchObject({
|
||||
paddingLeft: "0px",
|
||||
paddingRight: "0px",
|
||||
});
|
||||
|
||||
tree.find("div.components-grid-alertgrid-alertgroup").forEach((node) => {
|
||||
expect(node.prop("style")).toMatchObject({
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import Bricks from "bricks.js";
|
||||
|
||||
const useGrid = (sizes) => {
|
||||
const ref = useRef(null);
|
||||
const grid = useRef(null);
|
||||
|
||||
const repack = () => {
|
||||
if (grid.current) {
|
||||
grid.current.pack();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!grid.current && ref.current) {
|
||||
grid.current = new Bricks({
|
||||
container: ref.current,
|
||||
sizes: sizes,
|
||||
packed: "packed",
|
||||
position: false,
|
||||
});
|
||||
window.addEventListener("resize", repack);
|
||||
grid.current.pack();
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", repack);
|
||||
grid.current = null;
|
||||
};
|
||||
}, [sizes]);
|
||||
|
||||
return { ref, repack };
|
||||
};
|
||||
|
||||
export { useGrid };
|
||||
@@ -0,0 +1,60 @@
|
||||
import React from "react";
|
||||
|
||||
import { renderHook, act } from "@testing-library/react-hooks";
|
||||
|
||||
import { mount } from "enzyme";
|
||||
|
||||
import { useGrid } from "./useGrid";
|
||||
|
||||
describe("useGrid", () => {
|
||||
const sizes = [{ columns: 2, gutter: 0 }];
|
||||
const Component = ({ count }) => {
|
||||
const { ref, repack } = useGrid(sizes);
|
||||
|
||||
return (
|
||||
<div ref={ref} id="root" onClick={repack}>
|
||||
{Array.from(Array(count).keys()).map((i) => (
|
||||
<div key={i} id={`item${i}`} style={{ width: 400 }}></div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
it("does nothing if ref is null", () => {
|
||||
const { result } = renderHook(() => useGrid());
|
||||
expect(result.current.ref.current).toBe(null);
|
||||
});
|
||||
|
||||
it("repack does nothing if ref is null", () => {
|
||||
const { result } = renderHook(() => useGrid());
|
||||
expect(result.current.ref.current).toBe(null);
|
||||
result.current.repack();
|
||||
});
|
||||
|
||||
it("packs grid if ref is set", () => {
|
||||
const tree = mount(<Component count={4} />);
|
||||
expect(tree.find("#item0").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item1").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item2").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item3").html()).toMatch(/data-packed/);
|
||||
});
|
||||
|
||||
it("repack will repack the grid if ref is set", () => {
|
||||
const tree = mount(<Component count={4} />);
|
||||
expect(tree.find("#item0").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item1").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item2").html()).toMatch(/data-packed/);
|
||||
expect(tree.find("#item3").html()).toMatch(/data-packed/);
|
||||
|
||||
tree.setProps({ count: 5 });
|
||||
expect(tree.find("#item4").html()).not.toMatch(/data-packed/);
|
||||
|
||||
tree.find("#root").simulate("click");
|
||||
expect(tree.find("#item4").html()).toMatch(/data-packed/);
|
||||
});
|
||||
|
||||
it("unmounts cleanly", () => {
|
||||
const tree = mount(<Component count={4} />);
|
||||
tree.unmount();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user