From 8b8a7fb8136bde3d72873694ee252d9f14c85c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Sat, 6 Oct 2018 13:34:37 +0100 Subject: [PATCH] feat(ui): silence match preview should use selected Alertmanagers Counter should use selected Alertmanager instances, rather than match all alerts from all instances --- ui/src/Components/SilenceModal/SilenceForm.js | 1 + .../SilenceMatch/LabelValueInput.js | 10 ++++- .../SilenceMatch/LabelValueInput.test.js | 24 ++++++++-- .../SilenceModal/SilenceMatch/MatchCounter.js | 44 ++++++++++++++---- .../SilenceMatch/MatchCounter.test.js | 45 ++++++++++++++++++- .../LabelValueInput.test.js.snap | 2 +- .../__snapshots__/MatchCounter.test.js.snap | 2 +- .../SilenceModal/SilenceMatch/index.js | 16 ++++++- 8 files changed, 124 insertions(+), 20 deletions(-) diff --git a/ui/src/Components/SilenceModal/SilenceForm.js b/ui/src/Components/SilenceModal/SilenceForm.js index b54c82d47..75370767b 100644 --- a/ui/src/Components/SilenceModal/SilenceForm.js +++ b/ui/src/Components/SilenceModal/SilenceForm.js @@ -141,6 +141,7 @@ const SilenceForm = observer( {silenceFormStore.data.matchers.map(matcher => ( { silenceFormStore.data.deleteMatcher(matcher.id); diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js index 6c0c1be1a..c1b0b7241 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.js @@ -6,6 +6,7 @@ import { observer } from "mobx-react"; import { components } from "react-select"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceFormMatcher } from "Models/SilenceForm"; import { MultiSelect } from "Components/MultiSelect"; import { ValidationError } from "Components/MultiSelect/ValidationError"; @@ -21,7 +22,10 @@ const Placeholder = props => { const ValueContainer = ({ children, ...props }) => ( - + {children} ); @@ -29,6 +33,7 @@ const ValueContainer = ({ children, ...props }) => ( const LabelValueInput = observer( class LabelValueInput extends MultiSelect { static propTypes = { + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, matcher: SilenceFormMatcher.isRequired, isValid: PropTypes.bool.isRequired }; @@ -47,7 +52,7 @@ const LabelValueInput = observer( }); renderProps = () => { - const { matcher, isValid } = this.props; + const { silenceFormStore, matcher, isValid } = this.props; return { instanceId: `silence-input-label-value-${matcher.id}`, @@ -57,6 +62,7 @@ const LabelValueInput = observer( isMulti: true, onChange: this.onChange, components: { ValueContainer, Placeholder }, + silenceFormStore: silenceFormStore, matcher: matcher }; }; diff --git a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.js b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.js index bf6f76bc5..852a0618f 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/LabelValueInput.test.js @@ -4,12 +4,18 @@ import { shallow, mount } from "enzyme"; import toDiffableHtml from "diffable-html"; -import { NewEmptyMatcher, MatcherValueToObject } from "Stores/SilenceFormStore"; +import { + SilenceFormStore, + NewEmptyMatcher, + MatcherValueToObject +} from "Stores/SilenceFormStore"; import { LabelValueInput } from "./LabelValueInput"; +let silenceFormStore; let matcher; beforeEach(() => { + silenceFormStore = new SilenceFormStore(); matcher = NewEmptyMatcher(); matcher.name = "name"; matcher.suggestions.names = [ @@ -23,11 +29,23 @@ beforeEach(() => { }); const ShallowLabelValueInput = isValid => { - return shallow(); + return shallow( + + ); }; const MountedLabelValueInput = isValid => { - return mount(); + return mount( + + ); }; const ValidateSuggestions = () => { diff --git a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js index f2ea8b3be..ce372d2d1 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/MatchCounter.js @@ -1,4 +1,5 @@ import React, { Component } from "react"; +import PropTypes from "prop-types"; import { observable, action } from "mobx"; import { observer } from "mobx-react"; @@ -10,13 +11,15 @@ import hash from "object-hash"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons/faExclamationCircle"; -import { FormatQuery, QueryOperators } from "Common/Query"; +import { FormatQuery, QueryOperators, StaticLabels } from "Common/Query"; import { FormatBackendURI, FormatAlertsQ } from "Stores/AlertStore"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceFormMatcher } from "Models/SilenceForm"; const MatchCounter = observer( class MatchCounter extends Component { static propTypes = { + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, matcher: SilenceFormMatcher.isRequired }; @@ -39,7 +42,7 @@ const MatchCounter = observer( ); onFetch = throttle(() => { - const { matcher } = this.props; + const { silenceFormStore, matcher } = this.props; const filters = []; @@ -59,6 +62,26 @@ const MatchCounter = observer( ) ); + if (silenceFormStore.data.alertmanagers.length > 1) { + filters.push( + FormatQuery( + StaticLabels.AlertManager, + QueryOperators.Regex, + `^(${silenceFormStore.data.alertmanagers + .map(am => am.label) + .join("|")})$` + ) + ); + } else if (silenceFormStore.data.alertmanagers.length === 1) { + filters.push( + FormatQuery( + StaticLabels.AlertManager, + QueryOperators.Equal, + silenceFormStore.data.alertmanagers[0].label + ) + ); + } + const alertsURI = FormatBackendURI("alerts.json?") + FormatAlertsQ(filters); @@ -97,13 +120,7 @@ const MatchCounter = observer( } render() { - const { matcher } = this.props; - - const matcherHash = hash({ - name: matcher.name, - values: matcher.values, - isRegex: matcher.isRegex - }); + const { silenceFormStore, matcher } = this.props; if (this.matchedAlerts.error !== null) { return ( @@ -111,6 +128,15 @@ const MatchCounter = observer( ); } + const matcherHash = hash({ + alertmanagers: silenceFormStore.data.alertmanagers, + matcher: { + name: matcher.name, + values: matcher.values, + isRegex: matcher.isRegex + } + }); + return ( { fetch.resetMocks(); + silenceFormStore = new SilenceFormStore(); matcher = NewEmptyMatcher(); }); @@ -20,7 +26,9 @@ afterEach(() => { }); const MountedMatchCounter = () => { - return mount(); + return mount( + + ); }; describe("", () => { @@ -112,4 +120,37 @@ describe("", () => { "./alerts.json?q=foo%3D~%5E%28bar%7Cbaz%29%24" ); }); + + it("selecting one Alertmanager instance appends it to the filters", async () => { + fetch.mockResponse(JSON.stringify({ totalAlerts: 0 })); + + silenceFormStore.data.alertmanagers = [MatcherValueToObject("am1")]; + matcher.name = "foo"; + matcher.values = [MatcherValueToObject("bar")]; + matcher.isRegex = false; + + const tree = MountedMatchCounter(); + await expect(tree.instance().matchedAlerts.fetch).resolves.toBeUndefined(); + expect(fetch.mock.calls[0][0]).toBe( + "./alerts.json?q=foo%3Dbar&q=%40alertmanager%3Dam1" + ); + }); + + it("selecting two Alertmanager instances appends it correctly to the filters", async () => { + fetch.mockResponse(JSON.stringify({ totalAlerts: 0 })); + + silenceFormStore.data.alertmanagers = [ + MatcherValueToObject("am1"), + MatcherValueToObject("am1") + ]; + matcher.name = "foo"; + matcher.values = [MatcherValueToObject("bar")]; + matcher.isRegex = false; + + const tree = MountedMatchCounter(); + await expect(tree.instance().matchedAlerts.fetch).resolves.toBeUndefined(); + expect(fetch.mock.calls[0][0]).toBe( + "./alerts.json?q=foo%3Dbar&q=%40alertmanager%3D~%5E%28am1%7Cam1%29%24" + ); + }); }); diff --git a/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/LabelValueInput.test.js.snap b/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/LabelValueInput.test.js.snap index 78194af2b..185dfd6c6 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/LabelValueInput.test.js.snap +++ b/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/LabelValueInput.test.js.snap @@ -7,7 +7,7 @@ exports[` matches snapshot 1`] = `
0 diff --git a/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/MatchCounter.test.js.snap b/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/MatchCounter.test.js.snap index 06e358fea..9064ad4b4 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/MatchCounter.test.js.snap +++ b/ui/src/Components/SilenceModal/SilenceMatch/__snapshots__/MatchCounter.test.js.snap @@ -4,7 +4,7 @@ exports[` matches snapshot with empty matcher 1`] = ` " 0 diff --git a/ui/src/Components/SilenceModal/SilenceMatch/index.js b/ui/src/Components/SilenceModal/SilenceMatch/index.js index 9c4357b3e..cd3163627 100644 --- a/ui/src/Components/SilenceModal/SilenceMatch/index.js +++ b/ui/src/Components/SilenceModal/SilenceMatch/index.js @@ -7,6 +7,7 @@ import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faTrash } from "@fortawesome/free-solid-svg-icons/faTrash"; +import { SilenceFormStore } from "Stores/SilenceFormStore"; import { SilenceFormMatcher } from "Models/SilenceForm"; import { LabelNameInput } from "./LabelNameInput"; import { LabelValueInput } from "./LabelValueInput"; @@ -14,6 +15,7 @@ import { LabelValueInput } from "./LabelValueInput"; const SilenceMatch = observer( class SilenceMatch extends Component { static propTypes = { + silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired, matcher: SilenceFormMatcher.isRequired, showDelete: PropTypes.bool.isRequired, onDelete: PropTypes.func.isRequired, @@ -30,7 +32,13 @@ const SilenceMatch = observer( }); render() { - const { matcher, showDelete, onDelete, isValid } = this.props; + const { + silenceFormStore, + matcher, + showDelete, + onDelete, + isValid + } = this.props; return (
@@ -38,7 +46,11 @@ const SilenceMatch = observer(
- +