From 9d639278f4ad89fa61ed2fe1b547bc364df2fd5d Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Mon, 29 Jul 2024 17:22:31 +0100 Subject: [PATCH] Alerting: Refactor simplified routing contact points hook (#90762) --- .../alerting/unified/RuleList.test.tsx | 3 +- .../features/alerting/unified/api/grafana.ts | 6 +- .../useContactPoints.test.tsx.snap | 356 +++++++++--------- .../contact-points/useContactPoints.test.tsx | 56 ++- .../contact-points/useContactPoints.tsx | 177 +++++---- .../components/contact-points/utils.ts | 4 +- .../export/GrafanaModifyExport.test.tsx | 14 +- .../simplifiedRouting/AlertManagerRouting.tsx | 7 +- .../SimplifiedRuleEditor.test.tsx | 205 +++------- .../components/rules/RulesFilter.test.tsx | 5 +- .../app/features/alerting/unified/mockApi.ts | 5 - .../unified/mocks/server/all-handlers.ts | 5 + .../mocks/server/handlers/notifications.ts | 13 + .../unified/mocks/server/handlers/search.ts | 34 ++ .../alerting/unified/state/actions.ts | 8 +- .../alerting/unified/state/reducers.ts | 2 - 16 files changed, 421 insertions(+), 479 deletions(-) create mode 100644 public/app/features/alerting/unified/mocks/server/handlers/notifications.ts create mode 100644 public/app/features/alerting/unified/mocks/server/handlers/search.ts diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index aeeaf8d87cf..fdb5bd8280e 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -17,7 +17,7 @@ import { } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; -import { mockSearchApi, mockUserApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { mockUserApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; import * as actions from 'app/features/alerting/unified/state/actions'; import { getMockUser } from 'app/features/users/__mocks__/userMocks'; @@ -156,7 +156,6 @@ const ui = { const server = setupMswServer(); const configureMockServer = (server: SetupServer) => { - mockSearchApi(server).search([]); mockUserApi(server).user(getMockUser()); setAlertmanagerChoices(AlertmanagerChoice.All, 1); }; diff --git a/public/app/features/alerting/unified/api/grafana.ts b/public/app/features/alerting/unified/api/grafana.ts index d4edba6afbe..db160e2b031 100644 --- a/public/app/features/alerting/unified/api/grafana.ts +++ b/public/app/features/alerting/unified/api/grafana.ts @@ -1,14 +1,10 @@ import { lastValueFrom } from 'rxjs'; import { getBackendSrv } from '@grafana/runtime'; -import { ContactPointsState, NotifierDTO, ReceiversStateDTO, ReceiverState } from 'app/types'; +import { ContactPointsState, ReceiversStateDTO, ReceiverState } from 'app/types'; import { getDatasourceAPIUid } from '../utils/datasource'; -export function fetchNotifiers(): Promise { - return getBackendSrv().get(`/api/alert-notifiers`); -} - interface IntegrationNameObject { type: string; index?: string; diff --git a/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap b/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap index 6ea7debf203..445f90eb10e 100644 --- a/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap +++ b/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap @@ -1,6 +1,190 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`useContactPoints should return contact points with status 1`] = ` +{ + "contactPoints": [ + { + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "grafana-default-email", + "secureFields": {}, + "settings": { + "addresses": "gilles.demey@grafana.com", + "singleEmail": false, + }, + "type": "email", + "uid": "xeKQrBrnk", + Symbol(receiver_status): { + "lastNotifyAttempt": "2023-07-02T21:35:34.841+02:00", + "lastNotifyAttemptDuration": "1ms", + "lastNotifyAttemptError": "failed to send notification to email addresses: gilles.demey@grafana.com: dial tcp 192.168.1.21:1025: connect: connection refused", + "name": "email", + "sendResolved": true, + }, + Symbol(receiver_metadata): { + "description": "Sends notifications using Grafana server configured SMTP settings", + "name": "Email", + }, + Symbol(receiver_plugin_metadata): undefined, + }, + ], + "name": "grafana-default-email", + "policies": [ + { + "receiver": "grafana-default-email", + "route": { + "type": "normal", + }, + }, + ], + }, + { + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "lotsa-emails", + "secureFields": {}, + "settings": { + "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com", + "singleEmail": false, + }, + "type": "email", + "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2", + Symbol(receiver_status): { + "lastNotifyAttempt": "", + "lastNotifyAttemptDuration": "", + "name": "email", + "sendResolved": true, + }, + Symbol(receiver_metadata): { + "description": "Sends notifications using Grafana server configured SMTP settings", + "name": "Email", + }, + Symbol(receiver_plugin_metadata): undefined, + }, + ], + "name": "lotsa-emails", + "policies": [], + }, + { + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "Oncall-integration", + "settings": { + "url": "https://oncall-endpoint.example.com", + }, + "type": "oncall", + Symbol(receiver_status): undefined, + Symbol(receiver_metadata): { + "description": "Sends notifications to Grafana OnCall", + "name": "Grafana OnCall", + }, + Symbol(receiver_plugin_metadata): { + "icon": "public/img/alerting/oncall_logo.svg", + "title": "Grafana OnCall", + }, + }, + ], + "name": "OnCall Conctact point", + "policies": [], + }, + { + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "provisioned-contact-point", + "provenance": "api", + "secureFields": {}, + "settings": { + "addresses": "gilles.demey@grafana.com", + "singleEmail": false, + }, + "type": "email", + "uid": "s8SdCVjnk", + Symbol(receiver_status): { + "lastNotifyAttempt": "", + "lastNotifyAttemptDuration": "", + "name": "email", + "sendResolved": true, + }, + Symbol(receiver_metadata): { + "description": "Sends notifications using Grafana server configured SMTP settings", + "name": "Email", + }, + Symbol(receiver_plugin_metadata): undefined, + }, + ], + "name": "provisioned-contact-point", + "policies": [ + { + "receiver": "provisioned-contact-point", + "route": { + "type": "normal", + }, + }, + ], + }, + { + "grafana_managed_receiver_configs": [ + { + "disableResolveMessage": false, + "name": "Slack with multiple channels", + "secureFields": { + "token": true, + }, + "settings": { + "recipient": "test-alerts", + }, + "type": "slack", + "uid": "c02ad56a-31da-46b9-becb-4348ec0890fd", + Symbol(receiver_status): { + "lastNotifyAttempt": "", + "lastNotifyAttemptDuration": "", + "name": "slack", + "sendResolved": true, + }, + Symbol(receiver_metadata): { + "description": "Sends notifications to Slack", + "name": "Slack", + }, + Symbol(receiver_plugin_metadata): undefined, + }, + { + "disableResolveMessage": false, + "name": "Slack with multiple channels", + "secureFields": { + "token": true, + }, + "settings": { + "recipient": "test-alerts2", + }, + "type": "slack", + "uid": "b286a3be-f690-49e2-8605-b075cbace2df", + Symbol(receiver_status): { + "lastNotifyAttempt": "", + "lastNotifyAttemptDuration": "", + "name": "slack", + "sendResolved": true, + }, + Symbol(receiver_metadata): { + "description": "Sends notifications to Slack", + "name": "Slack", + }, + Symbol(receiver_plugin_metadata): undefined, + }, + ], + "name": "Slack with multiple channels", + "policies": [], + }, + ], + "error": undefined, + "isLoading": false, +} +`; + +exports[`useContactPoints when having oncall plugin installed and no alert manager config data should return contact points with oncall metadata 1`] = ` { "contactPoints": [ { @@ -184,177 +368,5 @@ exports[`useContactPoints should return contact points with status 1`] = ` ], "error": undefined, "isLoading": false, - "refetchReceivers": [Function], -} -`; - -exports[`useContactPoints when having oncall plugin installed and no alert manager config data should return contact points with oncall metadata 1`] = ` -{ - "contactPoints": [ - { - "grafana_managed_receiver_configs": [ - { - "disableResolveMessage": false, - "name": "grafana-default-email", - "secureFields": {}, - "settings": { - "addresses": "gilles.demey@grafana.com", - "singleEmail": false, - }, - "type": "email", - "uid": "xeKQrBrnk", - Symbol(receiver_status): { - "lastNotifyAttempt": "2023-07-02T21:35:34.841+02:00", - "lastNotifyAttemptDuration": "1ms", - "lastNotifyAttemptError": "failed to send notification to email addresses: gilles.demey@grafana.com: dial tcp 192.168.1.21:1025: connect: connection refused", - "name": "email", - "sendResolved": true, - }, - Symbol(receiver_metadata): { - "description": "Sends notifications using Grafana server configured SMTP settings", - "name": "Email", - }, - Symbol(receiver_plugin_metadata): undefined, - }, - ], - "name": "grafana-default-email", - "policies": undefined, - }, - { - "grafana_managed_receiver_configs": [ - { - "disableResolveMessage": false, - "name": "lotsa-emails", - "secureFields": {}, - "settings": { - "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com", - "singleEmail": false, - }, - "type": "email", - "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2", - Symbol(receiver_status): { - "lastNotifyAttempt": "", - "lastNotifyAttemptDuration": "", - "name": "email", - "sendResolved": true, - }, - Symbol(receiver_metadata): { - "description": "Sends notifications using Grafana server configured SMTP settings", - "name": "Email", - }, - Symbol(receiver_plugin_metadata): undefined, - }, - ], - "name": "lotsa-emails", - "policies": undefined, - }, - { - "grafana_managed_receiver_configs": [ - { - "disableResolveMessage": false, - "name": "Oncall-integration", - "settings": { - "url": "https://oncall-endpoint.example.com", - }, - "type": "oncall", - Symbol(receiver_status): undefined, - Symbol(receiver_metadata): { - "description": "Sends notifications to Grafana OnCall", - "name": "Grafana OnCall", - }, - Symbol(receiver_plugin_metadata): { - "icon": "public/img/alerting/oncall_logo.svg", - "title": "Grafana OnCall", - }, - }, - ], - "name": "OnCall Conctact point", - "policies": undefined, - }, - { - "grafana_managed_receiver_configs": [ - { - "disableResolveMessage": false, - "name": "provisioned-contact-point", - "provenance": "api", - "secureFields": {}, - "settings": { - "addresses": "gilles.demey@grafana.com", - "singleEmail": false, - }, - "type": "email", - "uid": "s8SdCVjnk", - Symbol(receiver_status): { - "lastNotifyAttempt": "", - "lastNotifyAttemptDuration": "", - "name": "email", - "sendResolved": true, - }, - Symbol(receiver_metadata): { - "description": "Sends notifications using Grafana server configured SMTP settings", - "name": "Email", - }, - Symbol(receiver_plugin_metadata): undefined, - }, - ], - "name": "provisioned-contact-point", - "policies": undefined, - }, - { - "grafana_managed_receiver_configs": [ - { - "disableResolveMessage": false, - "name": "Slack with multiple channels", - "secureFields": { - "token": true, - }, - "settings": { - "recipient": "test-alerts", - }, - "type": "slack", - "uid": "c02ad56a-31da-46b9-becb-4348ec0890fd", - Symbol(receiver_status): { - "lastNotifyAttempt": "", - "lastNotifyAttemptDuration": "", - "name": "slack", - "sendResolved": true, - }, - Symbol(receiver_metadata): { - "description": "Sends notifications to Slack", - "name": "Slack", - }, - Symbol(receiver_plugin_metadata): undefined, - }, - { - "disableResolveMessage": false, - "name": "Slack with multiple channels", - "secureFields": { - "token": true, - }, - "settings": { - "recipient": "test-alerts2", - }, - "type": "slack", - "uid": "b286a3be-f690-49e2-8605-b075cbace2df", - Symbol(receiver_status): { - "lastNotifyAttempt": "", - "lastNotifyAttemptDuration": "", - "name": "slack", - "sendResolved": true, - }, - Symbol(receiver_metadata): { - "description": "Sends notifications to Slack", - "name": "Slack", - }, - Symbol(receiver_plugin_metadata): undefined, - }, - ], - "name": "Slack with multiple channels", - "policies": undefined, - }, - ], - "error": undefined, - "isLoading": false, - "refetchReceivers": [Function], } `; diff --git a/public/app/features/alerting/unified/components/contact-points/useContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/useContactPoints.test.tsx index 88391972a81..72f53409c25 100644 --- a/public/app/features/alerting/unified/components/contact-points/useContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/useContactPoints.test.tsx @@ -1,17 +1,30 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { ReactNode } from 'react'; +import { getWrapper } from 'test/test-utils'; -import alertmanagerMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json'; +import { disablePlugin } from 'app/features/alerting/unified/mocks/server/configure'; import { setOnCallIntegrations } from 'app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins'; +import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { AccessControlAction } from 'app/types'; -import { mockApi, setupMswServer } from '../../mockApi'; +import { setupMswServer } from '../../mockApi'; import { grantUserPermissions } from '../../mocks'; import { AlertmanagerProvider } from '../../state/AlertmanagerContext'; import { useContactPointsWithStatus } from './useContactPoints'; -const server = setupMswServer(); +const wrapper = ({ children }: { children: ReactNode }) => { + const ProviderWrapper = getWrapper({ renderWithRouter: true }); + return ( + + + {children} + + + ); +}; + +setupMswServer(); describe('useContactPoints', () => { beforeAll(() => { @@ -19,23 +32,10 @@ describe('useContactPoints', () => { }); it('should return contact points with status', async () => { - setOnCallIntegrations([ - { - display_name: 'grafana-integration', - value: 'ABC123', - integration_url: 'https://oncall-endpoint.example.com', - }, - ]); - mockApi(server).getContactPointsList(receivers); + disablePlugin(SupportedPlugin.OnCall); const { result } = renderHook(() => useContactPointsWithStatus(), { - wrapper: ({ children }) => ( - - - {children} - - - ), + wrapper, }); await waitFor(() => { @@ -53,20 +53,10 @@ describe('useContactPoints', () => { integration_url: 'https://oncall-endpoint.example.com', }, ]); - mockApi(server).getContactPointsList(receivers); - const { result } = renderHook( - () => useContactPointsWithStatus({ includePoliciesCount: false, receiverStatusPollingInterval: 0 }), - { - wrapper: ({ children }) => ( - - - {children} - - - ), - } - ); + const { result } = renderHook(() => useContactPointsWithStatus(), { + wrapper, + }); await waitFor(() => { expect(result.current.isLoading).toBe(false); @@ -75,5 +65,3 @@ describe('useContactPoints', () => { }); }); }); - -const receivers = JSON.parse(JSON.stringify(alertmanagerMock)).alertmanager_config.receivers; diff --git a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx index 8d6ca99a720..b4638a8cc00 100644 --- a/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx +++ b/public/app/features/alerting/unified/components/contact-points/useContactPoints.tsx @@ -5,9 +5,10 @@ import { produce } from 'immer'; import { remove } from 'lodash'; +import { useMemo } from 'react'; import { alertmanagerApi } from '../../api/alertmanagerApi'; -import { onCallApi, OnCallIntegrationDTO } from '../../api/onCallApi'; +import { onCallApi } from '../../api/onCallApi'; import { usePluginBridge } from '../../hooks/usePluginBridge'; import { useAlertmanager } from '../../state/AlertmanagerContext'; import { SupportedPlugin } from '../../types/pluginBridges'; @@ -27,82 +28,116 @@ const RECEIVER_STATUS_POLLING_INTERVAL = 10 * 1000; // 10 seconds * 3. (if available) additional metadata about Grafana Managed contact points * 4. (if available) the OnCall plugin metadata */ -interface UseContactPointsWithStatusOptions { - includePoliciesCount: boolean; - receiverStatusPollingInterval?: number; -} -const defaultHookOptions = { - includePoliciesCount: true, - receiverStatusPollingInterval: RECEIVER_STATUS_POLLING_INTERVAL, +const { + useGetAlertmanagerConfigurationQuery, + useGetContactPointsListQuery, + useGetContactPointsStatusQuery, + useGrafanaNotifiersQuery, + useLazyGetAlertmanagerConfigurationQuery, + useUpdateAlertmanagerConfigurationMutation, +} = alertmanagerApi; + +const { useGrafanaOnCallIntegrationsQuery } = onCallApi; + +/** + * Check if OnCall is installed, and fetch the list of integrations if so. + * + * Otherwise, returns no data + */ +const useOnCallIntegrations = ({ skip }: { skip?: boolean } = {}) => { + const { installed, loading } = usePluginBridge(SupportedPlugin.OnCall); + const oncallIntegrationsResponse = useGrafanaOnCallIntegrationsQuery(undefined, { skip: skip || !installed }); + + return useMemo(() => { + if (installed) { + return oncallIntegrationsResponse; + } + return { + isLoading: loading, + data: undefined, + }; + }, [installed, loading, oncallIntegrationsResponse]); }; -export function useContactPointsWithStatus({ - includePoliciesCount, - receiverStatusPollingInterval, -}: UseContactPointsWithStatusOptions = defaultHookOptions) { - const { selectedAlertmanager, isGrafanaAlertmanager } = useAlertmanager(); - const { installed: onCallPluginInstalled, loading: onCallPluginStatusLoading } = usePluginBridge( - SupportedPlugin.OnCall - ); +/** + * Fetch contact points from separate endpoint (i.e. not the Alertmanager config) and combine with + * OnCall integrations and any additional metadata from list of notifiers + * (e.g. hydrate with additional names/descriptions) + */ +export const useGetContactPoints = () => { + const onCallResponse = useOnCallIntegrations(); + const alertNotifiers = useGrafanaNotifiersQuery(); + const contactPointsListResponse = useGetContactPointsListQuery(); - // fetch receiver status if we're dealing with a Grafana Managed Alertmanager - const fetchContactPointsStatus = alertmanagerApi.endpoints.getContactPointsStatus.useQuery(undefined, { + return useMemo(() => { + const isLoading = onCallResponse.isLoading || alertNotifiers.isLoading || contactPointsListResponse.isLoading; + + if (isLoading || !contactPointsListResponse.data) { + return { + ...contactPointsListResponse, + // If we're inside this block, it means that at least one of the endpoints we care about is still loading, + // but the contactPointsListResponse may have in fact finished. + // If we were to use _that_ loading state, it might be inaccurate elsewhere when consuming this hook, + // so we explicitly say "yes, this is definitely still loading" + isLoading: true, + contactPoints: [], + }; + } + + const enhanced = enhanceContactPointsWithMetadata( + [], + alertNotifiers.data, + onCallResponse?.data, + contactPointsListResponse.data, + undefined + ); + + return { + ...contactPointsListResponse, + contactPoints: enhanced, + }; + }, [ + alertNotifiers.data, + alertNotifiers.isLoading, + contactPointsListResponse, + onCallResponse?.data, + onCallResponse.isLoading, + ]); +}; + +export function useContactPointsWithStatus() { + const { selectedAlertmanager, isGrafanaAlertmanager } = useAlertmanager(); + + const defaultOptions = { refetchOnFocus: true, refetchOnReconnect: true, - // re-fetch status every so often for up-to-date information, allow disabling by passing "receiverStatusPollingInterval: 0" - pollingInterval: receiverStatusPollingInterval, + }; + + // fetch receiver status if we're dealing with a Grafana Managed Alertmanager + const fetchContactPointsStatus = useGetContactPointsStatusQuery(undefined, { + ...defaultOptions, + // re-fetch status every so often for up-to-date information + pollingInterval: RECEIVER_STATUS_POLLING_INTERVAL, // skip fetching receiver statuses if not Grafana AM skip: !isGrafanaAlertmanager, }); // fetch notifier metadata from the Grafana API if we're using a Grafana AM – this will be used to add additional // metadata and canonical names to the receiver - const fetchReceiverMetadata = alertmanagerApi.endpoints.grafanaNotifiers.useQuery(undefined, { + const fetchReceiverMetadata = useGrafanaNotifiersQuery(undefined, { skip: !isGrafanaAlertmanager, }); // if the OnCall plugin is installed, fetch its list of integrations so we can match those to the Grafana Managed contact points - const { data: onCallIntegrations, isLoading: onCallPluginIntegrationsLoading } = - onCallApi.endpoints.grafanaOnCallIntegrations.useQuery(undefined, { - skip: !onCallPluginInstalled || !isGrafanaAlertmanager, - }); - - // null = no installed, undefined = loading, [n] is installed with integrations - let onCallMetadata: null | undefined | OnCallIntegrationDTO[] = undefined; - if (onCallPluginInstalled) { - onCallMetadata = onCallIntegrations ?? []; - } else if (onCallPluginInstalled === false) { - onCallMetadata = null; - } + const { data: onCallMetadata, isLoading: onCallPluginIntegrationsLoading } = useOnCallIntegrations({ + skip: !isGrafanaAlertmanager, + }); // fetch the latest config from the Alertmanager // we use this endpoint only when we need to get the number of policies - const fetchAlertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( - selectedAlertmanager!, - { - refetchOnFocus: true, - refetchOnReconnect: true, - selectFromResult: (result) => ({ - ...result, - contactPoints: result.data - ? enhanceContactPointsWithMetadata( - fetchContactPointsStatus.data, - fetchReceiverMetadata.data, - onCallMetadata, - result.data.alertmanager_config.receivers ?? [], - result.data - ) - : [], - }), - skip: !includePoliciesCount, - } - ); - - // for Grafana Managed Alertmanager, we use the new read-only endpoint for getting the list of contact points - const fetchGrafanaContactPoints = alertmanagerApi.endpoints.getContactPointsList.useQuery(undefined, { - refetchOnFocus: true, - refetchOnReconnect: true, + const fetchAlertmanagerConfiguration = useGetAlertmanagerConfigurationQuery(selectedAlertmanager!, { + ...defaultOptions, selectFromResult: (result) => ({ ...result, contactPoints: result.data @@ -110,40 +145,28 @@ export function useContactPointsWithStatus({ fetchContactPointsStatus.data, fetchReceiverMetadata.data, onCallMetadata, - result.data, // contact points from the new readonly endpoint - undefined //no config data + result.data.alertmanager_config.receivers ?? [], + result.data ) : [], }), - skip: includePoliciesCount || !isGrafanaAlertmanager, }); // we will fail silently for fetching OnCall plugin status and integrations - const error = - fetchAlertmanagerConfiguration.error || fetchGrafanaContactPoints.error || fetchContactPointsStatus.error; + const error = fetchAlertmanagerConfiguration.error || fetchContactPointsStatus.error; const isLoading = - fetchAlertmanagerConfiguration.isLoading || - fetchGrafanaContactPoints.isLoading || - fetchContactPointsStatus.isLoading || - onCallPluginStatusLoading || - onCallPluginIntegrationsLoading; + fetchAlertmanagerConfiguration.isLoading || fetchContactPointsStatus.isLoading || onCallPluginIntegrationsLoading; - const unsortedContactPoints = includePoliciesCount - ? fetchAlertmanagerConfiguration.contactPoints - : fetchGrafanaContactPoints.contactPoints; - const contactPoints = unsortedContactPoints.sort((a, b) => a.name.localeCompare(b.name)); return { error, isLoading, - contactPoints, - refetchReceivers: fetchGrafanaContactPoints.refetch, + contactPoints: fetchAlertmanagerConfiguration.contactPoints, }; } export function useDeleteContactPoint(selectedAlertmanager: string) { - const [fetchAlertmanagerConfig] = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useLazyQuery(); - const [updateAlertManager, updateAlertmanagerState] = - alertmanagerApi.endpoints.updateAlertmanagerConfiguration.useMutation(); + const [fetchAlertmanagerConfig] = useLazyGetAlertmanagerConfigurationQuery(); + const [updateAlertManager, updateAlertmanagerState] = useUpdateAlertmanagerConfigurationMutation(); const deleteTrigger = (contactPointName: string) => { return fetchAlertmanagerConfig(selectedAlertmanager).then(({ data }) => { diff --git a/public/app/features/alerting/unified/components/contact-points/utils.ts b/public/app/features/alerting/unified/components/contact-points/utils.ts index d95ee7dc369..98fce602748 100644 --- a/public/app/features/alerting/unified/components/contact-points/utils.ts +++ b/public/app/features/alerting/unified/components/contact-points/utils.ts @@ -127,7 +127,7 @@ export function enhanceContactPointsWithMetadata( ? (alertmanagerConfiguration?.alertmanager_config.receivers ?? []) : (contactPoints ?? []); - return contactPointsList.map((contactPoint) => { + const enhanced = contactPointsList.map((contactPoint) => { const receivers = extractReceivers(contactPoint); const statusForReceiver = status.find((status) => status.name === contactPoint.name); @@ -152,6 +152,8 @@ export function enhanceContactPointsWithMetadata( }), }; }); + + return enhanced.sort((a, b) => a.name.localeCompare(b.name)); } export function isAutoGeneratedPolicy(route: Route) { diff --git a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.test.tsx b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.test.tsx index f588ad6d921..a24860ace5d 100644 --- a/public/app/features/alerting/unified/components/export/GrafanaModifyExport.test.tsx +++ b/public/app/features/alerting/unified/components/export/GrafanaModifyExport.test.tsx @@ -4,9 +4,8 @@ import { Props } from 'react-virtualized-auto-sizer'; import { render, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; -import { DashboardSearchItemType } from '../../../../search/types'; -import { mockExportApi, mockSearchApi, setupMswServer } from '../../mockApi'; -import { mockDashboardSearchItem, mockDataSource } from '../../mocks'; +import { mockExportApi, setupMswServer } from '../../mockApi'; +import { mockDataSource } from '../../mocks'; import { grafanaRulerRule } from '../../mocks/grafanaRulerApi'; import { setupDataSources } from '../../testSetup/datasources'; @@ -67,15 +66,6 @@ describe('GrafanaModifyExport', () => { setupDataSources(dataSources.default); it('Should render edit form for the specified rule', async () => { - mockSearchApi(server).search([ - mockDashboardSearchItem({ - title: grafanaRulerRule.grafana_alert.title, - uid: grafanaRulerRule.grafana_alert.namespace_uid, - url: '', - tags: [], - type: DashboardSearchItemType.DashFolder, - }), - ]); mockExportApi(server).modifiedExport(grafanaRulerRule.grafana_alert.namespace_uid, { yaml: 'Yaml Export Content', json: 'Json Export Content', diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx index 5dd38c4c1bd..25921e40e4a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx @@ -8,7 +8,7 @@ import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form'; import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource'; import { ContactPointReceiverSummary } from '../../../contact-points/ContactPoint'; -import { useContactPointsWithStatus } from '../../../contact-points/useContactPoints'; +import { useGetContactPoints } from '../../../contact-points/useContactPoints'; import { ContactPointWithMetadata } from '../../../contact-points/utils'; import { ContactPointDetails } from './contactPoint/ContactPointDetails'; @@ -28,8 +28,9 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo isLoading, error: errorInContactPointStatus, contactPoints, - refetchReceivers, - } = useContactPointsWithStatus({ includePoliciesCount: false, receiverStatusPollingInterval: 0 }); + refetch: refetchReceivers, + } = useGetContactPoints(); + const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState< ContactPointWithMetadata | undefined >(); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index 5b635094ba5..be567a58d46 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -1,72 +1,39 @@ -import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import * as React from 'react'; +import { ReactNode } from 'react'; import { Route } from 'react-router-dom'; -import { TestProvider } from 'test/helpers/TestProvider'; import { ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; +import { render, screen, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; -import { config, locationService } from '@grafana/runtime'; +import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import RuleEditor from 'app/features/alerting/unified/RuleEditor'; import * as ruler from 'app/features/alerting/unified/api/ruler'; -import * as useContactPoints from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; +import { FOLDER_TITLE_HAPPY_PATH } from 'app/features/alerting/unified/mocks/server/handlers/search'; import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext'; -import * as utils_config from 'app/features/alerting/unified/utils/config'; import { DataSourceType, GRAFANA_DATASOURCE_NAME, GRAFANA_RULES_SOURCE_NAME, - useGetAlertManagerDataSourcesByPermissionAndConfig, } from 'app/features/alerting/unified/utils/datasource'; import { getDefaultQueries } from 'app/features/alerting/unified/utils/rule-form'; -import { searchFolders } from 'app/features/manage-dashboards/state/actions'; -import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; -import { grafanaRulerEmptyGroup, grafanaRulerNamespace2, grafanaRulerRule } from '../../../../mocks/grafanaRulerApi'; +import { grafanaRulerEmptyGroup, grafanaRulerNamespace2 } from '../../../../mocks/grafanaRulerApi'; import { setupDataSources } from '../../../../testSetup/datasources'; -import { RECEIVER_META_KEY } from '../../../contact-points/useContactPoints'; -import { ContactPointWithMetadata } from '../../../contact-points/utils'; -import { ExpressionEditorProps } from '../../ExpressionEditor'; - -jest.mock('app/features/alerting/unified/components/rule-editor/ExpressionEditor', () => ({ - ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => ( - onChange(e.target.value)} /> - ), -})); - -jest.mock('app/features/manage-dashboards/state/actions'); jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ - AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) =>
{actions}
, + AppChromeUpdate: ({ actions }: { actions: ReactNode }) =>
{actions}
, })); -// there's no angular scope in test and things go terribly wrong when trying to render the query editor row. -// lets just skip it -jest.mock('app/features/query/components/QueryEditorRow', () => ({ - QueryEditorRow: () =>

