mirror of
https://github.com/prymitive/karma
synced 2026-05-07 03:26:52 +00:00
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:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -52,6 +52,7 @@ const MountedAlert = (alert, group, showAlertmanagers, showReceiver) => {
|
||||
showAlertmanagers={showAlertmanagers}
|
||||
showReceiver={showReceiver}
|
||||
afterUpdate={MockAfterUpdate}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -64,6 +64,7 @@ const MountedAlertGroup = (afterUpdate, showAlertmanagers) => {
|
||||
group={group}
|
||||
showAlertmanagers={showAlertmanagers}
|
||||
settingsStore={settingsStore}
|
||||
alertStore={alertStore}
|
||||
silenceFormStore={silenceFormStore}
|
||||
/>
|
||||
</Provider>
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -177,6 +177,11 @@ class AlertStore {
|
||||
label: "alertname"
|
||||
},
|
||||
valueMapping: {}
|
||||
},
|
||||
silenceForm: {
|
||||
strip: {
|
||||
labels: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -42,6 +42,11 @@ const EmptyAPIResponse = () => ({
|
||||
}
|
||||
}
|
||||
},
|
||||
silenceForm: {
|
||||
strip: {
|
||||
labels: []
|
||||
}
|
||||
},
|
||||
staticColorLabels: ["job"],
|
||||
annotationsDefaultHidden: false,
|
||||
annotationsHidden: [],
|
||||
|
||||
Reference in New Issue
Block a user