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