Alerting: Refactor simplified routing contact points hook (#90762)

This commit is contained in:
Tom Ratcliffe 2024-07-29 17:22:31 +01:00 committed by GitHub
parent 24c64fdffa
commit 9d639278f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 421 additions and 479 deletions

View File

@ -17,7 +17,7 @@ import {
} from '@grafana/runtime'; } from '@grafana/runtime';
import appEvents from 'app/core/app_events'; import appEvents from 'app/core/app_events';
import * as ruleActionButtons from 'app/features/alerting/unified/components/rules/RuleActionsButtons'; 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 { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure';
import * as actions from 'app/features/alerting/unified/state/actions'; import * as actions from 'app/features/alerting/unified/state/actions';
import { getMockUser } from 'app/features/users/__mocks__/userMocks'; import { getMockUser } from 'app/features/users/__mocks__/userMocks';
@ -156,7 +156,6 @@ const ui = {
const server = setupMswServer(); const server = setupMswServer();
const configureMockServer = (server: SetupServer) => { const configureMockServer = (server: SetupServer) => {
mockSearchApi(server).search([]);
mockUserApi(server).user(getMockUser()); mockUserApi(server).user(getMockUser());
setAlertmanagerChoices(AlertmanagerChoice.All, 1); setAlertmanagerChoices(AlertmanagerChoice.All, 1);
}; };

View File

@ -1,14 +1,10 @@
import { lastValueFrom } from 'rxjs'; import { lastValueFrom } from 'rxjs';
import { getBackendSrv } from '@grafana/runtime'; 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'; import { getDatasourceAPIUid } from '../utils/datasource';
export function fetchNotifiers(): Promise<NotifierDTO[]> {
return getBackendSrv().get(`/api/alert-notifiers`);
}
interface IntegrationNameObject { interface IntegrationNameObject {
type: string; type: string;
index?: string; index?: string;

View File

@ -1,6 +1,190 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`useContactPoints should return contact points with status 1`] = ` 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": [ "contactPoints": [
{ {
@ -184,177 +368,5 @@ exports[`useContactPoints should return contact points with status 1`] = `
], ],
"error": undefined, "error": undefined,
"isLoading": false, "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],
} }
`; `;

View File

