From 32f249f2d8df570077bdb8f206828f3a13cfee53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Mon, 29 Jun 2020 12:35:02 +0100 Subject: [PATCH] fix(ui): only select alertmanagers from alert/group Fixes #1904 --- .../AlertGrid/AlertGroup/Alert/AlertMenu.js | 16 +++++++++- .../AlertGroup/Alert/AlertMenu.test.js | 31 ++++++++++++++++++- .../AlertGroup/GroupHeader/GroupMenu.js | 20 ++++++++++-- .../AlertGroup/GroupHeader/GroupMenu.test.js | 31 ++++++++++++++++++- ui/src/Stores/SilenceFormStore.js | 6 +++- ui/src/Stores/SilenceFormStore.test.js | 20 ++++++++++-- 6 files changed, 116 insertions(+), 8 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js index 6e9725fa9..72c052a98 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.js @@ -12,17 +12,31 @@ import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalL import { APIAlert, APIGroup } from "Models/API"; import { AlertStore } from "Stores/AlertStore"; -import { SilenceFormStore, SilenceTabNames } from "Stores/SilenceFormStore"; +import { + SilenceFormStore, + SilenceTabNames, + AlertmanagerClustersToOption, +} from "Stores/SilenceFormStore"; import { FetchPauser } from "Components/FetchPauser"; import { DropdownSlide } from "Components/Animations/DropdownSlide"; import { DateFromNow } from "Components/DateFromNow"; import { useOnClickOutside } from "Hooks/useOnClickOutside"; const onSilenceClick = (alertStore, silenceFormStore, group, alert) => { + let clusters = {}; + Object.entries(alertStore.data.clustersWithoutReadOnly).forEach( + ([cluster, members]) => { + if (alert.alertmanager.map((am) => am.cluster).includes(cluster)) { + clusters[cluster] = members; + } + } + ); + silenceFormStore.data.resetProgress(); silenceFormStore.data.fillMatchersFromGroup( group, alertStore.settings.values.silenceForm.strip.labels, + AlertmanagerClustersToOption(clusters), [alert] ); silenceFormStore.tab.setTab(SilenceTabNames.Editor); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js index 389b962ab..cb352762c 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js @@ -31,7 +31,7 @@ beforeEach(() => { group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {}); alertStore.data.upstreams = { - clusters: { default: ["am1"] }, + clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, instances: [ { name: "am1", @@ -45,6 +45,30 @@ beforeEach(() => { cluster: "default", clusterMembers: ["am1"], }, + { + name: "ro", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: true, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "ro", + clusterMembers: ["ro"], + }, + { + name: "am2", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "am2", + clusterMembers: ["am2"], + }, ], }; }); @@ -131,14 +155,19 @@ const MountedMenuContent = (group) => { describe("", () => { it("clicking on 'Silence' icon opens the silence form modal", () => { + group.alertmanagerCount = { am1: 1, ro: 1 }; const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(1); button.simulate("click"); expect(silenceFormStore.toggle.visible).toBe(true); + expect(silenceFormStore.data.alertmanagers).toMatchObject([ + { label: "am1", value: ["am1"] }, + ]); }); it("'Silence' menu entry is disabled when all Alertmanager instances are read-only", () => { alertStore.data.upstreams.instances[0].readonly = true; + alertStore.data.upstreams.instances[2].readonly = true; const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(1); expect(button.hasClass("disabled")).toBe(true); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js index 8808d6fd6..bd31a3d18 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.js @@ -13,17 +13,33 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash"; import { APIGroup } from "Models/API"; import { FormatAlertsQ } from "Stores/AlertStore"; import { AlertStore } from "Stores/AlertStore"; -import { SilenceFormStore, SilenceTabNames } from "Stores/SilenceFormStore"; +import { + SilenceFormStore, + SilenceTabNames, + AlertmanagerClustersToOption, +} from "Stores/SilenceFormStore"; import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query"; import { DropdownSlide } from "Components/Animations/DropdownSlide"; import { FetchPauser } from "Components/FetchPauser"; import { useOnClickOutside } from "Hooks/useOnClickOutside"; const onSilenceClick = (alertStore, silenceFormStore, group) => { + let clusters = {}; + Object.entries(alertStore.data.clustersWithoutReadOnly).forEach( + ([cluster, members]) => { + members.forEach((member) => { + if (Object.keys(group.alertmanagerCount).includes(member)) { + clusters[cluster] = members; + } + }); + } + ); + silenceFormStore.data.resetProgress(); silenceFormStore.data.fillMatchersFromGroup( group, - alertStore.settings.values.silenceForm.strip.labels + alertStore.settings.values.silenceForm.strip.labels, + AlertmanagerClustersToOption(clusters) ); silenceFormStore.tab.setTab(SilenceTabNames.Editor); silenceFormStore.toggle.show(); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js index 6c6635b27..07ffa9b90 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js @@ -29,7 +29,7 @@ beforeEach(() => { MockSetIsMenuOpen = jest.fn(); alertStore.data.upstreams = { - clusters: { default: ["am1"] }, + clusters: { default: ["am1"], ro: ["ro"], am2: ["am2"] }, instances: [ { name: "am1", @@ -43,6 +43,30 @@ beforeEach(() => { cluster: "default", clusterMembers: ["am1"], }, + { + name: "ro", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: true, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "ro", + clusterMembers: ["ro"], + }, + { + name: "am2", + uri: "http://localhost:8080", + publicURI: "http://example.com", + readonly: false, + headers: {}, + corsCredentials: "include", + error: "", + version: "0.17.0", + cluster: "am2", + clusterMembers: ["am2"], + }, ], }; }); @@ -141,14 +165,19 @@ describe("", () => { it("clicking on 'Silence' icon opens the silence form modal", () => { const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); + group.alertmanagerCount = { am1: 1, ro: 1 }; const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(1); button.simulate("click"); expect(silenceFormStore.toggle.visible).toBe(true); + expect(silenceFormStore.data.alertmanagers).toMatchObject([ + { label: "am1", value: ["am1"] }, + ]); }); it("'Silence' menu entry is disabled when all Alertmanager instances are read-only", () => { alertStore.data.upstreams.instances[0].readonly = true; + alertStore.data.upstreams.instances[2].readonly = true; const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedMenuContent(group); diff --git a/ui/src/Stores/SilenceFormStore.js b/ui/src/Stores/SilenceFormStore.js index edb05ac80..980234ec6 100644 --- a/ui/src/Stores/SilenceFormStore.js +++ b/ui/src/Stores/SilenceFormStore.js @@ -316,13 +316,17 @@ class SilenceFormStore { }, // if alerts argument is not passed all group alerts will be used - fillMatchersFromGroup(group, stripLabels, alerts) { + fillMatchersFromGroup(group, stripLabels, alertmanagers, alerts) { + this.alertmanagers = alertmanagers; + this.matchers = MatchersFromGroup(group, stripLabels, alerts); // ensure that silenceID is nulled, since it's used to edit silences // and this is used to silence groups this.silenceID = null; // disable matcher autofill this.autofillMatchers = false; + // disable alertmanager input reset + this.resetInputs = false; }, fillFormFromSilence(alertmanager, silence) { diff --git a/ui/src/Stores/SilenceFormStore.test.js b/ui/src/Stores/SilenceFormStore.test.js index f3a431272..e61ef823e 100644 --- a/ui/src/Stores/SilenceFormStore.test.js +++ b/ui/src/Stores/SilenceFormStore.test.js @@ -16,6 +16,7 @@ import { NewEmptyMatcher, SilenceTabNames, MatcherValueToObject, + AlertmanagerClustersToOption, } from "./SilenceFormStore"; let store; @@ -126,7 +127,14 @@ describe("SilenceFormStore.data", () => { it("fillMatchersFromGroup() creates correct matcher object for a group", () => { const group = MockGroup(); - store.data.fillMatchersFromGroup(group, []); + store.data.fillMatchersFromGroup( + group, + [], + AlertmanagerClustersToOption({ ha: ["am1", "am2"] }) + ); + expect(store.data.alertmanagers).toMatchObject([ + { label: "Cluster: ha", value: ["am1", "am2"] }, + ]); expect(store.data.matchers).toHaveLength(4); expect(store.data.matchers).toContainEqual( expect.objectContaining({ @@ -167,7 +175,15 @@ describe("SilenceFormStore.data", () => { it("fillMatchersFromGroup() creates correct matcher object for a group with only a subset of alerts passed", () => { const group = MockGroup(); - store.data.fillMatchersFromGroup(group, [], [group.alerts[0]]); + store.data.fillMatchersFromGroup( + group, + [], + AlertmanagerClustersToOption({ ha: ["am1", "am2"] }), + [group.alerts[0]] + ); + expect(store.data.alertmanagers).toMatchObject([ + { label: "Cluster: ha", value: ["am1", "am2"] }, + ]); expect(store.data.matchers).toHaveLength(4); expect(store.data.matchers).toContainEqual( expect.objectContaining({