hi

, -})); - -const user = userEvent.setup(); - -// jest.spyOn(utils_config, 'getAllDataSources'); -// jest.spyOn(dsByPermission, 'useAlertManagersByPermission'); -jest.spyOn(useContactPoints, 'useContactPointsWithStatus'); - jest.setTimeout(60 * 1000); const mocks = { - getAllDataSources: jest.mocked(utils_config.getAllDataSources), - searchFolders: jest.mocked(searchFolders), - useContactPointsWithStatus: jest.mocked(useContactPoints.useContactPointsWithStatus), - useGetAlertManagerDataSourcesByPermissionAndConfig: jest.mocked(useGetAlertManagerDataSourcesByPermissionAndConfig), api: { setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'), }, @@ -74,11 +41,37 @@ const mocks = { setupMswServer(); +const dataSources = { + default: mockDataSource( + { + type: 'prometheus', + name: 'Prom', + isDefault: true, + }, + { alerting: false } + ), + am: mockDataSource({ + name: 'Alertmanager', + type: DataSourceType.Alertmanager, + }), +}; +setupDataSources(dataSources.default, dataSources.am); + +const selectFolderAndGroup = async () => { + const user = userEvent.setup(); + const folderInput = await ui.inputs.folder.find(); + await clickSelectOption(folderInput, FOLDER_TITLE_HAPPY_PATH); + const groupInput = await ui.inputs.group.find(); + await user.click(await byRole('combobox').find(groupInput)); + await clickSelectOption(groupInput, grafanaRulerEmptyGroup.name); +}; + describe('Can create a new grafana managed alert unsing simplified routing', () => { beforeEach(() => { jest.clearAllMocks(); contextSrv.isEditor = true; contextSrv.hasEditPermissionInFolders = true; + config.featureToggles.alertingSimplifiedRouting = true; grantUserPermissions([ AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate, @@ -96,74 +89,26 @@ describe('Can create a new grafana managed alert unsing simplified routing', () ]); }); - const dataSources = { - default: mockDataSource( - { - type: 'prometheus', - name: 'Prom', - isDefault: true, - }, - { alerting: false } - ), - am: mockDataSource({ - name: 'Alertmanager', - type: DataSourceType.Alertmanager, - }), - }; - setupDataSources(dataSources.default, dataSources.am); - it('cannot create new grafana managed alert when using simplified routing and not selecting a contact point', async () => { - // no contact points found - mocks.useContactPointsWithStatus.mockReturnValue({ - contactPoints: [], - isLoading: false, - error: undefined, - refetchReceivers: jest.fn(), - }); + const user = userEvent.setup(); - mocks.api.setRulerRuleGroup.mockResolvedValue(); - mocks.searchFolders.mockResolvedValue([ - { - title: 'Folder A', - uid: grafanaRulerRule.grafana_alert.namespace_uid, - id: 1, - type: DashboardSearchItemType.DashDB, - }, - { - title: 'Folder B', - id: 2, - }, - { - title: 'Folder / with slash', - id: 2, - uid: 'b', - type: DashboardSearchItemType.DashDB, - }, - ] as DashboardSearchHit[]); - - config.featureToggles.alertingSimplifiedRouting = true; renderSimplifiedRuleEditor(); - await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')); await user.type(await ui.inputs.name.find(), 'my great new rule'); - const folderInput = await ui.inputs.folder.find(); - await clickSelectOption(folderInput, 'Folder A'); - const groupInput = await ui.inputs.group.find(); - await user.click(byRole('combobox').get(groupInput)); - await clickSelectOption(groupInput, grafanaRulerRule.grafana_alert.rule_group); + await selectFolderAndGroup(); + //select contact point routing await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); + // do not select a contact point // save and check that call to backend was not made await user.click(ui.buttons.saveAndExit.get()); - await waitFor(() => { - expect(screen.getByText('Contact point is required.')).toBeInTheDocument(); - expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled(); - }); + expect(await screen.findByText('Contact point is required.')).toBeInTheDocument(); + expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled(); }); + it('simplified routing is not available when Grafana AM is not enabled', async () => { - config.featureToggles.alertingSimplifiedRouting = true; setAlertmanagerChoices(AlertmanagerChoice.External, 1); renderSimplifiedRuleEditor(); await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')); @@ -172,69 +117,20 @@ describe('Can create a new grafana managed alert unsing simplified routing', () }); it('can create new grafana managed alert when using simplified routing and selecting a contact point', async () => { - const contactPointsAvailable: ContactPointWithMetadata[] = [ - { - name: 'contact_point1', - grafana_managed_receiver_configs: [ - { - name: 'contact_point1', - type: 'email', - disableResolveMessage: false, - [RECEIVER_META_KEY]: { - name: 'contact_point1', - description: 'contact_point1 description', - }, - settings: {}, - }, - ], - policies: [], - }, - ]; - mocks.useContactPointsWithStatus.mockReturnValue({ - contactPoints: contactPointsAvailable, - isLoading: false, - error: undefined, - refetchReceivers: jest.fn(), - }); + const user = userEvent.setup(); + const contactPointName = 'lotsa-emails'; - mocks.api.setRulerRuleGroup.mockResolvedValue(); - mocks.searchFolders.mockResolvedValue([ - { - title: 'Folder A', - uid: grafanaRulerNamespace2.uid, - id: 1, - type: DashboardSearchItemType.DashDB, - }, - { - title: 'Folder B', - id: 2, - uid: 'b', - type: DashboardSearchItemType.DashDB, - }, - { - title: 'Folder / with slash', - uid: 'c', - id: 2, - type: DashboardSearchItemType.DashDB, - }, - ] as DashboardSearchHit[]); - - config.featureToggles.alertingSimplifiedRouting = true; renderSimplifiedRuleEditor(); - await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')); await user.type(await ui.inputs.name.find(), 'my great new rule'); - const folderInput = await ui.inputs.folder.find(); - await clickSelectOption(folderInput, 'Folder A'); - const groupInput = await ui.inputs.group.find(); - await user.click(byRole('combobox').get(groupInput)); - await clickSelectOption(groupInput, grafanaRulerEmptyGroup.name); + await selectFolderAndGroup(); + //select contact point routing await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find(); await user.click(byRole('combobox').get(contactPointInput)); - await clickSelectOption(contactPointInput, 'contact_point1'); + await clickSelectOption(contactPointInput, contactPointName); // save and check what was sent to backend await user.click(ui.buttons.saveAndExit.get()); @@ -262,7 +158,7 @@ describe('Can create a new grafana managed alert unsing simplified routing', () group_interval: undefined, group_wait: undefined, mute_timings: undefined, - receiver: 'contact_point1', + receiver: contactPointName, repeat_interval: undefined, }, }, @@ -274,13 +170,10 @@ describe('Can create a new grafana managed alert unsing simplified routing', () }); function renderSimplifiedRuleEditor() { - locationService.push(`/alerting/new/alerting`); - return render( - - - - - + + + , + { historyOptions: { initialEntries: ['/alerting/new/alerting'] } } ); } diff --git a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx index 83c02716c9c..84d0dc752f5 100644 --- a/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesFilter.test.tsx @@ -4,7 +4,7 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { byLabelText, byRole } from 'testing-library-selector'; import { locationService, setDataSourceSrv } from '@grafana/runtime'; -import { mockSearchApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import * as analytics from '../../Analytics'; import { MockDataSourceSrv } from '../../mocks'; @@ -12,7 +12,7 @@ import { setupPluginsExtensionsHook } from '../../testSetup/plugins'; import RulesFilter from './RulesFilter'; -const server = setupMswServer(); +setupMswServer(); jest.spyOn(analytics, 'logInfo'); jest.mock('./MultipleDataSourcePicker', () => { @@ -43,7 +43,6 @@ const ui = { beforeEach(() => { locationService.replace({ search: '' }); - mockSearchApi(server).search([]); }); describe('RulesFilter', () => { diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index d1d40f5806f..00b76cddd80 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -20,7 +20,6 @@ import { AlertManagerCortexConfig, AlertmanagerReceiver, EmailConfig, - GrafanaManagedContactPoint, GrafanaManagedReceiverConfig, MatcherOperator, Route, @@ -170,10 +169,6 @@ export function mockApi(server: SetupServer) { ) ); }, - - getContactPointsList: (response: GrafanaManagedContactPoint[]) => { - server.use(http.get(`/api/v1/notifications/receivers`, () => HttpResponse.json(response))); - }, }; } diff --git a/public/app/features/alerting/unified/mocks/server/all-handlers.ts b/public/app/features/alerting/unified/mocks/server/all-handlers.ts index 3104b14a468..8114f9ca7b9 100644 --- a/public/app/features/alerting/unified/mocks/server/all-handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/all-handlers.ts @@ -9,9 +9,12 @@ import evalHandlers from 'app/features/alerting/unified/mocks/server/handlers/ev import folderHandlers from 'app/features/alerting/unified/mocks/server/handlers/folders'; import grafanaRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler'; import mimirRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/mimirRuler'; +import notificationsHandlers from 'app/features/alerting/unified/mocks/server/handlers/notifications'; import pluginsHandlers from 'app/features/alerting/unified/mocks/server/handlers/plugins'; import allPluginHandlers from 'app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers'; +import searchHandlers from 'app/features/alerting/unified/mocks/server/handlers/search'; import silenceHandlers from 'app/features/alerting/unified/mocks/server/handlers/silences'; + /** * Array of all mock handlers that are required across Alerting tests */ @@ -25,6 +28,8 @@ const allHandlers = [ ...folderHandlers, ...pluginsHandlers, ...silenceHandlers, + ...searchHandlers, + ...notificationsHandlers, ...allPluginHandlers, ]; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts new file mode 100644 index 00000000000..faac331d6df --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/notifications.ts @@ -0,0 +1,13 @@ +import { HttpResponse, http } from 'msw'; + +import alertmanagerConfig from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json'; +import { GrafanaManagedContactPoint } from 'app/plugins/datasource/alertmanager/types'; + +const defaultReceiversResponse: GrafanaManagedContactPoint[] = JSON.parse(JSON.stringify(alertmanagerConfig)) + .alertmanager_config.receivers; + +const getNotificationReceiversHandler = (response = defaultReceiversResponse) => + http.get('/api/v1/notifications/receivers', () => HttpResponse.json(response)); + +const handlers = [getNotificationReceiversHandler()]; +export default handlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/search.ts b/public/app/features/alerting/unified/mocks/server/handlers/search.ts new file mode 100644 index 00000000000..e7f09e5c75d --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/search.ts @@ -0,0 +1,34 @@ +import { HttpResponse, http } from 'msw'; + +import { grafanaRulerNamespace2 } from 'app/features/alerting/unified/mocks/grafanaRulerApi'; +import { DashboardSearchItemType } from 'app/features/search/types'; + +export const FOLDER_TITLE_HAPPY_PATH = 'Folder A'; + +// TODO: Generalise/scaffold out default response for search +// to be more multi purpose +const defaultSearchResponse = [ + { + title: FOLDER_TITLE_HAPPY_PATH, + uid: grafanaRulerNamespace2.uid, + id: 1, + type: DashboardSearchItemType.DashFolder, + }, + { + title: 'Folder B', + id: 2, + }, + { + title: 'Folder / with slash', + id: 2, + uid: 'b', + type: DashboardSearchItemType.DashFolder, + }, +]; + +export const searchHandler = (response = defaultSearchResponse) => + http.get(`/api/search`, () => HttpResponse.json(response)); + +const handlers = [searchHandler()]; + +export default handlers; diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 60f9af1a4ac..8a63d16a0d8 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -10,7 +10,7 @@ import { Receiver, TestReceiversAlert, } from 'app/plugins/datasource/alertmanager/types'; -import { FolderDTO, NotifierDTO, StoreState, ThunkResult } from 'app/types'; +import { FolderDTO, StoreState, ThunkResult } from 'app/types'; import { CombinedRuleGroup, CombinedRuleNamespace, @@ -49,7 +49,6 @@ import { import { alertmanagerApi } from '../api/alertmanagerApi'; import { fetchAnnotations } from '../api/annotations'; import { discoverFeatures } from '../api/buildInfo'; -import { fetchNotifiers } from '../api/grafana'; import { FetchPromRulesFilter, fetchRules } from '../api/prometheus'; import { FetchRulerRulesFilter, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler'; import { RuleFormValues } from '../types/rule-form'; @@ -437,11 +436,6 @@ function reportSwitchingRoutingType(values: RuleFormValues, existingRule: RuleWi } } -export const fetchGrafanaNotifiersAction = createAsyncThunk( - 'unifiedalerting/fetchGrafanaNotifiers', - (): Promise => withSerializedError(fetchNotifiers()) -); - export const fetchGrafanaAnnotationsAction = createAsyncThunk( 'unifiedalerting/fetchGrafanaAnnotations', (alertId: string): Promise => withSerializedError(fetchAnnotations(alertId)) diff --git a/public/app/features/alerting/unified/state/reducers.ts b/public/app/features/alerting/unified/state/reducers.ts index 24f1d0712cc..02217feac8e 100644 --- a/public/app/features/alerting/unified/state/reducers.ts +++ b/public/app/features/alerting/unified/state/reducers.ts @@ -7,7 +7,6 @@ import { fetchAlertGroupsAction, fetchFolderAction, fetchGrafanaAnnotationsAction, - fetchGrafanaNotifiersAction, fetchPromRulesAction, fetchRulerRulesAction, fetchRulesSourceBuildInfoAction, @@ -28,7 +27,6 @@ export const reducer = combineReducers({ ruleForm: combineReducers({ saveRule: createAsyncSlice('saveRule', saveRuleFormAction).reducer, }), - grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer, saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer, deleteAMConfig: createAsyncSlice('deleteAMConfig', deleteAlertManagerConfigAction).reducer, folders: createAsyncMapSlice('folders', fetchFolderAction, (uid) => uid).reducer,