feat(ui): render per grid state count

This commit is contained in:
Łukasz Mierzwa
2020-03-30 20:42:18 +01:00
parent f41066b6ba
commit 8698e5f674
4 changed files with 132 additions and 49 deletions

View File

@@ -16,8 +16,9 @@ import { faAngleDoubleDown } from "@fortawesome/free-solid-svg-icons/faAngleDoub
import { AlertStore } from "Stores/AlertStore";
import { Settings } from "Stores/Settings";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { APIGroup } from "Models/API";
import { APIGrid } from "Models/API";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
import { TooltipWrapper } from "Components/TooltipWrapper";
import { AlertGroup } from "./AlertGroup";
@@ -29,9 +30,7 @@ const Grid = observer(
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
gridSizesConfig: PropTypes.array.isRequired,
groupWidth: PropTypes.number.isRequired,
gridLabelName: PropTypes.string.isRequired,
gridLabelValue: PropTypes.string.isRequired,
gridAlertGroups: PropTypes.arrayOf(APIGroup).isRequired,
grid: APIGrid.isRequired,
};
// store reference to generated masonry component so we can call it
@@ -74,11 +73,11 @@ const Grid = observer(
loadMoreStep = 30;
loadMore = action(() => {
const { gridAlertGroups } = this.props;
const { grid } = this.props;
this.groupsToRender.value = Math.min(
this.groupsToRender.value + this.loadMoreStep,
gridAlertGroups.length
grid.alertGroups.length
);
});
@@ -95,13 +94,13 @@ const Grid = observer(
);
componentDidUpdate() {
const { gridAlertGroups } = this.props;
const { grid } = this.props;
this.masonryRepack();
if (this.groupsToRender.value > gridAlertGroups.length) {
if (this.groupsToRender.value > grid.alertGroups.length) {
this.groupsToRender.setValue(
Math.max(this.initial, gridAlertGroups.length)
Math.max(this.initial, grid.alertGroups.length)
);
}
}
@@ -113,28 +112,50 @@ const Grid = observer(
silenceFormStore,
gridSizesConfig,
groupWidth,
gridLabelName,
gridLabelValue,
gridAlertGroups,
grid,
} = this.props;
return (
<React.Fragment>
{gridLabelName !== "" && gridLabelValue !== "" && (
<h5 className="d-flex flex-row justify-content-between px-2 mx-0 mt-2 mb-0 bg-secondary">
<span className="flex-shrink-0 flex-grow-0 text-white badge px-0 components-label ml-0 mr-2">
{gridAlertGroups.length}
</span>
{alertStore.data.grids.length > 1 && (
<h5 className="d-flex flex-row justify-content-between px-2 py-1 mx-0 mt-2 mb-0 bg-light">
{grid.alertGroups.length > 1 && (
<span className="flex-shrink-0 flex-grow-0 ml-0 mr-2">
<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>
)}
<span
className="flex-shrink-1 flex-grow-1 text-center"
style={{ minWidth: "0px" }}
>
<FilteringLabel
key={gridLabelValue}
name={gridLabelName}
value={gridLabelValue}
alertStore={alertStore}
/>
{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 text-white cursor-pointer badge px-0 components-label ml-2 mr-0"
@@ -158,7 +179,7 @@ const Grid = observer(
hasMore={false}
>
{this.gridToggle.show
? gridAlertGroups
? grid.alertGroups
.slice(0, this.groupsToRender.value)
.map((group) => (
<AlertGroup
@@ -179,7 +200,7 @@ const Grid = observer(
))
: []}
</MasonryInfiniteScroller>
{gridAlertGroups.length > this.groupsToRender.value && (
{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">
<button

View File

@@ -104,9 +104,7 @@ const AlertGrid = observer(
settingsStore={settingsStore}
gridSizesConfig={this.viewport.gridSizesConfig}
groupWidth={this.viewport.groupWidth}
gridLabelName={grid.labelName}
gridLabelValue={grid.labelValue}
gridAlertGroups={grid.alertGroups}
grid={grid}
/>
))}
</React.Fragment>

View File

@@ -46,6 +46,19 @@ const ShallowAlertGrid = () => {
);
};
const MockGrid = () => ({
labelName: "",
labelValue: "",
alertGroups: alertStore.data.grids.length
? alertStore.data.grids[0].alertGroups
: [],
stateCount: {
unprocessed: 1,
suppressed: 2,
active: 3,
},
});
const ShallowGrid = () => {
return shallow(
<Grid
@@ -54,11 +67,7 @@ const ShallowGrid = () => {
settingsStore={settingsStore}
gridSizesConfig={GridSizesConfig(1024, 420)}
groupWidth={420}
gridLabelName=""
gridLabelValue=""
gridAlertGroups={
alertStore.data.grids.length ? alertStore.data.grids[0].alertGroups : []
}
grid={MockGrid()}
/>
);
};
@@ -71,11 +80,7 @@ const MountedGrid = () => {
settingsStore={settingsStore}
gridSizesConfig={GridSizesConfig(1024, 420)}
groupWidth={420}
gridLabelName=""
gridLabelValue=""
gridAlertGroups={
alertStore.data.grids.length ? alertStore.data.grids[0].alertGroups : []
}
grid={MockGrid()}
/>
);
};
@@ -113,6 +118,11 @@ const MockGroupList = (count, alertPerGroup) => {
labelName: "",
labelValue: "",
alertGroups: groups,
stateCount: {
unprocessed: 1,
suppressed: 2,
active: 3,
},
},
];
};
@@ -144,12 +154,12 @@ describe("<Grid />", () => {
expect(tree.instance().groupsToRender.value).toBe(80);
MockGroupList(10, 5);
tree.setProps({ gridAlertGroups: alertStore.data.grids[0].alertGroups });
tree.setProps({ grid: MockGrid() });
expect(tree.find("AlertGroup")).toHaveLength(10);
expect(tree.instance().groupsToRender.value).toBe(50);
MockGroupList(100, 5);
tree.setProps({ gridAlertGroups: alertStore.data.grids[0].alertGroups });
tree.setProps({ grid: MockGrid() });
expect(tree.find("AlertGroup")).toHaveLength(50);
expect(tree.instance().groupsToRender.value).toBe(50);
});
@@ -209,11 +219,20 @@ describe("<Grid />", () => {
it("click on the grid toggle toggles all groups", () => {
MockGroupList(10, 3);
const groups = alertStore.data.grids[0].alertGroups;
alertStore.data.grids = [
{
labelName: "foo",
labelValue: "bar",
alertGroups: groups,
},
{
labelName: "foo",
labelValue: "",
alertGroups: [],
},
];
const tree = MountedGrid();
tree.setProps({
gridLabelName: "foo",
gridLabelValue: "bar",
});
expect(tree.find("AlertGroup")).toHaveLength(10);
tree.find("span.cursor-pointer").at(0).simulate("click");
@@ -223,6 +242,41 @@ describe("<Grid />", () => {
expect(tree.find("AlertGroup")).toHaveLength(10);
});
it("renders filter badge for grids with a value", () => {
MockGroupList(1, 1);
const tree = MountedGrid();
const grid = MockGrid();
grid.labelName = "foo";
grid.labelValue = "bar";
grid.stateCount = {
unprocessed: 0,
suppressed: 0,
active: 0,
};
alertStore.data.grids = [MockGrid(), MockGrid()];
tree.setProps({ grid: grid });
expect(tree.find("h5").at(0).find("FilteringLabel")).toHaveLength(1);
expect(tree.find("h5").at(0).find("FilteringLabel").text()).toBe(
"foo: bar"
);
});
it("doesn't render filter badge for grids with no value", () => {
MockGroupList(1, 1);
const tree = MountedGrid();
const grid = MockGrid();
grid.labelName = "foo";
grid.labelValue = "";
grid.stateCount = {
unprocessed: 0,
suppressed: 0,
active: 0,
};
alertStore.data.grids = [MockGrid(), MockGrid()];
tree.setProps({ grid: grid });
expect(tree.find("h5").at(0).find("FilteringLabel")).toHaveLength(0);
});
it("left click on a group collapse toggle only toggles clicked group", () => {
MockGroupList(10, 3);
const tree = MountedGrid();

View File

@@ -28,17 +28,19 @@ const APIAlert = PropTypes.exact({
receiver: PropTypes.string.isRequired,
});
const APIStateCount = PropTypes.exact({
active: PropTypes.number.isRequired,
suppressed: PropTypes.number.isRequired,
unprocessed: PropTypes.number.isRequired,
});
const APIGroup = PropTypes.exact({
receiver: PropTypes.string.isRequired,
labels: PropTypes.object.isRequired,
alerts: PropTypes.arrayOf(APIAlert),
id: PropTypes.string.isRequired,
alertmanagerCount: PropTypes.objectOf(PropTypes.number).isRequired,
stateCount: PropTypes.exact({
active: PropTypes.number.isRequired,
suppressed: PropTypes.number.isRequired,
unprocessed: PropTypes.number.isRequired,
}),
stateCount: APIStateCount.isRequired,
shared: PropTypes.exact({
annotations: PropTypes.arrayOf(Annotation).isRequired,
labels: PropTypes.object.isRequired,
@@ -79,6 +81,13 @@ const APIAlertmanagerUpstream = PropTypes.exact({
clusterMembers: PropTypes.arrayOf(PropTypes.string).isRequired,
});
const APIGrid = PropTypes.exact({
labelName: PropTypes.string.isRequired,
labelValue: PropTypes.string.isRequired,
alertGroups: PropTypes.arrayOf(APIGroup).isRequired,
stateCount: APIStateCount.isRequired,
});
export {
APIAlert,
APIGroup,
@@ -86,4 +95,5 @@ export {
APISilenceMatcher,
APIAlertAlertmanagerState,
APIAlertmanagerUpstream,
APIGrid,
};