mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Make alert rule policies preview use k8s API (#97070)
* Add translations for notification preview * Make notifications endpoints use alertmanager config mock entity * Fix translations and error handling in preview component * Update preview hook to use new k8s APIs * Move receivers k8s mock logic so it always comes from the mock config * Fix test that wasn't using the correct receiver * Fix object_matchers * Remove mockApi method and update tests * Update translation for error case * Remove useMemo
This commit is contained in:
parent
1c60d51905
commit
a2c407854f
@ -1443,16 +1443,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
|
@ -240,7 +240,7 @@ describe.each([
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, {
|
||||
alertmanager_config: {
|
||||
route: {},
|
||||
receivers: [{ name: 'grafana-default-email' }],
|
||||
receivers: [{ name: 'lotsa-emails' }],
|
||||
},
|
||||
template_files: {},
|
||||
});
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { render, screen, userEvent, waitFor, within } from 'test/test-utils';
|
||||
import { render, screen, waitFor, within } from 'test/test-utils';
|
||||
import { byRole, byTestId, byText } from 'testing-library-selector';
|
||||
|
||||
import { setAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
||||
import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils';
|
||||
import { AccessControlAction } from 'app/types/accessControl';
|
||||
|
||||
import { MatcherOperator } from '../../../../../../plugins/datasource/alertmanager/types';
|
||||
import { Labels } from '../../../../../../types/unified-alerting-dto';
|
||||
import { mockApi, setupMswServer } from '../../../mockApi';
|
||||
import { getMockConfig, setupMswServer } from '../../../mockApi';
|
||||
import { grantUserPermissions, mockAlertQuery } from '../../../mocks';
|
||||
import { mockPreviewApiResponse } from '../../../mocks/grafanaRulerApi';
|
||||
import { Folder } from '../../../types/rule-form';
|
||||
@ -36,7 +38,7 @@ const ui = {
|
||||
route: byTestId('matching-policy-route'),
|
||||
routeButton: byRole('button', { name: /Expand policy route/ }),
|
||||
routeMatchingInstances: byTestId('route-matching-instance'),
|
||||
loadingIndicator: byText(/Loading/),
|
||||
loadingIndicator: byText(/Loading routing preview/i),
|
||||
previewButton: byRole('button', { name: /preview routing/i }),
|
||||
grafanaAlertManagerLabel: byText(/alertmanager:grafana/i),
|
||||
otherAlertManagerLabel: byText(/alertmanager:other_am/i),
|
||||
@ -62,9 +64,7 @@ const grafanaAlertManagerDataSource: AlertManagerDataSource = {
|
||||
hasConfigurationAPI: true,
|
||||
};
|
||||
|
||||
function mockOneAlertManager() {
|
||||
getAlertManagerDataSourcesByPermissionAndConfigMock.mockReturnValue([grafanaAlertManagerDataSource]);
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
@ -75,39 +75,22 @@ function mockOneAlertManager() {
|
||||
.addReceivers((b) => b.withName('email').addEmailConfig((eb) => eb.withTo('test@example.com')))
|
||||
.addReceivers((b) => b.withName('slack'))
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
);
|
||||
|
||||
function mockOneAlertManager() {
|
||||
getAlertManagerDataSourcesByPermissionAndConfigMock.mockReturnValue([grafanaAlertManagerDataSource]);
|
||||
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
}
|
||||
|
||||
function mockTwoAlertManagers() {
|
||||
getAlertManagerDataSourcesByPermissionAndConfigMock.mockReturnValue([
|
||||
{ name: 'OTHER_AM', imgUrl: '', hasConfigurationAPI: true },
|
||||
grafanaAlertManagerDataSource,
|
||||
{ name: 'OTHER_AM', imgUrl: '', hasConfigurationAPI: true },
|
||||
]);
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
.withReceiver('email')
|
||||
.addRoute((rb) => rb.withReceiver('slack').addMatcher('tomato', MatcherOperator.equal, 'red'))
|
||||
.addRoute((rb) => rb.withReceiver('opsgenie').addMatcher('team', MatcherOperator.equal, 'operations'))
|
||||
)
|
||||
.addReceivers((b) => b.withName('email').addEmailConfig((eb) => eb.withTo('test@example.com')))
|
||||
.addReceivers((b) => b.withName('slack'))
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
mockApi(server).getAlertmanagerConfig('OTHER_AM', (amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
.withReceiver('email')
|
||||
.addRoute((rb) => rb.withReceiver('slack').addMatcher('tomato', MatcherOperator.equal, 'red'))
|
||||
.addRoute((rb) => rb.withReceiver('opsgenie').addMatcher('team', MatcherOperator.equal, 'operations'))
|
||||
)
|
||||
.addReceivers((b) => b.withName('email').addEmailConfig((eb) => eb.withTo('test@example.com')))
|
||||
.addReceivers((b) => b.withName('slack'))
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
setAlertmanagerConfig('OTHER_AM', mockConfig);
|
||||
}
|
||||
|
||||
function mockHasEditPermission(enabled: boolean) {
|
||||
@ -131,14 +114,22 @@ const folder: Folder = {
|
||||
title: 'title',
|
||||
};
|
||||
|
||||
describe('NotificationPreview', () => {
|
||||
describe.each([
|
||||
// k8s API enabled
|
||||
true,
|
||||
// k8s API disabled
|
||||
false,
|
||||
])('NotificationPreview with alertingApiServer=%p', (apiServerEnabled) => {
|
||||
apiServerEnabled ? testWithFeatureToggles(['alertingApiServer']) : testWithFeatureToggles([]);
|
||||
it('should render notification preview without alert manager label, when having only one alert manager configured to receive alerts', async () => {
|
||||
mockOneAlertManager();
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
const { user } = render(
|
||||
<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />
|
||||
);
|
||||
|
||||
await userEvent.click(ui.previewButton.get());
|
||||
await user.click(ui.previewButton.get());
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
@ -160,23 +151,18 @@ describe('NotificationPreview', () => {
|
||||
mockTwoAlertManagers();
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
const { user } = render(
|
||||
<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />
|
||||
);
|
||||
|
||||
await userEvent.click(ui.previewButton.get());
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
await user.click(await ui.previewButton.find());
|
||||
|
||||
// we expect the alert manager label to be present as there is more than one alert manager configured to receive alerts
|
||||
await waitFor(() => {
|
||||
expect(ui.grafanaAlertManagerLabel.query()).toBeInTheDocument();
|
||||
});
|
||||
expect(ui.otherAlertManagerLabel.query()).toBeInTheDocument();
|
||||
expect(await ui.grafanaAlertManagerLabel.find()).toBeInTheDocument();
|
||||
expect(await ui.otherAlertManagerLabel.find()).toBeInTheDocument();
|
||||
|
||||
const matchingPoliciesElements = await ui.route.findAll();
|
||||
|
||||
const matchingPoliciesElements = ui.route.queryAll();
|
||||
expect(matchingPoliciesElements).toHaveLength(2);
|
||||
expect(matchingPoliciesElements[0]).toHaveTextContent(/tomato = red/);
|
||||
expect(matchingPoliciesElements[1]).toHaveTextContent(/tomato = red/);
|
||||
@ -187,13 +173,15 @@ describe('NotificationPreview', () => {
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
mockHasEditPermission(true);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
const { user } = render(
|
||||
<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(ui.previewButton.get());
|
||||
await userEvent.click(await ui.seeDetails.find());
|
||||
await user.click(ui.previewButton.get());
|
||||
await user.click(await ui.seeDetails.find());
|
||||
expect(ui.details.title.query()).toBeInTheDocument();
|
||||
//we expect seeing the default policy
|
||||
expect(screen.getByText(/default policy/i)).toBeInTheDocument();
|
||||
@ -209,13 +197,15 @@ describe('NotificationPreview', () => {
|
||||
mockPreviewApiResponse(server, [{ labels: [{ tomato: 'red', avocate: 'green' }] }]);
|
||||
mockHasEditPermission(false);
|
||||
|
||||
render(<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />);
|
||||
const { user } = render(
|
||||
<NotificationPreview alertQueries={[alertQuery]} customLabels={[]} condition="A" folder={folder} />
|
||||
);
|
||||
await waitFor(() => {
|
||||
expect(ui.loadingIndicator.query()).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(ui.previewButton.get());
|
||||
await userEvent.click(await ui.seeDetails.find());
|
||||
await user.click(ui.previewButton.get());
|
||||
await user.click(await ui.seeDetails.find());
|
||||
expect(ui.details.title.query()).toBeInTheDocument();
|
||||
//we expect seeing the default policy
|
||||
expect(screen.getByText(/default policy/i)).toBeInTheDocument();
|
||||
@ -234,7 +224,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
{ job: 'prometheus', severity: 'warning' },
|
||||
];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
@ -246,10 +236,9 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addReceivers((b) => b.withName('slack'))
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
const { user } = render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
@ -285,7 +274,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
{ job: 'prometheus', severity: 'warning' },
|
||||
];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
@ -300,10 +289,9 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addReceivers((b) => b.withName('slack'))
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
const user = userEvent.setup();
|
||||
|
||||
render(
|
||||
const { user } = render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
@ -339,7 +327,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
{ job: 'prometheus', severity: 'warning' },
|
||||
];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.withRoute((routeBuilder) =>
|
||||
routeBuilder
|
||||
@ -355,9 +343,9 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addReceivers((b) => b.withName('opsgenie'))
|
||||
);
|
||||
|
||||
const user = userEvent.setup();
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
render(
|
||||
const { user } = render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
potentialInstances={potentialInstances}
|
||||
@ -392,7 +380,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
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) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
@ -402,6 +390,8 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
)
|
||||
);
|
||||
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
alertManagerSource={grafanaAlertManagerDataSource}
|
||||
@ -417,7 +407,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
it('matches regex at the start of the word', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'baaaaaaah' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
@ -426,6 +416,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.regex, 'ba.*h'))
|
||||
)
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
@ -441,7 +432,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
it('handles negated regex correctly', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'thing' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
@ -450,6 +441,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.notRegex, 'thing'))
|
||||
)
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
@ -466,7 +458,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
it('matches regex with flags', async () => {
|
||||
const potentialInstances: Labels[] = [{ regexfield: 'baaaaaaah' }];
|
||||
|
||||
mockApi(server).getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, (amConfigBuilder) =>
|
||||
const mockConfig = getMockConfig((amConfigBuilder) =>
|
||||
amConfigBuilder
|
||||
.addReceivers((b) => b.withName('email'))
|
||||
.withRoute((routeBuilder) =>
|
||||
@ -475,6 +467,7 @@ describe('NotificationPreviewByAlertmanager', () => {
|
||||
.addRoute((rb) => rb.withReceiver('email').addMatcher('regexfield', MatcherOperator.regex, '(?i)BA.*h'))
|
||||
)
|
||||
);
|
||||
setAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME, mockConfig);
|
||||
|
||||
render(
|
||||
<NotificationPreviewByAlertManager
|
||||
|
@ -2,6 +2,7 @@ import { compact } from 'lodash';
|
||||
import { lazy, Suspense } from 'react';
|
||||
|
||||
import { Button, LoadingPlaceholder, Stack, Text } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { alertRuleApi } from 'app/features/alerting/unified/api/alertRuleApi';
|
||||
import { AlertQuery } from 'app/types/unified-alerting-dto';
|
||||
|
||||
@ -64,26 +65,32 @@ export const NotificationPreview = ({
|
||||
<Stack direction="column">
|
||||
<Stack direction="row" alignItems="flex-start" justifyContent="space-between">
|
||||
<Stack direction="column" gap={1}>
|
||||
<Text element="h5">Alert instance routing preview</Text>
|
||||
<Text element="h5">
|
||||
<Trans i18nKey="alerting.notification-preview.title">Alert instance routing preview</Trans>
|
||||
</Text>
|
||||
{isLoading && previewUninitialized && (
|
||||
<Text color="secondary" variant="bodySmall">
|
||||
Loading...
|
||||
<Trans i18nKey="alerting.common.loading">Loading...</Trans>
|
||||
</Text>
|
||||
)}
|
||||
{previewUninitialized ? (
|
||||
<Text color="secondary" variant="bodySmall">
|
||||
<Trans i18nKey="alerting.notification-preview.uninitialized">
|
||||
When you have your folder selected and your query and labels are configured, click "Preview
|
||||
routing" to see the results here.
|
||||
</Trans>
|
||||
</Text>
|
||||
) : (
|
||||
<Text color="secondary" variant="bodySmall">
|
||||
Based on the labels added, alert instances are routed to the following notification policies. Expand each
|
||||
notification policy below to view more details.
|
||||
<Trans i18nKey="alerting.notification-preview.initialized">
|
||||
Based on the labels added, alert instances are routed to the following notification policies. Expand
|
||||
each notification policy below to view more details.
|
||||
</Trans>
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
<Button icon="sync" variant="secondary" type="button" onClick={onPreview} disabled={disabled}>
|
||||
Preview routing
|
||||
<Trans i18nKey="alerting.notification-preview.preview-routing">Preview routing</Trans>
|
||||
</Button>
|
||||
</Stack>
|
||||
{!isLoading && !previewUninitialized && potentialInstances.length > 0 && (
|
||||
|
@ -2,6 +2,8 @@ import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Alert, LoadingPlaceholder, useStyles2, withErrorBoundary } from '@grafana/ui';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
|
||||
|
||||
import { Stack } from '../../../../../../plugins/datasource/parca/QueryEditor/Stack';
|
||||
import { Labels } from '../../../../../../types/unified-alerting-dto';
|
||||
@ -27,9 +29,12 @@ function NotificationPreviewByAlertManager({
|
||||
);
|
||||
|
||||
if (error) {
|
||||
const title = t('alerting.notification-preview.error', 'Could not load routing preview for {{alertmanager}}', {
|
||||
alertmanager: alertManagerSource.name,
|
||||
});
|
||||
return (
|
||||
<Alert title="Cannot load Alertmanager configuration" severity="error">
|
||||
{error.message}
|
||||
<Alert title={title} severity="error">
|
||||
{stringifyErrorLike(error)}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@ -46,8 +51,7 @@ function NotificationPreviewByAlertManager({
|
||||
<Stack direction="row" alignItems="center">
|
||||
<div className={styles.firstAlertManagerLine}></div>
|
||||
<div className={styles.alertManagerName}>
|
||||
{' '}
|
||||
Alertmanager:
|
||||
<Trans i18nKey="alerting.notification-preview.alertmanager">Alertmanager:</Trans>
|
||||
<img src={alertManagerSource.imgUrl} alt="" className={styles.img} />
|
||||
{alertManagerSource.name}
|
||||
</div>
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { useContactPointsWithStatus } from 'app/features/alerting/unified/components/contact-points/useContactPoints';
|
||||
import { useNotificationPolicyRoute } from 'app/features/alerting/unified/components/notification-policies/useNotificationPolicyRoute';
|
||||
|
||||
import { Receiver } from '../../../../../../plugins/datasource/alertmanager/types';
|
||||
import { Labels } from '../../../../../../types/unified-alerting-dto';
|
||||
import { useAlertmanagerConfig } from '../../../hooks/useAlertmanagerConfig';
|
||||
import { useRouteGroupsMatcher } from '../../../useRouteGroupsMatcher';
|
||||
import { addUniqueIdentifierToRoute } from '../../../utils/amroutes';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource';
|
||||
@ -11,41 +13,50 @@ import { AlertInstanceMatch, computeInheritedTree, normalizeRoute } from '../../
|
||||
|
||||
import { getRoutesByIdMap, RouteWithPath } from './route';
|
||||
|
||||
export const useAlertmanagerNotificationRoutingPreview = (
|
||||
alertManagerSourceName: string,
|
||||
potentialInstances: Labels[]
|
||||
) => {
|
||||
const { currentData, isLoading: configLoading, error: configError } = useAlertmanagerConfig(alertManagerSourceName);
|
||||
const config = currentData?.alertmanager_config;
|
||||
export const useAlertmanagerNotificationRoutingPreview = (alertmanager: string, potentialInstances: Labels[]) => {
|
||||
const {
|
||||
data: currentData,
|
||||
isLoading: isPoliciesLoading,
|
||||
error: policiesError,
|
||||
} = useNotificationPolicyRoute({ alertmanager });
|
||||
|
||||
const {
|
||||
contactPoints,
|
||||
isLoading: contactPointsLoading,
|
||||
error: contactPointsError,
|
||||
} = useContactPointsWithStatus({
|
||||
alertmanager,
|
||||
fetchPolicies: false,
|
||||
fetchStatuses: false,
|
||||
});
|
||||
|
||||
const { matchInstancesToRoute } = useRouteGroupsMatcher();
|
||||
|
||||
// to create the list of matching contact points we need to first get the rootRoute
|
||||
const { rootRoute, receivers } = useMemo(() => {
|
||||
if (!config) {
|
||||
return {
|
||||
receivers: [],
|
||||
rootRoute: undefined,
|
||||
};
|
||||
const [defaultPolicy] = currentData ?? [];
|
||||
const rootRoute = useMemo(() => {
|
||||
if (!defaultPolicy) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
rootRoute: config.route ? normalizeRoute(addUniqueIdentifierToRoute(config.route)) : undefined,
|
||||
receivers: config.receivers ?? [],
|
||||
};
|
||||
}, [config]);
|
||||
return normalizeRoute(addUniqueIdentifierToRoute(defaultPolicy));
|
||||
}, [defaultPolicy]);
|
||||
|
||||
// create maps for routes to be get by id, this map also contains the path to the route
|
||||
// ⚠️ don't forget to compute the inherited tree before using this map
|
||||
const routesByIdMap: Map<string, RouteWithPath> = rootRoute
|
||||
const routesByIdMap = rootRoute
|
||||
? getRoutesByIdMap(computeInheritedTree(rootRoute))
|
||||
: new Map();
|
||||
: new Map<string, RouteWithPath>();
|
||||
|
||||
// to create the list of matching contact points we need to first get the rootRoute
|
||||
const receiversByName = useMemo(() => {
|
||||
if (!contactPoints) {
|
||||
return new Map<string, Receiver>();
|
||||
}
|
||||
|
||||
// create map for receivers to be get by name
|
||||
const receiversByName =
|
||||
receivers.reduce((map, receiver) => {
|
||||
return contactPoints.reduce((map, receiver) => {
|
||||
return map.set(receiver.name, receiver);
|
||||
}, new Map<string, Receiver>()) ?? new Map<string, Receiver>();
|
||||
}, new Map<string, Receiver>());
|
||||
}, [contactPoints]);
|
||||
|
||||
// match labels in the tree => map of notification policies and the alert instances (list of labels) in each one
|
||||
const {
|
||||
@ -56,8 +67,9 @@ export const useAlertmanagerNotificationRoutingPreview = (
|
||||
if (!rootRoute) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await matchInstancesToRoute(rootRoute, potentialInstances, {
|
||||
unquoteMatchers: alertManagerSourceName !== GRAFANA_RULES_SOURCE_NAME,
|
||||
unquoteMatchers: alertmanager !== GRAFANA_RULES_SOURCE_NAME,
|
||||
});
|
||||
}, [rootRoute, potentialInstances]);
|
||||
|
||||
@ -65,7 +77,7 @@ export const useAlertmanagerNotificationRoutingPreview = (
|
||||
routesByIdMap,
|
||||
receiversByName,
|
||||
matchingMap,
|
||||
loading: configLoading || matchingLoading,
|
||||
error: configError ?? matchingError,
|
||||
loading: isPoliciesLoading || contactPointsLoading || matchingLoading,
|
||||
error: policiesError ?? contactPointsError ?? matchingError,
|
||||
};
|
||||
};
|
||||
|
@ -159,23 +159,11 @@ export class AlertmanagerReceiverBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export function mockApi(server: SetupServer) {
|
||||
return {
|
||||
getAlertmanagerConfig: (amName: string, configure: (builder: AlertmanagerConfigBuilder) => void) => {
|
||||
export const getMockConfig = (configure: (builder: AlertmanagerConfigBuilder) => void): AlertManagerCortexConfig => {
|
||||
const builder = new AlertmanagerConfigBuilder();
|
||||
configure(builder);
|
||||
|
||||
server.use(
|
||||
http.get(`api/alertmanager/${amName}/config/api/v1/alerts`, () =>
|
||||
HttpResponse.json<AlertManagerCortexConfig>({
|
||||
alertmanager_config: builder.build(),
|
||||
template_files: {},
|
||||
})
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
return { alertmanager_config: builder.build(), template_files: {} };
|
||||
};
|
||||
|
||||
export function mockAlertRuleApi(server: SetupServer) {
|
||||
return {
|
||||
|
@ -14,9 +14,8 @@ const normalizeMatchers = (route: Route) => {
|
||||
const routeMatchers: ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Matcher[] = [];
|
||||
|
||||
if (route.object_matchers) {
|
||||
// todo foreach
|
||||
route.object_matchers.map(([label, type, value]) => {
|
||||
return { label, type, value };
|
||||
route.object_matchers.forEach(([label, type, value]) => {
|
||||
routeMatchers.push({ label, type, value });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -7,10 +7,11 @@ import { ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Receiver } f
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||
import { PROVENANCE_NONE, K8sAnnotations } from 'app/features/alerting/unified/utils/k8s/constants';
|
||||
|
||||
const config = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||
const getReceiversList = () => {
|
||||
const config = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||
|
||||
// Turn our mock alertmanager config into the format that we expect to be returned by the k8s API
|
||||
const mappedReceivers =
|
||||
// Turn our mock alertmanager config into the format that we expect to be returned by the k8s API
|
||||
const mappedReceivers =
|
||||
config.alertmanager_config?.receivers?.map((contactPoint) => {
|
||||
const provenance =
|
||||
contactPoint.grafana_managed_receiver_configs?.find((integration) => {
|
||||
@ -34,14 +35,15 @@ const mappedReceivers =
|
||||
};
|
||||
}) || [];
|
||||
|
||||
const parsedReceivers = getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Receiver>(
|
||||
return getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Receiver>(
|
||||
'ReceiverList',
|
||||
mappedReceivers
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
const listNamespacedReceiverHandler = () =>
|
||||
http.get<{ namespace: string }>(`${ALERTING_API_SERVER_BASE_URL}/namespaces/:namespace/receivers`, () => {
|
||||
return HttpResponse.json(parsedReceivers);
|
||||
return HttpResponse.json(getReceiversList());
|
||||
});
|
||||
|
||||
const createNamespacedReceiverHandler = () =>
|
||||
@ -58,6 +60,7 @@ const deleteNamespacedReceiverHandler = () =>
|
||||
`${ALERTING_API_SERVER_BASE_URL}/namespaces/:namespace/receivers/:name`,
|
||||
({ params }) => {
|
||||
const { name } = params;
|
||||
const parsedReceivers = getReceiversList();
|
||||
const matchedReceiver = parsedReceivers.items.find((receiver) => receiver.metadata.uid === name);
|
||||
if (matchedReceiver) {
|
||||
return HttpResponse.json(parsedReceivers);
|
||||
|
@ -3,15 +3,19 @@ import { HttpResponse, http } from 'msw';
|
||||
import { getAlertmanagerConfig } from 'app/features/alerting/unified/mocks/server/entities/alertmanagers';
|
||||
import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource';
|
||||
|
||||
const alertmanagerConfig = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME);
|
||||
const defaultReceiversResponse = alertmanagerConfig.alertmanager_config.receivers;
|
||||
const defaultTimeIntervalsResponse = alertmanagerConfig.alertmanager_config.time_intervals;
|
||||
const getNotificationReceiversHandler = () =>
|
||||
http.get('/api/v1/notifications/receivers', () => {
|
||||
const receivers = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME).alertmanager_config.receivers || [];
|
||||
|
||||
const getNotificationReceiversHandler = (response = defaultReceiversResponse) =>
|
||||
http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response));
|
||||
return HttpResponse.json(receivers);
|
||||
});
|
||||
|
||||
const getTimeIntervalsHandler = (response = defaultTimeIntervalsResponse) =>
|
||||
http.get('/api/v1/notifications/time-intervals', () => HttpResponse.json(response));
|
||||
const getTimeIntervalsHandler = () =>
|
||||
http.get('/api/v1/notifications/time-intervals', () => {
|
||||
const intervals = getAlertmanagerConfig(GRAFANA_RULES_SOURCE_NAME).alertmanager_config.time_intervals;
|
||||
|
||||
return HttpResponse.json(intervals);
|
||||
});
|
||||
|
||||
const handlers = [getNotificationReceiversHandler(), getTimeIntervalsHandler()];
|
||||
|
||||
|
@ -245,6 +245,7 @@
|
||||
"edit": "Edit",
|
||||
"export": "Export",
|
||||
"export-all": "Export all",
|
||||
"loading": "Loading...",
|
||||
"view": "View"
|
||||
},
|
||||
"contact-points": {
|
||||
@ -329,6 +330,14 @@
|
||||
"save": "Save mute timing",
|
||||
"saving": "Saving mute timing"
|
||||
},
|
||||
"notification-preview": {
|
||||
"alertmanager": "Alertmanager:",
|
||||
"error": "Could not load routing preview for {{alertmanager}}",
|
||||
"initialized": "Based on the labels added, alert instances are routed to the following notification policies. Expand each notification policy below to view more details.",
|
||||
"preview-routing": "Preview routing",
|
||||
"title": "Alert instance routing preview",
|
||||
"uninitialized": "When you have your folder selected and your query and labels are configured, click \"Preview routing\" to see the results here."
|
||||
},
|
||||
"policies": {
|
||||
"default-policy": {
|
||||
"description": "All alert instances will be handled by the default policy if no other matching policies are found.",
|
||||
|
@ -245,6 +245,7 @@
|
||||
"edit": "Ēđįŧ",
|
||||
"export": "Ēχpőřŧ",
|
||||
"export-all": "Ēχpőřŧ äľľ",
|
||||
"loading": "Ŀőäđįʼnģ...",
|
||||
"view": "Vįęŵ"
|
||||
},
|
||||
"contact-points": {
|
||||
@ -329,6 +330,14 @@
|
||||
"save": "Ŝävę mūŧę ŧįmįʼnģ",
|
||||
"saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ"
|
||||
},
|
||||
"notification-preview": {
|
||||
"alertmanager": "Åľęřŧmäʼnäģęř:",
|
||||
"error": "Cőūľđ ʼnőŧ ľőäđ řőūŧįʼnģ přęvįęŵ ƒőř {{alertmanager}}",
|
||||
"initialized": "ßäşęđ őʼn ŧĥę ľäþęľş äđđęđ, äľęřŧ įʼnşŧäʼnčęş äřę řőūŧęđ ŧő ŧĥę ƒőľľőŵįʼnģ ʼnőŧįƒįčäŧįőʼn pőľįčįęş. Ēχpäʼnđ ęäčĥ ʼnőŧįƒįčäŧįőʼn pőľįčy þęľőŵ ŧő vįęŵ mőřę đęŧäįľş.",
|
||||
"preview-routing": "Přęvįęŵ řőūŧįʼnģ",
|
||||
"title": "Åľęřŧ įʼnşŧäʼnčę řőūŧįʼnģ přęvįęŵ",
|
||||
"uninitialized": "Ŵĥęʼn yőū ĥävę yőūř ƒőľđęř şęľęčŧęđ äʼnđ yőūř qūęřy äʼnđ ľäþęľş äřę čőʼnƒįģūřęđ, čľįčĸ \"Přęvįęŵ řőūŧįʼnģ\" ŧő şęę ŧĥę řęşūľŧş ĥęřę."
|
||||
},
|
||||
"policies": {
|
||||
"default-policy": {
|
||||
"description": "Åľľ äľęřŧ įʼnşŧäʼnčęş ŵįľľ þę ĥäʼnđľęđ þy ŧĥę đęƒäūľŧ pőľįčy įƒ ʼnő őŧĥęř mäŧčĥįʼnģ pőľįčįęş äřę ƒőūʼnđ.",
|
||||
|
Loading…
Reference in New Issue
Block a user