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
pull/97195/head
Tom Ratcliffe 7 months ago committed by GitHub
parent 1c60d51905
commit a2c407854f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 10
      .betterer.results
  2. 2
      public/app/features/alerting/unified/NotificationPolicies.test.tsx
  3. 147
      public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.test.tsx
  4. 21
      public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.tsx
  5. 12
      public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx
  6. 70
      public/app/features/alerting/unified/components/rule-editor/notificaton-preview/useAlertmanagerNotificationRoutingPreview.ts
  7. 22
      public/app/features/alerting/unified/mockApi.ts
  8. 5
      public/app/features/alerting/unified/mocks/server/entities/k8s/routingtrees.ts
  9. 61
      public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts
  10. 18
      public/app/features/alerting/unified/mocks/server/handlers/notifications.ts
  11. 9
      public/locales/en-US/grafana.json
  12. 9
      public/locales/pseudo-LOCALE/grafana.json

@ -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,52 +64,33 @@ const grafanaAlertManagerDataSource: AlertManagerDataSource = {
hasConfigurationAPI: true,
};
const mockConfig = getMockConfig((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'))
);
function mockOneAlertManager() {
getAlertManagerDataSourcesByPermissionAndConfigMock.mockReturnValue([grafanaAlertManagerDataSource]);
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'))
);
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">
When you have your folder selected and your query and labels are configured, click &quot;Preview
routing&quot; to see the results here.
<Trans i18nKey="alerting.notification-preview.uninitialized">
When you have your folder selected and your query and labels are configured, click &quot;Preview
routing&quot; 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>();
// create map for receivers to be get by name
const receiversByName =
receivers.reduce((map, receiver) => {
// 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
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) => {
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: {},
})
)
);
},
};
}
export const getMockConfig = (configure: (builder: AlertmanagerConfigBuilder) => void): AlertManagerCortexConfig => {
const builder = new AlertmanagerConfigBuilder();
configure(builder);
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,41 +7,43 @@ 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 =
config.alertmanager_config?.receivers?.map((contactPoint) => {
const provenance =
contactPoint.grafana_managed_receiver_configs?.find((integration) => {
return integration.provenance;
})?.provenance || PROVENANCE_NONE;
return {
metadata: {
// This isn't exactly accurate, but its the cleanest way to use the same data for AM config and K8S responses
uid: camelCase(contactPoint.name),
annotations: {
[K8sAnnotations.Provenance]: provenance,
[K8sAnnotations.AccessAdmin]: 'true',
[K8sAnnotations.AccessDelete]: 'true',
[K8sAnnotations.AccessWrite]: 'true',
// 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) => {
return integration.provenance;
})?.provenance || PROVENANCE_NONE;
return {
metadata: {
// This isn't exactly accurate, but its the cleanest way to use the same data for AM config and K8S responses
uid: camelCase(contactPoint.name),
annotations: {
[K8sAnnotations.Provenance]: provenance,
[K8sAnnotations.AccessAdmin]: 'true',
[K8sAnnotations.AccessDelete]: 'true',
[K8sAnnotations.AccessWrite]: 'true',
},
},
},
spec: {
title: contactPoint.name,
integrations: contactPoint.grafana_managed_receiver_configs || [],
},
};
}) || [];
spec: {
title: contactPoint.name,
integrations: contactPoint.grafana_managed_receiver_configs || [],
},
};
}) || [];
const parsedReceivers = getK8sResponse<ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1Receiver>(
'ReceiverList',
mappedReceivers
);
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…
Cancel
Save