feat(ui): use silence form settings exposed via the backend API

Silence form when populating matchers from alerts or alert groups will now ignore labels listed in silenceForm:strip:labels
This commit is contained in:
Łukasz Mierzwa
2019-04-29 20:54:17 +01:00
parent 2fea920e57
commit 8242ec64f8
14 changed files with 88 additions and 20 deletions

View File

@@ -17,13 +17,18 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons/faExternalLinkAlt";
import { APIAlert, APIGroup } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FetchPauser } from "Components/FetchPauser";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
const onSilenceClick = (silenceFormStore, group, alert) => {
const onSilenceClick = (alertStore, silenceFormStore, group, alert) => {
silenceFormStore.data.resetProgress();
silenceFormStore.data.fillMatchersFromGroup(group, [alert]);
silenceFormStore.data.fillMatchersFromGroup(
group,
alertStore.settings.values.silenceForm.strip.labels,
[alert]
);
silenceFormStore.toggle.show();
};
@@ -35,6 +40,7 @@ const MenuContent = onClickOutside(
group,
alert,
afterClick,
alertStore,
silenceFormStore
}) => {
return (
@@ -62,7 +68,9 @@ const MenuContent = onClickOutside(
<div className="dropdown-divider" />
<div
className="dropdown-item cursor-pointer"
onClick={() => onSilenceClick(silenceFormStore, group, alert)}
onClick={() =>
onSilenceClick(alertStore, silenceFormStore, group, alert)
}
>
<FontAwesomeIcon className="mr-1" icon={faBellSlash} />
Silence this alert
@@ -86,6 +94,7 @@ const AlertMenu = observer(
static propTypes = {
group: APIGroup.isRequired,
alert: APIAlert.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
@@ -108,7 +117,7 @@ const AlertMenu = observer(
});
render() {
const { group, alert, silenceFormStore } = this.props;
const { group, alert, alertStore, silenceFormStore } = this.props;
const uniqueClass = `components-grid-alert-${group.id}-${hash(
alert.labels
@@ -148,6 +157,7 @@ const AlertMenu = observer(
popperStyle={style}
group={group}
alert={alert}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
afterClick={this.collapse.hide}
handleClickOutside={this.collapse.hide}

View File

@@ -29,6 +29,7 @@ const MountedAlertMenu = group => {
<AlertMenu
group={group}
alert={alert}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</Provider>
@@ -70,6 +71,7 @@ const MountedMenuContent = group => {
group={group}
alert={alert}
afterClick={MockAfterClick}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</Provider>

View File

@@ -7,6 +7,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faVolumeMute } from "@fortawesome/free-solid-svg-icons/faVolumeMute";
import { APIAlert, APIGroup } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { BorderClassMap } from "Common/Colors";
import { StaticLabels } from "Common/Query";
@@ -26,6 +27,7 @@ const Alert = observer(
showAlertmanagers: PropTypes.bool.isRequired,
showReceiver: PropTypes.bool.isRequired,
afterUpdate: PropTypes.func.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired
};
@@ -36,6 +38,7 @@ const Alert = observer(
showAlertmanagers,
showReceiver,
afterUpdate,
alertStore,
silenceFormStore
} = this.props;
@@ -87,6 +90,7 @@ const Alert = observer(
<AlertMenu
group={group}
alert={alert}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
{alert.alertmanager

View File

@@ -52,6 +52,7 @@ const MountedAlert = (alert, group, showAlertmanagers, showReceiver) => {
showAlertmanagers={showAlertmanagers}
showReceiver={showReceiver}
afterUpdate={MockAfterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</Provider>

View File

@@ -16,14 +16,18 @@ import { faBellSlash } from "@fortawesome/free-solid-svg-icons/faBellSlash";
import { APIGroup } from "Models/API";
import { FormatAPIFilterQuery } from "Stores/AlertStore";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { QueryOperators, StaticLabels, FormatQuery } from "Common/Query";
import { DropdownSlide } from "Components/Animations/DropdownSlide";
import { FetchPauser } from "Components/FetchPauser";
const onSilenceClick = (silenceFormStore, group) => {
const onSilenceClick = (alertStore, silenceFormStore, group) => {
silenceFormStore.data.resetProgress();
silenceFormStore.data.fillMatchersFromGroup(group);
silenceFormStore.data.fillMatchersFromGroup(
group,
alertStore.settings.values.silenceForm.strip.labels
);
silenceFormStore.toggle.show();
};
@@ -34,6 +38,7 @@ const MenuContent = onClickOutside(
popperStyle,
group,
afterClick,
alertStore,
silenceFormStore
}) => {
let groupFilters = Object.keys(group.labels).map(name =>
@@ -65,7 +70,7 @@ const MenuContent = onClickOutside(
</div>
<div
className="dropdown-item cursor-pointer"
onClick={() => onSilenceClick(silenceFormStore, group)}
onClick={() => onSilenceClick(alertStore, silenceFormStore, group)}
>
<FontAwesomeIcon icon={faBellSlash} /> Silence this group
</div>
@@ -86,6 +91,7 @@ const GroupMenu = observer(
class GroupMenu extends Component {
static propTypes = {
group: APIGroup.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
themed: PropTypes.bool.isRequired
};
@@ -109,7 +115,7 @@ const GroupMenu = observer(
});
render() {
const { group, silenceFormStore, themed } = this.props;
const { group, alertStore, silenceFormStore, themed } = this.props;
return (
<Manager>
@@ -143,6 +149,7 @@ const GroupMenu = observer(
popperRef={ref}
popperStyle={style}
group={group}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
afterClick={this.collapse.hide}
handleClickOutside={this.collapse.hide}

View File

@@ -26,6 +26,7 @@ const MountedGroupMenu = (group, themed) => {
<Provider alertStore={alertStore}>
<GroupMenu
group={group}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
themed={themed}
/>
@@ -71,6 +72,7 @@ const MountedMenuContent = group => {
popperStyle={{}}
group={group}
afterClick={MockAfterClick}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</Provider>

View File

@@ -8,6 +8,7 @@ import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import { APIGroup } from "Models/API";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { FilteringLabel } from "Components/Labels/FilteringLabel";
import { FilteringCounterBadge } from "Components/Labels/FilteringCounterBadge";
@@ -22,6 +23,7 @@ const GroupHeader = observer(
toggle: PropTypes.func.isRequired
}).isRequired,
group: APIGroup.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
themedCounters: PropTypes.bool.isRequired
};
@@ -30,6 +32,7 @@ const GroupHeader = observer(
const {
collapseStore,
group,
alertStore,
silenceFormStore,
themedCounters
} = this.props;
@@ -43,6 +46,7 @@ const GroupHeader = observer(
<span className="flex-shrink-0 flex-grow-0">
<GroupMenu
group={group}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
themed={!themedCounters}
/>

View File

@@ -12,6 +12,7 @@ import { faMinus } from "@fortawesome/free-solid-svg-icons/faMinus";
import { APIGroup } from "Models/API";
import { Settings } from "Stores/Settings";
import { AlertStore } from "Stores/AlertStore";
import { SilenceFormStore } from "Stores/SilenceFormStore";
import { IsMobile } from "Common/Device";
import { BackgroundClassMap } from "Common/Colors";
@@ -55,6 +56,7 @@ const AlertGroup = observer(
afterUpdate: PropTypes.func.isRequired,
group: APIGroup.isRequired,
showAlertmanagers: PropTypes.bool.isRequired,
alertStore: PropTypes.instanceOf(AlertStore).isRequired,
settingsStore: PropTypes.instanceOf(Settings).isRequired,
silenceFormStore: PropTypes.instanceOf(SilenceFormStore).isRequired,
style: PropTypes.object
@@ -157,6 +159,7 @@ const AlertGroup = observer(
showAlertmanagers,
afterUpdate,
silenceFormStore,
alertStore,
settingsStore,
style
} = this.props;
@@ -198,6 +201,7 @@ const AlertGroup = observer(
<GroupHeader
collapseStore={this.collapse}
group={group}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
themedCounters={themedCounters}
/>
@@ -216,6 +220,7 @@ const AlertGroup = observer(
}
showReceiver={group.alerts.length === 1}
afterUpdate={afterUpdate}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
))}

View File

@@ -64,6 +64,7 @@ const MountedAlertGroup = (afterUpdate, showAlertmanagers) => {
group={group}
showAlertmanagers={showAlertmanagers}
settingsStore={settingsStore}
alertStore={alertStore}
silenceFormStore={silenceFormStore}
/>
</Provider>

View File

@@ -240,6 +240,7 @@ const AlertGrid = observer(
Object.keys(alertStore.data.upstreams.clusters).length > 1
}
afterUpdate={this.masonryRepack}
alertStore={alertStore}
settingsStore={settingsStore}
silenceFormStore={silenceFormStore}
style={{

View File

@@ -177,6 +177,11 @@ class AlertStore {
label: "alertname"
},
valueMapping: {}
},
silenceForm: {
strip: {
labels: []
}
}
}
},

View File

@@ -114,17 +114,19 @@ class SilenceFormStore {
},
// if alerts argument is not passed all group alerts will be used
fillMatchersFromGroup(group, alerts) {
fillMatchersFromGroup(group, stripLabels, alerts) {
let matchers = [];
// add matchers for all shared labels in this group
for (const [key, value] of Object.entries(
Object.assign({}, group.labels, group.shared.labels)
)) {
const matcher = NewEmptyMatcher();
matcher.name = key;
matcher.values = [MatcherValueToObject(value)];
matchers.push(matcher);
if (!stripLabels.includes(key)) {
const matcher = NewEmptyMatcher();
matcher.name = key;
matcher.values = [MatcherValueToObject(value)];
matchers.push(matcher);
}
}
// add matchers for all unique labels in this group
@@ -132,10 +134,12 @@ class SilenceFormStore {
const allAlerts = alerts ? alerts : group.alerts;
for (const alert of allAlerts) {
for (const [key, value] of Object.entries(alert.labels)) {
if (!labels[key]) {
labels[key] = new Set();
if (!stripLabels.includes(key)) {
if (!labels[key]) {
labels[key] = new Set();
}
labels[key].add(value);
}
labels[key].add(value);
}
}
for (const [key, values] of Object.entries(labels)) {

View File

@@ -120,7 +120,7 @@ describe("SilenceFormStore.data", () => {
it("fillMatchersFromGroup() creates correct matcher object for a group", () => {
const group = MockGroup();
store.data.fillMatchersFromGroup(group);
store.data.fillMatchersFromGroup(group, []);
expect(store.data.matchers).toHaveLength(4);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
@@ -161,7 +161,7 @@ describe("SilenceFormStore.data", () => {
it("fillMatchersFromGroup() creates correct matcher object for a group with only a subset of alets passed", () => {
const group = MockGroup();
store.data.fillMatchersFromGroup(group, [group.alerts[0]]);
store.data.fillMatchersFromGroup(group, [], [group.alerts[0]]);
expect(store.data.matchers).toHaveLength(4);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
@@ -193,10 +193,27 @@ describe("SilenceFormStore.data", () => {
);
});
it("fillMatchersFromGroup() ignores labels from stripLabels list", () => {
const group = MockGroup();
store.data.fillMatchersFromGroup(
group,
["job", "instance", "cluster"],
[group.alerts[0]]
);
expect(store.data.matchers).toHaveLength(1);
expect(store.data.matchers).toContainEqual(
expect.objectContaining({
name: "alertname",
values: [{ label: "FakeAlert", value: "FakeAlert" }],
isRegex: false
})
);
});
it("fillMatchersFromGroup() resets silenceID if set", () => {
store.data.silenceID = "12345";
const group = MockGroup();
store.data.fillMatchersFromGroup(group, [group.alerts[0]]);
store.data.fillMatchersFromGroup(group, [], [group.alerts[0]]);
expect(store.data.silenceID).toBeNull();
});
@@ -259,7 +276,7 @@ describe("SilenceFormStore.data", () => {
it("toAlertmanagerPayload creates payload that matches snapshot", () => {
const group = MockGroup();
store.data.fillMatchersFromGroup(group);
store.data.fillMatchersFromGroup(group, []);
// add empty matcher so we test empty string rendering
store.data.addEmptyMatcher();
store.data.startsAt = moment.utc([2000, 1, 1, 0, 0, 0]);

View File

@@ -42,6 +42,11 @@ const EmptyAPIResponse = () => ({
}
}
},
silenceForm: {
strip: {
labels: []
}
},
staticColorLabels: ["job"],
annotationsDefaultHidden: false,
annotationsHidden: [],