From 185f03814b036e8632f40380ee2aa1247e1d614f Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Tue, 27 Sep 2022 13:19:49 +0100 Subject: [PATCH 01/15] initial commit --- .../group10/perform_bulk_action.ts | 172 +++++++++++++++--- 1 file changed, 146 insertions(+), 26 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index b60ce575c4cd..7c5242f82275 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -34,6 +34,7 @@ import { installPrePackagedRules, getSimpleMlRule, getWebHookAction, + getSlackAction, } from '../../utils'; // eslint-disable-next-line import/no-default-export @@ -72,6 +73,15 @@ export default ({ getService }: FtrProviderContext): void => { .expect(200) ).body; + const createSlackAction = async () => + ( + await supertest + .post('/api/actions/action') + .set('kbn-xsrf', 'true') + .send(getSlackAction()) + .expect(200) + ).body; + describe('perform_bulk_action', () => { beforeEach(async () => { await createSignalsIndex(supertest, log); @@ -1092,12 +1102,12 @@ export default ({ getService }: FtrProviderContext): void => { const webHookActionMock = { group: 'default', params: { - body: '{}', + body: '{"test":"action to be saved in a rule"}', }, }; describe('set_rule_actions', () => { - it('should set action correctly', async () => { + it('should set action correctly to existing empty actions list', async () => { const ruleId = 'ruleId'; const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); @@ -1125,25 +1135,77 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql([ + const expectedRuleActions = [ { ...webHookActionMock, id: hookAction.id, action_type_id: '.webhook', }, - ]); + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql([ + expect(readRule.actions).to.eql(expectedRuleActions); + }); + + it('should set action correctly to existing non empty actions list', async () => { + const hookAction = await createWebHookAction(); + + const existingRuleAction = { + id: hookAction.id, + action_type_id: '.webhook', + group: 'default', + params: { + body: '{"test":"an existing action"}', + }, + }; + + const ruleId = 'ruleId'; + const createdRule = await createRule(supertest, log, { + ...getSimpleRule(ruleId), + actions: [existingRuleAction], + }); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkAction.edit, + [BulkAction.edit]: [ + { + type: BulkActionEditType.set_rule_actions, + value: { + throttle: '1h', + actions: [ + { + ...webHookActionMock, + id: hookAction.id, + }, + ], + }, + }, + ], + }) + .expect(200); + + const expectedRuleActions = [ { ...webHookActionMock, id: hookAction.id, action_type_id: '.webhook', }, - ]); + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + + // Check that the updates have been persisted + const { body: readRule } = await fetchRule(ruleId).expect(200); + + expect(readRule.actions).to.eql(expectedRuleActions); }); it('should set actions to empty list, actions payload is empty list', async () => { @@ -1221,28 +1283,24 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql([ + const expectedRuleActions = [ { ...webHookActionMock, id: hookAction.id, action_type_id: '.webhook', }, - ]); + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql([ - { - ...webHookActionMock, - id: hookAction.id, - action_type_id: '.webhook', - }, - ]); + expect(readRule.actions).to.eql(expectedRuleActions); }); - it('should add action correctly to non empty actions list', async () => { + it('should add action correctly to non empty actions list of the same type', async () => { // create a new action const hookAction = await createWebHookAction(); @@ -1283,27 +1341,89 @@ export default ({ getService }: FtrProviderContext): void => { }) .expect(200); - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].actions).to.eql([ + const expectedRuleActions = [ defaultRuleAction, { ...webHookActionMock, id: hookAction.id, action_type_id: '.webhook', }, - ]); + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); // Check that the updates have been persisted const { body: readRule } = await fetchRule(ruleId).expect(200); - expect(readRule.actions).to.eql([ + expect(readRule.actions).to.eql(expectedRuleActions); + }); + + it('should add action correctly to non empty actions list of a different type', async () => { + // create new actions + const webHookAction = await createWebHookAction(); + const slackAction = await createSlackAction(); + + const defaultRuleAction = { + id: webHookAction.id, + action_type_id: '.webhook', + group: 'default', + params: { + body: '{"test":"a default action"}', + }, + }; + + const slackActionMockProps = { + group: 'default', + params: { + message: 'test slack message', + }, + }; + + const ruleId = 'ruleId'; + const createdRule = await createRule(supertest, log, { + ...getSimpleRule(ruleId), + actions: [defaultRuleAction], + throttle: '1d', + }); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkAction.edit, + [BulkAction.edit]: [ + { + type: BulkActionEditType.add_rule_actions, + value: { + throttle: '1h', + actions: [ + { + ...slackActionMockProps, + id: slackAction.id, + }, + ], + }, + }, + ], + }) + .expect(200); + + const expectedRuleActions = [ defaultRuleAction, { - ...webHookActionMock, - id: hookAction.id, - action_type_id: '.webhook', + ...slackActionMockProps, + id: slackAction.id, + action_type_id: '.slack', }, - ]); + ]; + + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].actions).to.eql(expectedRuleActions); + + // Check that the updates have been persisted + const { body: readRule } = await fetchRule(ruleId).expect(200); + + expect(readRule.actions).to.eql(expectedRuleActions); }); it('should not change actions of rule if empty list of actions added', async () => { From fdcb49d5c4e95b56580924ba1fbbc6a78625a47f Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Wed, 28 Sep 2022 14:19:55 +0100 Subject: [PATCH 02/15] e2e tests --- .../bulk_edit_rules_actions.cy.ts | 199 ++++++++++++++++++ .../cypress/screens/common/rule_actions.ts | 10 + .../cypress/screens/rules_bulk_edit.ts | 12 ++ .../cypress/tasks/alerts_detection_rules.ts | 12 ++ .../cypress/tasks/api_calls/the_connectors.ts | 14 ++ .../cypress/tasks/common/rule_actions.ts | 13 ++ .../cypress/tasks/edit_rule.ts | 5 + .../cypress/tasks/rule_details.ts | 4 + .../cypress/tasks/rules_bulk_edit.ts | 23 ++ .../rules/rule_actions_field/index.tsx | 3 + .../validate_rule_actions_field.ts | 2 +- .../bulk_actions/forms/rule_actions_form.tsx | 2 +- 12 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts create mode 100644 x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts new file mode 100644 index 000000000000..53aab1a0b017 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { + RULES_BULK_EDIT_ACTIONS_INFO, + RULES_BULK_EDIT_ACTIONS_WARNING, +} from '../../screens/rules_bulk_edit'; + +import { + EMAIL_ACTION_BTN, + CREATE_ACTION_CONNECTOR_BTN, + SAVE_ACTION_CONNECTOR_BTN, + EMAIL_ACTION_TO_INPUT, + EMAIL_ACTION_SUBJECT_INPUT, +} from '../../screens/create_new_rule'; + +import { SLACK_ACTION_MESSAGE_TEXTAREA } from '../../screens/common/rule_actions'; + +import { fillEmailConnectorForm } from '../../tasks/create_new_rule'; +import { addSlackRuleAction } from '../../tasks/common/rule_actions'; +import { + waitForRulesTableToBeLoaded, + selectNumberOfRules, + loadPrebuiltDetectionRulesFromHeaderBtn, + goToEditRuleActionsSettings, +} from '../../tasks/alerts_detection_rules'; +import { + waitForBulkEditActionToFinish, + submitBulkEditForm, + checkOverwriteRuleActionsCheckbox, + openBulkEditRuleActionsForm, + pickActionFrequency, +} from '../../tasks/rules_bulk_edit'; +import { assertSelectedActionFrequency } from '../../tasks/edit_rule'; +import { login, visitWithoutDateRange } from '../../tasks/login'; + +import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; +import { + createMachineLearningRule, + createCustomIndicatorRule, + createEventCorrelationRule, + createThresholdRule, + createNewTermsRule, + createSavedQueryRule, + createCustomRuleEnabled, +} from '../../tasks/api_calls/rules'; +import { createConnector } from '../../tasks/api_calls/the_connectors'; +import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; + +import { + getEqlRule, + getNewThreatIndicatorRule, + getNewRule, + getNewThresholdRule, + getMachineLearningRule, + getNewTermsRule, +} from '../../objects/rule'; + +import { esArchiverResetKibana } from '../../tasks/es_archiver'; + +const RULE_NAME = 'Custom rule for bulk actions'; + +const expectedNumberOfCustomRulesToBeEdited = 7; +// 7 custom rules of different types + 3 prebuilt +const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + 3; + +const expectedSlackMessage = 'Slack action test message'; + +const defaultRuleData = {}; + +const createSlackConnector = () => + createConnector({ + actionTypeId: '.slack', + secrets: { + webhookUrl: 'http://localhost:123', + }, + name: 'Slack connector', + }); + +describe('Detection rules, bulk edit', () => { + before(() => { + cleanKibana(); + login(); + }); + beforeEach(() => { + deleteAlertsAndRules(); + esArchiverResetKibana(); + createCustomRuleEnabled( + { + ...getNewRule(), + name: RULE_NAME, + ...defaultRuleData, + }, + '1' + ); + createEventCorrelationRule({ ...getEqlRule(), ...defaultRuleData }, '2'); + createMachineLearningRule({ ...getMachineLearningRule(), ...defaultRuleData }); + createCustomIndicatorRule({ ...getNewThreatIndicatorRule(), ...defaultRuleData }, '4'); + createThresholdRule({ ...getNewThresholdRule(), ...defaultRuleData }, '5'); + createNewTermsRule({ ...getNewTermsRule(), ...defaultRuleData }, '6'); + createSavedQueryRule({ ...getNewRule(), ...defaultRuleData, savedId: 'mocked' }, '7'); + + createSlackConnector(); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + waitForRulesTableToBeLoaded(); + }); + + context('Restricted privileges', () => {}); + context('Editing custom and prebuilt rules', () => { + it('Add rule actions to rules', () => { + const expectedActionFrequency = 'Daily'; + + loadPrebuiltDetectionRulesFromHeaderBtn(); + + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); + + // ensure rule actions info callout is shown on the form + cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); + + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); + + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + + // check if rule has been updated + goToEditRuleActionsSettings(); + + assertSelectedActionFrequency(expectedActionFrequency); + cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', expectedSlackMessage); + }); + + it('Overwrite rule actions in rules', () => { + const expectedActionFrequency = 'On each rule execution'; + + loadPrebuiltDetectionRulesFromHeaderBtn(); + + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); + + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); + + // check overwrite box, ensure warning is displayed + checkOverwriteRuleActionsCheckbox(); + cy.get(RULES_BULK_EDIT_ACTIONS_WARNING).contains( + `You're about to overwrite rule actions for ${expectedNumberOfRulesToBeEdited} selected rules` + ); + + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + + // check if rule has been updated + goToEditRuleActionsSettings(); + + assertSelectedActionFrequency(expectedActionFrequency); + cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', expectedSlackMessage); + }); + + it('Add rule actions to rules when creating connector', () => { + const expectedActionFrequency = 'Hourly'; + const expectedEmail = 'test@example.com'; + const expectedSubject = 'Subject'; + + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + openBulkEditRuleActionsForm(); + + pickActionFrequency(expectedActionFrequency); + + cy.get(EMAIL_ACTION_BTN).click(); + cy.get(CREATE_ACTION_CONNECTOR_BTN).click(); + fillEmailConnectorForm(); + cy.get(SAVE_ACTION_CONNECTOR_BTN).click(); + + cy.get(EMAIL_ACTION_TO_INPUT).type(expectedEmail); + cy.get(EMAIL_ACTION_SUBJECT_INPUT).type(expectedSubject); + + submitBulkEditForm(); + + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + + // check if rule has been updated + goToEditRuleActionsSettings(); + + assertSelectedActionFrequency(expectedActionFrequency); + + cy.get(EMAIL_ACTION_TO_INPUT).contains(expectedEmail); + cy.get(EMAIL_ACTION_SUBJECT_INPUT).should('have.value', expectedSubject); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts new file mode 100644 index 000000000000..032fcff0624c --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const SLACK_ACTION_BTN = '[data-test-subj=".slack-siem-ActionTypeSelectOption"]'; + +export const SLACK_ACTION_MESSAGE_TEXTAREA = '[data-test-subj="messageTextArea"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts index 341633380a3f..a24841e9463a 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rules_bulk_edit.ts @@ -15,6 +15,8 @@ export const DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM = export const UPDATE_SCHEDULE_MENU_ITEM = '[data-test-subj="setScheduleBulk"]'; +export const ADD_RULE_ACTIONS_MENU_ITEM = '[data-test-subj="addRuleActionsBulk"]'; + export const APPLY_TIMELINE_RULE_BULK_MENU_ITEM = '[data-test-subj="applyTimelineTemplateBulk"]'; export const TAGS_RULE_BULK_MENU_ITEM = '[data-test-subj="tagsBulkEditRule"]'; @@ -57,3 +59,13 @@ export const UPDATE_SCHEDULE_LOOKBACK_INPUT = '[data-test-subj="bulkEditRulesScheduleLookbackSelector"]'; export const UPDATE_SCHEDULE_TIME_UNIT_SELECT = '[data-test-subj="timeType"]'; + +export const RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT = + '[data-test-subj="bulkEditRulesRuleActionThrottle"] [data-test-subj="select"]'; + +export const RULES_BULK_EDIT_ACTIONS_INFO = '[data-test-subj="bulkEditRulesRuleActionInfo"]'; + +export const RULES_BULK_EDIT_ACTIONS_WARNING = '[data-test-subj="bulkEditRulesRuleActionsWarning"]'; + +export const RULES_BULK_EDIT_OVERWRITE_ACTIONS_CHECKBOX = + '[data-test-subj="bulkEditRulesOverwriteRuleActions"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index 4432059c7f20..6c90e3fcf84e 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -59,8 +59,12 @@ import { } from '../screens/alerts_detection_rules'; import { EUI_CHECKBOX } from '../screens/common/controls'; import { ALL_ACTIONS } from '../screens/rule_details'; +import { EDIT_SUBMIT_BUTTON } from '../screens/edit_rule'; import { LOADING_INDICATOR } from '../screens/security_header'; +import { goToRuleEditSettings } from './rule_details'; +import { goToActionsStepTab } from './create_new_rule'; + export const enableRule = (rulePosition: number) => { cy.get(RULE_SWITCH).eq(rulePosition).click({ force: true }); }; @@ -387,3 +391,11 @@ export const cancelConfirmationModal = () => { export const clickErrorToastBtn = () => { cy.get(TOASTER_ERROR_BTN).click(); }; + +export const goToEditRuleActionsSettings = () => { + goToRuleDetails(); + goToRuleEditSettings(); + // wait until first step loads completely. Otherwise cypress stuck at the first edit page + cy.get(EDIT_SUBMIT_BUTTON).should('be.enabled'); + goToActionsStepTab(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts new file mode 100644 index 000000000000..3bfb49bd5112 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export const createConnector = (connector: Record) => + cy.request({ + method: 'POST', + url: '/api/actions/action', + body: connector, + headers: { 'kbn-xsrf': 'cypress-creds' }, + }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts new file mode 100644 index 000000000000..9beb913283dc --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SLACK_ACTION_BTN, SLACK_ACTION_MESSAGE_TEXTAREA } from '../../screens/common/rule_actions'; + +export const addSlackRuleAction = (message: string) => { + cy.get(SLACK_ACTION_BTN).click(); + cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).clear().type(message); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts index 42d5619c28a6..a016691328ff 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/edit_rule.ts @@ -6,6 +6,7 @@ */ import { BACK_TO_RULE_DETAILS, EDIT_SUBMIT_BUTTON } from '../screens/edit_rule'; +import { ACTIONS_THROTTLE_INPUT } from '../screens/create_new_rule'; export const saveEditedRule = () => { cy.get(EDIT_SUBMIT_BUTTON).should('exist').click({ force: true }); @@ -16,3 +17,7 @@ export const goBackToRuleDetails = () => { cy.get(BACK_TO_RULE_DETAILS).should('exist').click(); cy.get(BACK_TO_RULE_DETAILS).should('not.exist'); }; + +export const assertSelectedActionFrequency = (frequency: string) => { + cy.get(ACTIONS_THROTTLE_INPUT).find('option:selected').should('have.text', frequency); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 75668b49aa20..751da0377660 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -163,3 +163,7 @@ export const hasIndexPatterns = (indexPatterns: string) => { getDetails(INDEX_PATTERNS_DETAILS).should('have.text', indexPatterns); }); }; + +export const goToRuleEditSettings = () => { + cy.contains('Edit rule settings').click(); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts index c3f9336e19fc..a9a89a10a524 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rules_bulk_edit.ts @@ -19,6 +19,7 @@ import { import { INDEX_PATTERNS_RULE_BULK_MENU_ITEM, ADD_INDEX_PATTERNS_RULE_BULK_MENU_ITEM, + ADD_RULE_ACTIONS_MENU_ITEM, DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM, TAGS_RULE_BULK_MENU_ITEM, ADD_TAGS_RULE_BULK_MENU_ITEM, @@ -36,6 +37,8 @@ import { UPDATE_SCHEDULE_TIME_UNIT_SELECT, UPDATE_SCHEDULE_LOOKBACK_INPUT, RULES_BULK_EDIT_SCHEDULES_WARNING, + RULES_BULK_EDIT_OVERWRITE_ACTIONS_CHECKBOX, + RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT, } from '../screens/rules_bulk_edit'; import { SCHEDULE_DETAILS } from '../screens/rule_details'; @@ -80,6 +83,13 @@ export const openBulkEditAddIndexPatternsForm = () => { cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add index patterns'); }; +export const openBulkEditRuleActionsForm = () => { + cy.get(BULK_ACTIONS_BTN).click(); + cy.get(ADD_RULE_ACTIONS_MENU_ITEM).click(); + + cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add rule actions'); +}; + export const openBulkEditDeleteIndexPatternsForm = () => { cy.get(BULK_ACTIONS_BTN).click(); cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click(); @@ -159,6 +169,14 @@ export const checkOverwriteIndexPatternsCheckbox = () => { .should('be.checked'); }; +export const checkOverwriteRuleActionsCheckbox = () => { + cy.get(RULES_BULK_EDIT_OVERWRITE_ACTIONS_CHECKBOX) + .should('have.text', 'Overwrite all selected rules actions') + .find('input') + .click({ force: true }) + .should('be.checked'); +}; + export const selectTimelineTemplate = (timelineTitle: string) => { cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR).click(); cy.get(TIMELINE_SEARCHBOX).type(`${timelineTitle}{enter}`).should('not.exist'); @@ -185,6 +203,7 @@ export const typeScheduleInterval = (interval: string) => { .type(interval.toString()) .blur(); }; + export const typeScheduleLookback = (lookback: string) => { cy.get(UPDATE_SCHEDULE_LOOKBACK_INPUT) .find('input') @@ -225,3 +244,7 @@ export const assertRuleScheduleValues = ({ cy.get('dd').eq(1).should('contain.text', lookback); }); }; + +export const pickActionFrequency = (frequency: string) => { + cy.get(RULES_BULK_EDIT_ACTIONS_THROTTLE_INPUT).select(frequency); +}; diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 0babfc8de387..96603b7217ef 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -84,6 +84,7 @@ export const RuleActionsField: React.FC = ({ field, messageVariables }) = (id: string, index: number) => { const updatedActions = [...(actions as Array>)]; updatedActions[index] = deepMerge(updatedActions[index], { id }); + console.log('setActionIdByIndex', JSON.stringify(updatedActions, null, 2)); field.setValue(updatedActions); }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -107,6 +108,8 @@ export const RuleActionsField: React.FC = ({ field, messageVariables }) = [key]: value, }, }; + console.log('setActionParamsProperty', JSON.stringify(updatedActions, null, 2)); + return updatedActions; }); }, diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 18aa758d0a49..6dd65b9fc333 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -30,7 +30,7 @@ export const validateRuleActionsField = ...data: Parameters ): Promise | void | undefined> => { const [{ value, path }] = data as [{ value: RuleAction[]; path: string }]; - + console.log('validateRuleActionsField', JSON.stringify(value, null, 2)); const errors = []; for (const actionItem of value) { const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx index 8a9e52c9d552..e9a057279023 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/forms/rule_actions_form.tsx @@ -118,7 +118,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction const throttleFieldComponentProps = useMemo( () => ({ idAria: 'bulkEditRulesRuleActionThrottle', - dataTestSubj: 'bulkEditRulesRuleActionThrottle', + 'data-test-subj': 'bulkEditRulesRuleActionThrottle', hasNoInitialSelection: false, euiFieldProps: { options: THROTTLE_OPTIONS, From e40892072f323bb5c788adc07997c19c3d7f34b0 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:21:04 +0100 Subject: [PATCH 03/15] Update index.tsx --- .../detections/components/rules/rule_actions_field/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index 96603b7217ef..0babfc8de387 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -84,7 +84,6 @@ export const RuleActionsField: React.FC = ({ field, messageVariables }) = (id: string, index: number) => { const updatedActions = [...(actions as Array>)]; updatedActions[index] = deepMerge(updatedActions[index], { id }); - console.log('setActionIdByIndex', JSON.stringify(updatedActions, null, 2)); field.setValue(updatedActions); }, // eslint-disable-next-line react-hooks/exhaustive-deps @@ -108,8 +107,6 @@ export const RuleActionsField: React.FC = ({ field, messageVariables }) = [key]: value, }, }; - console.log('setActionParamsProperty', JSON.stringify(updatedActions, null, 2)); - return updatedActions; }); }, From 41d8fe5de56ae42d8bcf60e63af4ff7ba79f3127 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:21:24 +0100 Subject: [PATCH 04/15] Update validate_rule_actions_field.ts --- .../validate_rule_actions_field/validate_rule_actions_field.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 6dd65b9fc333..f92ef83b8d13 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -30,7 +30,6 @@ export const validateRuleActionsField = ...data: Parameters ): Promise | void | undefined> => { const [{ value, path }] = data as [{ value: RuleAction[]; path: string }]; - console.log('validateRuleActionsField', JSON.stringify(value, null, 2)); const errors = []; for (const actionItem of value) { const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); From 8cb33ba679a42813f18d440598e98d1e480ff603 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:22:09 +0100 Subject: [PATCH 05/15] Update validate_rule_actions_field.ts --- .../validate_rule_actions_field/validate_rule_actions_field.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index f92ef83b8d13..2fe781a9a380 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -31,6 +31,7 @@ export const validateRuleActionsField = ): Promise | void | undefined> => { const [{ value, path }] = data as [{ value: RuleAction[]; path: string }]; const errors = []; + for (const actionItem of value) { const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); From 607ac43e8654117abcb1b79a7227be2c0b986cf9 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:22:39 +0100 Subject: [PATCH 06/15] Update validate_rule_actions_field.ts --- .../validate_rule_actions_field/validate_rule_actions_field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 2fe781a9a380..0446dfefa4da 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -30,8 +30,8 @@ export const validateRuleActionsField = ...data: Parameters ): Promise | void | undefined> => { const [{ value, path }] = data as [{ value: RuleAction[]; path: string }]; + const errors = []; - for (const actionItem of value) { const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); From 636067e72097d2188c8e1ce65c727b4c3b398b8c Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko <92328789+vitaliidm@users.noreply.github.com> Date: Wed, 28 Sep 2022 14:23:08 +0100 Subject: [PATCH 07/15] Update validate_rule_actions_field.ts --- .../validate_rule_actions_field/validate_rule_actions_field.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 0446dfefa4da..18aa758d0a49 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -30,7 +30,7 @@ export const validateRuleActionsField = ...data: Parameters ): Promise | void | undefined> => { const [{ value, path }] = data as [{ value: RuleAction[]; path: string }]; - + const errors = []; for (const actionItem of value) { const errorsArray = await validateSingleAction(actionItem, actionTypeRegistry); From 323bbe954cd3605845938df83a15eeed421777d4 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Wed, 28 Sep 2022 17:56:09 +0100 Subject: [PATCH 08/15] refactoring --- .../bulk_edit_rules_actions.cy.ts | 82 ++++++------------- .../detection_rules/custom_query_rule.cy.ts | 17 +--- .../cypress/screens/common/rule_actions.ts | 26 ++++++ .../cypress/screens/create_new_rule.ts | 26 ------ .../cypress/tasks/api_calls/the_connectors.ts | 12 ++- .../cypress/tasks/common/rule_actions.ts | 59 ++++++++++++- .../cypress/tasks/create_new_rule.ts | 19 ----- 7 files changed, 121 insertions(+), 120 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index 53aab1a0b017..7cc740b2a4b8 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -10,18 +10,13 @@ import { RULES_BULK_EDIT_ACTIONS_WARNING, } from '../../screens/rules_bulk_edit'; +import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; import { - EMAIL_ACTION_BTN, - CREATE_ACTION_CONNECTOR_BTN, - SAVE_ACTION_CONNECTOR_BTN, - EMAIL_ACTION_TO_INPUT, - EMAIL_ACTION_SUBJECT_INPUT, -} from '../../screens/create_new_rule'; - -import { SLACK_ACTION_MESSAGE_TEXTAREA } from '../../screens/common/rule_actions'; - -import { fillEmailConnectorForm } from '../../tasks/create_new_rule'; -import { addSlackRuleAction } from '../../tasks/common/rule_actions'; + addSlackRuleAction, + assertSlackRuleAction, + addEmailConnectorAndRuleAction, + assertEmailRuleAction, +} from '../../tasks/common/rule_actions'; import { waitForRulesTableToBeLoaded, selectNumberOfRules, @@ -37,8 +32,10 @@ import { } from '../../tasks/rules_bulk_edit'; import { assertSelectedActionFrequency } from '../../tasks/edit_rule'; import { login, visitWithoutDateRange } from '../../tasks/login'; +import { esArchiverResetKibana } from '../../tasks/es_archiver'; import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; + import { createMachineLearningRule, createCustomIndicatorRule, @@ -48,8 +45,7 @@ import { createSavedQueryRule, createCustomRuleEnabled, } from '../../tasks/api_calls/rules'; -import { createConnector } from '../../tasks/api_calls/the_connectors'; -import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; +import { createSlackConnector } from '../../tasks/api_calls/the_connectors'; import { getEqlRule, @@ -60,27 +56,13 @@ import { getNewTermsRule, } from '../../objects/rule'; -import { esArchiverResetKibana } from '../../tasks/es_archiver'; - -const RULE_NAME = 'Custom rule for bulk actions'; - const expectedNumberOfCustomRulesToBeEdited = 7; -// 7 custom rules of different types + 3 prebuilt +// 7 custom rules of different types + 3 prebuilt. +// number of selected rules doesn't matter, we only want to make sure they will be edited an no modal window displayed as for other actions const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + 3; const expectedSlackMessage = 'Slack action test message'; -const defaultRuleData = {}; - -const createSlackConnector = () => - createConnector({ - actionTypeId: '.slack', - secrets: { - webhookUrl: 'http://localhost:123', - }, - name: 'Slack connector', - }); - describe('Detection rules, bulk edit', () => { before(() => { cleanKibana(); @@ -89,20 +71,14 @@ describe('Detection rules, bulk edit', () => { beforeEach(() => { deleteAlertsAndRules(); esArchiverResetKibana(); - createCustomRuleEnabled( - { - ...getNewRule(), - name: RULE_NAME, - ...defaultRuleData, - }, - '1' - ); - createEventCorrelationRule({ ...getEqlRule(), ...defaultRuleData }, '2'); - createMachineLearningRule({ ...getMachineLearningRule(), ...defaultRuleData }); - createCustomIndicatorRule({ ...getNewThreatIndicatorRule(), ...defaultRuleData }, '4'); - createThresholdRule({ ...getNewThresholdRule(), ...defaultRuleData }, '5'); - createNewTermsRule({ ...getNewTermsRule(), ...defaultRuleData }, '6'); - createSavedQueryRule({ ...getNewRule(), ...defaultRuleData, savedId: 'mocked' }, '7'); + + createCustomRuleEnabled(getNewRule(), '1'); + createEventCorrelationRule(getEqlRule(), '2'); + createMachineLearningRule(getMachineLearningRule(), '3'); + createCustomIndicatorRule(getNewThreatIndicatorRule(), '4'); + createThresholdRule(getNewThresholdRule(), '5'); + createNewTermsRule(getNewTermsRule(), '6'); + createSavedQueryRule({ ...getNewRule(), savedId: 'mocked' }, '7'); createSlackConnector(); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); @@ -121,7 +97,7 @@ describe('Detection rules, bulk edit', () => { selectNumberOfRules(expectedNumberOfRulesToBeEdited); openBulkEditRuleActionsForm(); - // ensure rule actions info callout is shown on the form + // ensure rule actions info callout displayed on the form cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); pickActionFrequency(expectedActionFrequency); @@ -134,7 +110,7 @@ describe('Detection rules, bulk edit', () => { goToEditRuleActionsSettings(); assertSelectedActionFrequency(expectedActionFrequency); - cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', expectedSlackMessage); + assertSlackRuleAction(expectedSlackMessage); }); it('Overwrite rule actions in rules', () => { @@ -162,7 +138,7 @@ describe('Detection rules, bulk edit', () => { goToEditRuleActionsSettings(); assertSelectedActionFrequency(expectedActionFrequency); - cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', expectedSlackMessage); + assertSlackRuleAction(expectedSlackMessage); }); it('Add rule actions to rules when creating connector', () => { @@ -174,26 +150,16 @@ describe('Detection rules, bulk edit', () => { openBulkEditRuleActionsForm(); pickActionFrequency(expectedActionFrequency); - - cy.get(EMAIL_ACTION_BTN).click(); - cy.get(CREATE_ACTION_CONNECTOR_BTN).click(); - fillEmailConnectorForm(); - cy.get(SAVE_ACTION_CONNECTOR_BTN).click(); - - cy.get(EMAIL_ACTION_TO_INPUT).type(expectedEmail); - cy.get(EMAIL_ACTION_SUBJECT_INPUT).type(expectedSubject); + addEmailConnectorAndRuleAction(expectedEmail, expectedSubject); submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); // check if rule has been updated goToEditRuleActionsSettings(); assertSelectedActionFrequency(expectedActionFrequency); - - cy.get(EMAIL_ACTION_TO_INPUT).contains(expectedEmail); - cy.get(EMAIL_ACTION_SUBJECT_INPUT).should('have.value', expectedSubject); + assertEmailRuleAction(expectedEmail, expectedSubject); }); }); }); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts index bdc76cc5f0a5..97e184f87665 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/custom_query_rule.cy.ts @@ -40,12 +40,6 @@ import { SEVERITY_DROPDOWN, TAGS_CLEAR_BUTTON, TAGS_FIELD, - EMAIL_ACTION_BTN, - CREATE_ACTION_CONNECTOR_BTN, - SAVE_ACTION_CONNECTOR_BTN, - FROM_VALIDATION_ERROR, - EMAIL_ACTION_TO_INPUT, - EMAIL_ACTION_SUBJECT_INPUT, } from '../../screens/create_new_rule'; import { ADDITIONAL_LOOK_BACK_DETAILS, @@ -83,12 +77,12 @@ import { import { createCustomRuleEnabled } from '../../tasks/api_calls/rules'; import { createTimeline } from '../../tasks/api_calls/timelines'; import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; +import { addEmailConnectorAndRuleAction } from '../../tasks/common/rule_actions'; import { createAndEnableRule, fillAboutRule, fillAboutRuleAndContinue, fillDefineCustomRuleWithImportedQueryAndContinue, - fillEmailConnectorForm, fillScheduleRuleAndContinue, goToAboutStepTab, goToActionsStepTab, @@ -371,15 +365,8 @@ describe('Custom query rules', () => { cy.get(ACTIONS_THROTTLE_INPUT).invoke('val').should('eql', 'no_actions'); cy.get(ACTIONS_THROTTLE_INPUT).select('Weekly'); - cy.get(EMAIL_ACTION_BTN).click(); - cy.get(CREATE_ACTION_CONNECTOR_BTN).click(); - fillEmailConnectorForm(); - cy.get(SAVE_ACTION_CONNECTOR_BTN).click(); - cy.get(EMAIL_ACTION_TO_INPUT).type('test@example.com'); - cy.get(EMAIL_ACTION_SUBJECT_INPUT).type('Subject'); - - cy.get(FROM_VALIDATION_ERROR).should('not.exist'); + addEmailConnectorAndRuleAction('test@example.com', 'Subject'); goToAboutStepTab(); cy.get(TAGS_CLEAR_BUTTON).click({ force: true }); diff --git a/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts index 032fcff0624c..b0960215a2bd 100644 --- a/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts @@ -5,6 +5,32 @@ * 2.0. */ +export const EMAIL_ACTION_BTN = '[data-test-subj=".email-siem-ActionTypeSelectOption"]'; + +export const CREATE_ACTION_CONNECTOR_BTN = '[data-test-subj="createActionConnectorButton-0"]'; + +export const SAVE_ACTION_CONNECTOR_BTN = '[data-test-subj="saveActionButtonModal"]'; + +export const EMAIL_ACTION_TO_INPUT = '[data-test-subj="toEmailAddressInput"]'; + +export const EMAIL_ACTION_SUBJECT_INPUT = '[data-test-subj="subjectInput"]'; + export const SLACK_ACTION_BTN = '[data-test-subj=".slack-siem-ActionTypeSelectOption"]'; export const SLACK_ACTION_MESSAGE_TEXTAREA = '[data-test-subj="messageTextArea"]'; + +export const CONNECTOR_NAME_INPUT = '[data-test-subj="nameInput"]'; + +export const EMAIL_CONNECTOR_FROM_INPUT = '[data-test-subj="emailFromInput"]'; + +export const EMAIL_CONNECTOR_HOST_INPUT = '[data-test-subj="emailHostInput"]'; + +export const EMAIL_CONNECTOR_PORT_INPUT = '[data-test-subj="emailPortInput"]'; + +export const EMAIL_CONNECTOR_USER_INPUT = '[data-test-subj="emailUserInput"]'; + +export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInput"]'; + +export const EMAIL_CONNECTOR_SERVICE_SELECTOR = '[data-test-subj="emailServiceSelectInput"]'; + +export const FORM_VALIDATION_ERROR = '.euiFormErrorText'; diff --git a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts index e46e8a75a434..20bd9965e8b3 100644 --- a/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/screens/create_new_rule.ts @@ -16,32 +16,6 @@ export const ACTIONS_EDIT_TAB = '[data-test-subj="edit-rule-actions-tab"]'; export const ACTIONS_THROTTLE_INPUT = '[data-test-subj="stepRuleActions"] [data-test-subj="select"]'; -export const EMAIL_ACTION_BTN = '[data-test-subj=".email-siem-ActionTypeSelectOption"]'; - -export const CREATE_ACTION_CONNECTOR_BTN = '[data-test-subj="createActionConnectorButton-0"]'; - -export const SAVE_ACTION_CONNECTOR_BTN = '[data-test-subj="saveActionButtonModal"]'; - -export const EMAIL_ACTION_TO_INPUT = '[data-test-subj="toEmailAddressInput"]'; - -export const EMAIL_ACTION_SUBJECT_INPUT = '[data-test-subj="subjectInput"]'; - -export const FROM_VALIDATION_ERROR = '.euiFormErrorText'; - -export const CONNECTOR_NAME_INPUT = '[data-test-subj="nameInput"]'; - -export const EMAIL_CONNECTOR_FROM_INPUT = '[data-test-subj="emailFromInput"]'; - -export const EMAIL_CONNECTOR_HOST_INPUT = '[data-test-subj="emailHostInput"]'; - -export const EMAIL_CONNECTOR_PORT_INPUT = '[data-test-subj="emailPortInput"]'; - -export const EMAIL_CONNECTOR_USER_INPUT = '[data-test-subj="emailUserInput"]'; - -export const EMAIL_CONNECTOR_PASSWORD_INPUT = '[data-test-subj="emailPasswordInput"]'; - -export const EMAIL_CONNECTOR_SERVICE_SELECTOR = '[data-test-subj="emailServiceSelectInput"]'; - export const ADD_FALSE_POSITIVE_BTN = '[data-test-subj="detectionEngineStepAboutRuleFalsePositives"] .euiButtonEmpty__text'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts index 3bfb49bd5112..a89e578764ee 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts @@ -5,10 +5,20 @@ * 2.0. */ -export const createConnector = (connector: Record) => +export const createConnector = (connector: Record) => cy.request({ method: 'POST', url: '/api/actions/action', body: connector, headers: { 'kbn-xsrf': 'cypress-creds' }, }); + +const slackConnectorAPIPayload = { + actionTypeId: '.slack', + secrets: { + webhookUrl: 'http://localhost:123', + }, + name: 'Slack cypress test e2e connector', +}; + +export const createSlackConnector = () => createConnector(slackConnectorAPIPayload); diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts index 9beb913283dc..fa2109decd5a 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts @@ -5,9 +5,66 @@ * 2.0. */ -import { SLACK_ACTION_BTN, SLACK_ACTION_MESSAGE_TEXTAREA } from '../../screens/common/rule_actions'; +import { + SLACK_ACTION_BTN, + SLACK_ACTION_MESSAGE_TEXTAREA, + EMAIL_ACTION_BTN, + CREATE_ACTION_CONNECTOR_BTN, + SAVE_ACTION_CONNECTOR_BTN, + EMAIL_ACTION_TO_INPUT, + EMAIL_ACTION_SUBJECT_INPUT, + CONNECTOR_NAME_INPUT, + EMAIL_CONNECTOR_SERVICE_SELECTOR, + EMAIL_CONNECTOR_FROM_INPUT, + EMAIL_CONNECTOR_HOST_INPUT, + EMAIL_CONNECTOR_PORT_INPUT, + EMAIL_CONNECTOR_USER_INPUT, + EMAIL_CONNECTOR_PASSWORD_INPUT, + FORM_VALIDATION_ERROR, +} from '../../screens/common/rule_actions'; + +import type { EmailConnector } from '../../objects/connector'; +import { getEmailConnector } from '../../objects/connector'; export const addSlackRuleAction = (message: string) => { cy.get(SLACK_ACTION_BTN).click(); cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).clear().type(message); }; + +export const assertSlackRuleAction = (message: string) => { + cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', message); +}; + +const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => { + cy.get(CONNECTOR_NAME_INPUT).type(connector.name); + cy.get(EMAIL_CONNECTOR_SERVICE_SELECTOR).select(connector.service); + cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from); + cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host); + cy.get(EMAIL_CONNECTOR_PORT_INPUT).type(connector.port); + cy.get(EMAIL_CONNECTOR_USER_INPUT).type(connector.user); + cy.get(EMAIL_CONNECTOR_PASSWORD_INPUT).type(connector.password); +}; + +export const createEmailConnector = () => { + cy.get(CREATE_ACTION_CONNECTOR_BTN).click(); + fillEmailConnectorForm(); + cy.get(SAVE_ACTION_CONNECTOR_BTN).click(); +}; + +export const fillEmailRuleActionForm = (email: string, subject: string) => { + cy.get(EMAIL_ACTION_TO_INPUT).type(email); + cy.get(EMAIL_ACTION_SUBJECT_INPUT).type(subject); +}; + +export const addEmailConnectorAndRuleAction = (email: string, subject: string) => { + cy.get(EMAIL_ACTION_BTN).click(); + createEmailConnector(); + fillEmailRuleActionForm(email, subject); + + cy.get(FORM_VALIDATION_ERROR).should('not.exist'); +}; + +export const assertEmailRuleAction = (email: string, subject: string) => { + cy.get(EMAIL_ACTION_TO_INPUT).contains(email); + cy.get(EMAIL_ACTION_SUBJECT_INPUT).should('have.value', subject); +}; diff --git a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts index 2b7e8e0b3375..6f8c0c48217c 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/create_new_rule.ts @@ -5,8 +5,6 @@ * 2.0. */ -import type { EmailConnector } from '../objects/connector'; -import { getEmailConnector } from '../objects/connector'; import type { CustomRule, MachineLearningRule, @@ -92,13 +90,6 @@ import { THREAT_MATCH_QUERY_INPUT, THRESHOLD_INPUT_AREA, THRESHOLD_TYPE, - CONNECTOR_NAME_INPUT, - EMAIL_CONNECTOR_FROM_INPUT, - EMAIL_CONNECTOR_HOST_INPUT, - EMAIL_CONNECTOR_PORT_INPUT, - EMAIL_CONNECTOR_USER_INPUT, - EMAIL_CONNECTOR_PASSWORD_INPUT, - EMAIL_CONNECTOR_SERVICE_SELECTOR, PREVIEW_HISTOGRAM, DATA_VIEW_COMBO_BOX, DATA_VIEW_OPTION, @@ -439,16 +430,6 @@ export const fillIndexAndIndicatorIndexPattern = ( getIndicatorIndicatorIndex().type(`{backspace}{enter}${indicatorIndex}{enter}`); }; -export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => { - cy.get(CONNECTOR_NAME_INPUT).type(connector.name); - cy.get(EMAIL_CONNECTOR_SERVICE_SELECTOR).select(connector.service); - cy.get(EMAIL_CONNECTOR_FROM_INPUT).type(connector.from); - cy.get(EMAIL_CONNECTOR_HOST_INPUT).type(connector.host); - cy.get(EMAIL_CONNECTOR_PORT_INPUT).type(connector.port); - cy.get(EMAIL_CONNECTOR_USER_INPUT).type(connector.user); - cy.get(EMAIL_CONNECTOR_PASSWORD_INPUT).type(connector.password); -}; - /** Returns the indicator index drop down field. Pass in row number, default is 1 */ export const getIndicatorIndexComboField = (row = 1) => cy.get(THREAT_COMBO_BOX_INPUT).eq(row * 2 - 2); From 0c734f78c9e09bd4b291fe9f1ed469fa30e199eb Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Fri, 30 Sep 2022 12:11:21 +0100 Subject: [PATCH 09/15] refactoring --- .../cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts | 2 +- .../tasks/api_calls/{the_connectors.ts => connectors.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename x-pack/plugins/security_solution/cypress/tasks/api_calls/{the_connectors.ts => connectors.ts} (100%) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index 7cc740b2a4b8..1c6542340950 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -45,7 +45,7 @@ import { createSavedQueryRule, createCustomRuleEnabled, } from '../../tasks/api_calls/rules'; -import { createSlackConnector } from '../../tasks/api_calls/the_connectors'; +import { createSlackConnector } from '../../tasks/api_calls/connectors'; import { getEqlRule, diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/connectors.ts similarity index 100% rename from x-pack/plugins/security_solution/cypress/tasks/api_calls/the_connectors.ts rename to x-pack/plugins/security_solution/cypress/tasks/api_calls/connectors.ts From 7ff763ce204eac91856d70b5ebceb919ebcd4a70 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Fri, 30 Sep 2022 13:08:23 +0100 Subject: [PATCH 10/15] refactoring --- .../plugins/security_solution/cypress/screens/rule_details.ts | 2 ++ x-pack/plugins/security_solution/cypress/tasks/rule_details.ts | 3 ++- .../detections/pages/detection_engine/rules/details/index.tsx | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts index 0c834c140061..853e6036aac1 100644 --- a/x-pack/plugins/security_solution/cypress/screens/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/screens/rule_details.ts @@ -123,3 +123,5 @@ export const BACK_TO_RULES = '[data-test-subj="ruleDetailsBackToAllRules"]'; export const DEFINE_RULE_PANEL_PROGRESS = '[data-test-subj="defineRule"] [data-test-subj="stepPanelProgress"]'; + +export const EDIT_RULE_SETTINGS_LINK = '[data-test-subj="editRuleSettingsLink"]'; diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts index 0a63807d3f1d..a412675ab533 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_details.ts @@ -32,6 +32,7 @@ import { DETAILS_DESCRIPTION, EXCEPTION_ITEM_ACTIONS_BUTTON, EDIT_EXCEPTION_BTN, + EDIT_RULE_SETTINGS_LINK, } from '../screens/rule_details'; import { addsFields, closeFieldsBrowser, filterFieldsBrowser } from './fields_browser'; @@ -168,5 +169,5 @@ export const hasIndexPatterns = (indexPatterns: string) => { }; export const goToRuleEditSettings = () => { - cy.contains('Edit rule settings').click(); + cy.get(EDIT_RULE_SETTINGS_LINK).click(); }; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 5149269c57d9..5e3e2760743b 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -616,6 +616,7 @@ const RuleDetailsPageComponent: React.FC = ({ } return ( Date: Fri, 30 Sep 2022 17:28:26 +0100 Subject: [PATCH 11/15] refactoring --- .../bulk_edit_rules_actions.cy.ts | 107 +++++++++--------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index 1c6542340950..66448def886f 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -63,7 +63,7 @@ const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + const expectedSlackMessage = 'Slack action test message'; -describe('Detection rules, bulk edit', () => { +describe('Detection rules, bulk edit actions', () => { before(() => { cleanKibana(); login(); @@ -86,80 +86,77 @@ describe('Detection rules, bulk edit', () => { waitForRulesTableToBeLoaded(); }); - context('Restricted privileges', () => {}); - context('Editing custom and prebuilt rules', () => { - it('Add rule actions to rules', () => { - const expectedActionFrequency = 'Daily'; + it('Add rule actions to rules', () => { + const expectedActionFrequency = 'Daily'; - loadPrebuiltDetectionRulesFromHeaderBtn(); + loadPrebuiltDetectionRulesFromHeaderBtn(); - // select both custom and prebuilt rules - selectNumberOfRules(expectedNumberOfRulesToBeEdited); - openBulkEditRuleActionsForm(); + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); - // ensure rule actions info callout displayed on the form - cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); + // ensure rule actions info callout displayed on the form + cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); - pickActionFrequency(expectedActionFrequency); - addSlackRuleAction(expectedSlackMessage); + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); - // check if rule has been updated - goToEditRuleActionsSettings(); + // check if rule has been updated + goToEditRuleActionsSettings(); - assertSelectedActionFrequency(expectedActionFrequency); - assertSlackRuleAction(expectedSlackMessage); - }); + assertSelectedActionFrequency(expectedActionFrequency); + assertSlackRuleAction(expectedSlackMessage); + }); - it('Overwrite rule actions in rules', () => { - const expectedActionFrequency = 'On each rule execution'; + it('Overwrite rule actions in rules', () => { + const expectedActionFrequency = 'On each rule execution'; - loadPrebuiltDetectionRulesFromHeaderBtn(); + loadPrebuiltDetectionRulesFromHeaderBtn(); - // select both custom and prebuilt rules - selectNumberOfRules(expectedNumberOfRulesToBeEdited); - openBulkEditRuleActionsForm(); + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); - pickActionFrequency(expectedActionFrequency); - addSlackRuleAction(expectedSlackMessage); + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); - // check overwrite box, ensure warning is displayed - checkOverwriteRuleActionsCheckbox(); - cy.get(RULES_BULK_EDIT_ACTIONS_WARNING).contains( - `You're about to overwrite rule actions for ${expectedNumberOfRulesToBeEdited} selected rules` - ); + // check overwrite box, ensure warning is displayed + checkOverwriteRuleActionsCheckbox(); + cy.get(RULES_BULK_EDIT_ACTIONS_WARNING).contains( + `You're about to overwrite rule actions for ${expectedNumberOfRulesToBeEdited} selected rules` + ); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); - // check if rule has been updated - goToEditRuleActionsSettings(); + // check if rule has been updated + goToEditRuleActionsSettings(); - assertSelectedActionFrequency(expectedActionFrequency); - assertSlackRuleAction(expectedSlackMessage); - }); + assertSelectedActionFrequency(expectedActionFrequency); + assertSlackRuleAction(expectedSlackMessage); + }); - it('Add rule actions to rules when creating connector', () => { - const expectedActionFrequency = 'Hourly'; - const expectedEmail = 'test@example.com'; - const expectedSubject = 'Subject'; + it('Add rule actions to rules when creating connector', () => { + const expectedActionFrequency = 'Hourly'; + const expectedEmail = 'test@example.com'; + const expectedSubject = 'Subject'; - selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); - openBulkEditRuleActionsForm(); + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + openBulkEditRuleActionsForm(); - pickActionFrequency(expectedActionFrequency); - addEmailConnectorAndRuleAction(expectedEmail, expectedSubject); + pickActionFrequency(expectedActionFrequency); + addEmailConnectorAndRuleAction(expectedEmail, expectedSubject); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); - // check if rule has been updated - goToEditRuleActionsSettings(); + // check if rule has been updated + goToEditRuleActionsSettings(); - assertSelectedActionFrequency(expectedActionFrequency); - assertEmailRuleAction(expectedEmail, expectedSubject); - }); + assertSelectedActionFrequency(expectedActionFrequency); + assertEmailRuleAction(expectedEmail, expectedSubject); }); }); From fce583ef045694c64ffe64d33f61b3ecee127909 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Fri, 30 Sep 2022 17:44:52 +0100 Subject: [PATCH 12/15] additional tests --- .../group10/perform_bulk_action.ts | 181 +++++++++--------- 1 file changed, 91 insertions(+), 90 deletions(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 95203ba1ca8b..69ed2642e721 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -64,23 +64,12 @@ export default ({ getService }: FtrProviderContext): void => { const fetchRuleByAlertApi = (ruleId: string) => supertest.get(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'true'); - const createWebHookAction = async () => - ( - await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getWebHookAction()) - .expect(200) - ).body; + const createConnector = async >(payload: T) => + (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) + .body; - const createSlackAction = async () => - ( - await supertest - .post('/api/actions/action') - .set('kbn-xsrf', 'true') - .send(getSlackAction()) - .expect(200) - ).body; + const createWebHookConnector = () => createConnector(getWebHookAction()); + const createSlackConnector = () => createConnector(getSlackAction()); describe('perform_bulk_action', () => { beforeEach(async () => { @@ -1111,8 +1100,8 @@ export default ({ getService }: FtrProviderContext): void => { const ruleId = 'ruleId'; const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const { body } = await postBulkAction() .send({ @@ -1126,7 +1115,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1138,7 +1127,7 @@ export default ({ getService }: FtrProviderContext): void => { const expectedRuleActions = [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]; @@ -1153,10 +1142,10 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should set action correctly to existing non empty actions list', async () => { - const hookAction = await createWebHookAction(); + const webHookConnector = await createWebHookConnector(); const existingRuleAction = { - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', group: 'default', params: { @@ -1182,7 +1171,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1194,7 +1183,7 @@ export default ({ getService }: FtrProviderContext): void => { const expectedRuleActions = [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]; @@ -1209,11 +1198,11 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should set actions to empty list, actions payload is empty list', async () => { - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const defaultRuleAction = { - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', group: 'default', params: { @@ -1259,8 +1248,8 @@ export default ({ getService }: FtrProviderContext): void => { const ruleId = 'ruleId'; const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const { body } = await postBulkAction() .send({ @@ -1274,7 +1263,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1286,7 +1275,7 @@ export default ({ getService }: FtrProviderContext): void => { const expectedRuleActions = [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]; @@ -1301,11 +1290,11 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should add action correctly to non empty actions list of the same type', async () => { - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const defaultRuleAction = { - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', group: 'default', params: { @@ -1332,7 +1321,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1345,7 +1334,7 @@ export default ({ getService }: FtrProviderContext): void => { defaultRuleAction, { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]; @@ -1361,8 +1350,8 @@ export default ({ getService }: FtrProviderContext): void => { it('should add action correctly to non empty actions list of a different type', async () => { // create new actions - const webHookAction = await createWebHookAction(); - const slackAction = await createSlackAction(); + const webHookAction = await createWebHookConnector(); + const slackConnector = await createSlackConnector(); const defaultRuleAction = { id: webHookAction.id, @@ -1373,7 +1362,7 @@ export default ({ getService }: FtrProviderContext): void => { }, }; - const slackActionMockProps = { + const slackConnectorMockProps = { group: 'default', params: { message: 'test slack message', @@ -1398,8 +1387,8 @@ export default ({ getService }: FtrProviderContext): void => { throttle: '1h', actions: [ { - ...slackActionMockProps, - id: slackAction.id, + ...slackConnectorMockProps, + id: slackConnector.id, }, ], }, @@ -1411,8 +1400,8 @@ export default ({ getService }: FtrProviderContext): void => { const expectedRuleActions = [ defaultRuleAction, { - ...slackActionMockProps, - id: slackAction.id, + ...slackConnectorMockProps, + id: slackConnector.id, action_type_id: '.slack', }, ]; @@ -1427,11 +1416,11 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should not change actions of rule if empty list of actions added', async () => { - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const defaultRuleAction = { - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', group: 'default', params: { @@ -1472,11 +1461,11 @@ export default ({ getService }: FtrProviderContext): void => { }); it('should change throttle if actions list in payload is empty', async () => { - // create a new action - const hookAction = await createWebHookAction(); + // create a new connector + const webHookConnector = await createWebHookConnector(); const defaultRuleAction = { - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', group: 'default', params: { @@ -1530,7 +1519,7 @@ export default ({ getService }: FtrProviderContext): void => { it(`should apply "${type}" rule action to prebuilt rule`, async () => { await installPrePackagedRules(supertest, log); const prebuiltRule = await fetchPrebuiltRule(); - const hookAction = await createWebHookAction(); + const webHookConnector = await createWebHookConnector(); const { body } = await postBulkAction() .send({ @@ -1544,7 +1533,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1558,7 +1547,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(editedRule.actions).to.eql([ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]); @@ -1571,7 +1560,7 @@ export default ({ getService }: FtrProviderContext): void => { expect(readRule.actions).to.eql([ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, action_type_id: '.webhook', }, ]); @@ -1584,7 +1573,7 @@ export default ({ getService }: FtrProviderContext): void => { it(`should return error if one of edit action is not eligible for prebuilt rule`, async () => { await installPrePackagedRules(supertest, log); const prebuiltRule = await fetchPrebuiltRule(); - const hookAction = await createWebHookAction(); + const webHookConnector = await createWebHookConnector(); const { body } = await postBulkAction() .send({ @@ -1598,7 +1587,7 @@ export default ({ getService }: FtrProviderContext): void => { actions: [ { ...webHookActionMock, - id: hookAction.id, + id: webHookConnector.id, }, ], }, @@ -1688,46 +1677,58 @@ export default ({ getService }: FtrProviderContext): void => { payloadThrottle: '1h', expectedThrottle: '1h', }, + { + payloadThrottle: '1d', + expectedThrottle: '1d', + }, + { + payloadThrottle: '7d', + expectedThrottle: '7d', + }, ]; - casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { - it(`throttle is set correctly, if payload throttle="${payloadThrottle}" and actions non empty`, async () => { - // create a new action - const hookAction = await createWebHookAction(); - - const ruleId = 'ruleId'; - const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); - - const { body } = await postBulkAction() - .send({ - ids: [createdRule.id], - action: BulkAction.edit, - [BulkAction.edit]: [ - { - type: BulkActionEditType.set_rule_actions, - value: { - throttle: payloadThrottle, - actions: [ - { - id: hookAction.id, - group: 'default', - params: { body: '{}' }, + [BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].forEach( + (ruleAction) => { + casesForNonEmptyActions.forEach(({ payloadThrottle, expectedThrottle }) => { + it(`throttle is updated correctly for rule action "${ruleAction}", if payload throttle="${payloadThrottle}" and actions non empty`, async () => { + // create a new connector + const webHookConnector = await createWebHookConnector(); + + const ruleId = 'ruleId'; + const createdRule = await createRule(supertest, log, getSimpleRule(ruleId)); + + const { body } = await postBulkAction() + .send({ + ids: [createdRule.id], + action: BulkAction.edit, + [BulkAction.edit]: [ + { + type: BulkActionEditType.set_rule_actions, + value: { + throttle: payloadThrottle, + actions: [ + { + id: webHookConnector.id, + group: 'default', + params: { body: '{}' }, + }, + ], }, - ], - }, - }, - ], - }) - .expect(200); + }, + ], + }) + .expect(200); - // Check that the updated rule is returned with the response - expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); + // Check that the updated rule is returned with the response + expect(body.attributes.results.updated[0].throttle).to.eql(expectedThrottle); - // Check that the updates have been persisted - const { body: rule } = await fetchRule(ruleId).expect(200); + // Check that the updates have been persisted + const { body: rule } = await fetchRule(ruleId).expect(200); - expect(rule.throttle).to.eql(expectedThrottle); - }); - }); + expect(rule.throttle).to.eql(expectedThrottle); + }); + }); + } + ); }); describe('notifyWhen', () => { From 159309aef0f407c0e4d9bc02f1fa179b7f179e0b Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Fri, 30 Sep 2022 17:49:02 +0100 Subject: [PATCH 13/15] remove generic --- .../security_and_spaces/group10/perform_bulk_action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts index 69ed2642e721..77655b8ba150 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/group10/perform_bulk_action.ts @@ -64,7 +64,7 @@ export default ({ getService }: FtrProviderContext): void => { const fetchRuleByAlertApi = (ruleId: string) => supertest.get(`/api/alerting/rule/${ruleId}`).set('kbn-xsrf', 'true'); - const createConnector = async >(payload: T) => + const createConnector = async (payload: Record) => (await supertest.post('/api/actions/action').set('kbn-xsrf', 'true').send(payload).expect(200)) .body; From dea07e318a55fd720ff265467b825a90bf04defa Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Mon, 3 Oct 2022 10:47:19 +0100 Subject: [PATCH 14/15] add non actions tests --- .../bulk_edit_rules_actions.cy.ts | 134 +++++++++++------- .../all/bulk_actions/use_bulk_actions.tsx | 4 +- 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index 66448def886f..d1d4aad72b44 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -5,12 +5,15 @@ * 2.0. */ +import { ROLES } from '../../../common/test'; + import { RULES_BULK_EDIT_ACTIONS_INFO, RULES_BULK_EDIT_ACTIONS_WARNING, + ADD_RULE_ACTIONS_MENU_ITEM, } from '../../screens/rules_bulk_edit'; -import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common'; +import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common'; import { addSlackRuleAction, assertSlackRuleAction, @@ -29,6 +32,7 @@ import { checkOverwriteRuleActionsCheckbox, openBulkEditRuleActionsForm, pickActionFrequency, + openBulkActionsMenu, } from '../../tasks/rules_bulk_edit'; import { assertSelectedActionFrequency } from '../../tasks/edit_rule'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -63,13 +67,15 @@ const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + const expectedSlackMessage = 'Slack action test message'; -describe('Detection rules, bulk edit actions', () => { +describe('Detection rules, bulk edit of rule actions', () => { before(() => { cleanKibana(); login(); }); + beforeEach(() => { deleteAlertsAndRules(); + deleteConnectors(); esArchiverResetKibana(); createCustomRuleEnabled(getNewRule(), '1'); @@ -81,82 +87,100 @@ describe('Detection rules, bulk edit actions', () => { createSavedQueryRule({ ...getNewRule(), savedId: 'mocked' }, '7'); createSlackConnector(); - visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + }); + + context('Restricted action privileges', () => { + it('User has no actions privileges', () => { + login(ROLES.hunter_no_actions); + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL, ROLES.hunter_no_actions); + waitForRulesTableToBeLoaded(); + + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); - waitForRulesTableToBeLoaded(); + openBulkActionsMenu(); + + cy.get(ADD_RULE_ACTIONS_MENU_ITEM).should('be.disabled'); + }); }); - it('Add rule actions to rules', () => { - const expectedActionFrequency = 'Daily'; + context('All actions privileges', () => { + beforeEach(() => { + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + waitForRulesTableToBeLoaded(); + }); - loadPrebuiltDetectionRulesFromHeaderBtn(); + it('Add rule actions to rules', () => { + const expectedActionFrequency = 'Daily'; - // select both custom and prebuilt rules - selectNumberOfRules(expectedNumberOfRulesToBeEdited); - openBulkEditRuleActionsForm(); + loadPrebuiltDetectionRulesFromHeaderBtn(); - // ensure rule actions info callout displayed on the form - cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); - pickActionFrequency(expectedActionFrequency); - addSlackRuleAction(expectedSlackMessage); + // ensure rule actions info callout displayed on the form + cy.get(RULES_BULK_EDIT_ACTIONS_INFO).should('be.visible'); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); - // check if rule has been updated - goToEditRuleActionsSettings(); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); - assertSelectedActionFrequency(expectedActionFrequency); - assertSlackRuleAction(expectedSlackMessage); - }); + // check if rule has been updated + goToEditRuleActionsSettings(); - it('Overwrite rule actions in rules', () => { - const expectedActionFrequency = 'On each rule execution'; + assertSelectedActionFrequency(expectedActionFrequency); + assertSlackRuleAction(expectedSlackMessage); + }); - loadPrebuiltDetectionRulesFromHeaderBtn(); + it('Overwrite rule actions in rules', () => { + const expectedActionFrequency = 'On each rule execution'; - // select both custom and prebuilt rules - selectNumberOfRules(expectedNumberOfRulesToBeEdited); - openBulkEditRuleActionsForm(); + loadPrebuiltDetectionRulesFromHeaderBtn(); - pickActionFrequency(expectedActionFrequency); - addSlackRuleAction(expectedSlackMessage); + // select both custom and prebuilt rules + selectNumberOfRules(expectedNumberOfRulesToBeEdited); + openBulkEditRuleActionsForm(); - // check overwrite box, ensure warning is displayed - checkOverwriteRuleActionsCheckbox(); - cy.get(RULES_BULK_EDIT_ACTIONS_WARNING).contains( - `You're about to overwrite rule actions for ${expectedNumberOfRulesToBeEdited} selected rules` - ); + pickActionFrequency(expectedActionFrequency); + addSlackRuleAction(expectedSlackMessage); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); + // check overwrite box, ensure warning is displayed + checkOverwriteRuleActionsCheckbox(); + cy.get(RULES_BULK_EDIT_ACTIONS_WARNING).contains( + `You're about to overwrite rule actions for ${expectedNumberOfRulesToBeEdited} selected rules` + ); - // check if rule has been updated - goToEditRuleActionsSettings(); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); - assertSelectedActionFrequency(expectedActionFrequency); - assertSlackRuleAction(expectedSlackMessage); - }); + // check if rule has been updated + goToEditRuleActionsSettings(); + + assertSelectedActionFrequency(expectedActionFrequency); + assertSlackRuleAction(expectedSlackMessage); + }); - it('Add rule actions to rules when creating connector', () => { - const expectedActionFrequency = 'Hourly'; - const expectedEmail = 'test@example.com'; - const expectedSubject = 'Subject'; + it('Add rule actions to rules when creating connector', () => { + const expectedActionFrequency = 'Hourly'; + const expectedEmail = 'test@example.com'; + const expectedSubject = 'Subject'; - selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); - openBulkEditRuleActionsForm(); + selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited); + openBulkEditRuleActionsForm(); - pickActionFrequency(expectedActionFrequency); - addEmailConnectorAndRuleAction(expectedEmail, expectedSubject); + pickActionFrequency(expectedActionFrequency); + addEmailConnectorAndRuleAction(expectedEmail, expectedSubject); - submitBulkEditForm(); - waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); + submitBulkEditForm(); + waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); - // check if rule has been updated - goToEditRuleActionsSettings(); + // check if rule has been updated + goToEditRuleActionsSettings(); - assertSelectedActionFrequency(expectedActionFrequency); - assertEmailRuleAction(expectedEmail, expectedSubject); + assertSelectedActionFrequency(expectedActionFrequency); + assertEmailRuleAction(expectedEmail, expectedSubject); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx index 368ffd77af58..17443855a6ab 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/bulk_actions/use_bulk_actions.tsx @@ -364,9 +364,9 @@ export const useBulkActions = ({ key: i18n.BULK_ACTION_ADD_RULE_ACTIONS, name: i18n.BULK_ACTION_ADD_RULE_ACTIONS, 'data-test-subj': 'addRuleActionsBulk', - disabled: isEditDisabled, + disabled: !hasActionsPrivileges || isEditDisabled, onClick: handleBulkEdit(BulkActionEditType.add_rule_actions), - toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, + toolTipContent: !hasActionsPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined, toolTipPosition: 'right', icon: undefined, }, From 7c10ab7382c75d24e2c0db9bd347ed21bb0a00c1 Mon Sep 17 00:00:00 2001 From: Vitalii Dmyterko Date: Thu, 6 Oct 2022 11:39:12 +0100 Subject: [PATCH 15/15] Code review feedback --- .../bulk_edit_rules_actions.cy.ts | 49 +++++++++++++++---- .../cypress/screens/common/rule_actions.ts | 3 ++ .../cypress/tasks/alerts_detection_rules.ts | 4 +- .../cypress/tasks/api_calls/rules.ts | 8 ++- .../cypress/tasks/common/rule_actions.ts | 4 +- 5 files changed, 54 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts index d1d4aad72b44..6a3718baa226 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_edit_rules_actions.cy.ts @@ -12,6 +12,7 @@ import { RULES_BULK_EDIT_ACTIONS_WARNING, ADD_RULE_ACTIONS_MENU_ITEM, } from '../../screens/rules_bulk_edit'; +import { actionFormSelector } from '../../screens/common/rule_actions'; import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common'; import { @@ -24,7 +25,7 @@ import { waitForRulesTableToBeLoaded, selectNumberOfRules, loadPrebuiltDetectionRulesFromHeaderBtn, - goToEditRuleActionsSettings, + goToEditRuleActionsSettingsOf, } from '../../tasks/alerts_detection_rules'; import { waitForBulkEditActionToFinish, @@ -60,11 +61,13 @@ import { getNewTermsRule, } from '../../objects/rule'; +const ruleNameToAssert = 'Custom rule name with actions'; const expectedNumberOfCustomRulesToBeEdited = 7; // 7 custom rules of different types + 3 prebuilt. // number of selected rules doesn't matter, we only want to make sure they will be edited an no modal window displayed as for other actions const expectedNumberOfRulesToBeEdited = expectedNumberOfCustomRulesToBeEdited + 3; +const expectedExistingSlackMessage = 'Existing slack action'; const expectedSlackMessage = 'Slack action test message'; describe('Detection rules, bulk edit of rule actions', () => { @@ -78,7 +81,30 @@ describe('Detection rules, bulk edit of rule actions', () => { deleteConnectors(); esArchiverResetKibana(); - createCustomRuleEnabled(getNewRule(), '1'); + createSlackConnector().then(({ body }) => { + const actions = [ + { + id: body.id, + action_type_id: '.slack', + group: 'default', + params: { + message: expectedExistingSlackMessage, + }, + }, + ]; + + createCustomRuleEnabled( + { + ...getNewRule(), + name: ruleNameToAssert, + }, + '1', + '100m', + 500, + actions + ); + }); + createEventCorrelationRule(getEqlRule(), '2'); createMachineLearningRule(getMachineLearningRule(), '3'); createCustomIndicatorRule(getNewThreatIndicatorRule(), '4'); @@ -90,7 +116,7 @@ describe('Detection rules, bulk edit of rule actions', () => { }); context('Restricted action privileges', () => { - it('User has no actions privileges', () => { + it("User with no privileges can't add rule actions", () => { login(ROLES.hunter_no_actions); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL, ROLES.hunter_no_actions); waitForRulesTableToBeLoaded(); @@ -109,7 +135,7 @@ describe('Detection rules, bulk edit of rule actions', () => { waitForRulesTableToBeLoaded(); }); - it('Add rule actions to rules', () => { + it('Add a rule action to rules (existing connector)', () => { const expectedActionFrequency = 'Daily'; loadPrebuiltDetectionRulesFromHeaderBtn(); @@ -128,10 +154,13 @@ describe('Detection rules, bulk edit of rule actions', () => { waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); // check if rule has been updated - goToEditRuleActionsSettings(); + goToEditRuleActionsSettingsOf(ruleNameToAssert); assertSelectedActionFrequency(expectedActionFrequency); - assertSlackRuleAction(expectedSlackMessage); + assertSlackRuleAction(expectedExistingSlackMessage, 0); + assertSlackRuleAction(expectedSlackMessage, 1); + // ensure there is no third action + cy.get(actionFormSelector(2)).should('not.exist'); }); it('Overwrite rule actions in rules', () => { @@ -156,13 +185,15 @@ describe('Detection rules, bulk edit of rule actions', () => { waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfRulesToBeEdited }); // check if rule has been updated - goToEditRuleActionsSettings(); + goToEditRuleActionsSettingsOf(ruleNameToAssert); assertSelectedActionFrequency(expectedActionFrequency); assertSlackRuleAction(expectedSlackMessage); + // ensure existing action was overwritten + cy.get(actionFormSelector(1)).should('not.exist'); }); - it('Add rule actions to rules when creating connector', () => { + it('Add a rule action to rules (new connector)', () => { const expectedActionFrequency = 'Hourly'; const expectedEmail = 'test@example.com'; const expectedSubject = 'Subject'; @@ -177,7 +208,7 @@ describe('Detection rules, bulk edit of rule actions', () => { waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited }); // check if rule has been updated - goToEditRuleActionsSettings(); + goToEditRuleActionsSettingsOf(ruleNameToAssert); assertSelectedActionFrequency(expectedActionFrequency); assertEmailRuleAction(expectedEmail, expectedSubject); diff --git a/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts index 0a86dbea0739..2fe606fc6bf6 100644 --- a/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts +++ b/x-pack/plugins/security_solution/cypress/screens/common/rule_actions.ts @@ -38,3 +38,6 @@ export const FORM_VALIDATION_ERROR = '.euiFormErrorText'; export const JSON_EDITOR = "[data-test-subj='actionJsonEditor']"; export const INDEX_SELECTOR = "[data-test-subj='.index-siem-ActionTypeSelectOption']"; + +export const actionFormSelector = (position: number) => + `[data-test-subj="alertActionAccordion-${position}"]`; diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts index f3260c9111cf..523744747e62 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts @@ -420,8 +420,8 @@ export const clickErrorToastBtn = () => { cy.get(TOASTER_ERROR_BTN).click(); }; -export const goToEditRuleActionsSettings = () => { - goToRuleDetails(); +export const goToEditRuleActionsSettingsOf = (name: string) => { + goToTheRuleDetailsOf(name); goToRuleEditSettings(); // wait until first step loads completely. Otherwise cypress stuck at the first edit page cy.get(EDIT_SUBMIT_BUTTON).should('be.enabled'); diff --git a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts index 1909030b726d..82eb2f9255b0 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts @@ -5,6 +5,8 @@ * 2.0. */ +import type { Actions } from '@kbn/securitysolution-io-ts-alerting-types'; + import type { CustomRule, ThreatIndicatorRule, @@ -70,6 +72,7 @@ export const createCustomRule = ( timeline_title: timeline.title, } : {}), + actions: rule.actions, }, headers: { 'kbn-xsrf': 'cypress-creds' }, failOnStatusCode: false, @@ -254,7 +257,8 @@ export const createCustomRuleEnabled = ( rule: CustomRule, ruleId = '1', interval = '100m', - maxSignals = 500 + maxSignals = 500, + actions?: Actions ) => { const riskScore = rule.riskScore != null ? parseInt(rule.riskScore, 10) : undefined; const severity = rule.severity != null ? rule.severity.toLocaleLowerCase() : undefined; @@ -280,6 +284,7 @@ export const createCustomRuleEnabled = ( tags: ['rule1'], max_signals: maxSignals, building_block_type: rule.buildingBlockType, + actions, }, headers: { 'kbn-xsrf': 'cypress-creds' }, failOnStatusCode: false, @@ -306,6 +311,7 @@ export const createCustomRuleEnabled = ( tags: ['rule1'], max_signals: maxSignals, building_block_type: rule.buildingBlockType, + actions, }, headers: { 'kbn-xsrf': 'cypress-creds' }, failOnStatusCode: false, diff --git a/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts index 4f49e8171859..2c289eea0f73 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/common/rule_actions.ts @@ -32,8 +32,8 @@ export const addSlackRuleAction = (message: string) => { cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).clear().type(message); }; -export const assertSlackRuleAction = (message: string) => { - cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).should('have.value', message); +export const assertSlackRuleAction = (message: string, position: number = 0) => { + cy.get(SLACK_ACTION_MESSAGE_TEXTAREA).eq(position).should('have.value', message); }; export const fillEmailConnectorForm = (connector: EmailConnector = getEmailConnector()) => {