mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Make regex notification routing preview consistent with notification policies implementation (#88413)
Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
parent
2a65a8a1d1
commit
3adb07cf4d
@ -1,12 +1,10 @@
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor, within, userEvent } from 'test/test-utils';
|
||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
|
||||
import 'core-js/stable/structured-clone';
|
||||
import { TestProvider } from '../../../../../../../test/helpers/TestProvider';
|
||||
import { MatcherOperator } from '../../../../../../plugins/datasource/alertmanager/types';
|
||||
import { Labels } from '../../../../../../types/unified-alerting-dto';
|
||||
import { mockApi, setupMswServer } from '../../../mockApi';
|
||||
@ -140,9 +138,7 @@ describe('NotificationPreview', () => {
|
||||
mockOneAlertManager();
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />, {
|
||||
wrapper: TestProvider,
|
||||
});
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
|
||||
await userEvent.click(ui.previewButton.get());
|
||||
await waitFor(() => {
|
||||
@ -166,9 +162,7 @@ describe('NotificationPreview', () => {
|
||||
mockTwoAlertManagers();
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />, {
|
||||
wrapper: TestProvider,
|
||||
});
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
@ -195,9 +189,7 @@ describe('NotificationPreview', () => {
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
mockHasEditPermission(true);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />, {
|
||||
wrapper: TestProvider,
|
||||
});
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
@ -219,9 +211,7 @@ describe('NotificationPreview', () => {
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
mockHasEditPermission(false);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />, {
|
||||
wrapper: TestProvider,
|
||||
});
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
@ -266,8 +256,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>,
|
||||
{ wrapper: TestProvider }
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@ -321,8 +310,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>,
|
||||
{ wrapper: TestProvider }
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@ -376,8 +364,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>,
|
||||
{ wrapper: TestProvider }
|
||||
/>
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
@ -402,4 +389,80 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
expect(matchingInstances1).toHaveTextContent(/job=prometheus/);
|
||||
expect(matchingInstances1).toHaveTextContent(/severity=warning/);
|
||||
});
|
||||
|
||||
describe('regex matching', () => {
|
||||
it('does not match regex in middle of the word as alertmanager will anchor when queried via API', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'foobarfoo' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
.withReceiver('email')
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.regex, 'bar'))
|
||||
)
|
||||
);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(await screen.findByText(/default policy/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/regexfield/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('matches regex at the start of the word', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'baaaaaaah' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
.withReceiver('email')
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.regex, 'ba.*h'))
|
||||
)
|
||||
);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(await screen.findByText(/regexfield/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles negated regex correctly', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'thing' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
.withReceiver('email')
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.notRegex, 'thing'))
|
||||
)
|
||||
);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
onlyOneAM={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(await screen.findByText(/default policy/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/regexfield/i)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -68,8 +68,7 @@ function NotificationRouteHeader({
|
||||
<Spacer />
|
||||
<Stack gap={2} direction="row" alignItems="center">
|
||||
<MetaText icon="layers-alt" data-testid="matching-instances">
|
||||
{instancesCount ?? '-'}
|
||||
<span>{pluralize('instance', instancesCount)}</span>
|
||||
{instancesCount ?? '-'} {pluralize('instance', instancesCount)}
|
||||
</MetaText>
|
||||
<Stack gap={1} direction="row" alignItems="center">
|
||||
<div>
|
||||
|
@ -476,6 +476,17 @@ describe('matchLabels', () => {
|
||||
expect(result).toHaveProperty('matches', false);
|
||||
expect(result.labelsMatch).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('does not match unanchored regular expressions', () => {
|
||||
const result = matchLabels([['foo', MatcherOperator.regex, 'bar']], [['foo', 'barbarbar']]);
|
||||
// This may seem unintuitive, but this is how Alertmanager matches, as it anchors the regex
|
||||
expect(result.matches).toEqual(false);
|
||||
});
|
||||
|
||||
it('matches regular expressions with wildcards', () => {
|
||||
const result = matchLabels([['foo', MatcherOperator.regex, '.*bar.*']], [['foo', 'barbarbar']]);
|
||||
expect(result.matches).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('unquoteRouteMatchers', () => {
|
||||
|
@ -232,8 +232,17 @@ type OperatorPredicate = (labelValue: string, matcherValue: string) => boolean;
|
||||
const OperatorFunctions: Record<MatcherOperator, OperatorPredicate> = {
|
||||
[MatcherOperator.equal]: (lv, mv) => lv === mv,
|
||||
[MatcherOperator.notEqual]: (lv, mv) => lv !== mv,
|
||||
[MatcherOperator.regex]: (lv, mv) => new RegExp(mv).test(lv),
|
||||
[MatcherOperator.notRegex]: (lv, mv) => !new RegExp(mv).test(lv),
|
||||
// At the time of writing, Alertmanager compiles to another (anchored) Regular Expression,
|
||||
// so we should also anchor our UI matches for consistency with this behaviour
|
||||
// https://github.com/prometheus/alertmanager/blob/fd37ce9c95898ca68be1ab4d4529517174b73c33/pkg/labels/matcher.go#L69
|
||||
[MatcherOperator.regex]: (lv, mv) => {
|
||||
const re = new RegExp(`^(?:${mv})$`);
|
||||
return re.test(lv);
|
||||
},
|
||||
[MatcherOperator.notRegex]: (lv, mv) => {
|
||||
const re = new RegExp(`^(?:${mv})$`);
|
||||
return !re.test(lv);
|
||||
},
|
||||
};
|
||||
|
||||
function isLabelMatchInSet(matcher: ObjectMatcher, labels: Label[]): boolean {
|
||||
|
Loading…
Reference in New Issue
Block a user