mirror of
https://github.com/prymitive/karma
synced 2026-05-09 03:36:44 +00:00
fix(ui): make sure all dropdowns are rendered above grid elements
This commit is contained in:
committed by
Łukasz Mierzwa
parent
cd52400b03
commit
d22c9271c8
@@ -7,6 +7,7 @@ import type {
|
||||
APIAlertGroupT,
|
||||
APIAlertT,
|
||||
APIAlertsResponseUpstreamsT,
|
||||
APIGridT,
|
||||
} from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
@@ -16,6 +17,7 @@ let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
let alert: APIAlertT;
|
||||
let group: APIAlertGroupT;
|
||||
let grid: APIGridT;
|
||||
|
||||
let MockAfterClick: () => void;
|
||||
let MockSetIsMenuOpen: () => void;
|
||||
@@ -74,6 +76,17 @@ beforeEach(() => {
|
||||
|
||||
alert = MockAlert([], { foo: "bar" }, "active");
|
||||
group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {});
|
||||
grid = {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
active: 0,
|
||||
suppressed: 0,
|
||||
unprocessed: 0,
|
||||
},
|
||||
};
|
||||
|
||||
alertStore.data.setUpstreams(generateUpstreams());
|
||||
});
|
||||
@@ -81,6 +94,7 @@ beforeEach(() => {
|
||||
const MountedAlertMenu = (group: APIAlertGroupT) => {
|
||||
return mount(
|
||||
<AlertMenu
|
||||
grid={grid}
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalL
|
||||
import { faWrench } from "@fortawesome/free-solid-svg-icons/faWrench";
|
||||
|
||||
import type {
|
||||
APIGridT,
|
||||
APIAlertT,
|
||||
APIAlertGroupT,
|
||||
APIAnnotationT,
|
||||
@@ -142,19 +143,25 @@ const MenuContent: FC<{
|
||||
};
|
||||
|
||||
const AlertMenu: FC<{
|
||||
grid: APIGridT;
|
||||
group: APIAlertGroupT;
|
||||
alert: APIAlertT;
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
setIsMenuOpen: (isOpen: boolean) => void;
|
||||
}> = observer(
|
||||
({ group, alert, alertStore, silenceFormStore, setIsMenuOpen }) => {
|
||||
({ grid, group, alert, alertStore, silenceFormStore, setIsMenuOpen }) => {
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: isHidden, labelValue: grid.labelValue },
|
||||
})
|
||||
);
|
||||
setIsMenuOpen(isHidden);
|
||||
setIsHidden(!isHidden);
|
||||
}, [isHidden, setIsMenuOpen]);
|
||||
}, [grid.labelValue, isHidden, setIsMenuOpen]);
|
||||
|
||||
const hide = useCallback(() => {
|
||||
setIsHidden(true);
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
MockSilence,
|
||||
} from "__fixtures__/Alerts";
|
||||
import { MockThemeContext } from "__fixtures__/Theme";
|
||||
import type { APIAlertGroupT, APIAlertT } from "Models/APITypes";
|
||||
import type { APIAlertGroupT, APIAlertT, APIGridT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import { BorderClassMap } from "Common/Colors";
|
||||
@@ -22,11 +22,24 @@ import Alert from ".";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
let grid: APIGridT;
|
||||
|
||||
beforeEach(() => {
|
||||
advanceTo(new Date(Date.UTC(2018, 7, 15, 20, 40, 0)));
|
||||
alertStore = new AlertStore([]);
|
||||
silenceFormStore = new SilenceFormStore();
|
||||
|
||||
grid = {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
active: 0,
|
||||
suppressed: 0,
|
||||
unprocessed: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -58,6 +71,7 @@ const MountedAlert = (
|
||||
) => {
|
||||
return mount(
|
||||
<Alert
|
||||
grid={grid}
|
||||
alert={alert}
|
||||
group={group}
|
||||
showReceiver={showReceiver}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import type {
|
||||
APIGridT,
|
||||
APIAlertT,
|
||||
APIAlertGroupT,
|
||||
APIAlertmanagerStateT,
|
||||
@@ -18,6 +19,7 @@ import { AlertMenu } from "./AlertMenu";
|
||||
import { RenderSilence } from "../Silences";
|
||||
|
||||
const Alert: FC<{
|
||||
grid: APIGridT;
|
||||
group: APIAlertGroupT;
|
||||
alert: APIAlertT;
|
||||
showReceiver: boolean;
|
||||
@@ -27,6 +29,7 @@ const Alert: FC<{
|
||||
silenceFormStore: SilenceFormStore;
|
||||
setIsMenuOpen: (isOpen: boolean) => void;
|
||||
}> = ({
|
||||
grid,
|
||||
group,
|
||||
alert,
|
||||
showReceiver,
|
||||
@@ -99,6 +102,7 @@ const Alert: FC<{
|
||||
))}
|
||||
</div>
|
||||
<AlertMenu
|
||||
grid={grid}
|
||||
group={group}
|
||||
alert={alert}
|
||||
alertStore={alertStore}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { MockAlertGroup } from "__fixtures__/Alerts";
|
||||
import type {
|
||||
APIAlertGroupT,
|
||||
APIAlertsResponseUpstreamsT,
|
||||
APIGridT,
|
||||
} from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
@@ -15,6 +16,7 @@ import { GroupMenu, MenuContent } from "./GroupMenu";
|
||||
|
||||
let alertStore: AlertStore;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
let grid: APIGridT;
|
||||
|
||||
let MockAfterClick: () => void;
|
||||
let MockSetIsMenuOpen: () => void;
|
||||
@@ -72,11 +74,24 @@ beforeEach(() => {
|
||||
MockSetIsMenuOpen = jest.fn();
|
||||
|
||||
alertStore.data.setUpstreams(generateUpstreams());
|
||||
|
||||
grid = {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
active: 0,
|
||||
suppressed: 0,
|
||||
unprocessed: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const MountedGroupMenu = (group: APIAlertGroupT, themed: boolean) => {
|
||||
return mount(
|
||||
<GroupMenu
|
||||
grid={grid}
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { faShareSquare } from "@fortawesome/free-solid-svg-icons/faShareSquare";
|
||||
import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
|
||||
import { faWrench } from "@fortawesome/free-solid-svg-icons/faWrench";
|
||||
|
||||
import type { APIAlertGroupT } from "Models/APITypes";
|
||||
import type { APIAlertGroupT, APIGridT } from "Models/APITypes";
|
||||
import { FormatAlertsQ } from "Stores/AlertStore";
|
||||
import type { AlertStore } from "Stores/AlertStore";
|
||||
import {
|
||||
@@ -143,18 +143,24 @@ const MenuContent: FC<{
|
||||
};
|
||||
|
||||
const GroupMenu: FC<{
|
||||
grid: APIGridT;
|
||||
group: APIAlertGroupT;
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
themed: boolean;
|
||||
setIsMenuOpen: (isOpen: boolean) => void;
|
||||
}> = ({ group, alertStore, silenceFormStore, themed, setIsMenuOpen }) => {
|
||||
}> = ({ grid, group, alertStore, silenceFormStore, themed, setIsMenuOpen }) => {
|
||||
const [isHidden, setIsHidden] = useState<boolean>(true);
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: isHidden, labelValue: grid.labelValue },
|
||||
})
|
||||
);
|
||||
setIsMenuOpen(isHidden);
|
||||
setIsHidden(!isHidden);
|
||||
}, [isHidden, setIsMenuOpen]);
|
||||
}, [setIsMenuOpen, isHidden, grid.labelValue]);
|
||||
|
||||
const hide = useCallback(() => {
|
||||
setIsHidden(true);
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { FC, MouseEvent } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
import type { APIAlertGroupT } from "Models/APITypes";
|
||||
import type { APIAlertGroupT, APIGridT } from "Models/APITypes";
|
||||
import type { AlertStore } from "Stores/AlertStore";
|
||||
import type { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
import FilteringLabel from "Components/Labels/FilteringLabel";
|
||||
@@ -15,6 +15,7 @@ import { GroupMenu } from "./GroupMenu";
|
||||
const GroupHeader: FC<{
|
||||
isCollapsed: boolean;
|
||||
setIsCollapsed: (isCollapsed: boolean) => void;
|
||||
grid: APIGridT;
|
||||
group: APIAlertGroupT;
|
||||
alertStore: AlertStore;
|
||||
silenceFormStore: SilenceFormStore;
|
||||
@@ -24,6 +25,7 @@ const GroupHeader: FC<{
|
||||
}> = ({
|
||||
isCollapsed,
|
||||
setIsCollapsed,
|
||||
grid,
|
||||
group,
|
||||
alertStore,
|
||||
silenceFormStore,
|
||||
@@ -55,6 +57,7 @@ const GroupHeader: FC<{
|
||||
>
|
||||
<span className="flex-shrink-0 flex-grow-0">
|
||||
<GroupMenu
|
||||
grid={grid}
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
MockThemeContextWithoutAnimations,
|
||||
} from "__fixtures__/Theme";
|
||||
import { RainbowHistoryResponse } from "__fixtures__/AlertHistory";
|
||||
import type { APIAlertGroupT } from "Models/APITypes";
|
||||
import type { APIAlertGroupT, APIGridT } from "Models/APITypes";
|
||||
import { AlertStore } from "Stores/AlertStore";
|
||||
import { Settings, CollapseStateT } from "Stores/Settings";
|
||||
import { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
@@ -21,6 +21,7 @@ let alertStore: AlertStore;
|
||||
let settingsStore: Settings;
|
||||
let silenceFormStore: SilenceFormStore;
|
||||
let group: APIAlertGroupT;
|
||||
let grid: APIGridT;
|
||||
let originalInnerWidth: number;
|
||||
|
||||
const MockGroup = (groupName: string) => {
|
||||
@@ -50,6 +51,18 @@ beforeEach(() => {
|
||||
...alertStore.settings.values,
|
||||
...{ historyEnabled: false },
|
||||
});
|
||||
|
||||
grid = {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
active: 0,
|
||||
suppressed: 0,
|
||||
unprocessed: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -67,16 +80,12 @@ const MockAlerts = (alertCount: number, totalAlerts: number) => {
|
||||
group.totalAlerts = totalAlerts;
|
||||
};
|
||||
|
||||
const MountedAlertGroup = (
|
||||
afterUpdate: () => void,
|
||||
showAlertmanagers: boolean,
|
||||
theme?: ThemeCtx
|
||||
) => {
|
||||
const MountedAlertGroup = (afterUpdate: () => void, theme?: ThemeCtx) => {
|
||||
return mount(
|
||||
<AlertGroup
|
||||
afterUpdate={afterUpdate}
|
||||
grid={grid}
|
||||
group={group}
|
||||
showAlertmanagers={showAlertmanagers}
|
||||
settingsStore={settingsStore}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
@@ -100,20 +109,20 @@ const ValidateCollapse = (
|
||||
settingsStore.alertGroupConfig.setDefaultCollapseState(defaultCollapseState);
|
||||
|
||||
MockAlerts(3, 3);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find("Alert")).toHaveLength(shouldBeCollapsed ? 0 : 3);
|
||||
};
|
||||
|
||||
describe("<AlertGroup />", () => {
|
||||
it("doesn't crash on unmount", () => {
|
||||
MockAlerts(5, 5);
|
||||
const tree = MountedAlertGroup(jest.fn(), true);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
tree.unmount();
|
||||
});
|
||||
|
||||
it("uses 'animate' class when settingsStore.themeConfig.config.animations is true", () => {
|
||||
MockAlerts(5, 5);
|
||||
const tree = MountedAlertGroup(jest.fn(), true, MockThemeContext);
|
||||
const tree = MountedAlertGroup(jest.fn(), MockThemeContext);
|
||||
expect(
|
||||
tree.find("div.components-grid-alertgrid-alertgroup").hasClass("animate")
|
||||
).toBe(true);
|
||||
@@ -123,7 +132,6 @@ describe("<AlertGroup />", () => {
|
||||
MockAlerts(5, 5);
|
||||
const tree = MountedAlertGroup(
|
||||
jest.fn(),
|
||||
true,
|
||||
MockThemeContextWithoutAnimations
|
||||
);
|
||||
expect(
|
||||
@@ -134,7 +142,7 @@ describe("<AlertGroup />", () => {
|
||||
it("renders Alertmanager cluster labels in footer if shared", () => {
|
||||
MockAlerts(2, 2);
|
||||
group.shared.clusters = ["default"];
|
||||
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
|
||||
const tree = MountedAlertGroup(jest.fn()).find("AlertGroup");
|
||||
expect(tree.find("GroupFooter").html()).toMatch(/@cluster/);
|
||||
});
|
||||
|
||||
@@ -163,7 +171,7 @@ describe("<AlertGroup />", () => {
|
||||
});
|
||||
}
|
||||
group.shared.clusters = ["default", "HA"];
|
||||
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
|
||||
const tree = MountedAlertGroup(jest.fn()).find("AlertGroup");
|
||||
const labels = tree.find("GroupFooter").find("FilteringLabel");
|
||||
expect(labels).toHaveLength(3);
|
||||
expect(labels.at(0).text()).toBe("@cluster: default");
|
||||
@@ -176,7 +184,7 @@ describe("<AlertGroup />", () => {
|
||||
for (let i = 0; i < group.alerts.length; i++) {
|
||||
group.alerts[i].alertmanager = [];
|
||||
}
|
||||
const tree = MountedAlertGroup(jest.fn(), true).find("AlertGroup");
|
||||
const tree = MountedAlertGroup(jest.fn()).find("AlertGroup");
|
||||
const labels = tree.find("GroupFooter").find("FilteringLabel");
|
||||
expect(labels).toHaveLength(1);
|
||||
expect(labels.at(0).text()).toBe("@receiver: by-name");
|
||||
@@ -194,7 +202,7 @@ describe("<AlertGroup />", () => {
|
||||
fakeAlertmanager3: 1,
|
||||
fakeAlertmanager4: 1,
|
||||
};
|
||||
const tree = MountedAlertGroup(jest.fn(), true);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
|
||||
const alerts = tree.find("ul.list-group");
|
||||
expect(alerts.html()).toMatch(/@cluster/);
|
||||
@@ -205,7 +213,7 @@ describe("<AlertGroup />", () => {
|
||||
|
||||
it("only renders titlebar when collapsed", () => {
|
||||
MockAlerts(5, 10);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
tree.find("span.badge.cursor-pointer").at(1).simulate("click");
|
||||
expect(tree.find("Alert")).toHaveLength(0);
|
||||
expect(tree.find("ul.list-group")).toHaveLength(0);
|
||||
@@ -214,7 +222,7 @@ describe("<AlertGroup />", () => {
|
||||
it("renders reduced details when idle", () => {
|
||||
MockAlerts(5, 10);
|
||||
alertStore.ui.setIsIdle(true);
|
||||
const tree = MountedAlertGroup(jest.fn(), true, MockThemeContext);
|
||||
const tree = MountedAlertGroup(jest.fn(), MockThemeContext);
|
||||
expect(tree.find("Alert")).toHaveLength(1);
|
||||
});
|
||||
|
||||
@@ -251,14 +259,14 @@ describe("<AlertGroup />", () => {
|
||||
it("renders @receiver label when alertStore.data.receivers.length > 1", () => {
|
||||
alertStore.data.setReceivers(["foo", "bar"]);
|
||||
MockAlerts(5, 10);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.html()).toMatch(/@receiver:/);
|
||||
});
|
||||
|
||||
it("doesn't render @receiver label when alertStore.data.receivers.length == 0", () => {
|
||||
alertStore.data.setReceivers([]);
|
||||
MockAlerts(5, 10);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.html()).not.toMatch(/@receiver:/);
|
||||
});
|
||||
});
|
||||
@@ -269,7 +277,7 @@ const ValidateLoadButtonPresent = (
|
||||
isPresent: boolean
|
||||
) => {
|
||||
MockAlerts(alertCount, totalAlerts);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
const buttons = tree.find("button");
|
||||
expect(buttons).toHaveLength(isPresent ? 2 : 0);
|
||||
};
|
||||
@@ -282,7 +290,7 @@ const ValidateLoadButtonAction = (
|
||||
loadedAlerts: number
|
||||
) => {
|
||||
MockAlerts(alertCount, totalAlerts);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
const loadMore = tree.find("button").at(buttonIndex);
|
||||
expect(loadMore.html()).toMatch(iconMatch);
|
||||
loadMore.simulate("click");
|
||||
@@ -348,7 +356,7 @@ describe("<AlertGroup /> renderConfig", () => {
|
||||
|
||||
const promise = Promise.resolve();
|
||||
MockAlerts(5, 5);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
|
||||
tree
|
||||
.find("Alert")
|
||||
@@ -366,7 +374,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, 5);
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
|
||||
tree.find("span.cursor-pointer").at(0).simulate("click");
|
||||
await act(() => promise);
|
||||
@@ -381,7 +389,7 @@ describe("<AlertGroup /> card theme", () => {
|
||||
it("renders bg-light background when colorTitleBar=false", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(false);
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find(".card").hasClass("bg-light")).toBe(true);
|
||||
expect(tree.find(".card").hasClass("bg-danger")).toBe(false);
|
||||
expect(tree.find(".card").hasClass("bg-success")).toBe(false);
|
||||
@@ -391,14 +399,14 @@ describe("<AlertGroup /> card theme", () => {
|
||||
it("renders themed titlebar when colorTitleBar=false", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(false);
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find("GroupHeader").prop("themedCounters")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders bg-light border when colorTitleBar=true and there are multiple alert states", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(false);
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find(".card").hasClass("bg-light")).toBe(true);
|
||||
expect(tree.find(".card").hasClass("bg-danger")).toBe(false);
|
||||
expect(tree.find(".card").hasClass("bg-success")).toBe(false);
|
||||
@@ -408,14 +416,14 @@ describe("<AlertGroup /> card theme", () => {
|
||||
it("renders themed titlebar when colorTitleBar=true and there are multiple alert states", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(true);
|
||||
group.stateCount = { active: 5, suppressed: 6, unprocessed: 7 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find("GroupHeader").prop("themedCounters")).toBe(true);
|
||||
});
|
||||
|
||||
it("renders state based background when colorTitleBar=true and there's only one alert state", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(true);
|
||||
group.stateCount = { active: 0, suppressed: 5, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find(".card").hasClass("bg-light")).toBe(false);
|
||||
expect(tree.find(".card").hasClass("bg-danger")).toBe(false);
|
||||
expect(tree.find(".card").hasClass("bg-success")).toBe(true);
|
||||
@@ -425,7 +433,7 @@ describe("<AlertGroup /> card theme", () => {
|
||||
it("renders unthemed titlebar when colorTitleBar=true and there's only one alert state", () => {
|
||||
settingsStore.alertGroupConfig.setColorTitleBar(true);
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find("GroupHeader").prop("themedCounters")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -447,7 +455,7 @@ describe("<AlertGroup /> card theme", () => {
|
||||
...{ historyEnabled: true },
|
||||
});
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
await act(async () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
@@ -462,7 +470,7 @@ describe("<AlertGroup /> card theme", () => {
|
||||
...{ historyEnabled: false },
|
||||
});
|
||||
group.stateCount = { active: 5, suppressed: 0, unprocessed: 0 };
|
||||
const tree = MountedAlertGroup(jest.fn(), false);
|
||||
const tree = MountedAlertGroup(jest.fn());
|
||||
expect(tree.find("AlertHistory")).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus";
|
||||
import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
|
||||
import { faEllipsisH } from "@fortawesome/free-solid-svg-icons/faEllipsisH";
|
||||
|
||||
import type { APIAlertGroupT, AlertStateT } from "Models/APITypes";
|
||||
import type { APIGridT, APIAlertGroupT, AlertStateT } from "Models/APITypes";
|
||||
import type { Settings } from "Stores/Settings";
|
||||
import type { AlertStore } from "Stores/AlertStore";
|
||||
import type { SilenceFormStore } from "Stores/SilenceFormStore";
|
||||
@@ -40,8 +40,8 @@ const LoadButton: FC<{
|
||||
};
|
||||
|
||||
const AlertGroup: FC<{
|
||||
grid: APIGridT;
|
||||
group: APIAlertGroupT;
|
||||
showAlertmanagers: boolean;
|
||||
afterUpdate: () => void;
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
@@ -49,8 +49,8 @@ const AlertGroup: FC<{
|
||||
groupWidth: number;
|
||||
gridLabelValue: string;
|
||||
}> = ({
|
||||
grid,
|
||||
group,
|
||||
showAlertmanagers,
|
||||
afterUpdate,
|
||||
silenceFormStore,
|
||||
alertStore,
|
||||
@@ -159,6 +159,7 @@ const AlertGroup: FC<{
|
||||
<GroupHeader
|
||||
isCollapsed={isCollapsed}
|
||||
setIsCollapsed={setIsCollapsed}
|
||||
grid={grid}
|
||||
group={group}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
@@ -177,6 +178,7 @@ const AlertGroup: FC<{
|
||||
.map((alert) => (
|
||||
<Alert
|
||||
key={alert.id}
|
||||
grid={grid}
|
||||
group={group}
|
||||
alert={alert}
|
||||
showReceiver={
|
||||
|
||||
@@ -110,6 +110,20 @@ const Grid: FC<{
|
||||
});
|
||||
|
||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||
const onMenuOpen = useCallback(
|
||||
(event) => {
|
||||
if (event.detail.labelValue === grid.labelValue) {
|
||||
setIsMenuOpen(event.detail.isOpen);
|
||||
}
|
||||
},
|
||||
[grid.labelValue]
|
||||
);
|
||||
useEffect(() => {
|
||||
window.addEventListener("gridMenuOpen", onMenuOpen);
|
||||
return () => {
|
||||
window.removeEventListener("gridMenuOpen", onMenuOpen);
|
||||
};
|
||||
}, [onMenuOpen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -133,8 +147,6 @@ const Grid: FC<{
|
||||
isExpanded={isExpanded}
|
||||
onToggle={onCollapseClick}
|
||||
paddingTop={paddingTop}
|
||||
onMenuOpen={() => setIsMenuOpen(true)}
|
||||
onMenuClose={() => setIsMenuOpen(false)}
|
||||
/>
|
||||
</CSSTransition>
|
||||
<div
|
||||
@@ -162,10 +174,8 @@ const Grid: FC<{
|
||||
unmountOnExit
|
||||
>
|
||||
<AlertGroup
|
||||
grid={grid}
|
||||
group={group}
|
||||
showAlertmanagers={
|
||||
Object.keys(alertStore.data.upstreams.clusters).length > 1
|
||||
}
|
||||
afterUpdate={debouncedRepack}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
|
||||
@@ -64,8 +64,6 @@ const MountedGridLabelSelect = () => {
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onMenuOpen={jest.fn()}
|
||||
onMenuClose={jest.fn()}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -190,4 +188,78 @@ describe("<GridLabelSelect />", () => {
|
||||
|
||||
await act(() => promise);
|
||||
});
|
||||
|
||||
it("sending event from current grid sets z-index", () => {
|
||||
alertStore.data.setGrids([
|
||||
{
|
||||
labelName: "foo",
|
||||
labelValue: "baz",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
active: 3,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = mount(
|
||||
<AlertGrid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeContext.Provider,
|
||||
wrappingComponentProps: { value: MockThemeContextWithoutAnimations },
|
||||
}
|
||||
);
|
||||
|
||||
act(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: true, labelValue: "baz" },
|
||||
})
|
||||
);
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div").at(1).props().style?.zIndex).toBe(102);
|
||||
});
|
||||
|
||||
it("sending event from a different grid is ignored", () => {
|
||||
alertStore.data.setGrids([
|
||||
{
|
||||
labelName: "foo",
|
||||
labelValue: "baz",
|
||||
alertGroups: [],
|
||||
totalGroups: 0,
|
||||
stateCount: {
|
||||
unprocessed: 1,
|
||||
suppressed: 2,
|
||||
active: 3,
|
||||
},
|
||||
},
|
||||
]);
|
||||
const tree = mount(
|
||||
<AlertGrid
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: ThemeContext.Provider,
|
||||
wrappingComponentProps: { value: MockThemeContextWithoutAnimations },
|
||||
}
|
||||
);
|
||||
|
||||
act(() => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: true, labelValue: "fake" },
|
||||
})
|
||||
);
|
||||
});
|
||||
tree.update();
|
||||
expect(tree.find("div").at(1).props().style?.zIndex).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -131,57 +131,61 @@ const GridLabelSelect: FC<{
|
||||
alertStore: AlertStore;
|
||||
settingsStore: Settings;
|
||||
grid: APIGridT;
|
||||
onMenuOpen: () => void;
|
||||
onMenuClose: () => void;
|
||||
}> = observer(
|
||||
({ alertStore, settingsStore, grid, onMenuOpen, onMenuClose }) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
const hide = useCallback(() => setIsVisible(false), []);
|
||||
const toggle = useCallback(() => {
|
||||
if (isVisible) {
|
||||
onMenuClose();
|
||||
} else {
|
||||
onMenuOpen();
|
||||
}
|
||||
setIsVisible(!isVisible);
|
||||
}, [isVisible, onMenuOpen, onMenuClose]);
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
}> = observer(({ alertStore, settingsStore, grid }) => {
|
||||
const [isVisible, setIsVisible] = useState<boolean>(false);
|
||||
const hide = useCallback(() => setIsVisible(false), []);
|
||||
const toggle = useCallback(() => {
|
||||
if (isVisible) {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: false, labelValue: grid.labelValue },
|
||||
})
|
||||
);
|
||||
} else {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent("gridMenuOpen", {
|
||||
detail: { isOpen: true, labelValue: grid.labelValue },
|
||||
})
|
||||
);
|
||||
}
|
||||
setIsVisible(!isVisible);
|
||||
}, [isVisible, grid.labelValue]);
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
useOnClickOutside(ref, hide, isVisible);
|
||||
|
||||
return (
|
||||
<div ref={ref} className="components-label badge ps-1 pe-2">
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className="border-0 rounded-0 bg-inherit cursor-pointer px-1 py-0 components-grid-label-select-dropdown"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon className="text-muted" icon={faCaretDown} />
|
||||
</span>
|
||||
return (
|
||||
<div ref={ref} className="components-label badge ps-1 pe-2">
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref }) => (
|
||||
<span
|
||||
ref={ref}
|
||||
onClick={toggle}
|
||||
className="border-0 rounded-0 bg-inherit cursor-pointer px-1 py-0 components-grid-label-select-dropdown"
|
||||
data-toggle="dropdown"
|
||||
>
|
||||
<FontAwesomeIcon className="text-muted" icon={faCaretDown} />
|
||||
</span>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Popper modifiers={CommonPopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<Dropdown
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onClose={toggle}
|
||||
/>
|
||||
)}
|
||||
</Reference>
|
||||
<DropdownSlide in={isVisible} unmountOnExit>
|
||||
<Popper modifiers={CommonPopperModifiers}>
|
||||
{({ placement, ref, style }) => (
|
||||
<Dropdown
|
||||
popperPlacement={placement}
|
||||
popperRef={ref}
|
||||
popperStyle={style}
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onClose={toggle}
|
||||
/>
|
||||
)}
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
</Popper>
|
||||
</DropdownSlide>
|
||||
</Manager>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export { GridLabelSelect };
|
||||
|
||||
@@ -19,8 +19,6 @@ const Swimlane: FC<{
|
||||
isExpanded: boolean;
|
||||
onToggle: (event: MouseEvent) => void;
|
||||
paddingTop: number;
|
||||
onMenuOpen: () => void;
|
||||
onMenuClose: () => void;
|
||||
}> = ({
|
||||
alertStore,
|
||||
settingsStore,
|
||||
@@ -28,8 +26,6 @@ const Swimlane: FC<{
|
||||
isExpanded,
|
||||
onToggle,
|
||||
paddingTop,
|
||||
onMenuOpen,
|
||||
onMenuClose,
|
||||
}) => {
|
||||
return (
|
||||
<h5
|
||||
@@ -63,8 +59,6 @@ const Swimlane: FC<{
|
||||
alertStore={alertStore}
|
||||
settingsStore={settingsStore}
|
||||
grid={grid}
|
||||
onMenuOpen={onMenuOpen}
|
||||
onMenuClose={onMenuClose}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user