mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
Alerting: Display alert instances instead of alert rules when creating silence (#47396)
* modify matchers util for instances * filter alerts * change label to include instances * re add empty case
This commit is contained in:
parent
bb5f77703c
commit
4570615afc
@ -3,21 +3,22 @@ import { useFormContext } from 'react-hook-form';
|
||||
import { useDebounce } from 'react-use';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { dateTime, GrafanaTheme2 } from '@grafana/data';
|
||||
import { Badge, useStyles2 } from '@grafana/ui';
|
||||
import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
|
||||
import { RuleState } from '../rules/RuleState';
|
||||
import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces';
|
||||
import { Annotation } from '../../utils/constants';
|
||||
import { findAlertRulesWithMatchers } from '../../utils/matchers';
|
||||
import { findAlertInstancesWithMatchers } from '../../utils/matchers';
|
||||
import { fetchAllPromAndRulerRulesAction } from '../../state/actions';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
import { Alert, AlertingRule } from 'app/types/unified-alerting';
|
||||
import { MatcherFieldValue, SilenceFormFields } from '../../types/silence-form';
|
||||
import { isAlertingRule } from '../../utils/rules';
|
||||
import { AlertStateTag } from '../rules/AlertStateTag';
|
||||
import { AlertLabels } from '../AlertLabels';
|
||||
|
||||
type MatchedRulesTableItemProps = DynamicTableItemProps<{
|
||||
matchedRule: CombinedRule;
|
||||
matchedInstance: Alert;
|
||||
}>;
|
||||
type MatchedRulesTableColumnProps = DynamicTableColumnProps<{ matchedRule: CombinedRule }>;
|
||||
type MatchedRulesTableColumnProps = DynamicTableColumnProps<{ matchedInstance: Alert }>;
|
||||
|
||||
export const MatchedSilencedRules = () => {
|
||||
const [matchedAlertRules, setMatchedAlertRules] = useState<MatchedRulesTableItemProps[]>([]);
|
||||
@ -35,12 +36,15 @@ export const MatchedSilencedRules = () => {
|
||||
const combinedNamespaces = useCombinedRuleNamespaces();
|
||||
useDebounce(
|
||||
() => {
|
||||
const matchedRules = combinedNamespaces.flatMap((namespace) => {
|
||||
const matchedInstances = combinedNamespaces.flatMap((namespace) => {
|
||||
return namespace.groups.flatMap((group) => {
|
||||
return findAlertRulesWithMatchers(group.rules, matchers);
|
||||
return group.rules
|
||||
.map((combinedRule) => combinedRule.promRule)
|
||||
.filter((rule): rule is AlertingRule => isAlertingRule(rule))
|
||||
.flatMap((rule) => findAlertInstancesWithMatchers(rule.alerts ?? [], matchers));
|
||||
});
|
||||
});
|
||||
setMatchedAlertRules(matchedRules);
|
||||
setMatchedAlertRules(matchedInstances);
|
||||
},
|
||||
500,
|
||||
[combinedNamespaces, matchers]
|
||||
@ -49,7 +53,7 @@ export const MatchedSilencedRules = () => {
|
||||
return (
|
||||
<div>
|
||||
<h4 className={styles.title}>
|
||||
Affected alerts
|
||||
Affected alert instances
|
||||
{matchedAlertRules.length > 0 ? (
|
||||
<Badge className={styles.badge} color="blue" text={matchedAlertRules.length} />
|
||||
) : null}
|
||||
@ -75,24 +79,30 @@ function useColumns(): MatchedRulesTableColumnProps[] {
|
||||
{
|
||||
id: 'state',
|
||||
label: 'State',
|
||||
renderCell: function renderStateTag({ data: { matchedRule } }) {
|
||||
return <RuleState rule={matchedRule} isCreating={false} isDeleting={false} />;
|
||||
renderCell: function renderStateTag({ data: { matchedInstance } }) {
|
||||
return <AlertStateTag state={matchedInstance.state} />;
|
||||
},
|
||||
size: '160px',
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
label: 'Name',
|
||||
renderCell: function renderName({ data: { matchedRule } }) {
|
||||
return matchedRule.name;
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
renderCell: function renderName({ data: { matchedInstance } }) {
|
||||
return <AlertLabels labels={matchedInstance.labels} />;
|
||||
},
|
||||
size: '250px',
|
||||
},
|
||||
{
|
||||
id: 'summary',
|
||||
label: 'Summary',
|
||||
renderCell: function renderSummary({ data: { matchedRule } }) {
|
||||
return matchedRule.annotations[Annotation.summary] ?? '';
|
||||
id: 'created',
|
||||
label: 'Created',
|
||||
renderCell: function renderSummary({ data: { matchedInstance } }) {
|
||||
return (
|
||||
<>
|
||||
{matchedInstance.activeAt.startsWith('0001')
|
||||
? '-'
|
||||
: dateTime(matchedInstance.activeAt).format('YYYY-MM-DD HH:mm:ss')}
|
||||
</>
|
||||
);
|
||||
},
|
||||
size: '400px',
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types';
|
||||
import { getMatcherQueryParams, findAlertRulesWithMatchers, parseQueryParamMatchers } from './matchers';
|
||||
import { mockCombinedRule } from '../mocks';
|
||||
import { getMatcherQueryParams, findAlertInstancesWithMatchers, parseQueryParamMatchers } from './matchers';
|
||||
import { mockPromAlert } from '../mocks';
|
||||
|
||||
describe('Unified Alerting matchers', () => {
|
||||
describe('getMatcherQueryParams tests', () => {
|
||||
@ -39,42 +39,42 @@ describe('Unified Alerting matchers', () => {
|
||||
describe('matchLabelsToMatchers', () => {
|
||||
it('should match for equal', () => {
|
||||
const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.equal }];
|
||||
const rules = [mockCombinedRule({ labels: { foo: 'bar' } }), mockCombinedRule({ labels: { foo: 'baz' } })];
|
||||
const matchedRules = findAlertRulesWithMatchers(rules, matchers);
|
||||
const alerts = [mockPromAlert({ labels: { foo: 'bar' } }), mockPromAlert({ labels: { foo: 'baz' } })];
|
||||
const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers);
|
||||
|
||||
expect(matchedRules).toHaveLength(1);
|
||||
expect(matchedAlerts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should match for not equal', () => {
|
||||
const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.notEqual }];
|
||||
const rules = [mockCombinedRule({ labels: { foo: 'bar' } }), mockCombinedRule({ labels: { foo: 'baz' } })];
|
||||
const alerts = [mockPromAlert({ labels: { foo: 'bar' } }), mockPromAlert({ labels: { foo: 'baz' } })];
|
||||
|
||||
const matchedRules = findAlertRulesWithMatchers(rules, matchers);
|
||||
expect(matchedRules).toHaveLength(1);
|
||||
const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers);
|
||||
expect(matchedAlerts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should match for regex', () => {
|
||||
const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.regex }];
|
||||
const rules = [
|
||||
mockCombinedRule({ labels: { foo: 'bar' } }),
|
||||
mockCombinedRule({ labels: { foo: 'baz' } }),
|
||||
mockCombinedRule({ labels: { foo: 'bas' } }),
|
||||
const alerts = [
|
||||
mockPromAlert({ labels: { foo: 'bar' } }),
|
||||
mockPromAlert({ labels: { foo: 'baz' } }),
|
||||
mockPromAlert({ labels: { foo: 'bas' } }),
|
||||
];
|
||||
|
||||
const matchedRules = findAlertRulesWithMatchers(rules, matchers);
|
||||
expect(matchedRules).toHaveLength(1);
|
||||
const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers);
|
||||
expect(matchedAlerts).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not match regex', () => {
|
||||
const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.notRegex }];
|
||||
const rules = [
|
||||
mockCombinedRule({ labels: { foo: 'bar' } }),
|
||||
mockCombinedRule({ labels: { foo: 'baz' } }),
|
||||
mockCombinedRule({ labels: { foo: 'bas' } }),
|
||||
const alerts = [
|
||||
mockPromAlert({ labels: { foo: 'bar' } }),
|
||||
mockPromAlert({ labels: { foo: 'baz' } }),
|
||||
mockPromAlert({ labels: { foo: 'bas' } }),
|
||||
];
|
||||
|
||||
const matchedRules = findAlertRulesWithMatchers(rules, matchers);
|
||||
expect(matchedRules).toHaveLength(2);
|
||||
const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers);
|
||||
expect(matchedAlerts).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -3,7 +3,7 @@ import { Labels } from '@grafana/data';
|
||||
import { parseMatcher } from './alertmanager';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { MatcherFieldValue } from '../types/silence-form';
|
||||
import { CombinedRule } from 'app/types/unified-alerting';
|
||||
import { Alert } from 'app/types/unified-alerting';
|
||||
|
||||
// Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[]
|
||||
export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] {
|
||||
@ -27,16 +27,19 @@ export const getMatcherQueryParams = (labels: Labels) => {
|
||||
return matcherUrlParams;
|
||||
};
|
||||
|
||||
interface MatchedRule {
|
||||
interface MatchedInstance {
|
||||
id: string;
|
||||
data: {
|
||||
matchedRule: CombinedRule;
|
||||
matchedInstance: Alert;
|
||||
};
|
||||
}
|
||||
|
||||
export const findAlertRulesWithMatchers = (rules: CombinedRule[], matchers: MatcherFieldValue[]): MatchedRule[] => {
|
||||
const hasMatcher = (rule: CombinedRule, matcher: MatcherFieldValue) => {
|
||||
return Object.entries(rule.labels).some(([key, value]) => {
|
||||
export const findAlertInstancesWithMatchers = (
|
||||
instances: Alert[],
|
||||
matchers: MatcherFieldValue[]
|
||||
): MatchedInstance[] => {
|
||||
const hasMatcher = (instance: Alert, matcher: MatcherFieldValue) => {
|
||||
return Object.entries(instance.labels).some(([key, value]) => {
|
||||
if (!matcher.name || !matcher.value) {
|
||||
return false;
|
||||
}
|
||||
@ -56,13 +59,13 @@ export const findAlertRulesWithMatchers = (rules: CombinedRule[], matchers: Matc
|
||||
});
|
||||
};
|
||||
|
||||
const filteredRules = rules.filter((rule) => {
|
||||
return matchers.every((matcher) => hasMatcher(rule, matcher));
|
||||
const filteredInstances = instances.filter((instance) => {
|
||||
return matchers.every((matcher) => hasMatcher(instance, matcher));
|
||||
});
|
||||
const mappedRules = filteredRules.map((rule) => ({
|
||||
id: `${rule.namespace}-${rule.name}`,
|
||||
data: { matchedRule: rule },
|
||||
const mappedInstances = filteredInstances.map((instance) => ({
|
||||
id: `${instance.activeAt}-${instance.value}`,
|
||||
data: { matchedInstance: instance },
|
||||
}));
|
||||
|
||||
return mappedRules;
|
||||
return mappedInstances;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user