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 ecc328666..4aee57a12 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/AlertMenu.test.js @@ -18,7 +18,7 @@ beforeEach(() => { alertStore = new AlertStore([]); silenceFormStore = new SilenceFormStore(); alert = MockAlert([], { foo: "bar" }, "active"); - group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}); + group = MockAlertGroup({ alertname: "Fake Alert" }, [alert], [], {}, {}); }); const MockAfterClick = jest.fn(); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js index 941365145..04bbdf4fd 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js @@ -58,7 +58,10 @@ const Alert = observer( }; } for (let silenceID of am.silencedBy) { - if (!silences[am.cluster].silences.includes(silenceID)) { + if ( + !silences[am.cluster].silences.includes(silenceID) && + !(group.shared.silences[am.cluster] === silenceID) + ) { silences[am.cluster].silences.push(silenceID); } } diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js index 29490ee35..158d53c64 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.test.js @@ -61,7 +61,7 @@ const MountedAlert = (alert, group, showAlertmanagers, showReceiver) => { describe("", () => { it("matches snapshot with showAlertmanagers=false showReceiver=false", () => { const alert = MockedAlert(); - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); @@ -69,7 +69,7 @@ describe("", () => { it("matches snapshot when inhibited", () => { const alert = MockedAlert(); alert.alertmanager[0].inhibitedBy = ["123456"]; - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); @@ -77,14 +77,14 @@ describe("", () => { it("renders inhibition icon when inhibited", () => { 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 @alertmanager label with showAlertmanagers=true", () => { const alert = MockedAlert(); - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, true, false); const label = tree .find("FilteringLabel") @@ -94,7 +94,7 @@ describe("", () => { it("renders @receiver label with showReceiver=true", () => { const alert = MockedAlert(); - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, true); const label = tree .find("FilteringLabel") @@ -105,7 +105,7 @@ describe("", () => { it("renders a silence if alert is silenced", () => { const alert = MockedAlert(); alert.alertmanager[0].silencedBy = ["silence123456789"]; - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false); const silence = tree.find("Silence"); expect(silence).toHaveLength(1); @@ -136,7 +136,7 @@ describe("", () => { inhibitedBy: [] } ]; - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false); const silence = tree.find("Silence"); expect(silence).toHaveLength(1); @@ -146,7 +146,7 @@ describe("", () => { 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 @@ -158,7 +158,7 @@ describe("", () => { 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 @@ -170,7 +170,7 @@ describe("", () => { 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 @@ -184,7 +184,7 @@ describe("", () => { const alert = MockedAlert(); alert.state = "foobar"; - const group = MockAlertGroup({}, [alert], [], {}); + const group = MockAlertGroup({}, [alert], [], {}, {}); const tree = MountedAlert(alert, group, false, false); expect( tree diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap index 30226511c..eb12ad65a 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/__snapshots__/index.test.js.snap @@ -128,3 +128,184 @@ exports[` matches snapshot 1`] = ` " `; + +exports[` mathes snapshot when silence is rendered 1`] = ` +" + +" +`; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.css b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.css new file mode 100644 index 000000000..a2dca88f6 --- /dev/null +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.css @@ -0,0 +1,8 @@ +.components-grid-alertgrid-alertgroup-shared-silence { + border-width: 3px; + border-style: solid; +} + +.components-grid-alertgrid-alertgroup-shared-silence > .card { + background-color: inherit; +} diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js index 72a51d520..1b723e44f 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.js @@ -5,19 +5,29 @@ import { observer } from "mobx-react"; import { APIGroup } from "Models/API"; import { StaticLabels } from "Common/Query"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { FilteringLabel } from "Components/Labels/FilteringLabel"; import { RenderNonLinkAnnotation, RenderLinkAnnotation } from "../Annotation"; +import { Silence } from "../Silence"; + +import "./index.css"; const GroupFooter = observer( class GroupFooter extends Component { static propTypes = { group: APIGroup.isRequired, alertmanagers: PropTypes.arrayOf(PropTypes.string).isRequired, - afterUpdate: PropTypes.func.isRequired + afterUpdate: PropTypes.func.isRequired, + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired }; render() { - const { group, alertmanagers, afterUpdate } = this.props; + const { + group, + alertmanagers, + afterUpdate, + silenceFormStore + } = this.props; return (
@@ -54,6 +64,26 @@ const GroupFooter = observer( value={a.value} /> ))} + {Object.keys(group.shared.silences).length === 0 ? null : ( +
+ {Object.entries(group.shared.silences).map( + ([cluster, silenceID]) => ( + + a.alertmanager.filter(am => am.cluster === cluster)[0] + )[0] + } + silenceID={silenceID} + afterUpdate={afterUpdate} + /> + ) + )} +
+ )}
); } diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js index c4d39ea2b..03b2c2446 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupFooter/index.test.js @@ -6,23 +6,38 @@ import { mount } from "enzyme"; import toDiffableHtml from "diffable-html"; -import { MockAlertGroup, MockAnnotation } from "__mocks__/Alerts.js"; +import moment from "moment"; +import { advanceTo, clear } from "jest-date-mock"; + +import { + MockAlertGroup, + MockAnnotation, + MockAlert, + MockSilence +} from "__mocks__/Alerts.js"; import { AlertStore } from "Stores/AlertStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { GroupFooter } from "."; let group; let alertStore; +let silenceFormStore; const MockGroup = () => { const group = MockAlertGroup( { alertname: "Fake Alert" }, - [], + [ + MockAlert([], {}, "suppressed"), + MockAlert([], {}, "suppressed"), + MockAlert([], {}, "suppressed") + ], [ MockAnnotation("summary", "This is summary", true, false), MockAnnotation("hidden", "This is hidden annotation", false, false), MockAnnotation("link", "http://link.example.com", true, true) ], - { label1: "foo", label2: "bar" } + { label1: "foo", label2: "bar" }, + {} ); return group; }; @@ -31,7 +46,15 @@ const MockAfterUpdate = jest.fn(); beforeEach(() => { alertStore = new AlertStore([]); + silenceFormStore = new SilenceFormStore(); group = MockGroup(); + advanceTo(moment.utc([2000, 0, 1, 15, 0, 0])); +}); + +afterEach(() => { + jest.restoreAllMocks(); + // reset Date() to current time + clear(); }); const MountedGroupFooter = () => { @@ -41,6 +64,7 @@ const MountedGroupFooter = () => { group={group} alertmanagers={["default"]} afterUpdate={MockAfterUpdate} + silenceFormStore={silenceFormStore} /> ); @@ -51,4 +75,30 @@ describe("", () => { const tree = MountedGroupFooter().find("GroupFooter"); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); + + it("render deduplicated silence if present", () => { + for (const id of Object.keys(group.alerts)) { + group.alerts[id].alertmanager[0].silencedBy = ["123456789"]; + } + group.shared.silences = { default: "123456789" }; + const tree = MountedGroupFooter().find("GroupFooter"); + expect(tree.find("Silence")).toHaveLength(1); + }); + + it("mathes snapshot when silence is rendered", () => { + for (const id of Object.keys(group.alerts)) { + group.alerts[id].alertmanager[0].silencedBy = ["123456789"]; + } + group.shared.silences = { default: "123456789" }; + + alertStore.data.silences = { + default: { + "123456789": MockSilence() + } + }; + alertStore.data.silences["default"]["123456789"].id = "123456789"; + + const tree = MountedGroupFooter().find("GroupFooter"); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); + }); }); 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 5f5f1e58a..722ba9e5f 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/GroupHeader/GroupMenu.test.js @@ -31,13 +31,13 @@ const MountedGroupMenu = group => { describe("", () => { it("is collapsed by default", () => { - const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}); + const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedGroupMenu(group); expect(tree.instance().collapse.value).toBe(true); }); it("clicking toggle sets collapse value to 'false'", () => { - const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}); + const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedGroupMenu(group); const toggle = tree.find(".cursor-pointer"); toggle.simulate("click"); @@ -45,7 +45,7 @@ describe("", () => { }); it("handleClickOutside() call sets collapse value to 'true'", () => { - const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}); + const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedGroupMenu(group); const toggle = tree.find(".cursor-pointer"); @@ -75,7 +75,7 @@ const MountedMenuContent = group => { describe("", () => { it("clicking on 'Copy' icon copies the link to clickboard", () => { - const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}); + const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(0); button.simulate("click"); @@ -83,7 +83,7 @@ describe("", () => { }); it("clicking on 'Silence' icon opens the silence form modal", () => { - const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}); + const group = MockAlertGroup({ alertname: "Fake Alert" }, [], [], {}, {}); const tree = MountedMenuContent(group); const button = tree.find(".dropdown-item").at(1); button.simulate("click"); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js index a5e253bf9..9da08c88d 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/DeleteSilence.test.js @@ -32,7 +32,8 @@ const MockAPIResponse = () => { { alertname: "foo" }, [MockAlert([], { instance: "foo" }, "suppressed")], [], - { job: "foo" } + { job: "foo" }, + {} ) }; return response; diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js index 52cba769f..8ede3fadb 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.js @@ -219,6 +219,7 @@ const AlertGroup = observer( group={group} alertmanagers={footerAlertmanagers} afterUpdate={afterUpdate} + silenceFormStore={silenceFormStore} /> ) : null} diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js index fc695534b..0eac2cd75 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/index.test.js @@ -22,6 +22,7 @@ const MockGroup = groupName => { { alertname: "Fake Alert", groupName: groupName }, [], [], + {}, {} ); return group; diff --git a/ui/src/Components/Grid/AlertGrid/index.test.js b/ui/src/Components/Grid/AlertGrid/index.test.js index 00f4025ab..f9d765d24 100644 --- a/ui/src/Components/Grid/AlertGrid/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/index.test.js @@ -41,6 +41,7 @@ const MockGroup = (groupName, alertCount) => { { alertname: "Fake Alert", group: groupName }, alerts, [], + {}, {} ); return group; diff --git a/ui/src/Components/SilenceModal/SilencePreview/index.test.js b/ui/src/Components/SilenceModal/SilencePreview/index.test.js index 5b4d278c6..6b94c30bb 100644 --- a/ui/src/Components/SilenceModal/SilencePreview/index.test.js +++ b/ui/src/Components/SilenceModal/SilencePreview/index.test.js @@ -42,7 +42,8 @@ const MockAPIResponse = () => { { alertname: "foo" }, [MockAlert([], { instance: "foo1" }, "active")], [], - { job: "foo" } + { job: "foo" }, + {} ), "2": MockAlertGroup( { alertname: "bar" }, @@ -51,7 +52,8 @@ const MockAPIResponse = () => { MockAlert([], { instance: "bar2" }, "active") ], [], - { job: "bar" } + { job: "bar" }, + {} ) }; return response; diff --git a/ui/src/Models/API.js b/ui/src/Models/API.js index f0294b602..c973fc102 100644 --- a/ui/src/Models/API.js +++ b/ui/src/Models/API.js @@ -44,7 +44,8 @@ const APIGroup = PropTypes.exact({ }), shared: PropTypes.exact({ annotations: PropTypes.arrayOf(Annotation).isRequired, - labels: PropTypes.object.isRequired + labels: PropTypes.object.isRequired, + silences: PropTypes.object.isRequired }).isRequired }); diff --git a/ui/src/Stores/SilenceFormStore.test.js b/ui/src/Stores/SilenceFormStore.test.js index 44b9195bc..4f0e97b5e 100644 --- a/ui/src/Stores/SilenceFormStore.test.js +++ b/ui/src/Stores/SilenceFormStore.test.js @@ -23,9 +23,15 @@ const MockGroup = () => { MockAlert([], { instance: "prod2", cluster: "prod" }), MockAlert([], { instance: "dev1", cluster: "dev" }) ]; - const group = MockAlertGroup({ alertname: "FakeAlert" }, alerts, [], { - job: "mock" - }); + const group = MockAlertGroup( + { alertname: "FakeAlert" }, + alerts, + [], + { + job: "mock" + }, + {} + ); return group; }; diff --git a/ui/src/__mocks__/Alerts.js b/ui/src/__mocks__/Alerts.js index 2d88a36ae..4c98c17ec 100644 --- a/ui/src/__mocks__/Alerts.js +++ b/ui/src/__mocks__/Alerts.js @@ -30,7 +30,8 @@ const MockAlertGroup = ( rootLabels, alerts, sharedAnnotations, - sharedLabels + sharedLabels, + sharedSilences ) => ({ receiver: "by-name", labels: rootLabels, @@ -47,7 +48,8 @@ const MockAlertGroup = ( }, shared: { annotations: sharedAnnotations, - labels: sharedLabels + labels: sharedLabels, + silences: sharedSilences } });