mirror of
https://github.com/prymitive/karma
synced 2026-08-23 11:56:20 +00:00
fix(ui): use slices for labels instead of maps
This commit is contained in:
committed by
Łukasz Mierzwa
parent
8f7cca40cc
commit
31c9468368
@@ -58,11 +58,17 @@ beforeEach(() => {
|
||||
});
|
||||
|
||||
alerts = [
|
||||
MockAlert([], { foo: "bar" }, "active"),
|
||||
MockAlert([], { foo: "baz" }, "active"),
|
||||
MockAlert([], { foo: "ignore" }, "suppressed"),
|
||||
MockAlert([], [{ name: "foo", value: "bar" }], "active"),
|
||||
MockAlert([], [{ name: "foo", value: "baz" }], "active"),
|
||||
MockAlert([], [{ name: "foo", value: "ignore" }], "suppressed"),
|
||||
];
|
||||
group = MockAlertGroup({ alertname: "Fake Alert" }, alerts, [], {}, {});
|
||||
group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
alerts,
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
alertname: ["Fake Alert"],
|
||||
foo: ["bar", "baz"],
|
||||
|
||||
@@ -20,18 +20,22 @@ import type {
|
||||
APIAlertGroupT,
|
||||
APIGridT,
|
||||
HistoryResponseT,
|
||||
LabelsT,
|
||||
} from "Models/APITypes";
|
||||
import { AlertHistory } from ".";
|
||||
|
||||
let group: APIAlertGroupT;
|
||||
let grid: APIGridT;
|
||||
|
||||
const MockGroup = (groupName: string) => {
|
||||
const MockGroup = (groupName: string, sharedLabels: LabelsT = []) => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", groupName: groupName },
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "groupName", value: groupName },
|
||||
],
|
||||
[],
|
||||
[],
|
||||
{},
|
||||
sharedLabels,
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
@@ -39,7 +43,11 @@ const MockGroup = (groupName: string) => {
|
||||
|
||||
const MockAlerts = (alertCount: number) => {
|
||||
for (let i = 1; i <= alertCount; i++) {
|
||||
const alert = MockAlert([], { instance: `instance${i}` }, "active");
|
||||
const alert = MockAlert(
|
||||
[],
|
||||
[{ name: "instance", value: `instance${i}` }],
|
||||
"active"
|
||||
);
|
||||
const startsAt = new Date();
|
||||
alert.startsAt = startsAt.toISOString();
|
||||
for (let j = 0; j < alert.alertmanager.length; j++) {
|
||||
@@ -171,6 +179,47 @@ describe("<AlertHistory />", () => {
|
||||
tree.unmount();
|
||||
});
|
||||
|
||||
it("send a correct payload with shared labels", async () => {
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.mock(
|
||||
"*",
|
||||
{
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(EmptyHistoryResponse),
|
||||
},
|
||||
{
|
||||
overwriteRoutes: true,
|
||||
}
|
||||
);
|
||||
|
||||
MockAlerts(3);
|
||||
group = MockGroup("fakeGroup", [
|
||||
{ name: "shared1", value: "value1" },
|
||||
{ name: "shared2", value: "value2" },
|
||||
]);
|
||||
const tree = mount(<AlertHistory group={group} grid={grid}></AlertHistory>);
|
||||
await act(async () => {
|
||||
await fetchMock.flush(true);
|
||||
});
|
||||
expect(fetchMock.calls()).toHaveLength(1);
|
||||
expect(fetchMock.calls()[0][1]?.body).toStrictEqual(
|
||||
JSON.stringify({
|
||||
sources: [
|
||||
"https://secure.example.com/graph",
|
||||
"http://plain.example.com/",
|
||||
],
|
||||
labels: {
|
||||
alertname: "Fake Alert",
|
||||
groupName: "fakeGroup",
|
||||
shared1: "value1",
|
||||
shared2: "value2",
|
||||
foo: "bar",
|
||||
},
|
||||
})
|
||||
);
|
||||
tree.unmount();
|
||||
});
|
||||
|
||||
it("matches snapshot with empty response", async () => {
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.mock(
|
||||
@@ -433,7 +482,11 @@ describe("<AlertHistory />", () => {
|
||||
for (const testCase of testCases) {
|
||||
const g = MockGroup("fakeGroup");
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
const alert = MockAlert([], { instance: `instance${i}` }, "active");
|
||||
const alert = MockAlert(
|
||||
[],
|
||||
[{ name: "instance", value: `instance${i}` }],
|
||||
"active"
|
||||
);
|
||||
const startsAt = new Date();
|
||||
alert.startsAt = startsAt.toISOString();
|
||||
alert.alertmanager.push(alert.alertmanager[0]);
|
||||
|
||||
@@ -34,9 +34,9 @@ export const AlertHistory: FC<{ group: APIAlertGroupT; grid: APIGridT }> = ({
|
||||
|
||||
const [lastUpdate, setLastUpdate] = useState<number>(GetUTCSeconds());
|
||||
const [upstreams, setUpstreams] = useState<UpstreamT[]>([]);
|
||||
const [labels] = useState({
|
||||
...group.labels,
|
||||
...group.shared.labels,
|
||||
const [labels] = useState<{ [key: string]: string }>({
|
||||
...Object.fromEntries(group.labels.map((l) => [l.name, l.value])),
|
||||
...Object.fromEntries(group.shared.labels.map((l) => [l.name, l.value])),
|
||||
...(grid.labelName !== "" && grid.labelName[0] !== "@"
|
||||
? { [grid.labelName]: grid.labelValue }
|
||||
: {}),
|
||||
|
||||
@@ -74,8 +74,14 @@ beforeEach(() => {
|
||||
MockAfterClick = jest.fn();
|
||||
MockSetIsMenuOpen = jest.fn();
|
||||
|
||||
alert = MockAlert([], { foo: "bar" }, "active");
|
||||
group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {});
|
||||
alert = MockAlert([], [{ name: "foo", value: "bar Alert" }], "active");
|
||||
group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[alert],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
grid = {
|
||||
labelName: "foo",
|
||||
labelValue: "bar",
|
||||
@@ -233,11 +239,11 @@ describe("<MenuContent />", () => {
|
||||
isAction: false,
|
||||
},
|
||||
],
|
||||
{ foo: "bar" },
|
||||
[{ name: "foo", value: "bar" }],
|
||||
"active"
|
||||
);
|
||||
group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert" },
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[alert],
|
||||
[
|
||||
{
|
||||
@@ -262,7 +268,7 @@ describe("<MenuContent />", () => {
|
||||
isAction: false,
|
||||
},
|
||||
],
|
||||
{},
|
||||
[],
|
||||
{}
|
||||
);
|
||||
|
||||
|
||||
@@ -58,7 +58,10 @@ const MockedAlert = () => {
|
||||
MockAnnotation("hidden", "some hidden text", false, false, false),
|
||||
MockAnnotation("link", "http://localhost", true, true, false),
|
||||
],
|
||||
{ job: "node_exporter", cluster: "dev" },
|
||||
[
|
||||
{ name: "job", value: "node_exporter" },
|
||||
{ name: "cluster", value: "dev" },
|
||||
],
|
||||
"active"
|
||||
);
|
||||
};
|
||||
@@ -91,7 +94,7 @@ const MountedAlert = (
|
||||
describe("<Alert />", () => {
|
||||
it("matches snapshot with showAlertmanagers=false showReceiver=false", () => {
|
||||
const alert = MockedAlert();
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
group.shared.clusters = ["default"];
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
@@ -100,7 +103,7 @@ describe("<Alert />", () => {
|
||||
it("matches snapshot when inhibited", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.alertmanager[0].inhibitedBy = ["123456"];
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
group.shared.clusters = ["default"];
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
@@ -119,7 +122,7 @@ describe("<Alert />", () => {
|
||||
silencedBy: [],
|
||||
inhibitedBy: ["123456"],
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(tree.find(".fa-volume-mute")).toHaveLength(1);
|
||||
});
|
||||
@@ -127,14 +130,14 @@ describe("<Alert />", () => {
|
||||
it("inhibition icon passes only unique fingerprints", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.alertmanager[0].inhibitedBy = ["123456"];
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(tree.find(".fa-volume-mute")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("renders @cluster label for non-shared clusters", () => {
|
||||
const alert = MockedAlert();
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const label = tree
|
||||
.find("FilteringLabel")
|
||||
@@ -164,7 +167,7 @@ describe("<Alert />", () => {
|
||||
silencedBy: [],
|
||||
inhibitedBy: [],
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const labels = tree
|
||||
.find("FilteringLabel")
|
||||
@@ -176,7 +179,7 @@ describe("<Alert />", () => {
|
||||
|
||||
it("renders @receiver label with showReceiver=true", () => {
|
||||
const alert = MockedAlert();
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, true, false);
|
||||
const label = tree
|
||||
.find("FilteringLabel")
|
||||
@@ -192,7 +195,7 @@ describe("<Alert />", () => {
|
||||
silence123456789: MockSilence(),
|
||||
},
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, { default: [] });
|
||||
const group = MockAlertGroup([], [alert], [], [], { default: [] });
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const silence = tree.find("ManagedSilence");
|
||||
expect(silence).toHaveLength(1);
|
||||
@@ -207,7 +210,7 @@ describe("<Alert />", () => {
|
||||
"123": MockSilence(),
|
||||
},
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, { default: [] });
|
||||
const group = MockAlertGroup([], [alert], [], [], { default: [] });
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const silence = tree.find("FallbackSilenceDesciption");
|
||||
expect(silence).toHaveLength(1);
|
||||
@@ -222,7 +225,7 @@ describe("<Alert />", () => {
|
||||
"123": MockSilence(),
|
||||
},
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, { default: [] });
|
||||
const group = MockAlertGroup([], [alert], [], [], { default: [] });
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const silence = tree.find("FallbackSilenceDesciption");
|
||||
expect(silence).toHaveLength(1);
|
||||
@@ -258,7 +261,7 @@ describe("<Alert />", () => {
|
||||
silence123456789: MockSilence(),
|
||||
},
|
||||
});
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const silence = tree.find("ManagedSilence");
|
||||
expect(silence).toHaveLength(1);
|
||||
@@ -268,13 +271,9 @@ describe("<Alert />", () => {
|
||||
it("doesn't render shared silences", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.alertmanager[0].silencedBy = ["silence123456789"];
|
||||
const group = MockAlertGroup(
|
||||
{},
|
||||
[alert],
|
||||
[],
|
||||
{},
|
||||
{ default: ["silence123456789"] }
|
||||
);
|
||||
const group = MockAlertGroup([], [alert], [], [], {
|
||||
default: ["silence123456789"],
|
||||
});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const silence = tree.find("ManagedSilence");
|
||||
expect(silence).toHaveLength(0);
|
||||
@@ -298,7 +297,7 @@ describe("<Alert />", () => {
|
||||
isAction: false,
|
||||
},
|
||||
];
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
const annotations = tree.find("div.components-grid-annotation");
|
||||
expect(annotations).toHaveLength(2);
|
||||
@@ -322,7 +321,7 @@ describe("<Alert />", () => {
|
||||
isAction: false,
|
||||
},
|
||||
];
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, true);
|
||||
const annotations = tree.find("div.components-grid-annotation");
|
||||
expect(annotations).toHaveLength(1);
|
||||
@@ -331,7 +330,7 @@ describe("<Alert />", () => {
|
||||
it("uses BorderClassMap.active when @state=active", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.state = "active";
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(
|
||||
tree
|
||||
@@ -343,7 +342,7 @@ describe("<Alert />", () => {
|
||||
it("uses BorderClassMap.suppressed when @state=suppressed", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.state = "suppressed";
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(
|
||||
tree
|
||||
@@ -355,7 +354,7 @@ describe("<Alert />", () => {
|
||||
it("uses BorderClassMap.unprocessed when @state=unprocessed", () => {
|
||||
const alert = MockedAlert();
|
||||
alert.state = "unprocessed";
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(
|
||||
tree
|
||||
@@ -369,7 +368,7 @@ describe("<Alert />", () => {
|
||||
|
||||
const alert = MockedAlert();
|
||||
(alert.state as string) = "foobar";
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(
|
||||
tree
|
||||
@@ -384,7 +383,7 @@ describe("<Alert />", () => {
|
||||
advanceTo(new Date(Date.UTC(2018, 7, 14, 17, 36, 41)));
|
||||
|
||||
const alert = MockedAlert();
|
||||
const group = MockAlertGroup({}, [alert], [], {}, {});
|
||||
const group = MockAlertGroup([], [alert], [], [], {});
|
||||
const tree = MountedAlert(alert, group, false, false);
|
||||
expect(
|
||||
tree
|
||||
|
||||
@@ -112,11 +112,11 @@ const Alert: FC<{
|
||||
{inhibitedBy.length > 0 ? (
|
||||
<InhibitedByModal alertStore={alertStore} fingerprints={inhibitedBy} />
|
||||
) : null}
|
||||
{Object.entries(alert.labels).map(([name, value]) => (
|
||||
{alert.labels.map((label) => (
|
||||
<FilteringLabel
|
||||
key={name}
|
||||
name={name}
|
||||
value={value}
|
||||
key={label.name}
|
||||
name={label.name}
|
||||
value={label.value}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -23,11 +23,11 @@ let silenceFormStore: SilenceFormStore;
|
||||
|
||||
const MockGroup = () => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert" },
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[
|
||||
MockAlert([], {}, "suppressed"),
|
||||
MockAlert([], {}, "suppressed"),
|
||||
MockAlert([], {}, "suppressed"),
|
||||
MockAlert([], [], "suppressed"),
|
||||
MockAlert([], [], "suppressed"),
|
||||
MockAlert([], [], "suppressed"),
|
||||
],
|
||||
[
|
||||
MockAnnotation("summary", "This is summary", true, false, false),
|
||||
@@ -40,7 +40,10 @@ const MockGroup = () => {
|
||||
),
|
||||
MockAnnotation("link", "http://link.example.com", true, true, false),
|
||||
],
|
||||
{ label1: "foo", label2: "bar" },
|
||||
[
|
||||
{ name: "label1", value: "foo" },
|
||||
{ name: "label2", value: "bar" },
|
||||
],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
|
||||
@@ -43,11 +43,11 @@ const GroupFooter: FC<{
|
||||
))
|
||||
: null}
|
||||
</div>
|
||||
{Object.entries(group.shared.labels).map(([name, value]) => (
|
||||
{group.shared.labels.map((label) => (
|
||||
<FilteringLabel
|
||||
key={name}
|
||||
name={name}
|
||||
value={value}
|
||||
key={label.name}
|
||||
name={label.name}
|
||||
value={label.value}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -103,7 +103,13 @@ const MountedGroupMenu = (group: APIAlertGroupT, themed: boolean) => {
|
||||
|
||||
describe("<GroupMenu />", () => {
|
||||
it("menu content is hidden by default", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedGroupMenu(group, true);
|
||||
expect(tree.find("div.dropdown-menu")).toHaveLength(0);
|
||||
expect(MockSetIsMenuOpen).not.toHaveBeenCalled();
|
||||
@@ -111,7 +117,13 @@ describe("<GroupMenu />", () => {
|
||||
|
||||
it("clicking toggle renders menu content", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedGroupMenu(group, true);
|
||||
const toggle = tree.find("span.cursor-pointer");
|
||||
toggle.simulate("click");
|
||||
@@ -122,7 +134,13 @@ describe("<GroupMenu />", () => {
|
||||
|
||||
it("clicking toggle twice hides menu content", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedGroupMenu(group, true);
|
||||
const toggle = tree.find("span.cursor-pointer");
|
||||
|
||||
@@ -145,7 +163,13 @@ describe("<GroupMenu />", () => {
|
||||
|
||||
it("clicking menu item hides menu content", async () => {
|
||||
const promise = Promise.resolve();
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedGroupMenu(group, true);
|
||||
const toggle = tree.find("span.cursor-pointer");
|
||||
|
||||
@@ -180,7 +204,13 @@ const MountedMenuContent = (group: APIAlertGroupT) => {
|
||||
|
||||
describe("<MenuContent />", () => {
|
||||
it("clicking on 'Copy' icon copies the link to clickboard", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedMenuContent(group);
|
||||
const button = tree.find(".dropdown-item").at(0);
|
||||
button.simulate("click");
|
||||
@@ -188,7 +218,13 @@ describe("<MenuContent />", () => {
|
||||
});
|
||||
|
||||
it("clicking on 'Silence' icon opens the silence form modal", () => {
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
group.alertmanagerCount = { am1: 1, ro: 1 };
|
||||
const tree = MountedMenuContent(group);
|
||||
const button = tree.find(".dropdown-item").at(1);
|
||||
@@ -205,7 +241,13 @@ describe("<MenuContent />", () => {
|
||||
upstreams.instances[2].readonly = true;
|
||||
alertStore.data.setUpstreams(upstreams);
|
||||
|
||||
const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {});
|
||||
const group = MockAlertGroup(
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{}
|
||||
);
|
||||
const tree = MountedMenuContent(group);
|
||||
const button = tree.find(".dropdown-item").at(1);
|
||||
expect(button.hasClass("disabled")).toBe(true);
|
||||
@@ -215,7 +257,7 @@ describe("<MenuContent />", () => {
|
||||
|
||||
it("renders action annotations when present", () => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert" },
|
||||
[{ name: "alertname", value: "Fake Alert" }],
|
||||
[],
|
||||
[
|
||||
{
|
||||
@@ -240,7 +282,7 @@ describe("<MenuContent />", () => {
|
||||
isAction: false,
|
||||
},
|
||||
],
|
||||
{},
|
||||
[],
|
||||
{}
|
||||
);
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ const MenuContent: FC<{
|
||||
alertStore,
|
||||
silenceFormStore,
|
||||
}) => {
|
||||
const groupFilters = Object.keys(group.labels).map((name) =>
|
||||
FormatQuery(name, QueryOperators.Equal, group.labels[name])
|
||||
const groupFilters = group.labels.map((label) =>
|
||||
FormatQuery(label.name, QueryOperators.Equal, label.value)
|
||||
);
|
||||
groupFilters.push(
|
||||
FormatQuery(StaticLabels.Receiver, QueryOperators.Equal, group.receiver)
|
||||
|
||||
@@ -66,11 +66,11 @@ const GroupHeader: FC<{
|
||||
/>
|
||||
</span>
|
||||
<span className="flex-shrink-1 flex-grow-1" style={{ minWidth: 0 }}>
|
||||
{Object.keys(group.labels).map((name) => (
|
||||
{group.labels.map((label) => (
|
||||
<FilteringLabel
|
||||
key={name}
|
||||
name={name}
|
||||
value={group.labels[name]}
|
||||
key={label.name}
|
||||
name={label.name}
|
||||
value={label.value}
|
||||
alertStore={alertStore}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -26,10 +26,13 @@ let originalInnerWidth: number;
|
||||
|
||||
const MockGroup = (groupName: string) => {
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", groupName: groupName },
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "groupName", value: "groupName" },
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
{},
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
@@ -71,7 +74,11 @@ afterEach(() => {
|
||||
|
||||
const MockAlerts = (alertCount: number, totalAlerts: number) => {
|
||||
for (let i = 1; i <= alertCount; i++) {
|
||||
const alert = MockAlert([], { instance: `instance${i}` }, "active");
|
||||
const alert = MockAlert(
|
||||
[],
|
||||
[{ name: "instance", value: `instance${i}` }],
|
||||
"active"
|
||||
);
|
||||
const startsAt = new Date();
|
||||
alert.startsAt = startsAt.toISOString();
|
||||
alert.alertmanager[0].startsAt = startsAt.toISOString();
|
||||
|
||||
@@ -143,13 +143,18 @@ const MountedGrid = (theme?: ThemeCtx) => {
|
||||
const MockGroup = (groupName: string, alertCount: number) => {
|
||||
const alerts = [];
|
||||
for (let i = 1; i <= alertCount; i++) {
|
||||
alerts.push(MockAlert([], { instance: `instance${i}` }, "active"));
|
||||
alerts.push(
|
||||
MockAlert([], [{ name: "instance", value: `instance${i}` }], "active")
|
||||
);
|
||||
}
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", group: groupName },
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "group", value: "groupName" },
|
||||
],
|
||||
alerts,
|
||||
[],
|
||||
{},
|
||||
[],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
|
||||
@@ -35,17 +35,17 @@ describe("<LabelSetList />", () => {
|
||||
});
|
||||
|
||||
it("renders labels on populated list", () => {
|
||||
const tree = MountedLabelSetList([{ foo: "bar" }]);
|
||||
const tree = MountedLabelSetList([[{ name: "foo", value: "bar" }]]);
|
||||
expect(tree.text()).not.toBe("No alerts matched");
|
||||
expect(tree.find("ul.list-group").text()).toBe("foo: bar");
|
||||
});
|
||||
|
||||
it("matches snapshot with populated list", () => {
|
||||
const tree = MountedLabelSetList([
|
||||
{ foo: "bar" },
|
||||
{ job: "node_exporter" },
|
||||
{ instance: "server1" },
|
||||
{ cluster: "prod" },
|
||||
[{ name: "foo", value: "bar" }],
|
||||
[{ name: "job", value: "node_exporter" }],
|
||||
[{ name: "instance", value: "server1" }],
|
||||
[{ name: "cluster", value: "prod" }],
|
||||
]);
|
||||
expect(toDiffableHtml(tree.html())).toMatchSnapshot();
|
||||
});
|
||||
@@ -53,7 +53,9 @@ describe("<LabelSetList />", () => {
|
||||
it("doesn't render pagination when list has 10 elements on desktop", () => {
|
||||
global.window.innerWidth = 1024;
|
||||
const tree = MountedLabelSetList(
|
||||
Array.from(Array(10), (_, i) => ({ instance: `server${i}` }))
|
||||
Array.from(Array(10), (_, i) => [
|
||||
{ name: "instance", value: `server${i}` },
|
||||
])
|
||||
);
|
||||
expect(tree.find(".pagination")).toHaveLength(0);
|
||||
});
|
||||
@@ -61,7 +63,9 @@ describe("<LabelSetList />", () => {
|
||||
it("doesn't render pagination when list has 5 elements on desktop", () => {
|
||||
global.window.innerWidth = 500;
|
||||
const tree = MountedLabelSetList(
|
||||
Array.from(Array(5), (_, i) => ({ instance: `server${i}` }))
|
||||
Array.from(Array(5), (_, i) => [
|
||||
{ name: "instance", value: `server${i}` },
|
||||
])
|
||||
);
|
||||
expect(tree.find(".pagination")).toHaveLength(0);
|
||||
});
|
||||
@@ -69,7 +73,9 @@ describe("<LabelSetList />", () => {
|
||||
it("renders pagination when list has 11 elements on desktop", () => {
|
||||
global.window.innerWidth = 1024;
|
||||
const tree = MountedLabelSetList(
|
||||
Array.from(Array(11), (_, i) => ({ instance: `server${i}` }))
|
||||
Array.from(Array(11), (_, i) => [
|
||||
{ name: "instance", value: `server${i}` },
|
||||
])
|
||||
);
|
||||
expect(tree.find(".pagination")).toHaveLength(1);
|
||||
});
|
||||
@@ -77,14 +83,18 @@ describe("<LabelSetList />", () => {
|
||||
it("renders pagination when list has 6 elements on mobile", () => {
|
||||
global.window.innerWidth = 500;
|
||||
const tree = MountedLabelSetList(
|
||||
Array.from(Array(6), (_, i) => ({ instance: `server${i}` }))
|
||||
Array.from(Array(6), (_, i) => [
|
||||
{ name: "instance", value: `server${i}` },
|
||||
])
|
||||
);
|
||||
expect(tree.find(".pagination")).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("clicking on pagination changes displayed elements", () => {
|
||||
const tree = MountedLabelSetList(
|
||||
Array.from(Array(21), (_, i) => ({ instance: `server${i + 1}` }))
|
||||
Array.from(Array(21), (_, i) => [
|
||||
{ name: "instance", value: `server${i + 1}` },
|
||||
])
|
||||
);
|
||||
const pageLink = tree.find(".page-link").at(3);
|
||||
pageLink.simulate("click");
|
||||
|
||||
@@ -4,10 +4,11 @@ import type { AlertStore } from "Stores/AlertStore";
|
||||
import { IsMobile } from "Common/Device";
|
||||
import StaticLabel from "Components/Labels/StaticLabel";
|
||||
import { PageSelect } from "Components/Pagination";
|
||||
import type { LabelsT } from "Models/APITypes";
|
||||
|
||||
const LabelSetList: FC<{
|
||||
alertStore: AlertStore;
|
||||
labelsList: { [labelName: string]: string }[];
|
||||
labelsList: LabelsT[];
|
||||
title?: string;
|
||||
}> = ({ alertStore, labelsList, title }) => {
|
||||
const [activePage, setActivePage] = useState<number>(1);
|
||||
@@ -26,12 +27,12 @@ const LabelSetList: FC<{
|
||||
key={`${index}/${labels.length}`}
|
||||
className="list-group-item px-0 pt-2 pb-1"
|
||||
>
|
||||
{Object.entries(labels).map(([name, value]) => (
|
||||
{labels.map((label) => (
|
||||
<StaticLabel
|
||||
key={name}
|
||||
key={label.name}
|
||||
alertStore={alertStore}
|
||||
name={name}
|
||||
value={value}
|
||||
name={label.name}
|
||||
value={label.value}
|
||||
/>
|
||||
))}
|
||||
</li>
|
||||
|
||||
@@ -78,11 +78,11 @@ describe("<PaginatedAlertList />", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
alerts: [
|
||||
{
|
||||
alertname: "Fake Alert",
|
||||
foo: "1",
|
||||
bar: "2",
|
||||
},
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "foo", value: "1" },
|
||||
{ name: "bar", value: "2" },
|
||||
],
|
||||
],
|
||||
},
|
||||
error: undefined,
|
||||
@@ -125,11 +125,11 @@ describe("<PaginatedAlertList />", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: {
|
||||
alerts: [
|
||||
{
|
||||
alertname: "Fake Alert",
|
||||
foo: "1",
|
||||
bar: "2",
|
||||
},
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "foo", value: "1" },
|
||||
{ name: "bar", value: "2" },
|
||||
],
|
||||
],
|
||||
},
|
||||
error: undefined,
|
||||
|
||||
@@ -35,7 +35,11 @@ const MountedMatchCounter = () => {
|
||||
describe("<MatchCounter />", () => {
|
||||
it("matches snapshot", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: { alerts: Array(25).map((i) => ({ alertname: `alert${i}` })) },
|
||||
response: {
|
||||
alerts: Array(25).map((i) => [
|
||||
{ name: "alertname", value: `alert${i}` },
|
||||
]),
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
@@ -110,7 +114,11 @@ describe("<MatchCounter />", () => {
|
||||
|
||||
it("updates totalAlerts after successful fetch", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: { alerts: Array(25).map((i) => ({ alertname: `alert${i}` })) },
|
||||
response: {
|
||||
alerts: Array(25).map((i) => [
|
||||
{ name: "alertname", value: `alert${i}` },
|
||||
]),
|
||||
},
|
||||
error: null,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
|
||||
@@ -65,7 +65,11 @@ describe("<SilencePreview />", () => {
|
||||
|
||||
it("matches snapshot", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: { alerts: Array(25).map((i) => ({ alertname: `alert${i}` })) },
|
||||
response: {
|
||||
alerts: Array(25).map((i) => [
|
||||
{ name: "alertname", value: `alert${i}` },
|
||||
]),
|
||||
},
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
@@ -94,7 +98,15 @@ describe("<SilencePreview />", () => {
|
||||
|
||||
it("renders StaticLabel after fetch", () => {
|
||||
useFetchGetMock.fetch.setMockedData({
|
||||
response: { alerts: [{ alertname: "Fake Alert", foo: "1", bar: "1" }] },
|
||||
response: {
|
||||
alerts: [
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "foo", value: "1" },
|
||||
{ name: "bar", value: "1" },
|
||||
],
|
||||
],
|
||||
},
|
||||
error: undefined,
|
||||
isLoading: false,
|
||||
isRetrying: false,
|
||||
|
||||
@@ -128,7 +128,7 @@ storiesOf("SilenceModal", module)
|
||||
);
|
||||
fetchMock.mock(
|
||||
"begin:/alertList.json?q=instance",
|
||||
{ alerts: Array(23).fill(MockAlert([], {}, "active")) },
|
||||
{ alerts: Array(23).fill(MockAlert([], [], "active")) },
|
||||
{
|
||||
overwriteRoutes: true,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export type AlertStateT = "unprocessed" | "active" | "suppressed";
|
||||
|
||||
export type LabelsT = { [key: string]: string };
|
||||
export interface LabelT {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export type LabelsT = LabelT[];
|
||||
|
||||
export interface AlertmanagerSilenceMatcherT {
|
||||
name: string;
|
||||
|
||||
@@ -25,17 +25,41 @@ beforeEach(() => {
|
||||
|
||||
const MockGroup = () => {
|
||||
const alerts = [
|
||||
MockAlert([], { instance: "prod1", cluster: "prod" }, "active"),
|
||||
MockAlert([], { instance: "prod2", cluster: "prod" }, "active"),
|
||||
MockAlert([], { instance: "dev1", cluster: "dev" }, "active"),
|
||||
MockAlert(
|
||||
[],
|
||||
[
|
||||
{ name: "instance", value: "prod1" },
|
||||
{ name: "cluster", value: "prod" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert(
|
||||
[],
|
||||
[
|
||||
{ name: "instance", value: "prod2" },
|
||||
{ name: "cluster", value: "prod" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert(
|
||||
[],
|
||||
[
|
||||
{ name: "instance", value: "dev1" },
|
||||
{ name: "cluster", value: "dev" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
];
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "FakeAlert" },
|
||||
[{ name: "alertname", value: "FakeAlert" }],
|
||||
alerts,
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
[
|
||||
{
|
||||
name: "job",
|
||||
value: "mock",
|
||||
},
|
||||
],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
@@ -281,17 +305,27 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a list of alerts with uncommon labels", () => {
|
||||
const alerts = [
|
||||
MockAlert([], { instance: "1", banana: "ignore" }, "active"),
|
||||
MockAlert([], { instance: "2" }, "suppressed"),
|
||||
MockAlert([], { instance: "3" }, "active"),
|
||||
MockAlert(
|
||||
[],
|
||||
[
|
||||
{ name: "instance", value: "1" },
|
||||
{ name: "banana", value: "ignore" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert([], [{ name: "instance", value: "2" }], "suppressed"),
|
||||
MockAlert([], [{ name: "instance", value: "3" }], "active"),
|
||||
];
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "FakeAlert" },
|
||||
[{ name: "alertname", value: "FakeAlert" }],
|
||||
alerts,
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
[
|
||||
{
|
||||
name: "job",
|
||||
value: "mock",
|
||||
},
|
||||
],
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
@@ -332,17 +366,15 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a list of alerts with no labels", () => {
|
||||
const alerts = [
|
||||
MockAlert([], {}, "active"),
|
||||
MockAlert([], {}, "suppressed"),
|
||||
MockAlert([], {}, "active"),
|
||||
MockAlert([], [], "active"),
|
||||
MockAlert([], [], "suppressed"),
|
||||
MockAlert([], [], "active"),
|
||||
];
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "FakeAlert" },
|
||||
[{ name: "alertname", value: "FakeAlert" }],
|
||||
alerts,
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
[{ name: "job", value: "mock" }],
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
@@ -430,32 +462,37 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("fillMatchersFromGroup() handles alerts with different label sets", () => {
|
||||
const group = MockAlertGroup(
|
||||
{ region: "AF" },
|
||||
[{ name: "region", value: "AF" }],
|
||||
[
|
||||
MockAlert(
|
||||
[],
|
||||
{
|
||||
alertname: "Alert1",
|
||||
cluster: "prod",
|
||||
foo: "bar",
|
||||
},
|
||||
[
|
||||
{ name: "alertname", value: "Alert1" },
|
||||
{ name: "cluster", value: "prod" },
|
||||
{ name: "foo", value: "bar" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert(
|
||||
[],
|
||||
{
|
||||
alertname: "Alert2",
|
||||
instance: "prod2",
|
||||
cluster: "prod",
|
||||
},
|
||||
[
|
||||
{ name: "alertname", value: "Alert2" },
|
||||
{ name: "cluster", value: "prod2" },
|
||||
{ name: "foo", value: "prod" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert(
|
||||
[],
|
||||
[
|
||||
{ name: "alertname", value: "Alert3" },
|
||||
{ name: "instance", value: "dev1" },
|
||||
],
|
||||
"active"
|
||||
),
|
||||
MockAlert([], { alertname: "Alert3", instance: "dev1" }, "active"),
|
||||
],
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
[{ name: "job", value: "mock" }],
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
|
||||
@@ -119,7 +119,11 @@ const MatchersFromAlerts = (
|
||||
|
||||
// add matchers for all shared labels in this group
|
||||
for (const [key, value] of Object.entries(
|
||||
Object.assign({}, group.labels, group.shared.labels)
|
||||
Object.assign(
|
||||
{},
|
||||
Object.fromEntries(group.labels.map((l) => [l.name, l.value])),
|
||||
Object.fromEntries(group.shared.labels.map((l) => [l.name, l.value]))
|
||||
)
|
||||
)) {
|
||||
if (!stripLabels.includes(key)) {
|
||||
const matcher = NewEmptyMatcher();
|
||||
@@ -131,7 +135,7 @@ const MatchersFromAlerts = (
|
||||
|
||||
// array of arrays with label keys for each alert
|
||||
const allLabelKeys = alerts
|
||||
.map((alert) => Object.keys(alert.labels))
|
||||
.map((alert) => alert.labels.map((l) => l.name))
|
||||
.filter((a) => a.length > 0);
|
||||
|
||||
// this is the list of label key that are shared across all alerts in the group
|
||||
@@ -153,12 +157,15 @@ const MatchersFromAlerts = (
|
||||
// add matchers for all unique labels in this group
|
||||
const labels: { [key: string]: Set<string> } = {};
|
||||
for (const alert of alerts) {
|
||||
for (const [key, value] of Object.entries(alert.labels)) {
|
||||
if (sharedLabelKeys.includes(key) && !stripLabels.includes(key)) {
|
||||
if (!labels[key]) {
|
||||
labels[key] = new Set();
|
||||
for (const label of alert.labels) {
|
||||
if (
|
||||
sharedLabelKeys.includes(label.name) &&
|
||||
!stripLabels.includes(label.name)
|
||||
) {
|
||||
if (!labels[label.name]) {
|
||||
labels[label.name] = new Set();
|
||||
}
|
||||
labels[key].add(value);
|
||||
labels[label.name].add(label.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ const MockAlertGroup = (
|
||||
suppressed: {},
|
||||
unprocessed: {},
|
||||
},
|
||||
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
|
||||
id: "839708582c92ce59088d0af392601eec2d0fc02b",
|
||||
alertmanagerCount: {
|
||||
default: 1,
|
||||
},
|
||||
|
||||
@@ -95,10 +95,10 @@ const MockAPIResponse = (): APIAlertsResponseT => {
|
||||
labelValue: "",
|
||||
alertGroups: [
|
||||
MockAlertGroup(
|
||||
{ alertname: "foo" },
|
||||
[MockAlert([], { instance: "foo" }, "suppressed")],
|
||||
[{ name: "alertname", value: "foo" }],
|
||||
[MockAlert([], [{ name: "instance", value: "foo" }], "suppressed")],
|
||||
[],
|
||||
{ cluster: "dev" },
|
||||
[{ name: "cluster", value: "dev" }],
|
||||
{}
|
||||
),
|
||||
],
|
||||
|
||||
@@ -64,17 +64,20 @@ const MockGroup = (
|
||||
},
|
||||
]
|
||||
: [],
|
||||
{ instance: `instance${i}` },
|
||||
[{ name: "instance", value: `instance${i}` }],
|
||||
state
|
||||
);
|
||||
alert.startsAt = subMinutes(new Date(), alertCount).toISOString();
|
||||
alerts.push(alert);
|
||||
}
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "Fake Alert", group: groupName },
|
||||
[
|
||||
{ name: "alertname", value: "Fake Alert" },
|
||||
{ name: "group", value: groupName },
|
||||
],
|
||||
alerts,
|
||||
[],
|
||||
{},
|
||||
[],
|
||||
{}
|
||||
);
|
||||
return group;
|
||||
@@ -170,10 +173,10 @@ const MockGrid = (alertStore: AlertStore): void => {
|
||||
group.shared.clusters = ["default"];
|
||||
}
|
||||
if (i < 3) {
|
||||
group.shared.labels = {
|
||||
cluster: `prod${i}`,
|
||||
job: "textfile_exporter",
|
||||
};
|
||||
group.shared.labels = [
|
||||
{ name: "cluster", value: `prod${i}` },
|
||||
{ name: "job", value: "textfile_exporter" },
|
||||
];
|
||||
}
|
||||
if (i < 5) {
|
||||
group.shared.annotations = [
|
||||
|
||||
@@ -105,7 +105,7 @@ const useFetchGetMock = (
|
||||
},
|
||||
{
|
||||
re: /^\.\/alertList\.json\?q=/,
|
||||
response: { alerts: [{ instance: "foo" }] },
|
||||
response: { alerts: [[{ name: "instance", value: "foo" }]] },
|
||||
},
|
||||
// silence browser
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user