[v9.5.x] Alerting: Fix filtering when panel variables are in use (#67038)
Alerting: Fix filtering when panel variables are in use (#66977)
Fix alert instances filtering when panel variables are in use
(cherry picked from commit 9f1fe51edc)
Co-authored-by: Konrad Lalik <konrad.lalik@grafana.com>
This commit is contained in:
co-authored by
Konrad Lalik
parent
900c18ef5b
commit
38fdfd6bfc
@@ -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 (
|
return (
|
||||||
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
<CustomScrollbar autoHeightMin="100%" autoHeightMax="100%">
|
||||||
<div className={styles.container}>
|
<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 && (
|
{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 && (
|
{props.options.viewMode === ViewMode.List && props.options.groupMode === GroupMode.Default && haveResults && (
|
||||||
<UngroupedModeView rules={rules} options={props.options} />
|
<UngroupedModeView rules={rules} options={parsedOptions} />
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -209,7 +216,15 @@ function filterRules(props: PanelProps<UnifiedAlertListOptions>, rules: Combined
|
|||||||
// when we display a rule with 0 instances
|
// when we display a rule with 0 instances
|
||||||
filteredRules = filteredRules.reduce<CombinedRuleWithLocation[]>((rules, rule) => {
|
filteredRules = filteredRules.reduce<CombinedRuleWithLocation[]>((rules, rule) => {
|
||||||
const alertingRule = getAlertingRule(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) {
|
if (filteredAlerts.length) {
|
||||||
// We intentionally don't set alerts to filteredAlerts
|
// We intentionally don't set alerts to filteredAlerts
|
||||||
// because later we couldn't display that some alerts are hidden (ref AlertInstances filtering)
|
// 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 { render } from '@testing-library/react';
|
||||||
|
import userEvent from '@testing-library/user-event';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
import { byRole, byText } from 'testing-library-selector';
|
||||||
|
|
||||||
import { getDefaultTimeRange, LoadingState, PanelProps, FieldConfigSource } from '@grafana/data';
|
import { getDefaultTimeRange, LoadingState, PanelProps, FieldConfigSource } from '@grafana/data';
|
||||||
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
|
import { TimeRangeUpdatedEvent } from '@grafana/runtime';
|
||||||
import { DashboardSrv, setDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
|
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 { UnifiedAlertList } from './UnifiedAlertList';
|
||||||
import { UnifiedAlertListOptions, SortOrder, GroupMode, ViewMode } from './types';
|
import { UnifiedAlertListOptions, SortOrder, GroupMode, ViewMode } from './types';
|
||||||
|
import * as utils from './util';
|
||||||
|
|
||||||
jest.mock('app/features/alerting/unified/api/alertmanager');
|
jest.mock('app/features/alerting/unified/api/alertmanager');
|
||||||
|
|
||||||
@@ -60,14 +72,36 @@ const dashboard = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderPanel = (options: UnifiedAlertListOptions = defaultOptions) => {
|
const renderPanel = (options: Partial<UnifiedAlertListOptions> = defaultOptions) => {
|
||||||
const store = configureStore();
|
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 };
|
const dashSrv: unknown = { getCurrent: () => dashboard };
|
||||||
setDashboardSrv(dashSrv as DashboardSrv);
|
setDashboardSrv(dashSrv as DashboardSrv);
|
||||||
|
|
||||||
defaultProps.options = options;
|
const props = { ...defaultProps, options: { ...defaultOptions, ...options } };
|
||||||
const props = { ...defaultProps };
|
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@@ -82,4 +116,38 @@ describe('UnifiedAlertList', () => {
|
|||||||
expect(dashboard.events.subscribe).toHaveBeenCalledTimes(1);
|
expect(dashboard.events.subscribe).toHaveBeenCalledTimes(1);
|
||||||
expect(dashboard.events.subscribe.mock.calls[0][0]).toEqual(TimeRangeUpdatedEvent);
|
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 { 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 { 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 { Alert, hasAlertState } from 'app/types/unified-alerting';
|
||||||
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { UnifiedAlertListOptions } from './types';
|
import { UnifiedAlertListOptions } from './types';
|
||||||
|
|
||||||
function hasLabelFilter(alertInstanceLabelFilter: string, labels: Labels) {
|
function hasLabelFilter(alertInstanceLabelFilter: string, labels: Labels) {
|
||||||
const replacedLabelFilter = replaceVariables(alertInstanceLabelFilter);
|
const matchers = parseMatchers(alertInstanceLabelFilter);
|
||||||
const matchers = parseMatchers(replacedLabelFilter);
|
|
||||||
return labelsMatchMatchers(labels, matchers);
|
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;
|
const { stateFilter, alertInstanceLabelFilter } = options;
|
||||||
|
|
||||||
if (isEmpty(stateFilter)) {
|
if (isEmpty(stateFilter)) {
|
||||||
@@ -31,8 +32,7 @@ export function filterAlerts(options: PanelProps<UnifiedAlertListOptions>['optio
|
|||||||
(stateFilter.normal && hasAlertState(alert, GrafanaAlertState.Normal)) ||
|
(stateFilter.normal && hasAlertState(alert, GrafanaAlertState.Normal)) ||
|
||||||
(stateFilter.error && hasAlertState(alert, GrafanaAlertState.Error)) ||
|
(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 ? hasLabelFilter(options.alertInstanceLabelFilter, alert.labels) : true)
|
||||||
!alertInstanceLabelFilter)
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user