From 9c7a355afd12cc1df75b493b5527bcb0694c1ef4 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:48:08 +0100 Subject: [PATCH] Alerting: Add tests for combination of switch modes in the alert rule form (#98052) * Add tests for combination of switch * refactor test * use alerting option for default datasource in RuleEditor Grafana rules test * add test for grafana recording rules * use enum for grafana steps in tests * dont make each test dependent on previous localstorage * add test for local storage --- .../RuleEditorGrafanaRecordingRules.test.tsx | 119 +++ .../unified/RuleEditorGrafanaRules.test.tsx | 7 +- .../unified/RuleEditorRecordingRule.test.tsx | 2 +- ...eEditorGrafanaRecordingRules.test.tsx.snap | 259 +++++++ .../RuleEditorGrafanaRules.test.tsx.snap | 168 +++++ .../SimplifiedRuleEditor.test.tsx | 166 ++-- .../SimplifiedRuleEditor.test.tsx.snap | 713 +++++++++++++++++- public/test/helpers/alertingRuleEditor.tsx | 16 +- 8 files changed, 1395 insertions(+), 55 deletions(-) create mode 100644 public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx create mode 100644 public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRecordingRules.test.tsx.snap create mode 100644 public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx new file mode 100644 index 00000000000..655b33b2e7e --- /dev/null +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRecordingRules.test.tsx @@ -0,0 +1,119 @@ +import { UserEvent } from '@testing-library/user-event'; +import * as React from 'react'; +import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; +import { clickSelectOption } from 'test/helpers/selectOptionInTest'; +import { screen } from 'test/test-utils'; +import { byRole } from 'testing-library-selector'; + +import { FeatureToggles } from '@grafana/data'; +import { contextSrv } from 'app/core/services/context_srv'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { PROMETHEUS_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants'; +import { AccessControlAction } from 'app/types'; + +import { grantUserPermissions, mockDataSource } from './mocks'; +import { grafanaRulerGroup } from './mocks/grafanaRulerApi'; +import { captureRequests, serializeRequests } from './mocks/server/events'; +import { FOLDER_TITLE_HAPPY_PATH } from './mocks/server/handlers/search'; +import { testWithFeatureToggles } from './test/test-utils'; +import { setupDataSources } from './testSetup/datasources'; + +jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ + AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) =>
{actions}
, +})); + +jest.setTimeout(60 * 1000); + +setupMswServer(); + +const selectFolderAndGroup = async (user: UserEvent) => { + await user.click(await screen.findByRole('button', { name: /select folder/i })); + await user.click(await screen.findByLabelText(FOLDER_TITLE_HAPPY_PATH)); + const groupInput = await ui.inputs.group.find(); + await user.click(await byRole('combobox').find(groupInput)); + await clickSelectOption(groupInput, grafanaRulerGroup.name); +}; + +const dataSources = { + default: mockDataSource( + { + type: 'prometheus', + name: 'Prom', + uid: PROMETHEUS_DATASOURCE_UID, + isDefault: true, + }, + { alerting: true, module: 'core:plugin/prometheus' } + ), +}; +describe('RuleEditor grafana recording rules', () => { + beforeEach(() => { + jest.clearAllMocks(); + setupDataSources(dataSources.default); + contextSrv.isEditor = true; + contextSrv.hasEditPermissionInFolders = true; + grantUserPermissions([ + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleUpdate, + AccessControlAction.AlertingRuleDelete, + AccessControlAction.AlertingRuleCreate, + AccessControlAction.DataSourcesRead, + AccessControlAction.DataSourcesWrite, + AccessControlAction.DataSourcesCreate, + AccessControlAction.FoldersWrite, + AccessControlAction.FoldersRead, + AccessControlAction.AlertingRuleExternalRead, + AccessControlAction.AlertingRuleExternalWrite, + ]); + }); + + const testCreateGrafanaRR = (featureToggles: Array, testName: string) => { + testWithFeatureToggles(featureToggles); + + it(testName, async () => { + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + + const { user } = renderRuleEditor(undefined, 'grafana-recording'); + + await user.type(await ui.inputs.name.find(), 'my great new rule'); + await user.type(await ui.inputs.metric.find(), 'metricName'); + await selectFolderAndGroup(user); + + await user.click(ui.buttons.saveAndExit.get()); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + }; + + const testCreateGrafanaRRWithInvalidMetricName = (featureToggles: Array, testName: string) => { + testWithFeatureToggles(featureToggles); + + it(testName, async () => { + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + const { user } = renderRuleEditor(undefined, 'grafana-recording'); + + await user.type(await ui.inputs.name.find(), 'my great new rule'); + await selectFolderAndGroup(user); + + await user.click(ui.buttons.saveAndExit.get()); + const requests = await capture; + expect(requests).toHaveLength(0); + }); + }; + + testCreateGrafanaRR([], 'can create new grafana recording rule with simplified steps feature toggles disabled'); + testCreateGrafanaRR( + ['alertingQueryAndExpressionsStepMode', 'alertingNotificationsStepMode'], + 'can create new grafana recording rule with simplified steps enabled' + ); + + testCreateGrafanaRRWithInvalidMetricName( + [], + 'cannot create new grafana recording rule with invalid metric name with simplified steps feature toggles disabled' + ); + testCreateGrafanaRRWithInvalidMetricName( + ['alertingQueryAndExpressionsStepMode', 'alertingNotificationsStepMode'], + 'cannot create new grafana recording rule with invalid metric name with simplified steps enabled' + ); +}); diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index 41a35d2ff60..9b863517b14 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -11,6 +11,7 @@ import { AccessControlAction } from 'app/types'; import { grantUserPermissions, mockDataSource } from './mocks'; import { grafanaRulerGroup } from './mocks/grafanaRulerApi'; +import { captureRequests, serializeRequests } from './mocks/server/events'; import { setupDataSources } from './testSetup/datasources'; jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ @@ -42,6 +43,7 @@ describe('RuleEditor grafana managed rules', () => { }); it('can create new grafana managed alert', async () => { + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); const dataSources = { default: mockDataSource( { @@ -50,7 +52,7 @@ describe('RuleEditor grafana managed rules', () => { uid: PROMETHEUS_DATASOURCE_UID, isDefault: true, }, - { alerting: false } + { alerting: true, module: 'core:plugin/prometheus' } ), }; @@ -69,5 +71,8 @@ describe('RuleEditor grafana managed rules', () => { await user.click(ui.buttons.saveAndExit.get()); expect(await screen.findByRole('status')).toHaveTextContent('Rule added successfully'); + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); }); }); diff --git a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx index f61352d06b8..766253febf1 100644 --- a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx @@ -67,7 +67,7 @@ describe('RuleEditor recording rules', () => { }); it('can create a new cloud recording rule', async () => { - renderRuleEditor(undefined, true); + renderRuleEditor(undefined, 'recording'); await waitForElementToBeRemoved(screen.queryAllByTestId('Spinner')); await userEvent.type(await ui.inputs.name.find(), 'my great new recording rule'); diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRecordingRules.test.tsx.snap b/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRecordingRules.test.tsx.snap new file mode 100644 index 00000000000..e6a833408b1 --- /dev/null +++ b/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRecordingRules.test.tsx.snap @@ -0,0 +1,259 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RuleEditor grafana recording rules can create new grafana recording rule with simplified steps enabled 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "grafana_alert": { + "condition": "B", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + ], + "is_paused": false, + "record": { + "from": "B", + "metric": "metricName", + }, + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`RuleEditor grafana recording rules can create new grafana recording rule with simplified steps feature toggles disabled 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "grafana_alert": { + "condition": "B", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + ], + "is_paused": false, + "record": { + "from": "B", + "metric": "metricName", + }, + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap b/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap new file mode 100644 index 00000000000..821c074014f --- /dev/null +++ b/public/app/features/alerting/unified/__snapshots__/RuleEditorGrafanaRules.test.tsx.snap @@ -0,0 +1,168 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RuleEditor grafana managed rules can create new grafana managed alert 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": { + "description": "some description", + }, + "for": "1m", + "grafana_alert": { + "condition": "C", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "no_data_state": "NoData", + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index bdd5fced16b..11b3a0f5dfc 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -1,27 +1,25 @@ +import { UserEvent } from '@testing-library/user-event'; import { ReactNode } from 'react'; -import { Route, Routes } from 'react-router-dom-v5-compat'; -import { ui } from 'test/helpers/alertingRuleEditor'; +import { GrafanaRuleFormStep, renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; -import { render, screen, userEvent } from 'test/test-utils'; +import { screen, waitFor } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; -import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; -import RuleEditor from 'app/features/alerting/unified/RuleEditor'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; import { PROMETHEUS_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants'; import { captureRequests, serializeRequests } from 'app/features/alerting/unified/mocks/server/events'; import { FOLDER_TITLE_HAPPY_PATH } from 'app/features/alerting/unified/mocks/server/handlers/search'; -import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext'; import { testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils'; -import { DataSourceType, GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; +import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; +import { DataSourceType } from 'app/features/alerting/unified/utils/datasource'; +import { MANUAL_ROUTING_KEY, SIMPLIFIED_QUERY_EDITOR_KEY } from 'app/features/alerting/unified/utils/rule-form'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; import { grafanaRulerGroup } from '../../../../mocks/grafanaRulerApi'; -import { setupDataSources } from '../../../../testSetup/datasources'; jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ AppChromeUpdate: ({ actions }: { actions: ReactNode }) =>
{actions}
, @@ -29,8 +27,6 @@ jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ jest.setTimeout(60 * 1000); -setupMswServer(); - const dataSources = { default: mockDataSource( { @@ -39,7 +35,7 @@ const dataSources = { uid: PROMETHEUS_DATASOURCE_UID, isDefault: true, }, - { alerting: false } + { alerting: true, module: 'core:plugin/prometheus' } ), am: mockDataSource({ name: 'Alertmanager', @@ -47,10 +43,7 @@ const dataSources = { }), }; -setupDataSources(dataSources.default, dataSources.am); - -const selectFolderAndGroup = async () => { - const user = userEvent.setup(); +const selectFolderAndGroup = async (user: UserEvent) => { await user.click(await screen.findByRole('button', { name: /select folder/i })); await user.click(await screen.findByLabelText(FOLDER_TITLE_HAPPY_PATH)); const groupInput = await ui.inputs.group.find(); @@ -58,12 +51,21 @@ const selectFolderAndGroup = async () => { await clickSelectOption(groupInput, grafanaRulerGroup.name); }; +const selectContactPoint = async (user: UserEvent, contactPointName: string) => { + const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find(); + await user.click(byRole('combobox').get(contactPointInput)); + await clickSelectOption(contactPointInput, contactPointName); +}; + +setupMswServer(); describe('Can create a new grafana managed alert using simplified routing', () => { + testWithFeatureToggles(['alertingSimplifiedRouting']); + beforeEach(() => { - jest.clearAllMocks(); + window.localStorage.clear(); + setupDataSources(dataSources.default, dataSources.am); contextSrv.isEditor = true; contextSrv.hasEditPermissionInFolders = true; - config.featureToggles.alertingSimplifiedRouting = true; grantUserPermissions([ AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate, @@ -84,11 +86,11 @@ describe('Can create a new grafana managed alert using simplified routing', () = it('cannot create new grafana managed alert when using simplified routing and not selecting a contact point', async () => { const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); - const { user } = renderSimplifiedRuleEditor(); + const { user } = renderRuleEditor(); await user.type(await ui.inputs.name.find(), 'my great new rule'); - await selectFolderAndGroup(); + await selectFolderAndGroup(user); //select contact point routing await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); @@ -104,26 +106,25 @@ describe('Can create a new grafana managed alert using simplified routing', () = it('simplified routing is not available when Grafana AM is not enabled', async () => { setAlertmanagerChoices(AlertmanagerChoice.External, 1); - renderSimplifiedRuleEditor(); + renderRuleEditor(); - expect(ui.inputs.simplifiedRouting.contactPointRouting.query()).not.toBeInTheDocument(); + await waitFor(() => expect(ui.inputs.simplifiedRouting.contactPointRouting.query()).not.toBeInTheDocument()); }); it('can create new grafana managed alert when using simplified routing and selecting a contact point', async () => { const contactPointName = 'lotsa-emails'; const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); - const { user } = renderSimplifiedRuleEditor(); + const { user } = renderRuleEditor(); await user.type(await ui.inputs.name.find(), 'my great new rule'); - await selectFolderAndGroup(); + await selectFolderAndGroup(user); //select contact point routing await user.click(ui.inputs.simplifiedRouting.contactPointRouting.get()); - const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find(); - await user.click(byRole('combobox').get(contactPointInput)); - await clickSelectOption(contactPointInput, contactPointName); + + await selectContactPoint(user, contactPointName); // save and check what was sent to backend await user.click(ui.buttons.saveAndExit.get()); @@ -137,27 +138,108 @@ describe('Can create a new grafana managed alert using simplified routing', () = testWithFeatureToggles(['alertingApiServer']); it('allows selecting a contact point when using alerting API server', async () => { - const { user } = renderSimplifiedRuleEditor(); + const { user } = renderRuleEditor(); await user.click(await ui.inputs.simplifiedRouting.contactPointRouting.find()); - const contactPointInput = await ui.inputs.simplifiedRouting.contactPoint.find(); - await user.click(byRole('combobox').get(contactPointInput)); - await clickSelectOption(contactPointInput, 'lotsa-emails'); + await selectContactPoint(user, 'Email'); expect(await screen.findByText('Email')).toBeInTheDocument(); }); }); -}); + describe('switch modes enabled', () => { + testWithFeatureToggles(['alertingQueryAndExpressionsStepMode', 'alertingNotificationsStepMode']); + + it('can create the new grafana-managed rule with default modes', async () => { + const contactPointName = 'lotsa-emails'; + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + + const { user } = renderRuleEditor(); + + await user.type(await ui.inputs.name.find(), 'my great new rule'); + + await selectFolderAndGroup(user); + + await selectContactPoint(user, contactPointName); + + // save and check what was sent to backend + await user.click(ui.buttons.saveAndExit.get()); + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + it('can create the new grafana-managed rule with advanced modes', async () => { + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + + const { user } = renderRuleEditor(); + + await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Query).get()); // switch to query step advanced mode + await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Notification).get()); // switch to notifications step advanced mode + await user.type(await ui.inputs.name.find(), 'my great new rule'); + + await selectFolderAndGroup(user); + + // save and check what was sent to backend + await user.click(ui.buttons.saveAndExit.get()); + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + it('can create the new grafana-managed rule with only notifications step advanced mode', async () => { + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + + const { user } = renderRuleEditor(); + + await user.type(await ui.inputs.name.find(), 'my great new rule'); + + await selectFolderAndGroup(user); + + await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Notification).get()); // switch notifications step to advanced mode + + // save and check what was sent to backend + await user.click(ui.buttons.saveAndExit.get()); + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + it('can create the new grafana-managed rule with only query step advanced mode', async () => { + const contactPointName = 'lotsa-emails'; + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); + + const { user } = renderRuleEditor(); + + await user.type(await ui.inputs.name.find(), 'my great new rule'); + + await selectFolderAndGroup(user); + await selectContactPoint(user, contactPointName); -function renderSimplifiedRuleEditor() { - return render( - - - } /> - } /> - - , - { historyOptions: { initialEntries: ['/alerting/new/alerting'] } } - ); -} + await user.click(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Query).get()); // switch query step to advanced mode + + // save and check what was sent to backend + await user.click(ui.buttons.saveAndExit.get()); + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + it('switch modes are intiallized depending on the local storage - 1', async () => { + localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'false'); + localStorage.setItem(MANUAL_ROUTING_KEY, 'true'); + + const { user } = renderRuleEditor(); + await selectFolderAndGroup(user); + + expect(ui.inputs.switchModeAdvanced(GrafanaRuleFormStep.Query).get()).toBeInTheDocument(); + expect(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Notification).get()).toBeInTheDocument(); + }); + it('switch modes are intiallized depending on the local storage - 2', async () => { + localStorage.setItem(SIMPLIFIED_QUERY_EDITOR_KEY, 'true'); + localStorage.setItem(MANUAL_ROUTING_KEY, 'false'); + + const { user } = renderRuleEditor(); + await selectFolderAndGroup(user); + + expect(ui.inputs.switchModeBasic(GrafanaRuleFormStep.Query).get()).toBeInTheDocument(); + expect(ui.inputs.switchModeAdvanced(GrafanaRuleFormStep.Notification).get()).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap index 5e8fc88cdb3..3cee4d53070 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap @@ -51,8 +51,534 @@ exports[`Can create a new grafana managed alert using simplified routing can cre "annotations": {}, "for": "1m", "grafana_alert": { - "condition": "B", + "condition": "C", "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "no_data_state": "NoData", + "notification_settings": { + "receiver": "lotsa-emails", + }, + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Can create a new grafana managed alert using simplified routing switch modes enabled can create the new grafana-managed rule with advanced modes 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "for": "1m", + "grafana_alert": { + "condition": "C", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "metadata": { + "editor_settings": { + "simplified_notifications_section": false, + "simplified_query_and_expressions_section": false, + }, + }, + "no_data_state": "NoData", + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Can create a new grafana managed alert using simplified routing switch modes enabled can create the new grafana-managed rule with default modes 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "for": "1m", + "grafana_alert": { + "condition": "C", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "B", + "type": "reduce", + }, + "queryType": "", + "refId": "B", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "metadata": { + "editor_settings": { + "simplified_notifications_section": true, + "simplified_query_and_expressions_section": true, + }, + }, + "no_data_state": "NoData", + "notification_settings": { + "receiver": "lotsa-emails", + }, + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Can create a new grafana managed alert using simplified routing switch modes enabled can create the new grafana-managed rule with only notifications step advanced mode 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "for": "1m", + "grafana_alert": { + "condition": "C", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, { "datasourceUid": "__expr__", "model": { @@ -67,7 +593,7 @@ exports[`Can create a new grafana managed alert using simplified routing can cre }, "query": { "params": [ - "A", + "B", ], }, "reducer": { @@ -83,11 +609,11 @@ exports[`Can create a new grafana managed alert using simplified routing can cre }, "expression": "A", "reducer": "last", - "refId": "A", + "refId": "B", "type": "reduce", }, "queryType": "", - "refId": "A", + "refId": "B", }, { "datasourceUid": "__expr__", @@ -103,6 +629,139 @@ exports[`Can create a new grafana managed alert using simplified routing can cre "operator": { "type": "and", }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "metadata": { + "editor_settings": { + "simplified_notifications_section": false, + "simplified_query_and_expressions_section": true, + }, + }, + "no_data_state": "NoData", + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Can create a new grafana managed alert using simplified routing switch modes enabled can create the new grafana-managed rule with only query step advanced mode 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "for": "1m", + "grafana_alert": { + "condition": "C", + "data": [ + { + "datasourceUid": "prometheus", + "model": { + "instant": true, + "refId": "A", + }, + "queryType": "", + "refId": "A", + "relativeTimeRange": { + "from": 600, + "to": 0, + }, + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, "query": { "params": [ "B", @@ -120,15 +779,59 @@ exports[`Can create a new grafana managed alert using simplified routing can cre "uid": "__expr__", }, "expression": "A", + "reducer": "last", "refId": "B", - "type": "threshold", + "type": "reduce", }, "queryType": "", "refId": "B", }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "C", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "B", + "refId": "C", + "type": "threshold", + }, + "queryType": "", + "refId": "C", + }, ], "exec_err_state": "Error", "is_paused": false, + "metadata": { + "editor_settings": { + "simplified_notifications_section": true, + "simplified_query_and_expressions_section": false, + }, + }, "no_data_state": "NoData", "notification_settings": { "receiver": "lotsa-emails", diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx index 465b2ef0dc3..0a2a8634ce8 100644 --- a/public/test/helpers/alertingRuleEditor.tsx +++ b/public/test/helpers/alertingRuleEditor.tsx @@ -1,15 +1,20 @@ -import { Routes, Route } from 'react-router-dom-v5-compat'; +import { Route, Routes } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; import { selectors } from '@grafana/e2e-selectors'; import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList'; import RuleEditor from 'app/features/alerting/unified/RuleEditor'; +export enum GrafanaRuleFormStep { + Query = 2, + Notification = 5, +} export const ui = { loadingIndicator: byText('Loading rule...'), inputs: { name: byRole('textbox', { name: 'name' }), + metric: byRole('textbox', { name: 'metric' }), alertType: byTestId('alert-type-picker'), dataSource: byTestId(selectors.components.DataSourcePicker.inputV2), folder: byTestId('folder-picker'), @@ -26,6 +31,8 @@ export const ui = { contactPoint: byTestId('contact-point-picker'), routingOptions: byText(/muting, grouping and timings \(optional\)/i), }, + switchModeBasic: (stepNo: GrafanaRuleFormStep) => byTestId(`advanced-switch-${stepNo}-basic`), + switchModeAdvanced: (stepNo: GrafanaRuleFormStep) => byTestId(`advanced-switch-${stepNo}-advanced`), }, buttons: { saveAndExit: byRole('button', { name: 'Save rule and exit' }), @@ -34,8 +41,7 @@ export const ui = { addLabel: byRole('button', { name: /Add label/ }), }, }; - -export function renderRuleEditor(identifier?: string, recording = false) { +export function renderRuleEditor(identifier?: string, recording?: 'recording' | 'grafana-recording') { return render( <> @@ -46,9 +52,7 @@ export function renderRuleEditor(identifier?: string, recording = false) { , { historyOptions: { - initialEntries: [ - identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ? 'recording' : 'alerting'}`, - ], + initialEntries: [identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ?? 'alerting'}`], }, } );