diff --git a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx index ad3d0d70770..d6af58c6bb3 100644 --- a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx @@ -12,8 +12,8 @@ import { searchFolders } from '../../manage-dashboards/state/actions'; import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; -import { mockApi, mockFeatureDiscoveryApi, setupMswServer } from './mockApi'; -import { grantUserPermissions, labelsPluginMetaMock, mockDataSource } from './mocks'; +import { mockFeatureDiscoveryApi, setupMswServer } from './mockApi'; +import { grantUserPermissions, mockDataSource } from './mocks'; import { emptyExternalAlertmanagersResponse, mockAlertmanagersResponse } from './mocks/alertmanagerApi'; import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; import { setupDataSources } from './testSetup/datasources'; @@ -51,7 +51,6 @@ const server = setupMswServer(); mockFeatureDiscoveryApi(server).discoverDsFeatures(dataSources.default, buildInfoResponse.mimir); mockAlertmanagersResponse(server, emptyExternalAlertmanagersResponse); -mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); // these tests are rather slow because we have to wait for various API calls and mocks to be called // and wait for the UI to be in particular states, drone seems to time out quite often so diff --git a/public/app/features/alerting/unified/api/alertmanagerApi.ts b/public/app/features/alerting/unified/api/alertmanagerApi.ts index f519acd1259..414407fdcad 100644 --- a/public/app/features/alerting/unified/api/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/api/alertmanagerApi.ts @@ -276,6 +276,7 @@ export const alertmanagerApi = alertingApi.injectEndpoints({ }, }), // Grafana Managed Alertmanager only + // TODO: Remove as part of migration to k8s API for receivers getContactPointsList: build.query({ query: () => ({ url: '/api/v1/notifications/receivers' }), }), diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index e82566ad970..29af24d60eb 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -1,11 +1,10 @@ -import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { MemoryHistoryBuildOptions } from 'history'; import { noop } from 'lodash'; -import { ComponentProps, PropsWithChildren } from 'react'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { ComponentProps, ReactNode } from 'react'; +import { render, screen, waitFor, waitForElementToBeRemoved } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; -import { locationService } from '@grafana/runtime'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; @@ -16,7 +15,6 @@ import { setupDataSources } from '../../testSetup/datasources'; import { DataSourceType } from '../../utils/datasource'; import ContactPoints, { ContactPoint } from './ContactPoints'; -import setupGrafanaManagedServer from './__mocks__/grafanaManagedServer'; import setupMimirFlavoredServer, { MIMIR_DATASOURCE_UID } from './__mocks__/mimirFlavoredServer'; import setupVanillaAlertmanagerFlavoredServer, { VANILLA_ALERTMANAGER_DATASOURCE_UID, @@ -42,66 +40,54 @@ import { RouteReference } from './utils'; const server = setupMswServer(); const renderWithProvider = ( - providerProps?: Partial>, - contactPointProps?: Partial> + children: ReactNode, + historyOptions?: MemoryHistoryBuildOptions, + providerProps?: Partial> ) => render( - - - - - + + {children} + , + { historyOptions } ); describe('contact points', () => { - beforeEach(() => { - // The location service is stateful between tests - `TestProvider` uses the same instance between each test - // and this results in the query params being persisted between tests - // To get round this for now, we can push "/" onto the history so there are no query params - locationService.push('/'); - }); - describe('Contact points with Grafana managed alertmanager', () => { beforeEach(() => { grantUserPermissions([ AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite, ]); - - setupGrafanaManagedServer(server); }); describe('tabs behaviour', () => { test('loads contact points tab', async () => { - locationService.push('/?tab=contact_points'); - renderWithProvider(); + renderWithProvider(, { initialEntries: ['/?tab=contact_points'] }); expect(await screen.findByText(/add contact point/i)).toBeInTheDocument(); }); test('loads templates tab', async () => { - locationService.push('/?tab=templates'); - renderWithProvider(); + renderWithProvider(, { initialEntries: ['/?tab=templates'] }); expect(await screen.findByText(/add notification template/i)).toBeInTheDocument(); }); test('defaults to contact points tab with invalid query param', async () => { - locationService.push('/?tab=foo_bar'); - renderWithProvider(); + renderWithProvider(, { initialEntries: ['/?tab=foo_bar'] }); expect(await screen.findByText(/add contact point/i)).toBeInTheDocument(); }); test('defaults to contact points tab with no query param', async () => { - renderWithProvider(); + renderWithProvider(); expect(await screen.findByText(/add contact point/i)).toBeInTheDocument(); }); }); it('should show / hide loading states, have all actions enabled', async () => { - renderWithProvider(); + renderWithProvider(); await waitFor(async () => { expect(screen.getByText('Loading...')).toBeInTheDocument(); @@ -140,7 +126,7 @@ describe('contact points', () => { it('should disable certain actions if the user has no write permissions', async () => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead]); - renderWithProvider(); + renderWithProvider(); // wait for loading to be done await waitFor(async () => { @@ -178,10 +164,7 @@ describe('contact points', () => { it('should call delete when clicked and not disabled', async () => { const onDelete = jest.fn(); - - render(, { - wrapper, - }); + renderWithProvider(); const moreActions = screen.getByRole('button', { name: /More/ }); await userEvent.click(moreActions); @@ -193,9 +176,7 @@ describe('contact points', () => { }); it('should disable edit button', async () => { - render(, { - wrapper, - }); + renderWithProvider(); const moreActions = screen.getByRole('button', { name: /More/ }); expect(moreActions).not.toBeDisabled(); @@ -205,9 +186,7 @@ describe('contact points', () => { }); it('should disable buttons when provisioned', async () => { - render(, { - wrapper, - }); + renderWithProvider(); expect(screen.getByText(/provisioned/i)).toBeInTheDocument(); @@ -235,9 +214,7 @@ describe('contact points', () => { }, ]; - render(, { - wrapper, - }); + renderWithProvider(); expect(screen.getByRole('link', { name: /1 notification policy/ })).toBeInTheDocument(); @@ -258,9 +235,7 @@ describe('contact points', () => { }, ]; - render(, { - wrapper, - }); + renderWithProvider(); const moreActions = screen.getByRole('button', { name: /More/ }); await userEvent.click(moreActions); @@ -270,7 +245,7 @@ describe('contact points', () => { }); it('should be able to search', async () => { - renderWithProvider(); + renderWithProvider(); const searchInput = screen.getByRole('textbox', { name: 'search contact points' }); await userEvent.type(searchInput, 'slack'); @@ -308,7 +283,7 @@ describe('contact points', () => { }); it('should show / hide loading states, have the right actions enabled', async () => { - renderWithProvider({ alertmanagerSourceName: MIMIR_DATASOURCE_UID }); + renderWithProvider(, undefined, { alertmanagerSourceName: MIMIR_DATASOURCE_UID }); await waitFor(async () => { expect(screen.getByText('Loading...')).toBeInTheDocument(); @@ -364,16 +339,7 @@ describe('contact points', () => { }); it("should not allow any editing because it's not supported", async () => { - render( - - - - - - ); + renderWithProvider(, undefined, { alertmanagerSourceName: VANILLA_ALERTMANAGER_DATASOURCE_UID }); await waitFor(async () => { expect(screen.getByText('Loading...')).toBeInTheDocument(); @@ -394,9 +360,3 @@ describe('contact points', () => { }); }); }); - -const wrapper = ({ children }: PropsWithChildren) => ( - - {children} - -); diff --git a/public/app/features/alerting/unified/components/contact-points/NewContactPoint.test.tsx b/public/app/features/alerting/unified/components/contact-points/NewContactPoint.test.tsx index bde239feebe..e9f8bc9820d 100644 --- a/public/app/features/alerting/unified/components/contact-points/NewContactPoint.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/NewContactPoint.test.tsx @@ -1,6 +1,4 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { TestProvider } from 'test/helpers/TestProvider'; +import { render, screen, waitFor, userEvent } from 'test/test-utils'; import { byLabelText, byPlaceholderText, byRole, byTestId } from 'testing-library-selector'; import { AccessControlAction } from 'app/types'; @@ -10,10 +8,7 @@ import { grantUserPermissions } from '../../mocks'; import { AlertmanagerProvider } from '../../state/AlertmanagerContext'; import NewContactPoint from './NewContactPoint'; -import setupGrafanaManagedServer, { - setupSaveEndpointMock, - setupTestEndpointMock, -} from './__mocks__/grafanaManagedServer'; +import { setupSaveEndpointMock, setupTestEndpointMock } from './__mocks__/grafanaManagedServer'; import 'core-js/stable/structured-clone'; @@ -22,7 +17,6 @@ const user = userEvent.setup(); beforeEach(() => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite]); - setupGrafanaManagedServer(server); }); it('should be able to test and save a receiver', async () => { @@ -32,15 +26,12 @@ it('should be able to test and save a receiver', async () => { render( - , - { wrapper: TestProvider } + ); // wait for loading to be done // type in a name for the new receiver - await waitFor(() => { - user.type(ui.inputs.name.get(), 'my new receiver'); - }); + await user.type(await ui.inputs.name.find(), 'my new receiver'); // enter some email const email = ui.inputs.email.addresses.get(); @@ -50,12 +41,8 @@ it('should be able to test and save a receiver', async () => { // try to test the contact point await user.click(await ui.testContactPointButton.find()); - await waitFor( - () => { - expect(ui.testContactPointModal.get()).toBeInTheDocument(); - }, - { timeout: 1000 } - ); + expect(await ui.testContactPointModal.find()).toBeInTheDocument(); + await user.click(ui.customContactPointOption.get()); // enter custom annotations and labels @@ -70,14 +57,14 @@ it('should be able to test and save a receiver', async () => { // it can't seem to assert on the success toast await waitFor(() => { expect(testMock).toHaveBeenCalled(); - expect(testMock.mock.lastCall).toMatchSnapshot(); }); + expect(testMock.mock.lastCall).toMatchSnapshot(); await user.click(ui.saveContactButton.get()); await waitFor(() => { expect(saveMock).toHaveBeenCalled(); - expect(saveMock.mock.lastCall).toMatchSnapshot(); }); + expect(saveMock.mock.lastCall).toMatchSnapshot(); }); const ui = { diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts index 74412e093bd..fdcd38efc47 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts @@ -1,48 +1,7 @@ import { http, HttpResponse } from 'msw'; import { SetupServer } from 'msw/node'; -import { AlertmanagerChoice, AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import { ReceiversStateDTO } from 'app/types'; - -import { mockApi } from '../../../mockApi'; -import { mockAlertmanagerChoiceResponse } from '../../../mocks/alertmanagerApi'; -import { grafanaNotifiersMock } from '../../../mocks/grafana-notifiers'; - -import alertmanagerMock from './alertmanager.config.mock.json'; -import receiversMock from './receivers.mock.json'; - -export default (server: SetupServer) => { - server.use( - // this endpoint is a grafana built-in alertmanager - http.get('/api/alertmanager/grafana/config/api/v1/alerts', () => - HttpResponse.json(alertmanagerMock) - ), - // this endpoint is only available for the built-in alertmanager - http.get('/api/alertmanager/grafana/config/api/v1/receivers', () => - HttpResponse.json(receiversMock) - ), - // this endpoint will respond if the OnCall plugin is installed - http.get('/api/plugins/grafana-oncall-app/settings', () => HttpResponse.json({}, { status: 404 })), - - // this endpoint looks up alerts when copying notification template - http.get('/api/alertmanager/grafana/api/v2/alerts', () => HttpResponse.json([])), - - // this endpoint returns preview of a template we're editing - http.post('/api/alertmanager/grafana/config/api/v1/templates/test', () => HttpResponse.json({}, { status: 200 })) - ); - - // this endpoint is for rendering the "additional AMs to configure" warning - mockAlertmanagerChoiceResponse(server, { - alertmanagersChoice: AlertmanagerChoice.Internal, - numExternalAlertmanagers: 1, - }); - - // mock the endpoint for contact point metadata - mockApi(server).grafanaNotifiers(grafanaNotifiersMock); - - return server; -}; - +/** @deprecated */ export const setupTestEndpointMock = (server: SetupServer) => { const mock = jest.fn(); @@ -64,6 +23,7 @@ export const setupTestEndpointMock = (server: SetupServer) => { return mock; }; +/** @deprecated */ export const setupSaveEndpointMock = (server: SetupServer) => { const mock = jest.fn(); diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts index a954a990ac4..6c921476014 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts @@ -21,9 +21,7 @@ export default (server: SetupServer) => { }, { status: 404 } ) - ), - // this endpoint will respond if the OnCall plugin is installed - http.get('/api/plugins/grafana-oncall-app/settings', () => HttpResponse.json({}, { status: 404 })) + ) ); return server; diff --git a/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts b/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts index 92f9bc44594..1ab40523b8a 100644 --- a/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts +++ b/public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts @@ -12,9 +12,7 @@ export default (server: SetupServer) => { server.use( http.get(`/api/alertmanager/${VANILLA_ALERTMANAGER_DATASOURCE_UID}/api/v2/status`, () => HttpResponse.json(vanillaAlertManagerConfig) - ), - // this endpoint will respond if the OnCall plugin is installed - http.get('/api/plugins/grafana-oncall-app/settings', () => HttpResponse.json({}, { status: 404 })) + ) ); return server; diff --git a/public/app/features/alerting/unified/components/contact-points/__snapshots__/NewContactPoint.test.tsx.snap b/public/app/features/alerting/unified/components/contact-points/__snapshots__/NewContactPoint.test.tsx.snap index b76bf0d151e..8a4a47ab936 100644 --- a/public/app/features/alerting/unified/components/contact-points/__snapshots__/NewContactPoint.test.tsx.snap +++ b/public/app/features/alerting/unified/components/contact-points/__snapshots__/NewContactPoint.test.tsx.snap @@ -19,7 +19,7 @@ exports[`should be able to test and save a receiver 1`] = ` "name": "test", "secureSettings": {}, "settings": { - "addresses": "nteews treerc@egirvaefrana.com", + "addresses": "tester@grafana.com", "singleEmail": false, }, "type": "email", @@ -132,16 +132,16 @@ exports[`should be able to test and save a receiver 2`] = ` "grafana_managed_receiver_configs": [ { "disableResolveMessage": false, - "name": "my ", + "name": "my new receiver", "secureSettings": {}, "settings": { - "addresses": "nteews treerc@egirvaefrana.com", + "addresses": "tester@grafana.com", "singleEmail": false, }, "type": "email", }, ], - "name": "my ", + "name": "my new receiver", }, ], "route": { 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 86cb914870c..6ea7debf203 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 @@ -78,8 +78,8 @@ exports[`useContactPoints should return contact points with status 1`] = ` "type": "oncall", Symbol(receiver_status): undefined, Symbol(receiver_metadata): { - "description": undefined, - "name": "Oncall", + "description": "Sends notifications to Grafana OnCall", + "name": "Grafana OnCall", }, Symbol(receiver_plugin_metadata): { "description": "grafana-integration", @@ -259,8 +259,8 @@ exports[`useContactPoints when having oncall plugin installed and no alert manag "type": "oncall", Symbol(receiver_status): undefined, Symbol(receiver_metadata): { - "description": undefined, - "name": "Oncall", + "description": "Sends notifications to Grafana OnCall", + "name": "Grafana OnCall", }, Symbol(receiver_plugin_metadata): { "icon": "public/img/alerting/oncall_logo.svg", 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 52a059e835d..88391972a81 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 @@ -2,31 +2,24 @@ import { renderHook, waitFor } from '@testing-library/react'; import { TestProvider } from 'test/helpers/TestProvider'; import alertmanagerMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json'; +import { setOnCallIntegrations } from 'app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins'; import { AccessControlAction } from 'app/types'; -import { ONCALL_INTEGRATION_V2_FEATURE } from '../../api/onCallApi'; import { mockApi, setupMswServer } from '../../mockApi'; -import { grantUserPermissions, onCallPluginMetaMock } from '../../mocks'; +import { grantUserPermissions } from '../../mocks'; import { AlertmanagerProvider } from '../../state/AlertmanagerContext'; -import setupGrafanaManagedServer from './__mocks__/grafanaManagedServer'; import { useContactPointsWithStatus } from './useContactPoints'; const server = setupMswServer(); describe('useContactPoints', () => { - beforeEach(() => { - setupGrafanaManagedServer(server); - }); - beforeAll(() => { grantUserPermissions([AccessControlAction.AlertingNotificationsRead]); }); it('should return contact points with status', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([ + setOnCallIntegrations([ { display_name: 'grafana-integration', value: 'ABC123', @@ -34,6 +27,7 @@ describe('useContactPoints', () => { }, ]); mockApi(server).getContactPointsList(receivers); + const { result } = renderHook(() => useContactPointsWithStatus(), { wrapper: ({ children }) => ( @@ -43,16 +37,16 @@ describe('useContactPoints', () => { ), }); + await waitFor(() => { expect(result.current.isLoading).toBe(false); - expect(result.current).toMatchSnapshot(); }); + expect(result.current).toMatchSnapshot(); }); + describe('when having oncall plugin installed and no alert manager config data', () => { it('should return contact points with oncall metadata', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([ + setOnCallIntegrations([ { display_name: 'grafana-integration', value: 'ABC123', @@ -60,6 +54,7 @@ describe('useContactPoints', () => { }, ]); mockApi(server).getContactPointsList(receivers); + const { result } = renderHook( () => useContactPointsWithStatus({ includePoliciesCount: false, receiverStatusPollingInterval: 0 }), { @@ -75,8 +70,8 @@ describe('useContactPoints', () => { await waitFor(() => { expect(result.current.isLoading).toBe(false); - expect(result.current).toMatchSnapshot(); }); + expect(result.current).toMatchSnapshot(); }); }); }); diff --git a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx index d4644cacc1e..105e6553fc5 100644 --- a/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx @@ -1,23 +1,24 @@ -import { render, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { TestProvider } from 'test/helpers/TestProvider'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; +import { render, waitFor, userEvent } from 'test/test-utils'; import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector'; +import { disablePlugin } from 'app/features/alerting/unified/mocks/server/configure'; +import { + setOnCallFeatures, + setOnCallIntegrations, +} from 'app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins'; +import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types'; -import { ONCALL_INTEGRATION_V2_FEATURE } from '../../../api/onCallApi'; -import { AlertmanagerConfigBuilder, mockApi, setupMswServer } from '../../../mockApi'; -import { grafanaAlertNotifiersMock } from '../../../mockGrafanaNotifiers'; -import { onCallPluginMetaMock } from '../../../mocks'; +import { AlertmanagerConfigBuilder, setupMswServer } from '../../../mockApi'; import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; import { GrafanaReceiverForm } from './GrafanaReceiverForm'; import 'core-js/stable/structured-clone'; -const server = setupMswServer(); +setupMswServer(); const ui = { loadingIndicator: byText('Loading notifiers...'), @@ -40,14 +41,11 @@ describe('GrafanaReceiverForm', () => { describe('OnCall contact point', () => { it('OnCall contact point should be disabled if OnCall integration is not enabled', async () => { - mockApi(server).grafanaNotifiers(grafanaAlertNotifiersMock); - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: false }); + disablePlugin(SupportedPlugin.OnCall); const amConfig = getAmCortexConfig((_) => {}); - render(, { - wrapper: TestProvider, - }); + render(); await waitFor(() => expect(ui.loadingIndicator.query()).not.toBeInTheDocument()); @@ -60,10 +58,7 @@ describe('GrafanaReceiverForm', () => { }); it('OnCall contact point should support new and existing integration options if OnCall integration V2 is enabled', async () => { - mockApi(server).grafanaNotifiers(grafanaAlertNotifiersMock); - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([ + setOnCallIntegrations([ { display_name: 'nasa-oncall', value: 'nasa-oncall', integration_url: 'https://nasa.oncall.example.com' }, { display_name: 'apac-oncall', value: 'apac-oncall', integration_url: 'https://apac.oncall.example.com' }, ]); @@ -72,9 +67,7 @@ describe('GrafanaReceiverForm', () => { const user = userEvent.setup(); - render(, { - wrapper: TestProvider, - }); + render(); await waitFor(() => expect(ui.loadingIndicator.query()).not.toBeInTheDocument()); @@ -112,10 +105,8 @@ describe('GrafanaReceiverForm', () => { }); it('Should render URL text input field for OnCall concact point if OnCall plugin uses legacy integration', async () => { - mockApi(server).grafanaNotifiers(grafanaAlertNotifiersMock); - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([]); - mockApi(server).oncall.getOnCallIntegrations([]); + setOnCallFeatures([]); + setOnCallIntegrations([]); const amConfig = getAmCortexConfig((config) => config.addReceivers((receiver) => @@ -130,10 +121,7 @@ describe('GrafanaReceiverForm', () => { alertManagerSourceName={GRAFANA_RULES_SOURCE_NAME} config={amConfig} existing={amConfig.alertmanager_config.receivers![0]} - />, - { - wrapper: TestProvider, - } + /> ); await waitFor(() => expect(ui.loadingIndicator.query()).not.toBeInTheDocument()); diff --git a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts index 1fb81862753..be07341b87c 100644 --- a/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts +++ b/public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts @@ -1,29 +1,37 @@ import { renderHook, waitFor } from '@testing-library/react'; import { TestProvider } from 'test/helpers/TestProvider'; -import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; -import { onCallPluginMetaMock } from 'app/features/alerting/unified/mocks'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { disablePlugin } from 'app/features/alerting/unified/mocks/server/configure'; +import { + setOnCallFeatures, + setOnCallIntegrations, +} from 'app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins'; +import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { option } from 'app/features/alerting/unified/utils/notifier-types'; import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; -import { ONCALL_INTEGRATION_V2_FEATURE } from '../../../../api/onCallApi'; - import { ReceiverTypes } from './onCall'; import { OnCallIntegrationSetting, OnCallIntegrationType, useOnCallIntegration } from './useOnCallIntegration'; -const server = setupMswServer(); +setupMswServer(); describe('useOnCallIntegration', () => { + beforeEach(() => { + setOnCallIntegrations([ + { + display_name: 'grafana-integration', + value: 'ABC123', + integration_url: 'https://oncall.com/grafana-integration', + }, + ]); + }); afterEach(() => { clearPluginSettingsCache(); }); describe('When OnCall Alerting V2 integration enabled', () => { - it('extendOnCalReceivers should add new settings to the oncall receiver', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([]); - + it('extendOnCallReceivers should add new settings to the oncall receiver', async () => { const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -31,7 +39,7 @@ describe('useOnCallIntegration', () => { const { extendOnCallReceivers } = result.current; const receiver = extendOnCallReceivers({ - name: 'OnCall Conctact point', + name: 'OnCall Contact point', grafana_managed_receiver_configs: [ { name: 'Oncall-integration', @@ -54,17 +62,6 @@ describe('useOnCallIntegration', () => { }); it('createOnCallIntegrations should provide integration name and url validators', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([ - { - display_name: 'grafana-integration', - value: 'ABC123', - integration_url: 'https://oncall.com/grafana-integration', - }, - ]); - mockApi(server).oncall.validateIntegrationName(['grafana-integration', 'alertmanager-integration']); - const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -86,16 +83,6 @@ describe('useOnCallIntegration', () => { }); it('extendOnCallNotifierFeatures should add integration type and name options and swap url to a select option', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([ONCALL_INTEGRATION_V2_FEATURE]); - mockApi(server).oncall.getOnCallIntegrations([ - { - display_name: 'grafana-integration', - value: 'ABC123', - integration_url: 'https://oncall.com/grafana-integration', - }, - ]); - const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -127,11 +114,12 @@ describe('useOnCallIntegration', () => { }); describe('When OnCall Alerting V2 integration disabled', () => { - it('extendOnCalReceivers should not add new settings to the oncall receiver', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([]); - mockApi(server).oncall.getOnCallIntegrations([]); + beforeEach(() => { + setOnCallFeatures([]); + setOnCallIntegrations([]); + }); + it('extendOnCalReceivers should not add new settings to the oncall receiver', async () => { const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -159,10 +147,6 @@ describe('useOnCallIntegration', () => { }); it('extendConCallNotifierFeatures should not extend notifier', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: true }); - mockApi(server).oncall.features([]); - mockApi(server).oncall.getOnCallIntegrations([]); - const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -183,9 +167,11 @@ describe('useOnCallIntegration', () => { }); describe('When OnCall plugin disabled', () => { - it('extendOnCalReceivers should not add new settings to the oncall receiver', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: false }); + beforeEach(() => { + disablePlugin(SupportedPlugin.OnCall); + }); + it('extendOnCalReceivers should not add new settings to the oncall receiver', async () => { const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); @@ -213,8 +199,6 @@ describe('useOnCallIntegration', () => { }); it('extendConCallNotifierFeatures should not extend notifier', async () => { - mockApi(server).plugins.getPluginSettings({ ...onCallPluginMetaMock, enabled: false }); - const { result } = renderHook(() => useOnCallIntegration(), { wrapper: TestProvider }); await waitFor(() => expect(result.current.isLoadingOnCallIntegration).toBe(false)); diff --git a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx index c1b3ab3286e..0da7d7ab252 100644 --- a/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx @@ -6,8 +6,8 @@ import { TestProvider } from 'test/helpers/TestProvider'; import { clearPluginSettingsCache } from 'app/features/plugins/pluginSettings'; -import { mockAlertRuleApi, mockApi, setupMswServer } from '../../../mockApi'; -import { getGrafanaRule, labelsPluginMetaMock } from '../../../mocks'; +import { mockAlertRuleApi, setupMswServer } from '../../../mockApi'; +import { getGrafanaRule } from '../../../mocks'; import { GRAFANA_RULES_SOURCE_NAME } from '../../../utils/datasource'; import LabelsField, { LabelsWithSuggestions } from './LabelsField'; @@ -70,7 +70,6 @@ describe('LabelsField with suggestions', () => { clearPluginSettingsCache(); }); beforeEach(() => { - mockApi(server).plugins.getPluginSettings({ ...labelsPluginMetaMock, enabled: false }); mockAlertRuleApi(server).rulerRules(GRAFANA_RULES_SOURCE_NAME, { [grafanaRule.namespace.name]: [{ name: grafanaRule.group.name, interval: '1m', rules: [grafanaRule.rulerRule!] }], }); diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx index 89cbc375386..7c60e174b49 100644 --- a/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesTable.test.tsx @@ -2,10 +2,10 @@ import { render, userEvent, screen } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; import { setPluginExtensionsHook } from '@grafana/runtime'; -import { mockApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { AlertRuleAction, useAlertRuleAbility } from '../../hooks/useAbilities'; -import { getCloudRule, getGrafanaRule, getMockPluginMeta } from '../../mocks'; +import { getCloudRule, getGrafanaRule } from '../../mocks'; import { RulesTable } from './RulesTable'; @@ -32,15 +32,9 @@ const ui = { }; const user = userEvent.setup(); -const server = setupMswServer(); +setupMswServer(); describe('RulesTable RBAC', () => { - beforeEach(() => { - mockApi(server).plugins.getPluginSettings({ - ...getMockPluginMeta('grafana-incident-app', 'Grafana Incident'), - }); - }); - describe('Grafana rules action buttons', () => { const grafanaRule = getGrafanaRule({ name: 'Grafana' }); diff --git a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx index c5865159778..df4813e52a9 100644 --- a/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx +++ b/public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx @@ -14,7 +14,6 @@ import AlertmanagerConfig from './AlertmanagerConfig'; import { EXTERNAL_VANILLA_ALERTMANAGER_UID, PROVISIONED_MIMIR_ALERTMANAGER_UID, - setupGrafanaManagedServer, setupVanillaAlertmanagerServer, } from './__mocks__/server'; @@ -43,10 +42,9 @@ const ui = { }; describe('Alerting Settings', () => { - const server = setupMswServer(); + setupMswServer(); beforeEach(() => { - setupGrafanaManagedServer(server); grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingInstanceRead]); }); @@ -54,7 +52,7 @@ describe('Alerting Settings', () => { const onReset = jest.fn(); renderConfiguration('grafana', { onReset }); - await userEvent.click(await ui.resetButton.get()); + await userEvent.click(await ui.resetButton.find()); await waitFor(() => { expect(ui.resetConfirmButton.query()).toBeInTheDocument(); diff --git a/public/app/features/alerting/unified/mockApi.ts b/public/app/features/alerting/unified/mockApi.ts index 17a26ddef85..d1d40f5806f 100644 --- a/public/app/features/alerting/unified/mockApi.ts +++ b/public/app/features/alerting/unified/mockApi.ts @@ -1,11 +1,11 @@ import { http, HttpResponse } from 'msw'; import { setupServer, SetupServer } from 'msw/node'; -import { DataSourceInstanceSettings, PluginMeta } from '@grafana/data'; +import { DataSourceInstanceSettings } from '@grafana/data'; import { setBackendSrv } from '@grafana/runtime'; import { AlertGroupUpdated } from 'app/features/alerting/unified/api/alertRuleApi'; import allHandlers from 'app/features/alerting/unified/mocks/server/all-handlers'; -import { DashboardDTO, FolderDTO, NotifierDTO, OrgUser } from 'app/types'; +import { DashboardDTO, FolderDTO, OrgUser } from 'app/types'; import { PromBuildInfoResponse, PromRulesResponse, @@ -27,8 +27,6 @@ import { } from '../../../plugins/datasource/alertmanager/types'; import { DashboardSearchItem } from '../../search/types'; -import { OnCallIntegrationDTO } from './api/onCallApi'; - type Configurator = (builder: T) => T; export class AlertmanagerConfigBuilder { @@ -173,47 +171,9 @@ export function mockApi(server: SetupServer) { ); }, - grafanaNotifiers: (response: NotifierDTO[]) => { - server.use(http.get(`api/alert-notifiers`, () => HttpResponse.json(response))); - }, - - plugins: { - getPluginSettings: (response: PluginMeta) => { - server.use(http.get(`api/plugins/${response.id}/settings`, () => HttpResponse.json(response))); - }, - }, getContactPointsList: (response: GrafanaManagedContactPoint[]) => { server.use(http.get(`/api/v1/notifications/receivers`, () => HttpResponse.json(response))); }, - - oncall: { - getOnCallIntegrations: (response: OnCallIntegrationDTO[]) => { - server.use( - http.get(`api/plugin-proxy/grafana-oncall-app/api/internal/v1/alert_receive_channels`, () => - HttpResponse.json(response) - ) - ); - }, - features: (response: string[]) => { - server.use( - http.get(`api/plugin-proxy/grafana-oncall-app/api/internal/v1/features`, () => HttpResponse.json(response)) - ); - }, - validateIntegrationName: (invalidNames: string[]) => { - server.use( - http.get( - `api/plugin-proxy/grafana-oncall-app/api/internal/v1/alert_receive_channels/validate_name`, - ({ request }) => { - const url = new URL(request.url); - const isValid = !invalidNames.includes(url.searchParams.get('verbal_name') ?? ''); - return HttpResponse.json(isValid, { - status: isValid ? 200 : 409, - }); - } - ) - ); - }, - }, }; } diff --git a/public/app/features/alerting/unified/mocks.ts b/public/app/features/alerting/unified/mocks.ts index 11d9ae12c1a..2d737ef6a74 100644 --- a/public/app/features/alerting/unified/mocks.ts +++ b/public/app/features/alerting/unified/mocks.ts @@ -12,8 +12,6 @@ import { DataSourceRef, PluginExtensionLink, PluginExtensionTypes, - PluginMeta, - PluginType, ScopedVars, TestDataSourceResponse, } from '@grafana/data'; @@ -796,28 +794,3 @@ export function mockDashboardDto( meta: { ...meta }, }; } - -export const getMockPluginMeta: (id: string, name: string) => PluginMeta = (id, name) => { - return { - name, - id, - type: PluginType.app, - module: `plugins/${id}/module`, - baseUrl: `public/plugins/${id}`, - info: { - author: { name: 'Grafana Labs' }, - description: name, - updated: '', - version: '', - links: [], - logos: { - small: '', - large: '', - }, - screenshots: [], - }, - }; -}; - -export const labelsPluginMetaMock = getMockPluginMeta('grafana-labels-app', 'Grafana IRM Labels'); -export const onCallPluginMetaMock = getMockPluginMeta('grafana-oncall-app', 'Grafana OnCall'); diff --git a/public/app/features/alerting/unified/mocks/grafana-notifiers.ts b/public/app/features/alerting/unified/mocks/grafana-notifiers.ts deleted file mode 100644 index eec2843582a..00000000000 --- a/public/app/features/alerting/unified/mocks/grafana-notifiers.ts +++ /dev/null @@ -1,378 +0,0 @@ -import { NotifierDTO } from 'app/types'; - -export const grafanaNotifiersMock: NotifierDTO[] = [ - { - type: 'teams', - name: 'Microsoft Teams', - heading: 'Teams settings', - description: 'Sends notifications using Incoming Webhook connector to Microsoft Teams', - info: '', - options: [ - { - element: 'input', - inputType: 'text', - label: 'URL', - description: '', - placeholder: 'Teams incoming webhook url', - propertyName: 'url', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - ], - }, - { - type: 'hipchat', - name: 'HipChat', - heading: 'HipChat settings', - description: 'Sends notifications uto a HipChat Room', - info: '', - options: [ - { - element: 'input', - inputType: 'text', - label: 'Hip Chat Url', - description: '', - placeholder: 'HipChat URL (ex https://grafana.hipchat.com)', - propertyName: 'url', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'API Key', - description: '', - placeholder: 'HipChat API Key', - propertyName: 'apiKey', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Room ID', - description: '', - placeholder: '', - propertyName: 'roomid', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - ], - }, - { - type: 'webhook', - name: 'webhook', - heading: 'Webhook settings', - description: 'Sends HTTP POST request to a URL', - info: '', - options: [ - { - element: 'input', - inputType: 'text', - label: 'Url', - description: '', - placeholder: '', - propertyName: 'url', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'select', - inputType: '', - label: 'Http Method', - description: '', - placeholder: '', - propertyName: 'httpMethod', - selectOptions: [ - { value: 'POST', label: 'POST' }, - { value: 'PUT', label: 'PUT' }, - ], - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Username', - description: '', - placeholder: '', - propertyName: 'username', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'password', - label: 'Password', - description: '', - placeholder: '', - propertyName: 'password', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: true, - dependsOn: '', - }, - ], - }, - { - type: 'prometheus-alertmanager', - name: 'Prometheus Alertmanager', - heading: 'Alertmanager settings', - description: 'Sends alert to Prometheus Alertmanager', - info: '', - options: [ - { - element: 'input', - inputType: 'text', - label: 'Url', - description: - 'As specified in Alertmanager documentation, do not specify a load balancer here. Enter all your Alertmanager URLs comma-separated.', - placeholder: 'http://localhost:9093', - propertyName: 'url', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Basic Auth User', - description: '', - placeholder: '', - propertyName: 'basicAuthUser', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'password', - label: 'Basic Auth Password', - description: '', - placeholder: '', - propertyName: 'basicAuthPassword', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: true, - dependsOn: '', - }, - ], - }, - { - type: 'email', - name: 'Email', - heading: 'Email settings', - description: 'Sends notifications using Grafana server configured SMTP settings', - info: '', - options: [ - { - element: 'checkbox', - inputType: '', - label: 'Single email', - description: 'Send a single email to all recipients', - placeholder: '', - propertyName: 'singleEmail', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'textarea', - inputType: '', - label: 'Addresses', - description: 'You can enter multiple email addresses using a ";" separator', - placeholder: '', - propertyName: 'addresses', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: '', - }, - ], - }, - { - type: 'slack', - name: 'Slack', - heading: 'Slack settings', - description: 'Sends notifications to Slack', - info: '', - options: [ - { - element: 'input', - inputType: 'text', - label: 'Recipient', - description: - 'Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace), or user/channel Slack ID - required unless you provide a webhook', - placeholder: '', - propertyName: 'recipient', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: false, - dependsOn: 'secureSettings.url', - }, - { - element: 'input', - inputType: 'text', - label: 'Token', - description: 'Provide a Slack API token (starts with "xoxb") - required unless you provide a webhook', - placeholder: '', - propertyName: 'token', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: true, - dependsOn: 'secureSettings.url', - }, - { - element: 'input', - inputType: 'text', - label: 'Username', - description: "Set the username for the bot's message", - placeholder: '', - propertyName: 'username', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Icon emoji', - description: "Provide an emoji to use as the icon for the bot's message. Overrides the icon URL.", - placeholder: '', - propertyName: 'iconEmoji', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Icon URL', - description: "Provide a URL to an image to use as the icon for the bot's message", - placeholder: '', - propertyName: 'iconUrl', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Mention Users', - description: - "Mention one or more users (comma separated) when notifying in a channel, by ID (you can copy this from the user's Slack profile)", - placeholder: '', - propertyName: 'mentionUsers', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Mention Groups', - description: - "Mention one or more groups (comma separated) when notifying in a channel (you can copy this from the group's Slack profile URL)", - placeholder: '', - propertyName: 'mentionGroups', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'select', - inputType: '', - label: 'Mention Channel', - description: 'Mention whole channel or just active members when notifying', - placeholder: '', - propertyName: 'mentionChannel', - selectOptions: [ - { value: '', label: 'Disabled' }, - { value: 'here', label: 'Every active channel member' }, - { value: 'channel', label: 'Every channel member' }, - ], - showWhen: { field: '', is: '' }, - required: false, - validationRule: '', - secure: false, - dependsOn: '', - }, - { - element: 'input', - inputType: 'text', - label: 'Webhook URL', - description: - "Optionally provide a Slack incoming webhook URL for sending messages, in this case the token isn't necessary", - placeholder: 'Slack incoming webhook URL', - propertyName: 'url', - selectOptions: null, - showWhen: { field: '', is: '' }, - required: true, - validationRule: '', - secure: true, - dependsOn: 'token', - }, - ], - }, -]; 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 ec189862903..3104b14a468 100644 --- a/public/app/features/alerting/unified/mocks/server/all-handlers.ts +++ b/public/app/features/alerting/unified/mocks/server/all-handlers.ts @@ -2,6 +2,7 @@ * Contains all handlers that are required for test rendering of components within Alerting */ +import alertNotifierHandlers from 'app/features/alerting/unified/mocks/server/handlers/alertNotifiers'; import alertmanagerHandlers from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers'; import datasourcesHandlers from 'app/features/alerting/unified/mocks/server/handlers/datasources'; import evalHandlers from 'app/features/alerting/unified/mocks/server/handlers/eval'; @@ -9,12 +10,13 @@ import folderHandlers from 'app/features/alerting/unified/mocks/server/handlers/ import grafanaRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/grafanaRuler'; import mimirRulerHandlers from 'app/features/alerting/unified/mocks/server/handlers/mimirRuler'; 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 silenceHandlers from 'app/features/alerting/unified/mocks/server/handlers/silences'; - /** * Array of all mock handlers that are required across Alerting tests */ const allHandlers = [ + ...alertNotifierHandlers, ...grafanaRulerHandlers, ...mimirRulerHandlers, ...alertmanagerHandlers, @@ -23,6 +25,8 @@ const allHandlers = [ ...folderHandlers, ...pluginsHandlers, ...silenceHandlers, + + ...allPluginHandlers, ]; export default allHandlers; diff --git a/public/app/features/alerting/unified/mocks/server/configure.ts b/public/app/features/alerting/unified/mocks/server/configure.ts index 7963a556b62..636cbe2ae1b 100644 --- a/public/app/features/alerting/unified/mocks/server/configure.ts +++ b/public/app/features/alerting/unified/mocks/server/configure.ts @@ -1,5 +1,6 @@ import { HttpResponse } from 'msw'; +import { config } from '@grafana/runtime'; import server, { mockFeatureDiscoveryApi } from 'app/features/alerting/unified/mockApi'; import { mockDataSource, mockFolder } from 'app/features/alerting/unified/mocks'; import { @@ -7,6 +8,11 @@ import { grafanaAlertingConfigurationStatusHandler, } from 'app/features/alerting/unified/mocks/server/handlers/alertmanagers'; import { getFolderHandler } from 'app/features/alerting/unified/mocks/server/handlers/folders'; +import { + getDisabledPluginHandler, + getPluginMissingHandler, +} from 'app/features/alerting/unified/mocks/server/handlers/plugins'; +import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { AlertManagerCortexConfig, AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { FolderDTO } from 'app/types'; @@ -87,3 +93,14 @@ export function mimirDataSource() { return { dataSource }; } + +/** Make a given plugin ID respond with a 404, as if it isn't installed at all */ +export const removePlugin = (pluginId: string) => { + delete config.apps[pluginId]; + server.use(getPluginMissingHandler(pluginId)); +}; + +/** Make a plugin respond with `enabled: false`, as if its installed but disabled */ +export const disablePlugin = (pluginId: SupportedPlugin) => { + server.use(getDisabledPluginHandler(pluginId)); +}; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/alertNotifiers.ts b/public/app/features/alerting/unified/mocks/server/handlers/alertNotifiers.ts new file mode 100644 index 00000000000..7ec80ca8379 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertNotifiers.ts @@ -0,0 +1,11 @@ +import { HttpResponse, http } from 'msw'; + +import { grafanaAlertNotifiersMock } from 'app/features/alerting/unified/mockGrafanaNotifiers'; + +const getAlertNotifiers = () => + http.get('/api/alert-notifiers', () => { + return HttpResponse.json(grafanaAlertNotifiersMock); + }); + +const handlers = [getAlertNotifiers()]; +export default handlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts index e789139f65b..e04e53c87e4 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts @@ -1,6 +1,7 @@ import { http, HttpResponse } from 'msw'; import alertmanagerConfigMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json'; +import receiversMock from 'app/features/alerting/unified/components/contact-points/__mocks__/receivers.mock.json'; import { MOCK_SILENCE_ID_EXISTING, mockAlertmanagerAlert } from 'app/features/alerting/unified/mocks'; import { defaultGrafanaAlertingConfigurationStatusResponse } from 'app/features/alerting/unified/mocks/alertmanagerApi'; import { MOCK_DATASOURCE_UID_BROKEN_ALERTMANAGER } from 'app/features/alerting/unified/mocks/server/handlers/datasources'; @@ -41,11 +42,15 @@ const getGrafanaAlertmanagerTemplatePreview = () => HttpResponse.json({}) ); +const getGrafanaReceiversHandler = () => + http.get('/api/alertmanager/grafana/config/api/v1/receivers', () => HttpResponse.json(receiversMock)); + const handlers = [ alertmanagerAlertsListHandler(), grafanaAlertingConfigurationStatusHandler(), getGrafanaAlertmanagerConfigHandler(), updateGrafanaAlertmanagerConfigHandler(), getGrafanaAlertmanagerTemplatePreview(), + getGrafanaReceiversHandler(), ]; export default handlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/plugins.ts b/public/app/features/alerting/unified/mocks/server/handlers/plugins.ts index f07cac89b0d..93f28c903b2 100644 --- a/public/app/features/alerting/unified/mocks/server/handlers/plugins.ts +++ b/public/app/features/alerting/unified/mocks/server/handlers/plugins.ts @@ -4,6 +4,8 @@ import { PluginMeta } from '@grafana/data'; import { config } from '@grafana/runtime'; import { plugins } from 'app/features/alerting/unified/testSetup/plugins'; +const PLUGIN_NOT_FOUND_RESPONSE = { message: 'Plugin not found, no installed plugin with that id' }; + /** * Returns a handler that maps from plugin ID to PluginMeta, and additionally sets up necessary * config side effects that are expected to come along with this API behaviour @@ -23,9 +25,23 @@ export const getPluginsHandler = (pluginsArray: PluginMeta[] = plugins) => { const matchingPlugin = pluginsArray.find((plugin) => plugin.id === pluginId); return matchingPlugin ? HttpResponse.json(matchingPlugin) - : HttpResponse.json({ message: 'Plugin not found, no installed plugin with that id' }, { status: 404 }); + : HttpResponse.json(PLUGIN_NOT_FOUND_RESPONSE, { status: 404 }); }); }; +export const getDisabledPluginHandler = (pluginIdToDisable: string) => { + return http.get<{ pluginId: string }>(`/api/plugins/${pluginIdToDisable}/settings`, ({ params: { pluginId } }) => { + const matchingPlugin = plugins.find((plugin) => plugin.id === pluginId); + return matchingPlugin + ? HttpResponse.json({ ...matchingPlugin, enabled: false }) + : HttpResponse.json(PLUGIN_NOT_FOUND_RESPONSE, { status: 404 }); + }); +}; + +export const getPluginMissingHandler = (pluginIdToRemove: string) => + http.get(`/api/plugins/${pluginIdToRemove}/settings`, () => + HttpResponse.json(PLUGIN_NOT_FOUND_RESPONSE, { status: 404 }) + ); + const handlers = [getPluginsHandler()]; export default handlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers.ts b/public/app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers.ts new file mode 100644 index 00000000000..53345209094 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers.ts @@ -0,0 +1,11 @@ +/** + * Re-exports all plugin proxy handlers + */ +import onCallHandlers from './grafana-oncall'; + +/** + * Array of all plugin handlers that are required across Alerting tests + */ +const allPluginProxyHandlers = [...onCallHandlers]; + +export default allPluginProxyHandlers; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins.ts b/public/app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins.ts new file mode 100644 index 00000000000..4f220b7317e --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins.ts @@ -0,0 +1,14 @@ +import { OnCallIntegrationDTO } from 'app/features/alerting/unified/api/onCallApi'; +import server from 'app/features/alerting/unified/mockApi'; +import { + getOnCallIntegrationsHandler, + getFeaturesHandler, +} from 'app/features/alerting/unified/mocks/server/handlers/plugins/grafana-oncall'; + +export const setOnCallFeatures = (features: string[]) => { + server.use(getFeaturesHandler(features)); +}; + +export const setOnCallIntegrations = (integrations: OnCallIntegrationDTO[]) => { + server.use(getOnCallIntegrationsHandler(integrations)); +}; diff --git a/public/app/features/alerting/unified/mocks/server/handlers/plugins/grafana-oncall.ts b/public/app/features/alerting/unified/mocks/server/handlers/plugins/grafana-oncall.ts new file mode 100644 index 00000000000..b1d042f54a4 --- /dev/null +++ b/public/app/features/alerting/unified/mocks/server/handlers/plugins/grafana-oncall.ts @@ -0,0 +1,30 @@ +import { HttpResponse, http } from 'msw'; + +import { ONCALL_INTEGRATION_V2_FEATURE, OnCallIntegrationDTO } from 'app/features/alerting/unified/api/onCallApi'; + +const BASE_URL = `/api/plugin-proxy/grafana-oncall-app`; + +export const getOnCallIntegrationsHandler = (receiveChannels: OnCallIntegrationDTO[] = []) => + http.get(`${BASE_URL}/api/internal/v1/alert_receive_channels`, () => { + return HttpResponse.json(receiveChannels); + }); + +export const getFeaturesHandler = (features = [ONCALL_INTEGRATION_V2_FEATURE]) => + http.get(`${BASE_URL}/api/internal/v1/features`, () => { + return HttpResponse.json(features); + }); + +const validateIntegrationNameHandler = ( + invalidNames: string[] = ['grafana-integration', 'alertmanager-integration'] +) => { + return http.get(`${BASE_URL}/api/internal/v1/alert_receive_channels/validate_name`, ({ request }) => { + const url = new URL(request.url); + const isValid = !invalidNames.includes(url.searchParams.get('verbal_name') ?? ''); + return HttpResponse.json(isValid, { + status: isValid ? 200 : 409, + }); + }); +}; + +const handlers = [getOnCallIntegrationsHandler(), getFeaturesHandler(), validateIntegrationNameHandler()]; +export default handlers; diff --git a/public/app/features/alerting/unified/testSetup/plugins.ts b/public/app/features/alerting/unified/testSetup/plugins.ts index 9ebd3133a0f..c786c087fae 100644 --- a/public/app/features/alerting/unified/testSetup/plugins.ts +++ b/public/app/features/alerting/unified/testSetup/plugins.ts @@ -1,5 +1,6 @@ import { PluginMeta, PluginType } from '@grafana/data'; import { setPluginExtensionsHook } from '@grafana/runtime'; +import { SupportedPlugin } from 'app/features/alerting/unified/types/pluginBridges'; import { mockPluginLinkExtension } from '../mocks'; @@ -18,7 +19,7 @@ export function setupPluginsExtensionsHook() { export const plugins: PluginMeta[] = [ { - id: 'grafana-slo-app', + id: SupportedPlugin.Slo, name: 'SLO dashboard', type: PluginType.app, enabled: true, @@ -41,7 +42,7 @@ export const plugins: PluginMeta[] = [ baseUrl: 'public/plugins/grafana-slo-app', }, { - id: 'grafana-incident-app', + id: SupportedPlugin.Incident, name: 'Incident management', type: PluginType.app, enabled: true, @@ -87,7 +88,7 @@ export const plugins: PluginMeta[] = [ baseUrl: 'public/plugins/grafana-asserts-app', }, { - id: 'grafana-oncall-app', + id: SupportedPlugin.OnCall, name: 'OnCall', type: PluginType.app, enabled: true,