Alerting: Fix filtering when panel variables are in use (#66977)

Fix alert instances filtering when panel variables are in use
pull/67039/head
Konrad Lalik 2 years ago committed by GitHub
parent f3dbb7b34a
commit 9f1fe51edc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 21
      public/app/plugins/panel/alertlist/UnifiedAlertList.tsx
  2. 78
      public/app/plugins/panel/alertlist/UnifiedalertList.test.tsx
  3. 14
      public/app/plugins/panel/alertlist/util.ts

@ -106,6 +106,13 @@ export function UnifiedAlertList(props: PanelProps<UnifiedAlertListOptions>) {
);
}
const { options, replaceVariables } = props;
const parsedOptions: UnifiedAlertListOptions = {
...props.options,
alertName: replaceVariables(options.alertName),
alertInstanceLabelFilter: replaceVariables(options.alertInstanceLabelFilter),
};
return (
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
<div className={styles.container}>
@ -124,10 +131,10 @@ export function UnifiedAlertList(props: PanelProps<UnifiedAlertListOptions>) {
/>
)}
{props.options.viewMode === ViewMode.List && props.options.groupMode === GroupMode.Custom && haveResults && (
<GroupedModeView rules={rules} options={props.options} />
<GroupedModeView rules={rules} options={parsedOptions} />
)}
{props.options.viewMode === ViewMode.List && props.options.groupMode === GroupMode.Default && haveResults && (
<UngroupedModeView rules={rules} options={props.options} />
<UngroupedModeView rules={rules} options={parsedOptions} />
)}
</section>
</div>
@ -209,7 +216,15 @@ function filterRules(props: PanelProps<UnifiedAlertListOptions>, rules: Combined
// when we display a rule with 0 instances
filteredRules = filteredRules.reduce<CombinedRuleWithLocation[]>((rules, rule) => {
const alertingRule = getAlertingRule(rule);
const filteredAlerts = alertingRule ? filterAlerts(options, alertingRule.alerts ?? []) : [];
const filteredAlerts = alertingRule
? filterAlerts(
{
stateFilter: options.stateFilter,
alertInstanceLabelFilter: replaceVariables(options.alertInstanceLabelFilter),
},
alertingRule.alerts ?? []
)
: [];
if (filteredAlerts.length) {
// We intentionally don't set alerts to filteredAlerts
// because later we couldn't display that some alerts are hidden (ref AlertInstances filtering)

@ -1,14 +1,26 @@
import { render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { byRole, byText } from 'testing-library-selector';
import { getDefaultTimeRange, LoadingState, PanelProps, FieldConfigSource } from '@grafana/data';
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
import { DashboardSrv, setDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { configureStore } from 'app/store/configureStore';
import { contextSrv } from '../../../core/services/context_srv';
import {
mockPromAlert,
mockPromAlertingRule,
mockPromRuleGroup,
mockPromRuleNamespace,
mockUnifiedAlertingStore,
} from '../../../features/alerting/unified/mocks';
import { GRAFANA_RULES_SOURCE_NAME } from '../../../features/alerting/unified/utils/datasource';
import { UnifiedAlertList } from './UnifiedAlertList';
import { UnifiedAlertListOptions, SortOrder, GroupMode, ViewMode } from './types';
import * as utils from './util';
jest.mock('app/features/alerting/unified/api/alertmanager');
@ -60,14 +72,36 @@ const dashboard = {
},
};
const renderPanel = (options: UnifiedAlertListOptions = defaultOptions) => {
const store = configureStore();
const renderPanel = (options: Partial<UnifiedAlertListOptions> = defaultOptions) => {
const store = mockUnifiedAlertingStore({
promRules: {
grafana: {
loading: false,
dispatched: true,
result: [
mockPromRuleNamespace({
name: 'ns1',
groups: [
mockPromRuleGroup({
name: 'group1',
rules: [
mockPromAlertingRule({
name: 'rule1',
alerts: [mockPromAlert({ labels: { severity: 'critical' } })],
}),
],
}),
],
}),
],
},
},
});
const dashSrv: unknown = { getCurrent: () => dashboard };
setDashboardSrv(dashSrv as DashboardSrv);
defaultProps.options = options;
const props = { ...defaultProps };
const props = { ...defaultProps, options: { ...defaultOptions, ...options } };
return render(
<Provider store={store}>
@ -82,4 +116,38 @@ describe('UnifiedAlertList', () => {
expect(dashboard.events.subscribe).toHaveBeenCalledTimes(1);
expect(dashboard.events.subscribe.mock.calls[0][0]).toEqual(TimeRangeUpdatedEvent);
});
it('should replace option variables before filtering', async () => {
jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
const filterAlertsSpy = jest.spyOn(utils, 'filterAlerts');
const replaceVarsSpy = jest.spyOn(defaultProps, 'replaceVariables').mockReturnValue('severity=critical');
const user = userEvent.setup();
renderPanel({
alertInstanceLabelFilter: '$label',
dashboardAlerts: false,
alertName: '',
datasource: GRAFANA_RULES_SOURCE_NAME,
folder: undefined,
});
expect(byText('rule1').get()).toBeInTheDocument();
const expandElement = byText('1 instance').get();
await user.click(expandElement);
const tagsElement = await byRole('list', { name: 'Tags' }).find();
expect(await byRole('listitem').find(tagsElement)).toHaveTextContent('severity=critical');
expect(replaceVarsSpy).toHaveBeenLastCalledWith('$label');
expect(filterAlertsSpy).toHaveBeenLastCalledWith(
expect.objectContaining({
alertInstanceLabelFilter: 'severity=critical',
}),
expect.anything()
);
});
});

@ -1,20 +1,21 @@
import { isEmpty } from 'lodash';
import { Labels, PanelProps } from '@grafana/data';
import { Labels } 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);
const matchers = parseMatchers(alertInstanceLabelFilter);
return labelsMatchMatchers(labels, matchers);
}
export function filterAlerts(options: PanelProps<UnifiedAlertListOptions>['options'], alerts: Alert[]): Alert[] {
export function filterAlerts(
options: Pick<UnifiedAlertListOptions, 'stateFilter' | 'alertInstanceLabelFilter'>,
alerts: Alert[]
): Alert[] {
const { stateFilter, alertInstanceLabelFilter } = options;
if (isEmpty(stateFilter)) {
@ -31,8 +32,7 @@ export function filterAlerts(options: PanelProps<UnifiedAlertListOptions>['optio
(stateFilter.normal && hasAlertState(alert, GrafanaAlertState.Normal)) ||
(stateFilter.error && hasAlertState(alert, GrafanaAlertState.Error)) ||
(stateFilter.inactive && hasAlertState(alert, PromAlertingRuleState.Inactive))) &&
((alertInstanceLabelFilter && hasLabelFilter(options.alertInstanceLabelFilter, alert.labels)) ||
!alertInstanceLabelFilter)
(alertInstanceLabelFilter ? hasLabelFilter(options.alertInstanceLabelFilter, alert.labels) : true)
);
});
}

Loading…
Cancel
Save