diff --git a/.betterer.results b/.betterer.results index 7b4ddd5ca1f..224812edb55 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1443,16 +1443,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], - "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"] - ], - "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationRoute.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], diff --git a/public/app/features/alerting/unified/NotificationPolicies.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx index 72001d0e596..f93e95cb5e2 100644 --- a/public/app/features/alerting/unified/NotificationPolicies.test.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx @@ -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: {}, }); diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.test.tsx b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.test.tsx index 89f4e72354c..8444c3169f7 100644 --- a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreview.test.tsx @@ -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(); + const { user } = render( + + ); - 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(); - await waitFor(() => { - expect(ui.loadingIndicator.query()).not.toBeInTheDocument(); - }); + const { user } = render( + + ); - 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(); + const { user } = render( + + ); 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(); + const { user } = render( + + ); 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( { { 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( { { 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( { 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( { 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( { 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( { 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( - Alert instance routing preview + + Alert instance routing preview + {isLoading && previewUninitialized && ( - Loading... + Loading... )} {previewUninitialized ? ( - When you have your folder selected and your query and labels are configured, click "Preview - routing" to see the results here. + + When you have your folder selected and your query and labels are configured, click "Preview + routing" to see the results here. + ) : ( - Based on the labels added, alert instances are routed to the following notification policies. Expand each - notification policy below to view more details. + + Based on the labels added, alert instances are routed to the following notification policies. Expand + each notification policy below to view more details. + )} {!isLoading && !previewUninitialized && potentialInstances.length > 0 && ( diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx index f437f80df74..28d35e9e056 100644 --- a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/NotificationPreviewByAlertManager.tsx @@ -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 ( - - {error.message} + + {stringifyErrorLike(error)} ); } @@ -46,8 +51,7 @@ function NotificationPreviewByAlertManager({
- {' '} - Alertmanager: + Alertmanager: {alertManagerSource.name}
diff --git a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/useAlertmanagerNotificationRoutingPreview.ts b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/useAlertmanagerNotificationRoutingPreview.ts index a8df723da12..b6dcfb3e746 100644 --- a/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/useAlertmanagerNotificationRoutingPreview.ts +++ b/public/app/features/alerting/unified/components/rule-editor/notificaton-preview/useAlertmanagerNotificationRoutingPreview.ts @@ -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 = rootRoute + const routesByIdMap = rootRoute ? getRoutesByIdMap(computeInheritedTree(rootRoute)) - : new Map(); + : new Map(); - // 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(); + } + + // create map for receivers to be get by name + return contactPoints.reduce((map, receiver) => { return map.set(receiver.name, receiver); - }, new Map()) ?? new Map(); + }, new Map()); + }, [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, }; }; diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index 81e78c10f52..5d57be50076 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -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({ - 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 { diff --git a/public/app/features/alerting/unified/mocks/server/entities/k8s/routingtrees.ts b/public/app/features/alerting/unified/mocks/server/entities/k8s/routingtrees.ts index 613ad3e114c..9babb1332bf 100644 --- a/public/app/features/alerting/unified/mocks/server/entities/k8s/routingtrees.ts +++ b/public/app/features/alerting/unified/mocks/server/entities/k8s/routingtrees.ts @@ -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 }); }); } diff --git a/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts b/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts index 84588563a0c..461ef3810ce 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/k8s/receivers.k8s.ts @@ -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( - 'ReceiverList', - mappedReceivers -); + return getK8sResponse( + '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); diff --git a/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts index 2e08a15c640..cf2ca614f15 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts @@ -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()]; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index ff385eca064..3aca6723ded 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -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.", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index e642ff56ecb..71c193eb5b1 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -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đ.",