Alerting: Tidy up contact points/misc other tests' mock server behaviour (#90469)

pull/90673/head
Tom Ratcliffe 10 months ago committed by GitHub
parent c5775b3778
commit a61cd94a70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 5
      public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx
  2. 1
      public/app/features/alerting/unified/api/alertmanagerApi.ts
  3. 88
      public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx
  4. 29
      public/app/features/alerting/unified/components/contact-points/NewContactPoint.test.tsx
  5. 44
      public/app/features/alerting/unified/components/contact-points/__mocks__/grafanaManagedServer.ts
  6. 4
      public/app/features/alerting/unified/components/contact-points/__mocks__/mimirFlavoredServer.ts
  7. 4
      public/app/features/alerting/unified/components/contact-points/__mocks__/vanillaAlertmanagerServer.ts
  8. 8
      public/app/features/alerting/unified/components/contact-points/__snapshots__/NewContactPoint.test.tsx.snap
  9. 8
      public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap
  10. 25
      public/app/features/alerting/unified/components/contact-points/useContactPoints.test.tsx
  11. 44
      public/app/features/alerting/unified/components/receivers/form/GrafanaReceiverForm.test.tsx
  12. 72
      public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/onCall/useOnCallIntegration.test.ts
  13. 5
      public/app/features/alerting/unified/components/rule-editor/labels/LabelsField.test.tsx
  14. 12
      public/app/features/alerting/unified/components/rules/RulesTable.test.tsx
  15. 6
      public/app/features/alerting/unified/components/settings/AlertmanagerConfig.test.tsx
  16. 44
      public/app/features/alerting/unified/mockApi.ts
  17. 27
      public/app/features/alerting/unified/mocks.ts
  18. 378
      public/app/features/alerting/unified/mocks/grafana-notifiers.ts
  19. 6
      public/app/features/alerting/unified/mocks/server/all-handlers.ts
  20. 17
      public/app/features/alerting/unified/mocks/server/configure.ts
  21. 11
      public/app/features/alerting/unified/mocks/server/handlers/alertNotifiers.ts
  22. 5
      public/app/features/alerting/unified/mocks/server/handlers/alertmanagers.ts
  23. 18
      public/app/features/alerting/unified/mocks/server/handlers/plugins.ts
  24. 11
      public/app/features/alerting/unified/mocks/server/handlers/plugins/all-plugin-handlers.ts
  25. 14
      public/app/features/alerting/unified/mocks/server/handlers/plugins/configure-plugins.ts
  26. 30
      public/app/features/alerting/unified/mocks/server/handlers/plugins/grafana-oncall.ts
  27. 7
      public/app/features/alerting/unified/testSetup/plugins.ts

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

@ -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<GrafanaManagedContactPoint[], void>({
query: () => ({ url: '/api/v1/notifications/receivers' }),
}),

