Alerting: Add label filters to the logic of showing hidden instances (#65674)

* Add label filters to the logic of showing hidden instances

Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>

* Add tests

Co-Authored-By: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com>

---------

Co-authored-by: Virginia Cepeda <virginia.cepeda@grafana.com>
This commit is contained in:
Sonia Aguilar 2023-04-03 15:39:06 +02:00 committed by GitHub
parent 96453c6e69
commit 008bf143ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 97 additions and 26 deletions

View File

@ -35,6 +35,7 @@ import {
} from 'app/types/unified-alerting';
import {
AlertQuery,
GrafanaAlertState,
GrafanaAlertStateDecision,
GrafanaRuleDefinition,
PromAlertingRuleState,
@ -630,3 +631,7 @@ export function getCloudRule(override?: Partial<CombinedRule>) {
...override,
});
}
export function mockAlertWithState(state: GrafanaAlertState, labels?: {}): Alert {
return { activeAt: '', annotations: {}, labels: labels || {}, state: state, value: '' };
}

View File

@ -21,7 +21,6 @@ import alertDef from 'app/features/alerting/state/alertDef';
import { useCombinedRuleNamespaces } from 'app/features/alerting/unified/hooks/useCombinedRuleNamespaces';
import { useUnifiedAlertingSelector } from 'app/features/alerting/unified/hooks/useUnifiedAlertingSelector';
import { fetchAllPromAndRulerRulesAction } from 'app/features/alerting/unified/state/actions';
import { labelsMatchMatchers, parseMatchers } from 'app/features/alerting/unified/utils/alertmanager';
import { Annotation } from 'app/features/alerting/unified/utils/constants';
import {
getAllRulesSourceNames,
@ -190,22 +189,6 @@ function filterRules(props: PanelProps<UnifiedAlertListOptions>, rules: Combined
);
});
if (options.alertInstanceLabelFilter) {
const replacedLabelFilter = replaceVariables(options.alertInstanceLabelFilter);
const matchers = parseMatchers(replacedLabelFilter);
// Reduce rules and instances to only those that match
filteredRules = filteredRules.reduce<CombinedRuleWithLocation[]>((rules, rule) => {
const alertingRule = getAlertingRule(rule);
const filteredAlerts = (alertingRule?.alerts ?? []).filter(({ labels }) => labelsMatchMatchers(labels, matchers));
if (filteredAlerts.length) {
const alertRule: AlertingRule | null = getAlertingRule(rule);
alertRule && rules.push({ ...rule, promRule: { ...alertRule, alerts: filteredAlerts } });
}
return rules;
}, []);
}
if (options.folder) {
filteredRules = filteredRules.filter((rule) => {
return rule.namespaceName === options.folder.title;

View File

@ -0,0 +1,73 @@
import { mockAlertWithState as withState } from 'app/features/alerting/unified/mocks';
import { Alert } from 'app/types/unified-alerting';
import { GrafanaAlertState } from 'app/types/unified-alerting-dto';
import { GroupMode, SortOrder, UnifiedAlertListOptions, ViewMode } from './types';
import { filterAlerts } from './util';
const defaultOption: UnifiedAlertListOptions = {
maxItems: 2,
sortOrder: SortOrder.AlphaAsc,
dashboardAlerts: true,
groupMode: GroupMode.Default,
groupBy: [''],
alertName: 'test',
showInstances: false,
folder: { id: 1, title: 'test folder' },
stateFilter: { firing: true, pending: true, noData: true, normal: true, error: true },
alertInstanceLabelFilter: '',
datasource: 'Alertmanager',
viewMode: ViewMode.List,
};
const alerts: Alert[] = [
withState(GrafanaAlertState.Pending, { severity: 'critical' }),
withState(GrafanaAlertState.Error, { severity: 'low' }),
withState(GrafanaAlertState.Error, { region: 'asia' }),
withState(GrafanaAlertState.Normal),
];
describe('filterAlerts', () => {
it('Returns all alert instances when there are no filters', () => {
const result = filterAlerts(defaultOption, alerts);
expect(result.length).toBe(4);
});
it('Filters by alert instance state ', () => {
const noNormalStateOptions = {
...defaultOption,
...{ stateFilter: { firing: true, pending: true, noData: true, normal: false, error: true } },
};
expect(filterAlerts(noNormalStateOptions, alerts).length).toBe(3);
const noErrorOrNormalStateOptions = {
...defaultOption,
...{ stateFilter: { firing: true, pending: true, noData: true, normal: false, error: false } },
};
expect(filterAlerts(noErrorOrNormalStateOptions, alerts).length).toBe(1);
});
it('Filters by alert instance label', () => {
const options = {
...defaultOption,
...{ alertInstanceLabelFilter: '{severity=low}' },
};
const result = filterAlerts(options, alerts);
expect(result.length).toBe(1);
});
it('Filters by alert instance state and label', () => {
const options = {
...defaultOption,
...{ stateFilter: { firing: false, pending: false, noData: false, normal: false, error: true } },
...{ alertInstanceLabelFilter: '{severity=low}' },
};
const result = filterAlerts(options, alerts);
expect(result.length).toBe(1);
});
});

View File

@ -1,13 +1,21 @@
import { isEmpty } from 'lodash';
import { PanelProps } from '@grafana/data';
import { Labels, PanelProps } from '@grafana/data';
import { labelsMatchMatchers, parseMatchers } from 'app/features/alerting/unified/utils/alertmanager';
import { replaceVariables } from 'app/plugins/datasource/prometheus/querybuilder/shared/parsingUtils';
import { Alert, hasAlertState } from 'app/types/unified-alerting';
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { UnifiedAlertListOptions } from './types';
function hasLabelFilter(alertInstanceLabelFilter: string, labels: Labels) {
const replacedLabelFilter = replaceVariables(alertInstanceLabelFilter);
const matchers = parseMatchers(replacedLabelFilter);
return labelsMatchMatchers(labels, matchers);
}
export function filterAlerts(options: PanelProps<UnifiedAlertListOptions>['options'], alerts: Alert[]): Alert[] {
const { stateFilter } = options;
const { stateFilter, alertInstanceLabelFilter } = options;
if (isEmpty(stateFilter)) {
return alerts;
@ -15,14 +23,16 @@ export function filterAlerts(options: PanelProps<UnifiedAlertListOptions>['optio
return alerts.filter((alert) => {
return (
(stateFilter.firing &&
((stateFilter.firing &&
(hasAlertState(alert, GrafanaAlertState.Alerting) || hasAlertState(alert, PromAlertingRuleState.Firing))) ||
(stateFilter.pending &&
(hasAlertState(alert, GrafanaAlertState.Pending) || hasAlertState(alert, PromAlertingRuleState.Pending))) ||
(stateFilter.noData && hasAlertState(alert, GrafanaAlertState.NoData)) ||
(stateFilter.normal && hasAlertState(alert, GrafanaAlertState.Normal)) ||
(stateFilter.error && hasAlertState(alert, GrafanaAlertState.Error)) ||
(stateFilter.inactive && hasAlertState(alert, PromAlertingRuleState.Inactive))
(stateFilter.inactive && hasAlertState(alert, PromAlertingRuleState.Inactive))) &&
((alertInstanceLabelFilter && hasLabelFilter(options.alertInstanceLabelFilter, alert.labels)) ||
!alertInstanceLabelFilter)
);
});
}