fix(ui): don't rely on alert list for silences

This commit is contained in:
Łukasz Mierzwa
2021-07-16 18:15:22 +01:00
committed by Łukasz Mierzwa
parent a2ee4812f7
commit 055f8d9f9a
6 changed files with 246 additions and 10 deletions

View File

@@ -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(

View File

@@ -84,7 +84,7 @@ const AlertAck: FC<{
payload: GenerateAlertmanagerSilenceData(
now,
addSeconds(now, durationSeconds),
MatchersFromGroup(group, [], group.alerts, true),
MatchersFromGroup(group, [], true),
author,
comment
),

View File

@@ -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: {

View File

@@ -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();

View File

@@ -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,

View File

@@ -58,6 +58,11 @@ const MockAlertGroup = (
labels: rootLabels,
totalAlerts: alerts.length,
alerts: alerts,
allLabels: {
active: {},
suppressed: {},
unprocessed: {},
},
id: "099c5ca6d1c92f615b13056b935d0c8dee70f18c",
alertmanagerCount: {
default: 1,