@ -1,17 +1,30 @@
import { renderHook, waitFor } from '@testing-library/react'; 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 { 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 { AccessControlAction } from 'app/types';
import { mockApi, setupMswServer } from '../../mockApi'; import { setupMswServer } from '../../mockApi';
import { grantUserPermissions } from '../../mocks'; import { grantUserPermissions } from '../../mocks';
import { AlertmanagerProvider } from '../../state/AlertmanagerContext'; import { AlertmanagerProvider } from '../../state/AlertmanagerContext';
import { useContactPointsWithStatus } from './useContactPoints'; import { useContactPointsWithStatus } from './useContactPoints';
const server = setupMswServer(); const wrapper = ({ children }: { children: ReactNode }) => {
const ProviderWrapper = getWrapper({ renderWithRouter: true });
return (
<ProviderWrapper>
<AlertmanagerProvider accessType="notification" alertmanagerSourceName="grafana">
{children}
</AlertmanagerProvider>
</ProviderWrapper>
);
};
setupMswServer();
describe('useContactPoints', () => { describe('useContactPoints', () => {
beforeAll(() => { beforeAll(() => {
@ -19,23 +32,10 @@ describe('useContactPoints', () => {
}); });
it('should return contact points with status', async () => { it('should return contact points with status', async () => {
setOnCallIntegrations([ disablePlugin(SupportedPlugin.OnCall);
{
display_name: 'grafana-integration',
value: 'ABC123',
integration_url: 'https://oncall-endpoint.example.com',
},
]);
mockApi(server).getContactPointsList(receivers);
const { result } = renderHook(() => useContactPointsWithStatus(), { const { result } = renderHook(() => useContactPointsWithStatus(), {
wrapper: ({ children }) => ( wrapper,
<TestProvider>
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={'grafana'}>
{children}
</AlertmanagerProvider>
</TestProvider>
),
}); });
await waitFor(() => { await waitFor(() => {
@ -53,20 +53,10 @@ describe('useContactPoints', () => {
integration_url: 'https://oncall-endpoint.example.com', integration_url: 'https://oncall-endpoint.example.com',
}, },
]); ]);
mockApi(server).getContactPointsList(receivers);
const { result } = renderHook( const { result } = renderHook(() => useContactPointsWithStatus(), {
() => useContactPointsWithStatus({ includePoliciesCount: false, receiverStatusPollingInterval: 0 }), wrapper,
{ });
wrapper: ({ children }) => (
<TestProvider>
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName={'grafana'}>
{children}
</AlertmanagerProvider>
</TestProvider>
),
}
);
await waitFor(() => { await waitFor(() => {
expect(result.current.isLoading).toBe(false); expect(result.current.isLoading).toBe(false);
@ -75,5 +65,3 @@ describe('useContactPoints', () => {
}); });
}); });
}); });
const receivers = JSON.parse(JSON.stringify(alertmanagerMock)).alertmanager_config.receivers;

View File

@ -5,9 +5,10 @@
import { produce } from 'immer'; import { produce } from 'immer';
import { remove } from 'lodash'; import { remove } from 'lodash';
import { useMemo } from 'react';
import { alertmanagerApi } from '../../api/alertmanagerApi'; import { alertmanagerApi } from '../../api/alertmanagerApi';
import { onCallApi, OnCallIntegrationDTO } from '../../api/onCallApi'; import { onCallApi } from '../../api/onCallApi';
import { usePluginBridge } from '../../hooks/usePluginBridge'; import { usePluginBridge } from '../../hooks/usePluginBridge';
import { useAlertmanager } from '../../state/AlertmanagerContext'; import { useAlertmanager } from '../../state/AlertmanagerContext';
import { SupportedPlugin } from '../../types/pluginBridges'; 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 * 3. (if available) additional metadata about Grafana Managed contact points
* 4. (if available) the OnCall plugin metadata * 4. (if available) the OnCall plugin metadata
*/ */
interface UseContactPointsWithStatusOptions {
includePoliciesCount: boolean;
receiverStatusPollingInterval?: number;
}
const defaultHookOptions = { const {
includePoliciesCount: true, useGetAlertmanagerConfigurationQuery,
receiverStatusPollingInterval: RECEIVER_STATUS_POLLING_INTERVAL, 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, * Fetch contact points from separate endpoint (i.e. not the Alertmanager config) and combine with
receiverStatusPollingInterval, * OnCall integrations and any additional metadata from list of notifiers
}: UseContactPointsWithStatusOptions = defaultHookOptions) { * (e.g. hydrate with additional names/descriptions)
const { selectedAlertmanager, isGrafanaAlertmanager } = useAlertmanager(); */
const { installed: onCallPluginInstalled, loading: onCallPluginStatusLoading } = usePluginBridge( export const useGetContactPoints = () => {
SupportedPlugin.OnCall const onCallResponse = useOnCallIntegrations();
); const alertNotifiers = useGrafanaNotifiersQuery();
const contactPointsListResponse = useGetContactPointsListQuery();
// fetch receiver status if we're dealing with a Grafana Managed Alertmanager return useMemo(() => {
const fetchContactPointsStatus = alertmanagerApi.endpoints.getContactPointsStatus.useQuery(undefined, { 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, refetchOnFocus: true,
refetchOnReconnect: 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 fetching receiver statuses if not Grafana AM
skip: !isGrafanaAlertmanager, skip: !isGrafanaAlertmanager,
}); });
// fetch notifier metadata from the Grafana API if we're using a Grafana AM this will be used to add additional // 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 // metadata and canonical names to the receiver
const fetchReceiverMetadata = alertmanagerApi.endpoints.grafanaNotifiers.useQuery(undefined, { const fetchReceiverMetadata = useGrafanaNotifiersQuery(undefined, {
skip: !isGrafanaAlertmanager, skip: !isGrafanaAlertmanager,
}); });
// if the OnCall plugin is installed, fetch its list of integrations so we can match those to the Grafana Managed contact points // 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 } = const { data: onCallMetadata, isLoading: onCallPluginIntegrationsLoading } = useOnCallIntegrations({
onCallApi.endpoints.grafanaOnCallIntegrations.useQuery(undefined, { skip: !isGrafanaAlertmanager,
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;
}
// fetch the latest config from the Alertmanager // fetch the latest config from the Alertmanager
// we use this endpoint only when we need to get the number of policies // we use this endpoint only when we need to get the number of policies
const fetchAlertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( const fetchAlertmanagerConfiguration = useGetAlertmanagerConfigurationQuery(selectedAlertmanager!, {
selectedAlertmanager!, ...defaultOptions,
{
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,
selectFromResult: (result) => ({ selectFromResult: (result) => ({
...result, ...result,
contactPoints: result.data contactPoints: result.data
@ -110,40 +145,28 @@ export function useContactPointsWithStatus({
fetchContactPointsStatus.data, fetchContactPointsStatus.data,
fetchReceiverMetadata.data, fetchReceiverMetadata.data,
onCallMetadata, onCallMetadata,
result.data, // contact points from the new readonly endpoint result.data.alertmanager_config.receivers ?? [],
undefined //no config data result.data
) )
: [], : [],
}), }),
skip: includePoliciesCount || !isGrafanaAlertmanager,
}); });
// we will fail silently for fetching OnCall plugin status and integrations // we will fail silently for fetching OnCall plugin status and integrations
const error = const error = fetchAlertmanagerConfiguration.error || fetchContactPointsStatus.error;
fetchAlertmanagerConfiguration.error || fetchGrafanaContactPoints.error || fetchContactPointsStatus.error;
const isLoading = const isLoading =
fetchAlertmanagerConfiguration.isLoading || fetchAlertmanagerConfiguration.isLoading || fetchContactPointsStatus.isLoading || onCallPluginIntegrationsLoading;
fetchGrafanaContactPoints.isLoading ||
fetchContactPointsStatus.isLoading ||
onCallPluginStatusLoading ||
onCallPluginIntegrationsLoading;
const unsortedContactPoints = includePoliciesCount
? fetchAlertmanagerConfiguration.contactPoints
: fetchGrafanaContactPoints.contactPoints;
const contactPoints = unsortedContactPoints.sort((a, b) => a.name.localeCompare(b.name));
return { return {
error, error,
isLoading, isLoading,
contactPoints, contactPoints: fetchAlertmanagerConfiguration.contactPoints,
refetchReceivers: fetchGrafanaContactPoints.refetch,
}; };
} }
export function useDeleteContactPoint(selectedAlertmanager: string) { export function useDeleteContactPoint(selectedAlertmanager: string) {
const [fetchAlertmanagerConfig] = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useLazyQuery(); const [fetchAlertmanagerConfig] = useLazyGetAlertmanagerConfigurationQuery();
const [updateAlertManager, updateAlertmanagerState] = const [updateAlertManager, updateAlertmanagerState] = useUpdateAlertmanagerConfigurationMutation();
alertmanagerApi.endpoints.updateAlertmanagerConfiguration.useMutation();
const deleteTrigger = (contactPointName: string) => { const deleteTrigger = (contactPointName: string) => {
return fetchAlertmanagerConfig(selectedAlertmanager).then(({ data }) => { return fetchAlertmanagerConfig(selectedAlertmanager).then(({ data }) => {

View File

@ -127,7 +127,7 @@ export function enhanceContactPointsWithMetadata(
? (alertmanagerConfiguration?.alertmanager_config.receivers ?? []) ? (alertmanagerConfiguration?.alertmanager_config.receivers ?? [])
: (contactPoints ?? []); : (contactPoints ?? []);
return contactPointsList.map((contactPoint) => { const enhanced = contactPointsList.map((contactPoint) => {
const receivers = extractReceivers(contactPoint); const receivers = extractReceivers(contactPoint);
const statusForReceiver = status.find((status) => status.name === contactPoint.name); 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) { export function isAutoGeneratedPolicy(route: Route) {

View File

@ -4,9 +4,8 @@ import { Props } from 'react-virtualized-auto-sizer';
import { render, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; import { render, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils';
import { byRole, byTestId, byText } from 'testing-library-selector'; import { byRole, byTestId, byText } from 'testing-library-selector';
import { DashboardSearchItemType } from '../../../../search/types'; import { mockExportApi, setupMswServer } from '../../mockApi';
import { mockExportApi, mockSearchApi, setupMswServer } from '../../mockApi'; import { mockDataSource } from '../../mocks';
import { mockDashboardSearchItem, mockDataSource } from '../../mocks';
import { grafanaRulerRule } from '../../mocks/grafanaRulerApi'; import { grafanaRulerRule } from '../../mocks/grafanaRulerApi';
import { setupDataSources } from '../../testSetup/datasources'; import { setupDataSources } from '../../testSetup/datasources';
@ -67,15 +66,6 @@ describe('GrafanaModifyExport', () => {
setupDataSources(dataSources.default); setupDataSources(dataSources.default);
it('Should render edit form for the specified rule', async () => { 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, { mockExportApi(server).modifiedExport(grafanaRulerRule.grafana_alert.namespace_uid, {
yaml: 'Yaml Export Content', yaml: 'Yaml Export Content',
json: 'Json Export Content', json: 'Json Export Content',

View File

@ -8,7 +8,7 @@ import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource'; import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource';
import { ContactPointReceiverSummary } from '../../../contact-points/ContactPoint'; 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 { ContactPointWithMetadata } from '../../../contact-points/utils';
import { ContactPointDetails } from './contactPoint/ContactPointDetails'; import { ContactPointDetails } from './contactPoint/ContactPointDetails';
@ -28,8 +28,9 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
isLoading, isLoading,
error: errorInContactPointStatus, error: errorInContactPointStatus,
contactPoints, contactPoints,
refetchReceivers, refetch: refetchReceivers,
} = useContactPointsWithStatus({ includePoliciesCount: false, receiverStatusPollingInterval: 0 }); } = useGetContactPoints();
const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState< const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState<
ContactPointWithMetadata | undefined ContactPointWithMetadata | undefined
>(); >();

View File

@ -1,72 +1,39 @@
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import { ReactNode } from 'react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { Route } from 'react-router-dom'; import { Route } from 'react-router-dom';
import { TestProvider } from 'test/helpers/TestProvider';
import { ui } from 'test/helpers/alertingRuleEditor'; import { ui } from 'test/helpers/alertingRuleEditor';
import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { clickSelectOption } from 'test/helpers/selectOptionInTest';
import { render, screen, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils';
import { byRole } from 'testing-library-selector'; 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 { contextSrv } from 'app/core/services/context_srv';
import RuleEditor from 'app/features/alerting/unified/RuleEditor'; import RuleEditor from 'app/features/alerting/unified/RuleEditor';
import * as ruler from 'app/features/alerting/unified/api/ruler'; 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 { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks';
import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; 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 { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
import * as utils_config from 'app/features/alerting/unified/utils/config';
import { import {
DataSourceType, DataSourceType,
GRAFANA_DATASOURCE_NAME, GRAFANA_DATASOURCE_NAME,
GRAFANA_RULES_SOURCE_NAME, GRAFANA_RULES_SOURCE_NAME,
useGetAlertManagerDataSourcesByPermissionAndConfig,
} from 'app/features/alerting/unified/utils/datasource'; } from 'app/features/alerting/unified/utils/datasource';
import { getDefaultQueries } from 'app/features/alerting/unified/utils/rule-form'; 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 { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types'; import { AccessControlAction } from 'app/types';
import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; 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 { 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) => (
<input value={value} data-testid="expr" onChange={(e) => onChange(e.target.value)} />
),
}));
jest.mock('app/features/manage-dashboards/state/actions');
jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({
AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) => <div>{actions}</div>, AppChromeUpdate: ({ actions }: { actions: ReactNode }) => <div>{actions}</div>,
})); }));
// 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: () => <p>hi</p>,
}));
const user = userEvent.setup();
// jest.spyOn(utils_config, 'getAllDataSources');
// jest.spyOn(dsByPermission, 'useAlertManagersByPermission');
jest.spyOn(useContactPoints, 'useContactPointsWithStatus');
jest.setTimeout(60 * 1000); jest.setTimeout(60 * 1000);
const mocks = { const mocks = {
getAllDataSources: jest.mocked(utils_config.getAllDataSources),
searchFolders: jest.mocked(searchFolders),
useContactPointsWithStatus: jest.mocked(useContactPoints.useContactPointsWithStatus),
useGetAlertManagerDataSourcesByPermissionAndConfig: jest.mocked(useGetAlertManagerDataSourcesByPermissionAndConfig),
api: { api: {
setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'), setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'),
}, },
@ -74,11 +41,37 @@ const mocks = {
setupMswServer(); 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', () => { describe('Can create a new grafana managed alert unsing simplified routing', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
contextSrv.isEditor = true; contextSrv.isEditor = true;
contextSrv.hasEditPermissionInFolders = true; contextSrv.hasEditPermissionInFolders = true;
config.featureToggles.alertingSimplifiedRouting = true;
grantUserPermissions([ grantUserPermissions([
AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleRead,
AccessControlAction.AlertingRuleUpdate, 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 () => { it('cannot create new grafana managed alert when using simplified routing and not selecting a contact point', async () => {
// no contact points found const user = userEvent.setup();
mocks.useContactPointsWithStatus.mockReturnValue({
contactPoints: [],
isLoading: false,
error: undefined,
refetchReceivers: jest.fn(),
});
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(); renderSimplifiedRuleEditor();
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
await user.type(await ui.inputs.name.find(), 'my great new rule'); await user.type(await ui.inputs.name.find(), 'my great new rule');
const folderInput = await ui.inputs.folder.find(); await selectFolderAndGroup();
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);
//select contact point routing //select contact point routing
await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get());
// do not select a contact point // do not select a contact point
// save and check that call to backend was not made // save and check that call to backend was not made
await user.click(ui.buttons.saveAndExit.get()); await user.click(ui.buttons.saveAndExit.get());
await waitFor(() => { expect(await screen.findByText('Contact point is required.')).toBeInTheDocument();
expect(screen.getByText('Contact point is required.')).toBeInTheDocument(); expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled();
expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled();
});
}); });
it('simplified routing is not available when Grafana AM is not enabled', async () => { it('simplified routing is not available when Grafana AM is not enabled', async () => {
config.featureToggles.alertingSimplifiedRouting = true;
setAlertmanagerChoices(AlertmanagerChoice.External, 1); setAlertmanagerChoices(AlertmanagerChoice.External, 1);
renderSimplifiedRuleEditor(); renderSimplifiedRuleEditor();
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner')); 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 () => { it('can create new grafana managed alert when using simplified routing and selecting a contact point', async () => {
const contactPointsAvailable: ContactPointWithMetadata[] = [ const user = userEvent.setup();
{ const contactPointName = 'lotsa-emails';
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(),
});
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(); renderSimplifiedRuleEditor();
await waitForElementToBeRemoved(screen.getAllByTestId('Spinner'));
await user.type(await ui.inputs.name.find(), 'my great new rule'); await user.type(await ui.inputs.name.find(), 'my great new rule');
const folderInput = await ui.inputs.folder.find(); await selectFolderAndGroup();
await clickSelectOption(folderInput, 'Folder A');
const groupInput = await ui.inputs.group.find();
await user.click(byRole('combobox').get(groupInput));
await clickSelectOption(groupInput, grafanaRulerEmptyGroup.name);
//select contact point routing //select contact point routing
await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get());
const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find(); const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find();
await user.click(byRole('combobox').get(contactPointInput)); await user.click(byRole('combobox').get(contactPointInput));
await clickSelectOption(contactPointInput, 'contact_point1'); await clickSelectOption(contactPointInput, contactPointName);
// save and check what was sent to backend // save and check what was sent to backend
await user.click(ui.buttons.saveAndExit.get()); 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_interval: undefined,
group_wait: undefined, group_wait: undefined,
mute_timings: undefined, mute_timings: undefined,
receiver: 'contact_point1', receiver: contactPointName,
repeat_interval: undefined, repeat_interval: undefined,
}, },
}, },
@ -274,13 +170,10 @@ describe('Can create a new grafana managed alert unsing simplified routing', ()
}); });
function renderSimplifiedRuleEditor() { function renderSimplifiedRuleEditor() {
locationService.push(`/alerting/new/alerting`);
return render( return render(
<TestProvider> <AlertmanagerProvider alertmanagerSourceName={GRAFANA_DATASOURCE_NAME} accessType="notification">
<AlertmanagerProvider alertmanagerSourceName={GRAFANA_DATASOURCE_NAME} accessType="notification"> <Route path={['/alerting/new/:type', '/alerting/:id/edit']} component={RuleEditor} />
<Route path={['/alerting/new/:type', '/alerting/:id/edit']} component={RuleEditor} /> </AlertmanagerProvider>,
</AlertmanagerProvider> { historyOptions: { initialEntries: ['/alerting/new/alerting'] } }
</TestProvider>
); );
} }

View File

@ -4,7 +4,7 @@ import { TestProvider } from 'test/helpers/TestProvider';
import { byLabelText, byRole } from 'testing-library-selector'; import { byLabelText, byRole } from 'testing-library-selector';
import { locationService, setDataSourceSrv } from '@grafana/runtime'; 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 * as analytics from '../../Analytics';
import { MockDataSourceSrv } from '../../mocks'; import { MockDataSourceSrv } from '../../mocks';
@ -12,7 +12,7 @@ import { setupPluginsExtensionsHook } from '../../testSetup/plugins';
import RulesFilter from './RulesFilter'; import RulesFilter from './RulesFilter';
const server = setupMswServer(); setupMswServer();
jest.spyOn(analytics, 'logInfo'); jest.spyOn(analytics, 'logInfo');
jest.mock('./MultipleDataSourcePicker', () => { jest.mock('./MultipleDataSourcePicker', () => {
@ -43,7 +43,6 @@ const ui = {
beforeEach(() => { beforeEach(() => {
locationService.replace({ search: '' }); locationService.replace({ search: '' });
mockSearchApi(server).search([]);
}); });
describe('RulesFilter', () => { describe('RulesFilter', () => {

View File

@ -20,7 +20,6 @@ import {
AlertManagerCortexConfig, AlertManagerCortexConfig,
AlertmanagerReceiver, AlertmanagerReceiver,
EmailConfig, EmailConfig,
GrafanaManagedContactPoint,
GrafanaManagedReceiverConfig, GrafanaManagedReceiverConfig,
MatcherOperator, MatcherOperator,
Route, Route,
@ -170,10 +169,6 @@ export function mockApi(server: SetupServer) {
) )
); );
}, },
getContactPointsList: (response: GrafanaManagedContactPoint[]) => {
server.use(http.get(`/api/v1/notifications/receivers`, () => HttpResponse.json(response)));
},
}; };
} }

View File

@ -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 folderHandlers from 'app/features/alerting/unified/mocks/server/handlers/folders';
import grafanaRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler'; import grafanaRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler';
import mimirRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/mimirRuler'; 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 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 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'; import silenceHandlers from 'app/features/alerting/unified/mocks/server/handlers/silences';
/** /**
* Array of all mock handlers that are required across Alerting tests * Array of all mock handlers that are required across Alerting tests
*/ */
@ -25,6 +28,8 @@ const allHandlers = [
...folderHandlers, ...folderHandlers,
...pluginsHandlers, ...pluginsHandlers,
...silenceHandlers, ...silenceHandlers,
...searchHandlers,
...notificationsHandlers,
...allPluginHandlers, ...allPluginHandlers,
]; ];

View File

@ -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;

View File

@ -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;

View File

@ -10,7 +10,7 @@ import {
Receiver, Receiver,
TestReceiversAlert, TestReceiversAlert,
} from 'app/plugins/datasource/alertmanager/types'; } from 'app/plugins/datasource/alertmanager/types';
import { FolderDTO, NotifierDTO, StoreState, ThunkResult } from 'app/types'; import { FolderDTO, StoreState, ThunkResult } from 'app/types';
import { import {
CombinedRuleGroup, CombinedRuleGroup,
CombinedRuleNamespace, CombinedRuleNamespace,
@ -49,7 +49,6 @@ import {
import { alertmanagerApi } from '../api/alertmanagerApi'; import { alertmanagerApi } from '../api/alertmanagerApi';
import { fetchAnnotations } from '../api/annotations'; import { fetchAnnotations } from '../api/annotations';
import { discoverFeatures } from '../api/buildInfo'; import { discoverFeatures } from '../api/buildInfo';
import { fetchNotifiers } from '../api/grafana';
import { FetchPromRulesFilter, fetchRules } from '../api/prometheus'; import { FetchPromRulesFilter, fetchRules } from '../api/prometheus';
import { FetchRulerRulesFilter, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler'; import { FetchRulerRulesFilter, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler';
import { RuleFormValues } from '../types/rule-form'; import { RuleFormValues } from '../types/rule-form';
@ -437,11 +436,6 @@ function reportSwitchingRoutingType(values: RuleFormValues, existingRule: RuleWi
} }
} }
export const fetchGrafanaNotifiersAction = createAsyncThunk(
'unifiedalerting/fetchGrafanaNotifiers',
(): Promise<NotifierDTO[]> => withSerializedError(fetchNotifiers())
);
export const fetchGrafanaAnnotationsAction = createAsyncThunk( export const fetchGrafanaAnnotationsAction = createAsyncThunk(
'unifiedalerting/fetchGrafanaAnnotations', 'unifiedalerting/fetchGrafanaAnnotations',
(alertId: string): Promise<StateHistoryItem[]> => withSerializedError(fetchAnnotations(alertId)) (alertId: string): Promise<StateHistoryItem[]> => withSerializedError(fetchAnnotations(alertId))

View File

@ -7,7 +7,6 @@ import {
fetchAlertGroupsAction, fetchAlertGroupsAction,
fetchFolderAction, fetchFolderAction,
fetchGrafanaAnnotationsAction, fetchGrafanaAnnotationsAction,
fetchGrafanaNotifiersAction,
fetchPromRulesAction, fetchPromRulesAction,
fetchRulerRulesAction, fetchRulerRulesAction,
fetchRulesSourceBuildInfoAction, fetchRulesSourceBuildInfoAction,
@ -28,7 +27,6 @@ export const reducer = combineReducers({
ruleForm: combineReducers({ ruleForm: combineReducers({
saveRule: createAsyncSlice('saveRule', saveRuleFormAction).reducer, saveRule: createAsyncSlice('saveRule', saveRuleFormAction).reducer,
}), }),
grafanaNotifiers: createAsyncSlice('grafanaNotifiers', fetchGrafanaNotifiersAction).reducer,
saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer, saveAMConfig: createAsyncSlice('saveAMConfig', updateAlertManagerConfigAction).reducer,
deleteAMConfig: createAsyncSlice('deleteAMConfig', deleteAlertManagerConfigAction).reducer, deleteAMConfig: createAsyncSlice('deleteAMConfig', deleteAlertManagerConfigAction).reducer,
folders: createAsyncMapSlice('folders', fetchFolderAction, (uid) => uid).reducer, folders: createAsyncMapSlice('folders', fetchFolderAction, (uid) => uid).reducer,