mirror of
https://github.com/prymitive/karma
synced 2026-05-09 03:36:44 +00:00
fix(ui): don't rely on alert list for silences
This commit is contained in:
committed by
Łukasz Mierzwa
parent
a2ee4812f7
commit
055f8d9f9a
@@ -63,6 +63,10 @@ beforeEach(() => {
|
||||
MockAlert([], { foo: "ignore" }, "suppressed"),
|
||||
];
|
||||
group = MockAlertGroup({ alertname: "Fake Alert" }, alerts, [], {}, {});
|
||||
group.allLabels.active = {
|
||||
alertname: ["Fake Alert"],
|
||||
foo: ["bar", "baz"],
|
||||
};
|
||||
|
||||
fetchMock.resetHistory();
|
||||
fetchMock.mock(
|
||||
|
||||
@@ -84,7 +84,7 @@ const AlertAck: FC<{
|
||||
payload: GenerateAlertmanagerSilenceData(
|
||||
now,
|
||||
addSeconds(now, durationSeconds),
|
||||
MatchersFromGroup(group, [], group.alerts, true),
|
||||
MatchersFromGroup(group, [], true),
|
||||
author,
|
||||
comment
|
||||
),
|
||||
|
||||
@@ -59,6 +59,11 @@ export interface APIAlertGroupT {
|
||||
labels: LabelsT;
|
||||
alerts: APIAlertT[];
|
||||
totalAlerts: number;
|
||||
allLabels: {
|
||||
active: { [key: string]: string[] };
|
||||
suppressed: { [key: string]: string[] };
|
||||
unprocessed: { [key: string]: string[] };
|
||||
};
|
||||
alertmanagerCount: { [key: string]: number };
|
||||
stateCount: StateCountT;
|
||||
shared: {
|
||||
|
||||
@@ -126,6 +126,12 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a group", () => {
|
||||
const group = MockGroup();
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
job: ["mock"],
|
||||
instance: ["dev1", "prod1", "prod2"],
|
||||
cluster: ["dev", "prod"],
|
||||
};
|
||||
store.data.fillMatchersFromGroup(
|
||||
group,
|
||||
[],
|
||||
@@ -172,6 +178,187 @@ describe("SilenceFormStore.data", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a list of alerts", () => {
|
||||
const group = MockGroup();
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
job: ["mock"],
|
||||
instance: ["dev1", "prod1", "prod2"],
|
||||
cluster: ["dev", "prod"],
|
||||
};
|
||||
store.data.fillMatchersFromGroup(
|
||||
group,
|
||||
["job"],
|
||||
AlertmanagerClustersToOption({ ha: ["am1", "am2"] }),
|
||||
group.alerts.slice(0, 2)
|
||||
);
|
||||
expect(store.data.alertmanagers).toMatchObject([
|
||||
{ label: "Cluster: ha", value: ["am1", "am2"] },
|
||||
]);
|
||||
expect(store.data.matchers).toHaveLength(3);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "alertname",
|
||||
values: [{ label: "FakeAlert", value: "FakeAlert" }],
|
||||
isRegex: false,
|
||||
})
|
||||
);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "instance",
|
||||
values: [
|
||||
{ label: "prod1", value: "prod1" },
|
||||
{ label: "prod2", value: "prod2" },
|
||||
],
|
||||
isRegex: true,
|
||||
})
|
||||
);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "cluster",
|
||||
values: [{ label: "prod", value: "prod" }],
|
||||
isRegex: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a list of alerts with common labels", () => {
|
||||
const group = MockGroup();
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
job: ["mock"],
|
||||
instance: ["dev1", "prod1", "prod2"],
|
||||
cluster: ["dev", "prod"],
|
||||
};
|
||||
|
||||
store.data.fillMatchersFromGroup(
|
||||
group,
|
||||
["job"],
|
||||
AlertmanagerClustersToOption({ ha: ["am1", "am2"] }),
|
||||
[group.alerts[0], group.alerts[2]]
|
||||
);
|
||||
expect(store.data.alertmanagers).toMatchObject([
|
||||
{ label: "Cluster: ha", value: ["am1", "am2"] },
|
||||
]);
|
||||
expect(store.data.matchers).toHaveLength(3);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "alertname",
|
||||
values: [{ label: "FakeAlert", value: "FakeAlert" }],
|
||||
isRegex: false,
|
||||
})
|
||||
);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "instance",
|
||||
values: [
|
||||
{ label: "dev1", value: "dev1" },
|
||||
{ label: "prod1", value: "prod1" },
|
||||
],
|
||||
isRegex: true,
|
||||
})
|
||||
);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "cluster",
|
||||
values: [
|
||||
{ label: "dev", value: "dev" },
|
||||
{ label: "prod", value: "prod" },
|
||||
],
|
||||
isRegex: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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"),
|
||||
];
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "FakeAlert" },
|
||||
alerts,
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
job: ["mock"],
|
||||
instance: ["dev1", "prod1", "prod2"],
|
||||
cluster: ["dev", "prod"],
|
||||
};
|
||||
|
||||
store.data.fillMatchersFromGroup(
|
||||
group,
|
||||
["job"],
|
||||
AlertmanagerClustersToOption({ ha: ["am1", "am2"] }),
|
||||
[group.alerts[0], group.alerts[2]]
|
||||
);
|
||||
expect(store.data.alertmanagers).toMatchObject([
|
||||
{ label: "Cluster: ha", value: ["am1", "am2"] },
|
||||
]);
|
||||
expect(store.data.matchers).toHaveLength(2);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "alertname",
|
||||
values: [{ label: "FakeAlert", value: "FakeAlert" }],
|
||||
isRegex: false,
|
||||
})
|
||||
);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "instance",
|
||||
values: [
|
||||
{ label: "1", value: "1" },
|
||||
{ label: "3", value: "3" },
|
||||
],
|
||||
isRegex: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a list of alerts with no labels", () => {
|
||||
const alerts = [
|
||||
MockAlert([], {}, "active"),
|
||||
MockAlert([], {}, "suppressed"),
|
||||
MockAlert([], {}, "active"),
|
||||
];
|
||||
const group = MockAlertGroup(
|
||||
{ alertname: "FakeAlert" },
|
||||
alerts,
|
||||
[],
|
||||
{
|
||||
job: "mock",
|
||||
},
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
};
|
||||
|
||||
store.data.fillMatchersFromGroup(
|
||||
group,
|
||||
["job"],
|
||||
AlertmanagerClustersToOption({ ha: ["am1", "am2"] }),
|
||||
[group.alerts[0], group.alerts[2]]
|
||||
);
|
||||
expect(store.data.alertmanagers).toMatchObject([
|
||||
{ label: "Cluster: ha", value: ["am1", "am2"] },
|
||||
]);
|
||||
expect(store.data.matchers).toHaveLength(1);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
expect.objectContaining({
|
||||
name: "alertname",
|
||||
values: [{ label: "FakeAlert", value: "FakeAlert" }],
|
||||
isRegex: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("fillMatchersFromGroup() creates correct matcher object for a group with only a subset of alerts passed", () => {
|
||||
const group = MockGroup();
|
||||
store.data.fillMatchersFromGroup(
|
||||
@@ -262,6 +449,11 @@ describe("SilenceFormStore.data", () => {
|
||||
},
|
||||
{}
|
||||
);
|
||||
group.allLabels.active = {
|
||||
alertname: ["Alert1", "Alert2", "Alert3"],
|
||||
job: ["mock"],
|
||||
region: ["AF"],
|
||||
};
|
||||
store.data.fillMatchersFromGroup(group, [], []);
|
||||
expect(store.data.matchers).toHaveLength(3);
|
||||
expect(store.data.matchers).toContainEqual(
|
||||
@@ -478,6 +670,12 @@ describe("SilenceFormStore.data", () => {
|
||||
|
||||
it("toAlertmanagerPayload creates payload that matches snapshot", () => {
|
||||
const group = MockGroup();
|
||||
group.allLabels.active = {
|
||||
alertname: ["FakeAlert"],
|
||||
job: ["mock"],
|
||||
instance: ["dev1", "prod1", "prod2"],
|
||||
cluster: ["dev", "prod"],
|
||||
};
|
||||
store.data.fillMatchersFromGroup(group, [], []);
|
||||
// add empty matcher so we test empty string rendering
|
||||
store.data.addEmptyMatcher();
|
||||
|
||||
@@ -80,11 +80,37 @@ const AlertmanagerClustersToOption = (clusterDict: {
|
||||
const MatchersFromGroup = (
|
||||
group: APIAlertGroupT,
|
||||
stripLabels: string[],
|
||||
alerts?: APIAlertT[],
|
||||
onlyActive?: boolean
|
||||
): MatcherWithIDT[] => {
|
||||
const matchers: MatcherWithIDT[] = [];
|
||||
|
||||
for (const [state, labels] of Object.entries(group.allLabels)) {
|
||||
if (onlyActive === true && state !== "active") {
|
||||
continue;
|
||||
}
|
||||
for (const [key, values] of Object.entries(labels).filter(
|
||||
([key, _]) => !stripLabels.includes(key)
|
||||
)) {
|
||||
matchers.push({
|
||||
id: uniqueId(),
|
||||
name: key,
|
||||
values: values.map((value) => StringToOption(value)),
|
||||
isRegex: values.length > 1,
|
||||
isEqual: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return matchers;
|
||||
};
|
||||
|
||||
const MatchersFromAlerts = (
|
||||
group: APIAlertGroupT,
|
||||
stripLabels: string[],
|
||||
alerts: APIAlertT[]
|
||||
): MatcherWithIDT[] => {
|
||||
const matchers: MatcherWithIDT[] = [];
|
||||
|
||||
// add matchers for all shared labels in this group
|
||||
for (const [key, value] of Object.entries(
|
||||
Object.assign({}, group.labels, group.shared.labels)
|
||||
@@ -97,13 +123,8 @@ const MatchersFromGroup = (
|
||||
}
|
||||
}
|
||||
|
||||
// this is the list of alerts we'll use to generate matchers
|
||||
const filteredAlerts = (alerts ? alerts : group.alerts).filter(
|
||||
(alert) => !onlyActive || alert.state === "active"
|
||||
);
|
||||
|
||||
// array of arrays with label keys for each alert
|
||||
const allLabelKeys = filteredAlerts
|
||||
const allLabelKeys = alerts
|
||||
.map((alert) => Object.keys(alert.labels))
|
||||
.filter((a) => a.length > 0);
|
||||
|
||||
@@ -125,7 +146,7 @@ const MatchersFromGroup = (
|
||||
|
||||
// add matchers for all unique labels in this group
|
||||
const labels: { [key: string]: Set<string> } = {};
|
||||
for (const alert of filteredAlerts) {
|
||||
for (const alert of alerts) {
|
||||
for (const [key, value] of Object.entries(alert.labels)) {
|
||||
if (sharedLabelKeys.includes(key) && !stripLabels.includes(key)) {
|
||||
if (!labels[key]) {
|
||||
@@ -486,7 +507,9 @@ class SilenceFormStore {
|
||||
) {
|
||||
this.alertmanagers = alertmanagers;
|
||||
|
||||
this.matchers = MatchersFromGroup(group, stripLabels, alerts);
|
||||
this.matchers = alerts
|
||||
? MatchersFromAlerts(group, stripLabels, alerts)
|
||||
: MatchersFromGroup(group, stripLabels);
|
||||
// ensure that silenceID is nulled, since it's used to edit silences
|
||||
// and this is used to silence groups
|
||||
this.silenceID = null;
|
||||
@@ -652,6 +675,7 @@ export {
|
||||
NewEmptyMatcher,
|
||||
AlertmanagerClustersToOption,
|
||||
MatchersFromGroup,
|
||||
MatchersFromAlerts,
|
||||
GenerateAlertmanagerSilenceData,
|
||||
NewClusterRequest,
|
||||
MatcherToOperator,
|
||||
|
||||
@@ -58,6 +58,11 @@ const MockAlertGroup = (
|
||||
labels: rootLabels,
|
||||
totalAlerts: alerts.length,
|
||||
alerts: alerts,
|
||||
allLabels: {
|
||||
active: {},
|
||||
suppressed: {},
|
||||
unprocessed: {},
|
||||
},
|
||||
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
|
||||
alertmanagerCount: {
|
||||
default: 1,
|
||||
|
||||
Reference in New Issue
Block a user