From af6c1662596f3058d48b6d397fc5b000da4428f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sun, 30 Sep 2018 21:11:18 +0100 Subject: [PATCH] feat(ui): allow editing exiting silences Fixes #87 --- .../Grid/AlertGrid/AlertGroup/Alert/index.js | 1 + .../Silence/__snapshots__/index.test.js.snap | 133 ++++++++++++++---- .../AlertGrid/AlertGroup/Silence/index.js | 106 +++++++++----- .../AlertGroup/Silence/index.test.js | 62 ++++++-- .../__snapshots__/index.test.js.snap | 52 +++++++ ui/src/Components/MultiSelect/index.js | 6 +- ui/src/Components/MultiSelect/index.test.js | 11 ++ .../SilenceModal/AlertManagerInput.js | 6 + .../SilenceModal/AlertManagerInput.test.js | 14 ++ ui/src/Components/SilenceModal/SilenceForm.js | 29 +++- .../SilenceModal/SilenceForm.test.js | 41 ++++++ .../SilenceModal/SilenceModalContent.js | 6 +- .../SilenceModal/SilenceModalContent.test.js | 14 ++ ui/src/Stores/SilenceFormStore.js | 36 +++++ ui/src/Stores/SilenceFormStore.test.js | 128 ++++++++++++----- .../SilenceFormStore.test.js.snap | 2 +- ui/src/__mocks__/Alerts.js | 30 +++- 17 files changed, 555 insertions(+), 122 deletions(-) diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js index 59f44db7f..6ad014d3a 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Alert/index.js @@ -96,6 +96,7 @@ const Alert = observer( am.silencedBy.map(silenceID => ( matches snapshot with expaned details 1`] = `
- - @alertmanager: default - - - 4cf5fd82-1edd-4169-99d1-ff8415e72179 - - - Silenced - - - - Expires - - - - alertname=MockAlert - - - instance=~foo[0-9]+ - +
+ + @alertmanager: default + + + + + + + 4cf5fd82-1edd-4169-99d1-ff8415e72179 + +
+
+ + + + + + Created + + + + + + + + Expires + + + + + + + + Edit + +
+
+ + + + + + Matchers: + + + alertname=MockAlert + + + instance=~foo[0-9]+ + +
" diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js index 67cbf2cfe..dff6dbb45 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.js @@ -13,6 +13,10 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt"; import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp"; import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; +import { faEdit } from "@fortawesome/free-solid-svg-icons/faEdit"; +import { faCalendarCheck } from "@fortawesome/free-solid-svg-icons/faCalendarCheck"; +import { faCalendarTimes } from "@fortawesome/free-solid-svg-icons/faCalendarTimes"; +import { faFilter } from "@fortawesome/free-solid-svg-icons/faFilter"; import { APIAlertAlertmanagerState, @@ -20,8 +24,10 @@ import { APISilence } from "Models/API"; import { AlertStore } from "Stores/AlertStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { StaticLabels, QueryOperators } from "Common/Query"; import { FilteringLabel } from "Components/Labels/FilteringLabel"; +import { RenderLinkAnnotation } from "../Annotation"; import "./index.css"; @@ -80,52 +86,68 @@ SilenceExpiryBadgeWithProgress.propTypes = { progress: PropTypes.number.isRequired }; -const SilenceDetails = ({ alertmanager, silence }) => { - let expiresClass = "secondary"; +const SilenceDetails = ({ alertmanager, silence, onEditSilence }) => { + let expiresClass = ""; let expiresLabel = "Expires"; if (moment(silence.endsAt) < moment()) { - expiresClass = "danger"; + expiresClass = "text-danger"; expiresLabel = "Expired"; } return (
- - - {silence.id} - - - Silenced {silence.startsAt} - - - {expiresLabel} {silence.endsAt} - - {silence.matchers.map(matcher => ( - - {matcher.name} - {matcher.isRegex ? QueryOperators.Regex : QueryOperators.Equal} - {matcher.value} +
+ + +
+
+ + + Created {silence.startsAt} - ))} + + + {expiresLabel} {silence.endsAt} + + + + Edit + +
+
+ + + Matchers: + + {silence.matchers.map(matcher => ( + + {matcher.name} + {matcher.isRegex ? QueryOperators.Regex : QueryOperators.Equal} + {matcher.value} + + ))} +
); }; SilenceDetails.propTypes = { alertmanager: APIAlertmanagerUpstream.isRequired, - silence: APISilence.isRequired + silence: APISilence.isRequired, + onEditSilence: PropTypes.func.isRequired }; // @@ -148,6 +170,7 @@ const Silence = inject("alertStore")( class Silence extends Component { static propTypes = { alertStore: PropTypes.instanceOf(AlertStore).isRequired, + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, alertmanagerState: APIAlertAlertmanagerState.isRequired, silenceID: PropTypes.string.isRequired, afterUpdate: PropTypes.func.isRequired @@ -231,6 +254,17 @@ const Silence = inject("alertStore")( } }; + onEditSilence = () => { + const { silenceFormStore } = this.props; + + const silence = this.getSilence(); + const alertmanager = this.getAlertmanager(); + + silenceFormStore.data.fillFormFromSilence(alertmanager, silence); + silenceFormStore.data.resetProgress(); + silenceFormStore.toggle.show(); + }; + componentDidUpdate() { const { afterUpdate } = this.props; afterUpdate(); @@ -282,7 +316,11 @@ const Silence = inject("alertStore")( {this.collapse.value ? null : ( - + )} ); diff --git a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js index 04a742b91..d7d0392fe 100644 --- a/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js +++ b/ui/src/Components/Grid/AlertGrid/AlertGroup/Silence/index.test.js @@ -3,13 +3,14 @@ import React from "react"; import { toJS } from "mobx"; import { Provider } from "mobx-react"; -import { mount, shallow } from "enzyme"; +import { mount } from "enzyme"; import toDiffableHtml from "diffable-html"; import { advanceTo, clear } from "jest-date-mock"; import { AlertStore } from "Stores/AlertStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { Silence, SilenceDetails } from "."; const mockAfterUpdate = jest.fn(); @@ -47,6 +48,7 @@ const silence = { }; let alertStore; +let silenceFormStore; beforeEach(() => { advanceTo(new Date(2000, 0, 1, 15, 0, 0)); @@ -71,6 +73,7 @@ beforeEach(() => { "4cf5fd82-1edd-4169-99d1-ff8415e72179": silence } }; + silenceFormStore = new SilenceFormStore(); }); afterEach(() => { @@ -83,6 +86,7 @@ const MountedSilence = alertmanagerState => { { ); }; -const ShallowSilenceDetails = () => { - return shallow( - - ); +const MountedSilenceDetails = onEditSilence => { + return mount( + + + + ).find("SilenceDetails"); }; describe("", () => { @@ -190,24 +197,49 @@ describe("", () => { name: "notDefault" }); }); + + it("clicking on silence edit button calls silenceFormStore.data.fillFormFromSilence", () => { + const fillSpy = jest.spyOn(silenceFormStore.data, "fillFormFromSilence"); + const tree = MountedSilence(alertmanager); + + // expand silence + tree.find("a.float-right.cursor-pointer").simulate("click"); + + const button = tree.find(".badge-secondary.components-label-with-hover"); + expect(button.text()).toBe("Edit"); + button.simulate("click"); + expect(fillSpy).toHaveBeenCalled(); + }); + + it("clicking on silence edit button opens the silence form", () => { + const tree = MountedSilence(alertmanager); + + // expand silence + tree.find("a.float-right.cursor-pointer").simulate("click"); + + const button = tree.find(".badge-secondary.components-label-with-hover"); + expect(button.text()).toBe("Edit"); + button.simulate("click"); + expect(silenceFormStore.toggle.visible).toBe(true); + }); }); describe("", () => { - it("unexpired silence endsAt label uses 'secondary' class", () => { - const tree = ShallowSilenceDetails(); + it("unexpired silence endsAt label doesn't use 'danger' class", () => { + const tree = MountedSilenceDetails(jest.fn()); const endsAt = tree.find("span.badge").at(1); - expect(endsAt.html()).toMatch(/badge-secondary/); + expect(endsAt.html()).not.toMatch(/text-danger/); }); it("expired silence endsAt label uses 'danger' class", () => { advanceTo(new Date(2000, 0, 1, 23, 0, 0)); - const tree = ShallowSilenceDetails(); - const endsAt = tree.find("span.badge").at(1); - expect(endsAt.html()).toMatch(/badge-danger/); + const tree = MountedSilenceDetails(jest.fn()); + const endsAt = tree.find("span.badge").at(2); + expect(endsAt.html()).toMatch(/text-danger/); }); it("id links to Alertmanager silence view via alertmanager.uri", () => { - const tree = ShallowSilenceDetails(); + const tree = MountedSilenceDetails(jest.fn()); const link = tree.find("a"); expect(link.props().href).toBe( "file:///mock/#/silences/4cf5fd82-1edd-4169-99d1-ff8415e72179" diff --git a/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap b/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap index edee4f5ad..2d127298f 100644 --- a/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap +++ b/ui/src/Components/MultiSelect/__snapshots__/index.test.js.snap @@ -163,6 +163,58 @@ exports[` matches snapshot with defaults 1`] = ` " `; +exports[` matches snapshot with isDisabled=true 1`] = ` +" +
+
+
+
+ foo +
+
+
+ +
+
+
+
+
+
+ + +
+ + + + +
+
+
+
+" +`; + exports[` matches snapshot with isMulti=true 1`] = ` "
diff --git a/ui/src/Components/MultiSelect/index.js b/ui/src/Components/MultiSelect/index.js index 3b4b82dc3..b4d68dd3a 100644 --- a/ui/src/Components/MultiSelect/index.js +++ b/ui/src/Components/MultiSelect/index.js @@ -27,7 +27,7 @@ const ReactSelectStyles = { ? { ...base, borderRadius: "0.25rem", - backgroundColor: "#fff", + backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff", paddingLeft: "4px", paddingRight: "4px", display: "flex", @@ -39,7 +39,7 @@ const ReactSelectStyles = { : { ...base, borderRadius: "0.25rem", - backgroundColor: "#fff" + backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff" }, multiValue: (base, state) => ({ ...base, @@ -72,7 +72,7 @@ const ReactSelectStyles = { }), indicatorsContainer: (base, state) => ({ ...base, - backgroundColor: "#fff", + backgroundColor: state.isDisabled ? "#ecf0f1" : "#fff", borderTopRightRadius: "0.25rem", borderBottomRightRadius: "0.25rem" }), diff --git a/ui/src/Components/MultiSelect/index.test.js b/ui/src/Components/MultiSelect/index.test.js index 59b0d236d..76db84977 100644 --- a/ui/src/Components/MultiSelect/index.test.js +++ b/ui/src/Components/MultiSelect/index.test.js @@ -62,4 +62,15 @@ describe("", () => { ); expect(toDiffableHtml(tree.html())).toMatchSnapshot(); }); + + it("matches snapshot with isDisabled=true", () => { + const tree = shallow( + + ); + expect(toDiffableHtml(tree.html())).toMatchSnapshot(); + }); }); diff --git a/ui/src/Components/SilenceModal/AlertManagerInput.js b/ui/src/Components/SilenceModal/AlertManagerInput.js index 8a7d5f351..a1c36d7e0 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput.js @@ -70,6 +70,11 @@ const AlertManagerInput = observer( render() { const { alertStore, silenceFormStore } = this.props; + const extraProps = {}; + if (silenceFormStore.data.silenceID !== null) { + extraProps.isDisabled = true; + } + return ( ); } diff --git a/ui/src/Components/SilenceModal/AlertManagerInput.test.js b/ui/src/Components/SilenceModal/AlertManagerInput.test.js index 1b7769bba..40ef5cdbb 100644 --- a/ui/src/Components/SilenceModal/AlertManagerInput.test.js +++ b/ui/src/Components/SilenceModal/AlertManagerInput.test.js @@ -148,4 +148,18 @@ describe("", () => { value: "http://am1.example.com/new" }); }); + + it("is enabled when silenceFormStore.data.silenceID is null", () => { + silenceFormStore.data.silenceID = null; + const tree = MountedAlertManagerInput(); + const select = tree.find("StateManager"); + expect(select.props().isDisabled).toBeFalsy(); + }); + + it("is disabled when silenceFormStore.data.silenceID is not null", () => { + silenceFormStore.data.silenceID = "1234"; + const tree = MountedAlertManagerInput(); + const select = tree.find("StateManager"); + expect(select.props().isDisabled).toBe(true); + }); }); diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.js index d11b59d5b..3f6d46564 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.js +++ b/ui/src/Components/SilenceModal/SilenceForm.js @@ -8,6 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/free-solid-svg-icons/faPlus"; import { faUser } from "@fortawesome/free-solid-svg-icons/faUser"; import { faCommentDots } from "@fortawesome/free-solid-svg-icons/faCommentDots"; +import { faUndoAlt } from "@fortawesome/free-solid-svg-icons/faUndoAlt"; import { faSave } from "@fortawesome/free-regular-svg-icons/faSave"; import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp"; import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown"; @@ -78,8 +79,12 @@ const SilenceForm = observer( componentDidMount() { const { silenceFormStore, settingsStore } = this.props; - // reset startsAt & endsAt on every mount - silenceFormStore.data.resetStartEnd(); + // reset startsAt & endsAt on every mount, unless we're editing a silence + if (silenceFormStore.data.silenceID === null) { + silenceFormStore.data.resetStartEnd(); + } else { + silenceFormStore.data.verifyStarEnd(); + } if (silenceFormStore.data.matchers.length === 0) { silenceFormStore.data.addEmptyMatcher(); @@ -177,10 +182,22 @@ const SilenceForm = observer( icon={this.previewCollapse.hidden ? faChevronUp : faChevronDown} /> - + + {silenceFormStore.data.silenceID === null ? null : ( + + )} + +
{this.previewCollapse.hidden ? null : ( diff --git a/ui/src/Components/SilenceModal/SilenceForm.test.js b/ui/src/Components/SilenceModal/SilenceForm.test.js index 1a3850b9c..bd7e33110 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.test.js +++ b/ui/src/Components/SilenceModal/SilenceForm.test.js @@ -182,3 +182,44 @@ describe("", () => { ); }); }); + +describe(" in edit mode", () => { + it("opening form with silenceID set disables AlertManagerInput", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = MountedSilenceForm(); + const select = tree.find("StateManager").at(0); + expect(select.props().isDisabled).toBe(true); + }); + + it("opening form with silenceID shows reset button", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = MountedSilenceForm(); + const button = tree.find("button.btn-outline-danger"); + expect(button).toHaveLength(1); + }); + + it("clicking on Reset button unsets silenceFormStore.data.silenceID", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = MountedSilenceForm(); + const button = tree.find("button.btn-outline-danger"); + button.simulate("click"); + expect(silenceFormStore.data.silenceID).toBeNull(); + }); + + it("clicking on Reset button hides it", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = MountedSilenceForm(); + const button = tree.find("button.btn-outline-danger"); + button.simulate("click"); + expect(tree.find("button.btn-outline-danger")).toHaveLength(0); + }); + + it("clicking on Reset button enables AlertManagerInput", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = MountedSilenceForm(); + const button = tree.find("button.btn-outline-danger"); + button.simulate("click"); + const select = tree.find("StateManager").at(0); + expect(select.props().isDisabled).toBeFalsy(); + }); +}); diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.js b/ui/src/Components/SilenceModal/SilenceModalContent.js index c67cdd580..6c6cbb3e1 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.js @@ -42,7 +42,11 @@ const SilenceModalContent = observer(
-
Add new silence
+
+ {silenceFormStore.data.silenceID === null + ? "Add new silence" + : `Editing silence ${silenceFormStore.data.silenceID}`} +
diff --git a/ui/src/Components/SilenceModal/SilenceModalContent.test.js b/ui/src/Components/SilenceModal/SilenceModalContent.test.js index 0854e9842..0e9497637 100644 --- a/ui/src/Components/SilenceModal/SilenceModalContent.test.js +++ b/ui/src/Components/SilenceModal/SilenceModalContent.test.js @@ -44,4 +44,18 @@ describe("", () => { const ctrl = tree.find("SilenceSubmitController"); expect(ctrl).toHaveLength(1); }); + + it("title is 'Add new silence' when silenceFormStore.data.silenceID is null", () => { + silenceFormStore.data.silenceID = null; + const tree = ShallowSilenceModalContent(); + const title = tree.find(".modal-title"); + expect(title.text()).toBe("Add new silence"); + }); + + it("title is 'Editing silence 12345' when silenceFormStore.data.silenceID is '12345'", () => { + silenceFormStore.data.silenceID = "12345"; + const tree = ShallowSilenceModalContent(); + const title = tree.find(".modal-title"); + expect(title.text()).toBe("Editing silence 12345"); + }); }); diff --git a/ui/src/Stores/SilenceFormStore.js b/ui/src/Stores/SilenceFormStore.js index e8f993619..54f939c14 100644 --- a/ui/src/Stores/SilenceFormStore.js +++ b/ui/src/Stores/SilenceFormStore.js @@ -45,6 +45,7 @@ class SilenceFormStore { { inProgress: false, wasValidated: false, + silenceID: null, alertmanagers: [], matchers: [], startsAt: moment(), @@ -79,6 +80,10 @@ class SilenceFormStore { this.wasValidated = false; }, + resetSilenceID() { + this.silenceID = null; + }, + // append a new empty matcher to the list addEmptyMatcher() { let m = NewEmptyMatcher(); @@ -135,6 +140,31 @@ class SilenceFormStore { this.matchers = matchers; }, + fillFormFromSilence(alertmanager, silence) { + this.silenceID = silence.id; + this.alertmanagers = [ + { + label: alertmanager.name, + value: alertmanager.publicURI + } + ]; + + const matchers = []; + for (const m of silence.matchers) { + const matcher = NewEmptyMatcher(); + matcher.name = m.name; + matcher.values = [MatcherValueToObject(m.value)]; + matcher.isRegex = m.isRegex; + matchers.push(matcher); + } + this.matchers = matchers; + + this.startsAt = moment(silence.startsAt); + this.endsAt = moment(silence.endsAt); + this.comment = silence.comment; + this.author = silence.createdBy; + }, + verifyStarEnd() { const now = moment().second(0); if (this.startsAt.isBefore(now)) { @@ -186,8 +216,12 @@ class SilenceFormStore { createdBy: this.author, comment: this.comment }; + if (this.silenceID !== null) { + payload.id = this.silenceID; + } return payload; }, + get toDuration() { const data = { days: this.endsAt.diff(this.startsAt, "days"), @@ -200,9 +234,11 @@ class SilenceFormStore { { resetStartEnd: action.bound, resetProgress: action.bound, + resetSilenceID: action.bound, addEmptyMatcher: action.bound, deleteMatcher: action.bound, fillMatchersFromGroup: action.bound, + fillFormFromSilence: action.bound, verifyStarEnd: action.bound, incStart: action.bound, decStart: action.bound, diff --git a/ui/src/Stores/SilenceFormStore.test.js b/ui/src/Stores/SilenceFormStore.test.js index fab7d93b0..6c85ccfca 100644 --- a/ui/src/Stores/SilenceFormStore.test.js +++ b/ui/src/Stores/SilenceFormStore.test.js @@ -1,6 +1,11 @@ import moment from "moment"; -import { MockAlert, MockAlertGroup } from "__mocks__/Alerts.js"; +import { + MockAlert, + MockAlertGroup, + MockSilence, + MockAlertmanager +} from "__mocks__/Alerts.js"; import { SilenceFormStore, NewEmptyMatcher } from "./SilenceFormStore"; let store; @@ -8,6 +13,30 @@ beforeEach(() => { store = new SilenceFormStore(); }); +const MockGroup = () => { + const alerts = [ + MockAlert([], { instance: "prod1", cluster: "prod" }), + MockAlert([], { instance: "prod2", cluster: "prod" }), + MockAlert([], { instance: "dev1", cluster: "dev" }) + ]; + const group = MockAlertGroup({ alertname: "FakeAlert" }, alerts, [], { + job: "mock" + }); + return group; +}; + +const MockAlertmanagerOption = () => ({ + label: "default", + value: "http://localhost" +}); + +const MockMatcher = (name, values) => { + const matcher = NewEmptyMatcher(); + matcher.name = name; + matcher.values = values; + return matcher; +}; + describe("SilenceFormStore.toggle", () => { it("toggle() toggles 'visible' correctly", () => { expect(store.toggle.visible).toBe(false); @@ -32,18 +61,6 @@ describe("SilenceFormStore.toggle", () => { }); }); -const MockGroup = () => { - const alerts = [ - MockAlert([], { instance: "prod1", cluster: "prod" }), - MockAlert([], { instance: "prod2", cluster: "prod" }), - MockAlert([], { instance: "dev1", cluster: "dev" }) - ]; - const group = MockAlertGroup({ alertname: "FakeAlert" }, alerts, [], { - job: "mock" - }); - return group; -}; - describe("SilenceFormStore.data", () => { it("resetStartEnd() sets startsAt and endsAt to defaults", () => { store.data.startsAt = moment([2000, 1, 1, 0, 1, 0]); @@ -167,6 +184,63 @@ describe("SilenceFormStore.data", () => { ); }); + it("fillFormFromSilence() sets silenceID", () => { + const alertmanager = MockAlertmanager(); + const silence = MockSilence(); + store.data.fillFormFromSilence(alertmanager, silence); + expect(store.data.silenceID).toBe(silence.id); + }); + + it("fillFormFromSilence() creates payload that matches silence data", () => { + const alertmanager = MockAlertmanager(); + const silence = MockSilence(); + store.data.fillFormFromSilence(alertmanager, silence); + + expect(store.data.alertmanagers).toHaveLength(1); + expect(store.data.alertmanagers[0]).toMatchObject({ + label: alertmanager.name, + value: alertmanager.publicURI + }); + + expect(store.data.matchers).toHaveLength(2); + expect(store.data.matchers).toContainEqual( + expect.objectContaining({ + name: "foo", + values: [{ label: "bar", value: "bar" }], + isRegex: false + }) + ); + expect(store.data.matchers).toContainEqual( + expect.objectContaining({ + name: "baz", + values: [{ label: "regex", value: "regex" }], + isRegex: true + }) + ); + + expect(store.data.startsAt.toISOString()).toBe( + moment([2000, 0, 1, 0, 0, 0]).toISOString() + ); + expect(store.data.endsAt.toISOString()).toBe( + moment([2000, 0, 1, 1, 0, 0]).toISOString() + ); + + expect(store.data.author).toBe("me@example.com"); + expect(store.data.comment).toBe("Mocked Silence"); + }); + + it("toAlertmanagerPayload constains id when store.data.silenceID is set", () => { + store.data.silenceID = "12345"; + expect(store.data.toAlertmanagerPayload).toMatchObject({ + id: "12345" + }); + }); + + it("toAlertmanagerPayload doesn't contain id when store.data.silenceID is null", () => { + store.data.silenceID = null; + expect(store.data.toAlertmanagerPayload.id).toBeUndefined(); + }); + it("toAlertmanagerPayload creates payload that matches snapshot", () => { const group = MockGroup(); store.data.fillMatchersFromGroup(group); @@ -174,24 +248,12 @@ describe("SilenceFormStore.data", () => { store.data.addEmptyMatcher(); store.data.startsAt = moment([2000, 1, 1, 0, 0, 0]); store.data.endsAt = moment([2000, 1, 1, 1, 0, 0]); - store.data.createdBy = "me@example.com"; + store.data.author = "me@example.com"; store.data.comment = "toAlertmanagerPayload test"; expect(store.data.toAlertmanagerPayload).toMatchSnapshot(); }); }); -const MockAlertmanager = () => ({ - label: "default", - value: "http://localhost" -}); - -const MockMatcher = (name, values) => { - const matcher = NewEmptyMatcher(); - matcher.name = name; - matcher.values = values; - return matcher; -}; - describe("SilenceFormStore.data.isValid", () => { it("isValid returns 'false' if alertmanagers list is empty", () => { store.data.matchers = [MockMatcher("foo", ["bar"])]; @@ -201,7 +263,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if matchers list is empty", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = []; store.data.author = "me@example.com"; store.data.comment = "fake silence"; @@ -209,7 +271,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if matchers list is pupulated when a matcher without any name", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("", ["bar"])]; store.data.author = "me@example.com"; store.data.comment = "fake silence"; @@ -217,7 +279,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if matchers list is pupulated when a matcher without any value ([])", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("foo", [])]; store.data.author = "me@example.com"; store.data.comment = "fake silence"; @@ -225,7 +287,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if matchers list is pupulated when a matcher with empty value ([''])", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("foo", [])]; store.data.author = "me@example.com"; store.data.comment = "fake silence"; @@ -233,7 +295,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if author is empty", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("foo", ["bar"])]; store.data.author = ""; store.data.comment = "fake silence"; @@ -241,7 +303,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'false' if comment is empty", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("foo", ["bar"])]; store.data.author = "me@example.com"; store.data.comment = ""; @@ -249,7 +311,7 @@ describe("SilenceFormStore.data.isValid", () => { }); it("isValid returns 'true' if all fileds are set", () => { - store.data.alertmanagers = [MockAlertmanager]; + store.data.alertmanagers = [MockAlertmanagerOption]; store.data.matchers = [MockMatcher("foo", ["bar"])]; store.data.author = "me@example.com"; store.data.comment = "fake silence"; diff --git a/ui/src/Stores/__snapshots__/SilenceFormStore.test.js.snap b/ui/src/Stores/__snapshots__/SilenceFormStore.test.js.snap index ed4b8cca6..2d39dc49c 100644 --- a/ui/src/Stores/__snapshots__/SilenceFormStore.test.js.snap +++ b/ui/src/Stores/__snapshots__/SilenceFormStore.test.js.snap @@ -3,7 +3,7 @@ exports[`SilenceFormStore.data toAlertmanagerPayload creates payload that matches snapshot 1`] = ` Object { "comment": "toAlertmanagerPayload test", - "createdBy": "", + "createdBy": "me@example.com", "endsAt": "2000-02-01T01:00:00.000Z", "matchers": Array [ Object { diff --git a/ui/src/__mocks__/Alerts.js b/ui/src/__mocks__/Alerts.js index f0dedab7e..ae8a012de 100644 --- a/ui/src/__mocks__/Alerts.js +++ b/ui/src/__mocks__/Alerts.js @@ -49,4 +49,32 @@ const MockAlertGroup = ( } }); -export { MockAnnotation, MockAlert, MockAlertGroup }; +const MockSilence = () => ({ + comment: "Mocked Silence", + createdAt: "0001-01-01T00:00:00Z", + createdBy: "me@example.com", + startsAt: "2000-01-01T00:00:00Z", + endsAt: "2000-01-01T01:00:00Z", + id: "04d37636-2350-4878-b382-e0b50353230f", + jiraID: "", + jiraURL: "", + matchers: [ + { name: "foo", value: "bar", isRegex: false }, + { name: "baz", value: "regex", isRegex: true } + ] +}); + +const MockAlertmanager = () => ({ + name: "default", + uri: "http://localhost", + publicURI: "http://am.example.com", + error: "" +}); + +export { + MockAnnotation, + MockAlert, + MockAlertGroup, + MockSilence, + MockAlertmanager +};