@ -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<ComponentProps<typeof AlertmanagerProvider>>,
contactPointProps?: Partial<ComponentProps<typeof ContactPoints>>
children: ReactNode,
historyOptions?: MemoryHistoryBuildOptions,
providerProps?: Partial<ComponentProps<typeof AlertmanagerProvider>>
) =>
render(
<TestProvider>
<AlertmanagerProvider accessType={'notification'} {...providerProps}>
<ContactPoints {...contactPointProps} />
</AlertmanagerProvider>
</TestProvider>
<AlertmanagerProvider accessType="notification" {...providerProps}>
{children}
</AlertmanagerProvider>,
{ 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(<ContactPoints />, { initialEntries: ['/?tab=contact_points'] });
expect(await screen.findByText(/add contact point/i)).toBeInTheDocument();
});
test('loads templates tab', async () => {
locationService.push('/?tab=templates');
renderWithProvider();
renderWithProvider(<ContactPoints />, { 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(<ContactPoints />, { 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(<ContactPoints />);
expect(await screen.findByText(/add contact point/i)).toBeInTheDocument();
});
});
it('should show / hide loading states, have all actions enabled', async () => {
renderWithProvider();
renderWithProvider(<ContactPoints />);
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(<ContactPoints />);
// 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(<ContactPoint name={'my-contact-point'} receivers={[]} onDelete={onDelete} />, {
wrapper,
});
renderWithProvider(<ContactPoint name={'my-contact-point'} receivers={[]} onDelete={onDelete} />);
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(<ContactPoint name={'my-contact-point'} disabled={true} receivers={[]} onDelete={noop} />, {
wrapper,
});
renderWithProvider(<ContactPoint name={'my-contact-point'} disabled={true} receivers={[]} onDelete={noop} />);
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(<ContactPoint name={'my-contact-point'} provisioned={true} receivers={[]} onDelete={noop} />, {
wrapper,
});
renderWithProvider(<ContactPoint name={'my-contact-point'} provisioned={true} receivers={[]} onDelete={noop} />);
expect(screen.getByText(/provisioned/i)).toBeInTheDocument();
@ -235,9 +214,7 @@ describe('contact points', () => {
},
];
render(<ContactPoint name={'my-contact-point'} receivers={[]} policies={policies} onDelete={noop} />, {
wrapper,
});
renderWithProvider(<ContactPoint name={'my-contact-point'} receivers={[]} policies={policies} onDelete={noop} />);
expect(screen.getByRole('link', { name: /1 notification policy/ })).toBeInTheDocument();
@ -258,9 +235,7 @@ describe('contact points', () => {
},
];
render(<ContactPoint name={'my-contact-point'} receivers={[]} policies={policies} onDelete={noop} />, {
wrapper,
});
renderWithProvider(<ContactPoint name={'my-contact-point'} receivers={[]} policies={policies} onDelete={noop} />);
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(<ContactPoints />);
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(<ContactPoints />, 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(
<TestProvider>
<AlertmanagerProvider
accessType={'notification'}
alertmanagerSourceName={VANILLA_ALERTMANAGER_DATASOURCE_UID}
>
<ContactPoints />
</AlertmanagerProvider>
</TestProvider>
);
renderWithProvider(<ContactPoints />, 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) => (
<TestProvider>
<AlertmanagerProvider accessType={'notification'}>{children}</AlertmanagerProvider>
</TestProvider>
);

@ -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(
<AlertmanagerProvider accessType={'notification'} alertmanagerSourceName="grafana">
<NewContactPoint />
</AlertmanagerProvider>,
{ wrapper: TestProvider }
</AlertmanagerProvider>
);
// 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 = {

@ -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<AlertManagerCortexConfig>(alertmanagerMock)
),
// this endpoint is only available for the built-in alertmanager
http.get('/api/alertmanager/grafana/config/api/v1/receivers', () =>
HttpResponse.json<ReceiversStateDTO[]>(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();

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

@ -12,9 +12,7 @@ export default (server: SetupServer) => {
server.use(
http.get(`/api/alertmanager/${VANILLA_ALERTMANAGER_DATASOURCE_UID}/api/v2/status`, () =>
HttpResponse.json<AlertmanagerStatus>(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;

@ -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": {

@ -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",

@ -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 }) => (
<TestProvider>
@ -43,16 +37,16 @@ describe('useContactPoints', () => {
</TestProvider>
),
});
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();
});
});
});

@ -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(<GrafanaReceiverForm alertManagerSourceName={GRAFANA_RULES_SOURCE_NAME} config={amConfig} />, {
wrapper: TestProvider,
});
render(<GrafanaReceiverForm alertManagerSourceName={GRAFANA_RULES_SOURCE_NAME} config={amConfig} />);
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(<GrafanaReceiverForm alertManagerSourceName={GRAFANA_RULES_SOURCE_NAME} config={amConfig} />, {
wrapper: TestProvider,
});
render(<GrafanaReceiverForm alertManagerSourceName={GRAFANA_RULES_SOURCE_NAME} config={amConfig} />);
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());

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

@ -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!] }],
});

@ -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' });

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

@ -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<T> = (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<OnCallIntegrationDTO[]>(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,
});
}
)
);
},
},
};
}

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

@ -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',
},
],
},
];

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

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

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

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

@ -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<PluginMeta>(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<PluginMeta>({ ...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;

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

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

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

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

Loading…
Cancel
Save