fix(ui): use slices for labels instead of maps

This commit is contained in:
Łukasz Mierzwa
2021-10-29 17:18:15 +01:00
committed by Łukasz Mierzwa
parent 8f7cca40cc
commit 31c9468368
26 changed files with 367 additions and 163 deletions
+10 -4
View File
@@ -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"],
+58 -5
View File
@@ -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]);
+3 -3
View File
@@ -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;
+20 -10
View File
@@ -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");
+6 -5
View File
@@ -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,
}
+6 -1
View File
@@ -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;
+73 -36
View File
@@ -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 = {
+14 -7
View File
@@ -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);
}
}
}
+1 -1
View File
@@ -63,7 +63,7 @@ const MockAlertGroup = (
suppressed: {},
unprocessed: {},
},
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
id: "839708582c92ce59088d0af392601eec2d0fc02b",
alertmanagerCount: {
default: 1,
},
+3 -3
View File
@@ -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" }],
{}
),
],
+10 -7
View File
@@ -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 = [
+1 -1
View File
@@ -105,7 +105,7 @@ const useFetchGetMock = (
},
{
re: /^\.\/alertList\.json\?q=/,
response: { alerts: [{ instance: "foo" }] },
response: { alerts: [[{ name: "instance", value: "foo" }]] },
},
// silence browser
{