feat(ui): support group limits

This commit is contained in:
Łukasz Mierzwa
2021-07-14 18:00:07 +01:00
committed by Łukasz Mierzwa
parent 7b8e8bb17a
commit 8663e94c0b
11 changed files with 298 additions and 113 deletions
+88 -12
View File
@@ -149,7 +149,7 @@ describe("<Fetcher />", () => {
const fetchSpy = jest.spyOn(alertStore, "fetchWithThrottle");
settingsStore.gridConfig.setSortOrder("default");
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", false, {}, 5, {});
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=disabled reverseSort=false", () => {
@@ -158,7 +158,16 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortOrder("disabled");
settingsStore.gridConfig.setSortReverse(false);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"disabled",
"",
false,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=disabled reverseSort=true", () => {
@@ -167,7 +176,16 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortOrder("disabled");
settingsStore.gridConfig.setSortReverse(true);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "disabled", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"disabled",
"",
false,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=startsAt reverseSort=false", () => {
@@ -176,7 +194,16 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortOrder("startsAt");
settingsStore.gridConfig.setSortReverse(false);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"startsAt",
"",
false,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=startsAt reverseSort=true", () => {
@@ -185,7 +212,16 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortOrder("startsAt");
settingsStore.gridConfig.setSortReverse(true);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "startsAt", "", true, {});
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"startsAt",
"",
true,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=label sortLabel=cluster reverseSort=false", () => {
@@ -201,6 +237,8 @@ describe("<Fetcher />", () => {
"label",
"cluster",
false,
{},
5,
{}
);
});
@@ -212,7 +250,16 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortLabel("job");
settingsStore.gridConfig.setSortReverse(true);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "label", "job", true, {});
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"label",
"job",
true,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with correct sort arguments when sortOrder=label sortLabel=instance reverseSort=null", () => {
@@ -228,6 +275,8 @@ describe("<Fetcher />", () => {
"label",
"instance",
false,
{},
5,
{}
);
});
@@ -239,7 +288,16 @@ describe("<Fetcher />", () => {
settingsStore.multiGridConfig.setGridLabel("cluster");
settingsStore.multiGridConfig.setGridSortReverse(false);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("cluster", false, "", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith(
"cluster",
false,
"",
"",
false,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with gridLabel=cluster gridSortReverse=true", () => {
@@ -249,7 +307,16 @@ describe("<Fetcher />", () => {
settingsStore.multiGridConfig.setGridLabel("cluster");
settingsStore.multiGridConfig.setGridSortReverse(true);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("cluster", true, "", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith(
"cluster",
true,
"",
"",
false,
{},
5,
{}
);
});
it("calls alertStore.fetchWithThrottle with gridLabel= gridSortReverse=true", () => {
@@ -259,7 +326,7 @@ describe("<Fetcher />", () => {
settingsStore.multiGridConfig.setGridLabel("");
settingsStore.multiGridConfig.setGridSortReverse(true);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", true, "", "", false, {});
expect(fetchSpy).toHaveBeenCalledWith("", true, "", "", false, {}, 5, {});
});
it("calls alertStore.fetchWithThrottle with limits set", () => {
@@ -268,10 +335,19 @@ describe("<Fetcher />", () => {
settingsStore.gridConfig.setSortOrder("default");
settingsStore.multiGridConfig.setGridLabel("");
settingsStore.multiGridConfig.setGridSortReverse(false);
alertStore.ui.setLimit("old", "bar", 10);
alertStore.ui.setLimit("foo", "bar", 5);
alertStore.ui.setGridGroupLimit("old", "bar", 10);
alertStore.ui.setGridGroupLimit("foo", "bar", 5);
mount(<Fetcher alertStore={alertStore} settingsStore={settingsStore} />);
expect(fetchSpy).toHaveBeenCalledWith("", false, "", "", false, { bar: 5 });
expect(fetchSpy).toHaveBeenCalledWith(
"",
false,
"",
"",
false,
{ bar: 5 },
5,
{}
);
});
it("internal timer is null after unmount", () => {
+10 -4
View File
@@ -176,9 +176,11 @@ const Fetcher: FC<{
sortSettings.sortOrder,
sortSettings.sortLabel,
sortSettings.sortReverse === "1",
Object.values(alertStore.ui.limits).length > 0
? toJS(Object.values(alertStore.ui.limits)[0])
: {}
Object.values(alertStore.ui.gridGroupLimits).length > 0
? toJS(Object.values(alertStore.ui.gridGroupLimits)[0])
: {},
settingsStore.alertGroupConfig.config.defaultRenderCount,
alertStore.ui.groupAlertLimits
);
};
@@ -197,7 +199,11 @@ const Fetcher: FC<{
grid: {
sortOrder: toJS(settingsStore.gridConfig.config.sortOrder),
sortLabel: toJS(settingsStore.gridConfig.config.sortLabel),
limits: toJS(alertStore.ui.limits),
gridGroupLimits: toJS(alertStore.ui.gridGroupLimits),
defaultGroupLimit: toJS(
settingsStore.alertGroupConfig.config.defaultRenderCount
),
groupAlertLimits: toJS(alertStore.ui.groupAlertLimits),
},
multigrid: {
gridLabel: toJS(settingsStore.multiGridConfig.config.gridLabel),
@@ -56,7 +56,7 @@ afterEach(() => {
global.innerWidth = originalInnerWidth;
});
const MockAlerts = (alertCount: number) => {
const MockAlerts = (alertCount: number, totalAlerts: number) => {
for (let i = 1; i <= alertCount; i++) {
const alert = MockAlert([], { instance: `instance${i}` }, "active");
const startsAt = new Date();
@@ -64,12 +64,12 @@ const MockAlerts = (alertCount: number) => {
alert.alertmanager[0].startsAt = startsAt.toISOString();
group.alerts.push(alert);
}
group.totalAlerts = totalAlerts;
};
const MountedAlertGroup = (
afterUpdate: () => void,
showAlertmanagers: boolean,
initialAlertsToRender?: number,
theme?: ThemeCtx
) => {
return mount(
@@ -77,7 +77,6 @@ const MountedAlertGroup = (
afterUpdate={afterUpdate}
group={group}
showAlertmanagers={showAlertmanagers}
initialAlertsToRender={initialAlertsToRender}
settingsStore={settingsStore}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
@@ -100,32 +99,31 @@ const ValidateCollapse = (
settingsStore.alertGroupConfig.setDefaultCollapseState(defaultCollapseState);
MockAlerts(3);
MockAlerts(3, 3);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.find("Alert")).toHaveLength(shouldBeCollapsed ? 0 : 3);
};
describe("<AlertGroup />", () => {
it("doesn't crash on unmount", () => {
MockAlerts(5);
MockAlerts(5, 5);
const tree = MountedAlertGroup(jest.fn(), true);
tree.unmount();
});
it("uses 'animate' class when settingsStore.themeConfig.config.animations is true", () => {
MockAlerts(5);
const tree = MountedAlertGroup(jest.fn(), true, 5, MockThemeContext);
MockAlerts(5, 5);
const tree = MountedAlertGroup(jest.fn(), true, MockThemeContext);
expect(
tree.find("div.components-grid-alertgrid-alertgroup").hasClass("animate")
).toBe(true);
});
it("doesn't use 'animate' class when settingsStore.themeConfig.config.animations is false", () => {
MockAlerts(5);
MockAlerts(5, 5);
const tree = MountedAlertGroup(
jest.fn(),
true,
5,
MockThemeContextWithoutAnimations
);
expect(
@@ -134,14 +132,14 @@ describe("<AlertGroup />", () => {
});
it("renders Alertmanager cluster labels in footer if shared", () => {
MockAlerts(2);
MockAlerts(2, 2);
group.shared.clusters = ["default"];
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
expect(tree.find("GroupFooter").html()).toMatch(/@cluster/);
});
it("only renders one @cluster label per cluster in the footer", () => {
MockAlerts(2);
MockAlerts(2, 2);
for (let i = 0; i < group.alerts.length; i++) {
group.alerts[i].alertmanager.push({
fingerprint: "123",
@@ -174,7 +172,7 @@ describe("<AlertGroup />", () => {
});
it("doesn't render @cluster labels with empty alertmanager array", () => {
MockAlerts(2);
MockAlerts(2, 2);
for (let i = 0; i < group.alerts.length; i++) {
group.alerts[i].alertmanager = [];
}
@@ -185,7 +183,7 @@ describe("<AlertGroup />", () => {
});
it("doesn't render @cluster labels in footer when they are unique", () => {
MockAlerts(5);
MockAlerts(5, 5);
for (let i = 0; i < group.alerts.length; i++) {
group.alerts[i].alertmanager[0].name = `fakeAlertmanager${i}`;
}
@@ -206,7 +204,7 @@ describe("<AlertGroup />", () => {
});
it("only renders titlebar when collapsed", () => {
MockAlerts(10);
MockAlerts(5, 10);
const tree = MountedAlertGroup(jest.fn(), false);
tree.find("span.badge.cursor-pointer").at(1).simulate("click");
expect(tree.find("Alert")).toHaveLength(0);
@@ -214,9 +212,9 @@ describe("<AlertGroup />", () => {
});
it("renders reduced details when idle", () => {
MockAlerts(10);
MockAlerts(5, 10);
alertStore.ui.setIsIdle(true);
const tree = MountedAlertGroup(jest.fn(), true, 10, MockThemeContext);
const tree = MountedAlertGroup(jest.fn(), true, MockThemeContext);
expect(tree.find("Alert")).toHaveLength(1);
});
@@ -252,40 +250,43 @@ describe("<AlertGroup />", () => {
it("renders @receiver label when alertStore.data.receivers.length > 1", () => {
alertStore.data.setReceivers(["foo", "bar"]);
MockAlerts(10);
MockAlerts(5, 10);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.html()).toMatch(/@receiver:/);
});
it("doesn't render @receiver label when alertStore.data.receivers.length == 0", () => {
alertStore.data.setReceivers([]);
MockAlerts(10);
MockAlerts(5, 10);
const tree = MountedAlertGroup(jest.fn(), false);
expect(tree.html()).not.toMatch(/@receiver:/);
});
});
const ValidateLoadButtonPresent = (totalAlerts: number, isPresent: boolean) => {
MockAlerts(totalAlerts);
const ValidateLoadButtonPresent = (
alertCount: number,
totalAlerts: number,
isPresent: boolean
) => {
MockAlerts(alertCount, totalAlerts);
const tree = MountedAlertGroup(jest.fn(), false);
const buttons = tree.find("button");
expect(buttons).toHaveLength(isPresent ? 2 : 0);
};
const ValidateLoadButtonAction = (
alertCount: number,
totalAlerts: number,
buttonIndex: number,
iconMatch: RegExp,
loadedAlerts: number,
alertsToRenderBeforeClick?: number
loadedAlerts: number
) => {
MockAlerts(totalAlerts);
const tree = MountedAlertGroup(jest.fn(), false, alertsToRenderBeforeClick);
MockAlerts(alertCount, totalAlerts);
const tree = MountedAlertGroup(jest.fn(), false);
const loadMore = tree.find("button").at(buttonIndex);
expect(loadMore.html()).toMatch(iconMatch);
loadMore.simulate("click");
tree.update();
expect(tree.find("Alert")).toHaveLength(loadedAlerts);
expect(alertStore.ui.groupAlertLimits[group.id]).toBe(loadedAlerts);
};
describe("<AlertGroup /> renderConfig", () => {
@@ -293,61 +294,52 @@ describe("<AlertGroup /> renderConfig", () => {
expect(settingsStore.alertGroupConfig.config.defaultRenderCount).toBe(5);
});
it("renders only up to settingsStore.alertGroupConfig.config.defaultRenderCount alerts", () => {
MockAlerts(50);
const tree = MountedAlertGroup(jest.fn(), false).find("AlertGroup");
const alerts = tree.find("Alert");
expect(alerts).toHaveLength(
settingsStore.alertGroupConfig.config.defaultRenderCount
);
});
it("load buttons are not rendered for 1 alert", () => {
ValidateLoadButtonPresent(1, false);
ValidateLoadButtonPresent(1, 1, false);
});
it("load buttons are not rendered for 5 alerts", () => {
ValidateLoadButtonPresent(5, false);
ValidateLoadButtonPresent(5, 5, false);
});
it("load buttons are rendered for 6 alert", () => {
ValidateLoadButtonPresent(6, true);
ValidateLoadButtonPresent(5, 6, true);
});
it("clicking - icon hides 1 alert if there's 6 in total", () => {
ValidateLoadButtonAction(6, 0, /fa-minus/, 5, 6);
ValidateLoadButtonAction(6, 6, 0, /fa-minus/, 5);
});
it("clicking - icon hides 1 alert if there's 6 in total and we're showing 3", () => {
ValidateLoadButtonAction(6, 0, /fa-minus/, 2, 3);
ValidateLoadButtonAction(3, 6, 0, /fa-minus/, 2);
});
it("clicking - icon hides 2 alerts if there's 7 in total and we're showing 7", () => {
ValidateLoadButtonAction(7, 0, /fa-minus/, 5, 7);
ValidateLoadButtonAction(7, 7, 0, /fa-minus/, 5);
});
it("clicking - icon hides 5 alerts if there's 10 in total and we're showing 10", () => {
ValidateLoadButtonAction(10, 0, /fa-minus/, 5, 10);
ValidateLoadButtonAction(10, 10, 0, /fa-minus/, 5);
});
it("clicking - icon hides 5 alerts if there's 18 in total and we're showing 17", () => {
ValidateLoadButtonAction(18, 0, /fa-minus/, 12, 17);
ValidateLoadButtonAction(17, 18, 0, /fa-minus/, 12);
});
it("clicking + icon loads 1 more alert if there's 6 in total", () => {
ValidateLoadButtonAction(6, 1, /fa-plus/, 6);
ValidateLoadButtonAction(5, 6, 1, /fa-plus/, 6);
});
it("clicking + icon loads 4 more alert if there's 9 in total", () => {
ValidateLoadButtonAction(9, 1, /fa-plus/, 9);
ValidateLoadButtonAction(5, 9, 1, /fa-plus/, 9);
});
it("clicking + icon loads 5 more alert if there's 14 in total", () => {
ValidateLoadButtonAction(14, 1, /fa-plus/, 10);
ValidateLoadButtonAction(5, 14, 1, /fa-plus/, 10);
});
it("clicking + icon loads 5 more alert if there's 25 in total and we're showing 16", () => {
ValidateLoadButtonAction(25, 1, /fa-plus/, 22, 17);
ValidateLoadButtonAction(16, 25, 1, /fa-plus/, 21);
});
it("uses 'z-index: 100' style after setIsMenuOpen() is called on any Alert", async () => {
@@ -355,7 +347,7 @@ describe("<AlertGroup /> renderConfig", () => {
fetchMock.mock("*", { body: "" });
const promise = Promise.resolve();
MockAlerts(5);
MockAlerts(5, 5);
const tree = MountedAlertGroup(jest.fn(), false);
tree
@@ -373,7 +365,7 @@ describe("<AlertGroup /> renderConfig", () => {
it("uses 'z-index: 100' style after setIsMenuOpen() is called on AlertGroup header menu", async () => {
const promise = Promise.resolve();
MockAlerts(5);
MockAlerts(5, 5);
const tree = MountedAlertGroup(jest.fn(), false);
tree.find("span.cursor-pointer").at(0).simulate("click");
@@ -48,7 +48,6 @@ const AlertGroup: FC<{
silenceFormStore: SilenceFormStore;
groupWidth: number;
gridLabelValue: string;
initialAlertsToRender?: number;
}> = ({
group,
showAlertmanagers,
@@ -58,17 +57,10 @@ const AlertGroup: FC<{
settingsStore,
groupWidth,
gridLabelValue,
initialAlertsToRender,
}) => {
const defaultRenderCount =
settingsStore.alertGroupConfig.config.defaultRenderCount;
const [alertsToRenderInternal, setAlertsToRender] = useState<number | null>(
initialAlertsToRender || null
);
const alertsToRender = alertsToRenderInternal || defaultRenderCount;
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const [isCollapsed, setIsCollapsed] = useState<boolean>(
@@ -94,15 +86,19 @@ const AlertGroup: FC<{
};
const loadMore = () => {
const step = getStepSize(group.alerts.length);
// show cur+step, but not more that total alert count
setAlertsToRender(Math.min(alertsToRender + step, group.alerts.length));
const step = getStepSize(group.totalAlerts);
alertStore.ui.setGroupAlertLimit(
group.id,
Math.min(group.alerts.length + step, group.totalAlerts)
);
};
const loadLess = () => {
const step = getStepSize(group.alerts.length);
// show cur-step, but not less than 1
setAlertsToRender(Math.max(alertsToRender - step, 1));
const step = getStepSize(group.totalAlerts);
alertStore.ui.setGroupAlertLimit(
group.id,
Math.max(group.alerts.length - step, 1)
);
};
const onAlertGroupCollapseEvent = useCallback(
@@ -177,7 +173,7 @@ const AlertGroup: FC<{
) : null}
<ul className="list-group">
{group.alerts
.slice(0, alertStore.ui.isIdle ? 1 : alertsToRender)
.slice(0, alertStore.ui.isIdle ? 1 : group.alerts.length)
.map((alert) => (
<Alert
key={alert.id}
@@ -194,7 +190,7 @@ const AlertGroup: FC<{
setIsMenuOpen={setIsMenuOpen}
/>
))}
{group.alerts.length > defaultRenderCount ? (
{group.totalAlerts > defaultRenderCount ? (
<li
className="list-group-item border-0 p-0 text-center bg-transparent"
style={{
@@ -214,9 +210,9 @@ const AlertGroup: FC<{
tooltip="Show fewer alerts in this group"
/>
<small className="text-muted mx-2">
{Math.min(alertsToRender, group.alerts.length)}
{" of "}
{group.alerts.length}
{" of "}
{group.totalAlerts}
</small>
<LoadButton
icon={faPlus}
+1 -1
View File
@@ -179,7 +179,7 @@ const Grid: FC<{
type="button"
className="btn btn-secondary mb-3"
onClick={() => {
alertStore.ui.setLimit(
alertStore.ui.setGridGroupLimit(
grid.labelName,
grid.labelValue,
grid.alertGroups.length +
@@ -241,7 +241,7 @@ describe("<Grid />", () => {
}
);
tree.find("button").simulate("click");
expect(alertStore.ui.limits).toStrictEqual({
expect(alertStore.ui.gridGroupLimits).toStrictEqual({
"": { "": 40 + alertStore.settings.values.gridGroupLimit },
});
});
@@ -276,7 +276,7 @@ describe("<Grid />", () => {
}
);
tree.find("button").simulate("click");
expect(alertStore.ui.limits).toStrictEqual({
expect(alertStore.ui.gridGroupLimits).toStrictEqual({
foo: { bar: 70 },
});
});
+3
View File
@@ -58,6 +58,7 @@ export interface APIAlertGroupT {
receiver: string;
labels: LabelsT;
alerts: APIAlertT[];
totalAlerts: number;
alertmanagerCount: { [key: string]: number };
stateCount: StateCountT;
shared: {
@@ -238,4 +239,6 @@ export interface AlertsRequestT {
sortOrder: string;
sortLabel: string;
sortReverse: boolean;
defaultGroupLimit: number;
groupLimits: { [key: string]: number };
}
+95 -19
View File
@@ -545,7 +545,7 @@ describe("AlertStore.fetch", () => {
const store = new AlertStore(["label=value"]);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(fetchMock.calls()).toHaveLength(1);
@@ -561,7 +561,7 @@ describe("AlertStore.fetch", () => {
const store = new AlertStore([]);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(fetchMock.calls()).toHaveLength(1);
@@ -580,7 +580,7 @@ describe("AlertStore.fetch", () => {
const store = new AlertStore([]);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toHaveProperty("error");
expect(fetchMock.calls()).toHaveLength(10);
@@ -600,7 +600,7 @@ describe("AlertStore.fetch", () => {
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toHaveProperty("error");
expect(fetchMock.calls()).toHaveLength(10);
});
@@ -615,7 +615,7 @@ describe("AlertStore.fetch", () => {
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toHaveProperty("error");
expect(fetchMock.calls()).toHaveLength(10);
@@ -626,7 +626,7 @@ describe("AlertStore.fetch", () => {
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(fetchMock.calls()).toHaveLength(1);
@@ -636,7 +636,7 @@ describe("AlertStore.fetch", () => {
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toHaveProperty("error");
expect(fetchMock.calls()).toHaveLength(10);
});
@@ -656,7 +656,7 @@ describe("AlertStore.fetch", () => {
);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(store.info.reloadNeeded).toBe(true);
@@ -673,7 +673,7 @@ describe("AlertStore.fetch", () => {
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toHaveProperty("error");
expect(store.filters.values[0].applied).toBe(true);
});
@@ -690,7 +690,7 @@ describe("AlertStore.fetch", () => {
// initial fetch, should update settings
store.settings.setValues({ foo: "bar" } as any);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(store.settings.values).toMatchObject({
staticColorLabels: ["job"],
@@ -702,7 +702,7 @@ describe("AlertStore.fetch", () => {
// second fetch, should keep same settings
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(store.settings.values).toMatchObject({
staticColorLabels: ["job"],
@@ -721,7 +721,7 @@ describe("AlertStore.fetch", () => {
});
const store = new AlertStore(["label=value"]);
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(store.info.upgradeReady).toBe(false);
@@ -731,7 +731,7 @@ describe("AlertStore.fetch", () => {
body: JSON.stringify(response),
});
await expect(
store.fetch("", false, "", "", false, {})
store.fetch("", false, "", "", false, {}, 5, {})
).resolves.toBeUndefined();
expect(store.info.upgradeReady).toBe(true);
});
@@ -797,7 +797,7 @@ describe("AlertStore.fetch", () => {
});
const store = new AlertStore(["label=value"]);
await expect(
store.fetch("", false, "sortOrder", "sortLabel", false, {})
store.fetch("", false, "sortOrder", "sortLabel", false, {}, 5, {})
).resolves.toBeUndefined();
expect(fetchMock.calls().length).toEqual(1);
expect(fetchMock.calls()[0][0]).toBe("/alerts.json");
@@ -809,6 +809,8 @@ describe("AlertStore.fetch", () => {
sortOrder: "sortOrder",
sortLabel: "sortLabel",
sortReverse: false,
defaultGroupLimit: 5,
groupLimits: {},
});
});
@@ -820,7 +822,7 @@ describe("AlertStore.fetch", () => {
});
const store = new AlertStore(["label=value"]);
await expect(
store.fetch("cluster", true, "sortOrder", "sortLabel", true, {})
store.fetch("cluster", true, "sortOrder", "sortLabel", true, {}, 5, {})
).resolves.toBeUndefined();
expect(fetchMock.calls().length).toEqual(1);
expect(fetchMock.calls()[0][0]).toBe("/alerts.json");
@@ -832,6 +834,8 @@ describe("AlertStore.fetch", () => {
sortOrder: "sortOrder",
sortLabel: "sortLabel",
sortReverse: true,
defaultGroupLimit: 5,
groupLimits: {},
});
});
@@ -842,11 +846,23 @@ describe("AlertStore.fetch", () => {
body: JSON.stringify(response),
});
const store = new AlertStore(["label=value"]);
store.ui.setLimit("foo", "bar", 7);
store.ui.setGridGroupLimit("foo", "bar", 7);
store.ui.setGroupAlertLimit("1234567890", 7);
store.ui.setGroupAlertLimit("1234567891", 1);
await expect(
store.fetch("cluster", true, "sortOrder", "sortLabel", false, {
bar: 7,
})
store.fetch(
"cluster",
true,
"sortOrder",
"sortLabel",
false,
{
bar: 7,
},
5,
{ "1234567890": 7, "1234567891": 1 }
)
).resolves.toBeUndefined();
expect(fetchMock.calls().length).toEqual(1);
expect(fetchMock.calls()[0][0]).toBe("/alerts.json");
@@ -858,6 +874,66 @@ describe("AlertStore.fetch", () => {
sortOrder: "sortOrder",
sortLabel: "sortLabel",
sortReverse: false,
defaultGroupLimit: 5,
groupLimits: { "1234567890": 7, "1234567891": 1 },
});
});
it("purges unknown group limits after fetch", async () => {
const store = new AlertStore(["label=value"]);
store.ui.setGridGroupLimit("foo", "bar", 7);
store.ui.setGroupAlertLimit("g1", 7);
store.ui.setGroupAlertLimit("g2", 1);
store.ui.setGroupAlertLimit("g4", 6);
const g1 = MockGroup("group1", 1, 1, 0);
g1.id = "g1";
const g2 = MockGroup("group2", 1, 1, 0);
g2.id = "g2";
const g3 = MockGroup("group3", 1, 1, 0);
g3.id = "g3";
const response = EmptyAPIResponse();
response.grids = [
{
labelName: "",
labelValue: "",
alertGroups: [g1, g2, g3],
totalGroups: 3,
stateCount: { unprocessed: 0, active: 3, suppressed: 0 },
},
];
fetchMock.reset();
fetchMock.mock("*", {
body: JSON.stringify(response),
});
await expect(
store.fetch(
"cluster",
true,
"sortOrder",
"sortLabel",
false,
{
bar: 7,
},
5,
{ g1: 7, g2: 1, g4: 6 }
)
).resolves.toBeUndefined();
expect(fetchMock.calls().length).toEqual(1);
expect(fetchMock.calls()[0][0]).toBe("/alerts.json");
expect(JSON.parse(fetchMock.calls()[0][1]?.body as string)).toStrictEqual({
filters: ["label=value"],
gridLabel: "cluster",
gridLimits: { bar: 7 },
gridSortReverse: true,
sortOrder: "sortOrder",
sortLabel: "sortLabel",
sortReverse: false,
defaultGroupLimit: 5,
groupLimits: { g1: 7, g2: 1, g4: 6 },
});
expect(store.ui.groupAlertLimits).toStrictEqual({ g1: 7, g2: 1 });
});
});
+41 -8
View File
@@ -209,8 +209,11 @@ interface AlertStoreStatusT {
interface AlertStoreUIT {
isIdle: boolean;
setIsIdle: (val: boolean) => void;
limits: { [key: string]: { [val: string]: number } };
setLimit: (key: string, val: string, limit: number) => void;
gridGroupLimits: { [key: string]: { [val: string]: number } };
setGridGroupLimit: (key: string, val: string, limit: number) => void;
groupAlertLimits: { [gid: string]: number };
setGroupAlertLimit: (gid: string, limit: number) => void;
purgeGroupAlertLimits: (knownGids: string[]) => void;
}
class AlertStore {
@@ -558,14 +561,30 @@ class AlertStore {
setIsIdle(val: boolean) {
this.isIdle = val;
},
limits: {} as { [key: string]: { [val: string]: number } },
setLimit(key: string, val: string, limit: number) {
this.limits = { [key]: { ...this.limits[key], [val]: limit } };
gridGroupLimits: {} as { [key: string]: { [val: string]: number } },
setGridGroupLimit(key: string, val: string, limit: number) {
this.gridGroupLimits = {
[key]: { ...this.gridGroupLimits[key], [val]: limit },
};
},
groupAlertLimits: {} as { [gid: string]: number },
setGroupAlertLimit(gid: string, limit: number) {
this.groupAlertLimits[gid] = limit;
},
purgeGroupAlertLimits(knownGids: string[]) {
const newLimits: { [gid: string]: number } = {};
Object.entries(this.groupAlertLimits)
.filter(([gid, _]) => knownGids.includes(gid))
.forEach(([gid, limit]) => {
newLimits[gid] = limit;
});
this.groupAlertLimits = newLimits;
},
},
{
setIsIdle: action.bound,
setLimit: action.bound,
setGridGroupLimit: action.bound,
setGroupAlertLimit: action.bound,
}
);
@@ -579,7 +598,9 @@ class AlertStore {
sortOrder: string,
sortLabel: string,
sortReverse: boolean,
limits: { [key: string]: number }
gridGroupLimits: { [key: string]: number },
defaultGroupLimit: number,
groupAlertLimits: { [key: string]: number }
) => {
this.status.setFetching();
@@ -587,10 +608,12 @@ class AlertStore {
filters: this.filters.values.map((f) => f.raw),
gridLabel: gridLabel,
gridSortReverse: gridSortReverse,
gridLimits: limits,
gridLimits: gridGroupLimits,
sortOrder: sortOrder,
sortLabel: sortLabel,
sortReverse: sortReverse,
defaultGroupLimit: defaultGroupLimit,
groupLimits: groupAlertLimits,
};
const alertsURI = FormatBackendURI("alerts.json");
@@ -676,6 +699,16 @@ class AlertStore {
updates.receivers = result.receivers;
this.data = Object.assign(this.data, updates);
const knowGroups: string[] = [];
result.grids.map((grid) =>
grid.alertGroups
.map((group) => group.id)
.forEach((id) => {
knowGroups.push(id);
})
);
this.ui.purgeGroupAlertLimits(knowGroups);
// before storing new version check if we need to reload
if (
this.info.version !== "unknown" &&
+1
View File
@@ -56,6 +56,7 @@ const MockAlertGroup = (
): APIAlertGroupT => ({
receiver: "by-name",
labels: rootLabels,
totalAlerts: alerts.length,
alerts: alerts,
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
alertmanagerCount: {
+2
View File
@@ -164,6 +164,8 @@ const MockGrid = (alertStore: AlertStore): void => {
group.stateCount.active = active;
group.stateCount.suppressed = suppressed;
group.stateCount.unprocessed = unprocessed;
group.totalAlerts = i;
group.alerts = group.alerts.slice(0, Math.min(i, 5));
if (i === 2 || i === 4) {
group.shared.clusters = ["default"];
}