From 8ac249972c9faee34e056a54034fc0260ab3ef1f Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 22 May 2023 15:29:07 +0200 Subject: [PATCH 1/9] remove unnecessary code --- .../rule_snooze_badge/rule_snooze_badge.tsx | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx index 8808f6d3033b5..f4aefc5be5a5a 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management/components/rule_snooze_badge/rule_snooze_badge.tsx @@ -5,7 +5,6 @@ * 2.0. */ -import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; import type { RuleObjectId } from '../../../../../common/detection_engine/rule_schema'; import { useUserData } from '../../../../detections/components/user_info'; @@ -32,20 +31,12 @@ export function RuleSnoozeBadge({ const hasCRUDPermissions = hasUserCRUDPermission(canUserCRUD); const invalidateFetchRuleSnoozeSettings = useInvalidateFetchRulesSnoozeSettingsQuery(); - if (error) { - return ( - - - - ); - } - return ( From e3ea986ef58ec1d8815014b005cdd11387998be8 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Mon, 22 May 2023 16:21:53 +0200 Subject: [PATCH 2/9] add e2e tests --- x-pack/plugins/alerting/common/index.ts | 7 +- .../alerting/server/routes/snooze_rule.ts | 4 +- .../e2e/detection_rules/rule_snoozing.cy.ts | 295 ++++++++++++++++++ .../cypress/screens/common.ts | 8 + .../cypress/screens/rule_snoozing.ts | 20 ++ .../cypress/tasks/api_calls/rules.ts | 27 +- .../cypress/tasks/rule_snoozing.ts | 131 ++++++++ .../cypress/urls/navigation.ts | 5 + 8 files changed, 492 insertions(+), 5 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts create mode 100644 x-pack/plugins/security_solution/cypress/screens/common.ts create mode 100644 x-pack/plugins/security_solution/cypress/screens/rule_snoozing.ts create mode 100644 x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index a13e15b7d5615..ce9886a178371 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -61,8 +61,11 @@ export interface AlertingFrameworkHealth { export const LEGACY_BASE_ALERT_API_PATH = '/api/alerts'; export const BASE_ALERTING_API_PATH = '/api/alerting'; -export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting'; -export const INTERNAL_ALERTING_API_FIND_RULES_PATH = `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_find`; +export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting' as const; +export const INTERNAL_ALERTING_SNOOZE_RULE = + `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_snooze` as const; +export const INTERNAL_ALERTING_API_FIND_RULES_PATH = + `${INTERNAL_BASE_ALERTING_API_PATH}/rules/_find` as const; export const INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH = `${INTERNAL_BASE_ALERTING_API_PATH}/rules/maintenance_window` as const; diff --git a/x-pack/plugins/alerting/server/routes/snooze_rule.ts b/x-pack/plugins/alerting/server/routes/snooze_rule.ts index 17208ed9c01fa..1ffac168fcd00 100644 --- a/x-pack/plugins/alerting/server/routes/snooze_rule.ts +++ b/x-pack/plugins/alerting/server/routes/snooze_rule.ts @@ -10,7 +10,7 @@ import { schema } from '@kbn/config-schema'; import { ILicenseState, RuleMutedError } from '../lib'; import { verifyAccessAndContext, rRuleSchema } from './lib'; import { SnoozeOptions } from '../rules_client'; -import { AlertingRequestHandlerContext, INTERNAL_BASE_ALERTING_API_PATH } from '../types'; +import { AlertingRequestHandlerContext, INTERNAL_ALERTING_SNOOZE_RULE } from '../types'; import { validateSnoozeSchedule } from '../lib/validate_snooze_schedule'; const paramSchema = schema.object({ @@ -42,7 +42,7 @@ export const snoozeRuleRoute = ( ) => { router.post( { - path: `${INTERNAL_BASE_ALERTING_API_PATH}/rule/{id}/_snooze`, + path: INTERNAL_ALERTING_SNOOZE_RULE, validate: { params: paramSchema, body: bodySchema, diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts new file mode 100644 index 0000000000000..2f46447de2d59 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts @@ -0,0 +1,295 @@ +/* + * 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 { INTERNAL_ALERTING_API_FIND_RULES_PATH, Rule } from '@kbn/alerting-plugin/common'; +import { createRule, snoozeRule as snoozeRuleViaAPI } from '../../tasks/api_calls/rules'; +import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common'; +import { login, visitWithoutDateRange } from '../../tasks/login'; +import { getNewRule } from '../../objects/rule'; +import { + internalAlertingSnoozeRule, + ruleDetailsUrl, + ruleEditUrl, + SECURITY_DETECTIONS_RULES_URL, +} from '../../urls/navigation'; +import { RULES_MANAGEMENT_TABLE, RULE_NAME } from '../../screens/alerts_detection_rules'; +import { + expectRuleSnoozed, + expectRuleSnoozedInTable, + expectRuleUnsnoozed, + expectRuleUnsnoozedInTable, + expectSnoozeErrorToast, + expectSnoozeSuccessToast, + expectUnsnoozeSuccessToast, + snoozeRule, + snoozeRuleInTable, + unsnoozeRuleInTable, +} from '../../tasks/rule_snoozing'; +import { createSlackConnector } from '../../tasks/api_calls/connectors'; +import { duplicateFirstRule, importRules } from '../../tasks/alerts_detection_rules'; +import { goToActionsStepTab } from '../../tasks/create_new_rule'; +import { goToRuleEditSettings } from '../../tasks/rule_details'; +import { actionFormSelector } from '../../screens/common/rule_actions'; +import { RULE_INDICES } from '../../screens/create_new_rule'; +import { addEmailConnectorAndRuleAction } from '../../tasks/common/rule_actions'; +import { saveEditedRule } from '../../tasks/edit_rule'; +import { DISABLED_SNOOZE_BADGE } from '../../screens/rule_snoozing'; +import { TOOLTIP } from '../../screens/common'; + +const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; + +describe('rule snoozing', () => { + before(() => { + cleanKibana(); + login(); + }); + + beforeEach(() => { + deleteAlertsAndRules(); + }); + + it('ensures the rule is snoozed on the rules management page , rule details page and rule editing page', () => { + createRule(getNewRule({ name: 'Test on all pages' })); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + snoozeRuleInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test on all pages', + duration: '1 hours', + }); + + // Open rule detail page + cy.get(RULE_NAME).contains('Test on all pages').click(); + + expectRuleSnoozed('1 hours'); + + // Open rule editing page actions tab + goToRuleEditSettings(); + goToActionsStepTab(); + + expectRuleSnoozed('1 hours'); + }); + + describe('Rules management table', () => { + it('snoozes a rule without actions for 3 hours', () => { + createRule(getNewRule({ name: 'Test rule without actions' })); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + snoozeRuleInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test rule without actions', + duration: '3 hours', + }); + + expectSnoozeSuccessToast(); + expectRuleSnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test rule without actions', + duration: '3 hours', + }); + }); + + it('snoozes a rule with actions for 2 days', () => { + createRuleWithActions({ name: 'Test rule with actions' }, createRule); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + snoozeRuleInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test rule with actions', + duration: '2 days', + }); + + expectSnoozeSuccessToast(); + expectRuleSnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test rule with actions', + duration: '2 days', + }); + }); + + it('unsnoozes a rule with actions', () => { + createSnoozedRule(getNewRule({ name: 'Snoozed rule' })); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + unsnoozeRuleInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Snoozed rule', + }); + + expectUnsnoozeSuccessToast(); + expectRuleUnsnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Snoozed rule', + }); + }); + + it('ensures snooze settings persist after page reload', () => { + createRule(getNewRule({ name: 'Test persistence' })); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + snoozeRuleInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test persistence', + duration: '3 days', + }); + + cy.reload(); + + expectRuleSnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test persistence', + duration: '3 days', + }); + }); + + it('ensures a duplicated rule is not snoozed', () => { + createRule(getNewRule({ name: 'Test rule' })); + + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + duplicateFirstRule(); + + // Make sure rules table is shown as it navigates to rule editing page after successful duplication + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + expectRuleUnsnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test rule', + }); + }); + }); + + describe('Rule editing page / actions tab', () => { + beforeEach(() => { + deleteConnectors(); + }); + + it('ensures actions are visible', () => { + createRuleWithActions({ name: 'Snoozed rule with actions' }, createSnoozedRule).then( + ({ body: rule }) => { + visitWithoutDateRange(ruleEditUrl(rule.id)); + // Wait for rule data being loaded + cy.get(RULE_INDICES).should('be.visible'); + goToActionsStepTab(); + } + ); + + // It's enough to check only the first action is visible + cy.get(actionFormSelector(0)).should('be.visible'); + }); + + it('adds an action to a snoozed rule', () => { + createSnoozedRule(getNewRule({ name: 'Snoozed rule' })).then(({ body: rule }) => { + visitWithoutDateRange(ruleEditUrl(rule.id)); + // Wait for rule data being loaded + cy.get(RULE_INDICES).should('be.visible'); + goToActionsStepTab(); + + addEmailConnectorAndRuleAction('abc@example.com', 'Test action'); + saveEditedRule(); + + goToRuleEditSettings(); + goToActionsStepTab(); + + cy.get(actionFormSelector(0)).should('be.visible'); + }); + }); + }); + + describe('importing rules', () => { + beforeEach(() => { + cy.intercept('POST', '/api/detection_engine/rules/_import*').as('import'); + }); + + it('ensures imported rules are unsnoozed', () => { + visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); + + importRules(RULES_TO_IMPORT_FILENAME); + + cy.wait('@import').then(({ response }) => { + cy.wrap(response?.statusCode).should('eql', 200); + + expectRuleUnsnoozedInTable({ + tableSelector: RULES_MANAGEMENT_TABLE, + ruleName: 'Test Custom Rule', + }); + }); + }); + }); + + describe('Handling errors', () => { + it('shows an error if unable to load snooze settings', () => { + createRule(getNewRule({ name: 'Test rule' })).then(({ body: rule }) => { + cy.intercept('GET', `${INTERNAL_ALERTING_API_FIND_RULES_PATH}*`, { + statusCode: 500, + }); + + visitWithoutDateRange(ruleDetailsUrl(rule.id)); + }); + + cy.get(DISABLED_SNOOZE_BADGE).trigger('mouseover'); + + cy.get(TOOLTIP).contains('Unable to fetch snooze settings'); + }); + + it('shows an error if unable to save snooze settings', () => { + createRule(getNewRule({ name: 'Test rule' })).then(({ body: rule }) => { + cy.intercept('POST', internalAlertingSnoozeRule(rule.id), { forceNetworkError: true }); + + visitWithoutDateRange(ruleDetailsUrl(rule.id)); + }); + + snoozeRule('3 days'); + + expectSnoozeErrorToast(); + expectRuleUnsnoozed(); + }); + }); +}); + +function createRuleWithActions( + ruleParams: Parameters[0], + ruleCreator: ( + ruleParams: Parameters[0] + ) => Cypress.Chainable>> +): Cypress.Chainable>> { + return createSlackConnector().then(({ body }) => + ruleCreator( + getNewRule({ + ...ruleParams, + actions: [ + { + id: body.id, + action_type_id: '.slack', + group: 'default', + params: { + message: 'Some message', + }, + }, + ], + }) + ) + ); +} + +function createSnoozedRule( + ruleParams: Parameters[0] +): Cypress.Chainable>> { + return createRule(ruleParams).then((response) => { + const createdRule = response.body; + const oneDayInMs = 24 * 60 * 60 * 1000; + + snoozeRuleViaAPI(createdRule.id, oneDayInMs); + + return cy.wrap(response); + }); +} diff --git a/x-pack/plugins/security_solution/cypress/screens/common.ts b/x-pack/plugins/security_solution/cypress/screens/common.ts new file mode 100644 index 0000000000000..8dc5517aae3c5 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/common.ts @@ -0,0 +1,8 @@ +/* + * 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 TOOLTIP = '[role="tooltip"]'; diff --git a/x-pack/plugins/security_solution/cypress/screens/rule_snoozing.ts b/x-pack/plugins/security_solution/cypress/screens/rule_snoozing.ts new file mode 100644 index 0000000000000..675807d81df37 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/screens/rule_snoozing.ts @@ -0,0 +1,20 @@ +/* + * 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 UNSNOOZED_BADGE = '[data-test-subj="rulesListNotifyBadge-unsnoozed"]'; + +export const SNOOZED_BADGE = '[data-test-subj="rulesListNotifyBadge-snoozed"]'; + +export const DISABLED_SNOOZE_BADGE = '[data-test-subj="rulesListNotifyBadge"]'; + +export const SNOOZE_POPOVER_INTERVAL_VALUE_INPUT = '[data-test-subj="ruleSnoozeIntervalValue"]'; + +export const SNOOZE_POPOVER_INTERVAL_UNIT_INPUT = '[data-test-subj="ruleSnoozeIntervalUnit"]'; + +export const SNOOZE_POPOVER_APPLY_BUTTON = '[data-test-subj="ruleSnoozeApply"]'; + +export const SNOOZE_POPOVER_CANCEL_BUTTON = '[data-test-subj="ruleSnoozeCancel"]'; 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 83fba358550c4..60573b2235116 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 @@ -6,10 +6,15 @@ */ import { rootRequest } from '../common'; +import moment from 'moment'; +import { Rule } from '@kbn/alerting-plugin/common'; import { DETECTION_ENGINE_RULES_URL } from '../../../common/constants'; import type { RuleCreateProps } from '../../../common/detection_engine/rule_schema'; +import { internalAlertingSnoozeRule } from '../../urls/navigation'; -export const createRule = (rule: RuleCreateProps) => { +export const createRule = ( + rule: RuleCreateProps +): Cypress.Chainable> => { return rootRequest({ method: 'POST', url: DETECTION_ENGINE_RULES_URL, @@ -19,6 +24,26 @@ export const createRule = (rule: RuleCreateProps) => { }); }; +/** + * Snoozes a rule via API + * + * @param id Rule's SO id + * @param duration Snooze duration in milliseconds, -1 for indefinite + */ +export const snoozeRule = (id: string, duration: number): Cypress.Chainable => + cy.request({ + method: 'POST', + url: internalAlertingSnoozeRule(id), + body: { + snooze_schedule: { + duration, + rRule: { dtstart: new Date().toISOString(), count: 1, tzid: moment().format('zz') }, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds' }, + failOnStatusCode: false, + }); + export const deleteCustomRule = (ruleId = '1') => { rootRequest({ method: 'DELETE', diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts new file mode 100644 index 0000000000000..f7a63452de0b8 --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts @@ -0,0 +1,131 @@ +/* + * 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 moment from 'moment'; +import { RULE_NAME, TOASTER } from '../screens/alerts_detection_rules'; +import { + SNOOZED_BADGE, + SNOOZE_POPOVER_APPLY_BUTTON, + SNOOZE_POPOVER_CANCEL_BUTTON, + SNOOZE_POPOVER_INTERVAL_UNIT_INPUT, + SNOOZE_POPOVER_INTERVAL_VALUE_INPUT, + UNSNOOZED_BADGE, +} from '../screens/rule_snoozing'; + +type SnoozeUnits = 'minutes' | 'hours' | 'days' | 'weeks' | 'months'; +type SnoozeDuration = `${number} ${SnoozeUnits}`; + +interface SnoozeParams { + duration: SnoozeDuration; +} + +interface TableRule { + tableSelector: string; + ruleName: string; +} + +type SnoozeInTableParams = TableRule & SnoozeParams; + +export function snoozeRule(duration: SnoozeDuration): void { + openSnoozePopover(); + snoozeRuleInOpenPopover(duration); +} + +export function expectSnoozeSuccessToast(): void { + cy.get(TOASTER).should('contain', 'Rule successfully snoozed'); +} + +export function expectSnoozeErrorToast(): void { + cy.get(TOASTER).should('contain', 'Unable to change rule snooze settings'); +} + +export function expectRuleSnoozed(duration: SnoozeDuration): void { + cy.get(SNOOZED_BADGE).contains(expectedSnoozeBadgeText(duration)); +} + +export function snoozeRuleInTable({ + tableSelector, + ruleName, + duration, +}: SnoozeInTableParams): void { + openSnoozePopoverInTable(tableSelector, ruleName); + snoozeRuleInOpenPopover(duration); +} + +export function expectRuleSnoozedInTable({ + tableSelector, + ruleName, + duration, +}: SnoozeInTableParams): void { + findRuleRowInTable(tableSelector, ruleName) + .find(SNOOZED_BADGE) + .contains(expectedSnoozeBadgeText(duration)); +} + +export function unsnoozeRule(): void { + cy.get(SNOOZED_BADGE).click(); + cy.get(SNOOZE_POPOVER_CANCEL_BUTTON).click(); +} + +export function expectUnsnoozeSuccessToast(): void { + cy.get(TOASTER).should('contain', 'Rule successfully unsnoozed'); +} + +export function expectRuleUnsnoozed(): void { + cy.get(UNSNOOZED_BADGE); +} + +export function unsnoozeRuleInTable({ tableSelector, ruleName }: TableRule): void { + findRuleRowInTable(tableSelector, ruleName).find(SNOOZED_BADGE).click(); + cy.get(SNOOZE_POPOVER_CANCEL_BUTTON).click(); +} + +export function expectRuleUnsnoozedInTable({ tableSelector, ruleName }: TableRule): void { + findRuleRowInTable(tableSelector, ruleName).find(UNSNOOZED_BADGE).should('exist'); +} + +export function findRuleRowInTable( + tableSelector: string, + ruleName: string +): Cypress.Chainable> { + return cy.get(tableSelector).find(RULE_NAME).contains(ruleName).parents('tr'); +} + +/** + * Opens a snooze popover by clicking the snooze badge. Fits to pages with one snooze icon per page. + */ +function openSnoozePopover(): void { + cy.get(UNSNOOZED_BADGE).click(); +} + +/** + * Opens a snooze popover by clicking the snooze badge in the table's row + */ +function openSnoozePopoverInTable(tableSelector: string, ruleName: string): void { + const parent = findRuleRowInTable(tableSelector, ruleName); + + parent.find(UNSNOOZED_BADGE).click(); +} + +/** + * Snoozes a rule in an open snooze popover + */ +function snoozeRuleInOpenPopover(duration: SnoozeDuration): void { + const [value, units] = duration.split(' '); + + cy.log(`Snooze a rule for ${value} ${units}`); + + cy.get(SNOOZE_POPOVER_INTERVAL_VALUE_INPUT).clear().type(value.toString()); + cy.get(SNOOZE_POPOVER_INTERVAL_UNIT_INPUT).select(units); + cy.get(SNOOZE_POPOVER_APPLY_BUTTON).click(); +} + +function expectedSnoozeBadgeText(duration: SnoozeDuration): string { + return moment() + .add(...duration.split(' ')) + .format('MMMM DD'); +} diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index 6048b2d9305c6..bfb523d166438 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -5,6 +5,8 @@ * 2.0. */ +import { INTERNAL_ALERTING_SNOOZE_RULE } from '@kbn/alerting-plugin/common'; + export const KIBANA_HOME = '/app/home#/'; export const KIBANA_SAVED_OBJECTS = '/app/management/kibana/objects'; @@ -69,3 +71,6 @@ export const hostDetailsUrl = (hostName: string) => export const userDetailsUrl = (userName: string) => `/app/security/users/${userName}/allUsers`; export const exceptionsListDetailsUrl = (listId: string) => `${EXCEPTIONS_LIST_URL}/${listId}`; + +export const internalAlertingSnoozeRule = (ruleId: string) => + INTERNAL_ALERTING_SNOOZE_RULE.replace('{id}', ruleId); From d2f2bd59edd8e957471f668dc924c72ee17710ad Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 1 Jun 2023 10:49:57 +0200 Subject: [PATCH 3/9] change createRule return type to RuleResponse --- .../bulk_duplicate_rules.cy.ts | 68 +++++++++---------- .../e2e/detection_rules/rule_snoozing.cy.ts | 9 +-- .../cypress/tasks/api_calls/rules.ts | 11 ++- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts index 038a6c6d32853..d906838996c18 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/bulk_duplicate_rules.cy.ts @@ -61,40 +61,40 @@ describe('Detection rules, bulk duplicate', () => { resetRulesTableState(); deleteAlertsAndRules(); esArchiverResetKibana(); - createRule<{ id: string }>( - getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1' }) - ).then((response) => { - createRuleExceptionItem(response.body.id, [ - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: expiredDate, - }, - { - description: 'Exception item for rule default exception list', - entries: [ - { - field: 'user.name', - operator: 'included', - type: 'match', - value: 'some value', - }, - ], - name: NON_EXPIRED_EXCEPTION_ITEM_NAME, - type: 'simple', - expire_time: futureDate, - }, - ]); - }); + createRule(getNewRule({ name: RULE_NAME, ...defaultRuleData, rule_id: '1' })).then( + (response) => { + createRuleExceptionItem(response.body.id, [ + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: expiredDate, + }, + { + description: 'Exception item for rule default exception list', + entries: [ + { + field: 'user.name', + operator: 'included', + type: 'match', + value: 'some value', + }, + ], + name: NON_EXPIRED_EXCEPTION_ITEM_NAME, + type: 'simple', + expire_time: futureDate, + }, + ]); + } + ); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts index 2f46447de2d59..4f333c585ace3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts @@ -5,7 +5,8 @@ * 2.0. */ -import { INTERNAL_ALERTING_API_FIND_RULES_PATH, Rule } from '@kbn/alerting-plugin/common'; +import { INTERNAL_ALERTING_API_FIND_RULES_PATH } from '@kbn/alerting-plugin/common'; +import type { RuleResponse } from '../../../common/detection_engine/rule_schema'; import { createRule, snoozeRule as snoozeRuleViaAPI } from '../../tasks/api_calls/rules'; import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; @@ -260,8 +261,8 @@ function createRuleWithActions( ruleParams: Parameters[0], ruleCreator: ( ruleParams: Parameters[0] - ) => Cypress.Chainable>> -): Cypress.Chainable>> { + ) => Cypress.Chainable> +): Cypress.Chainable> { return createSlackConnector().then(({ body }) => ruleCreator( getNewRule({ @@ -283,7 +284,7 @@ function createRuleWithActions( function createSnoozedRule( ruleParams: Parameters[0] -): Cypress.Chainable>> { +): Cypress.Chainable> { return createRule(ruleParams).then((response) => { const createdRule = response.body; const oneDayInMs = 24 * 60 * 60 * 1000; 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 60573b2235116..d4ba1f6c89b0f 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,17 +5,16 @@ * 2.0. */ -import { rootRequest } from '../common'; import moment from 'moment'; -import { Rule } from '@kbn/alerting-plugin/common'; +import { rootRequest } from '../common'; import { DETECTION_ENGINE_RULES_URL } from '../../../common/constants'; -import type { RuleCreateProps } from '../../../common/detection_engine/rule_schema'; +import type { RuleCreateProps, RuleResponse } from '../../../common/detection_engine/rule_schema'; import { internalAlertingSnoozeRule } from '../../urls/navigation'; -export const createRule = ( +export const createRule = ( rule: RuleCreateProps -): Cypress.Chainable> => { - return rootRequest({ +): Cypress.Chainable> => { + return rootRequest({ method: 'POST', url: DETECTION_ENGINE_RULES_URL, body: rule, From 582b6b353253436499deb40955da35a24b2d6041 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 1 Jun 2023 11:18:55 +0200 Subject: [PATCH 4/9] fix date pattern --- x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts index f7a63452de0b8..9a58be646b8a1 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts @@ -127,5 +127,5 @@ function snoozeRuleInOpenPopover(duration: SnoozeDuration): void { function expectedSnoozeBadgeText(duration: SnoozeDuration): string { return moment() .add(...duration.split(' ')) - .format('MMMM DD'); + .format('MMM D'); } From 6ed5c980877a5de5d9d4a62e4055c09a07f5d960 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 1 Jun 2023 11:19:31 +0200 Subject: [PATCH 5/9] login per each test --- .../cypress/e2e/detection_rules/rule_snoozing.cy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts index 4f333c585ace3..b896c6b34d957 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts @@ -46,14 +46,14 @@ const RULES_TO_IMPORT_FILENAME = 'cypress/fixtures/7_16_rules.ndjson'; describe('rule snoozing', () => { before(() => { cleanKibana(); - login(); }); beforeEach(() => { + login(); deleteAlertsAndRules(); }); - it('ensures the rule is snoozed on the rules management page , rule details page and rule editing page', () => { + it('ensures the rule is snoozed on the rules management page, rule details page and rule editing page', () => { createRule(getNewRule({ name: 'Test on all pages' })); visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL); From 7b6653c17501cf11da5d44350e77e750f3eac83d Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 1 Jun 2023 11:19:50 +0200 Subject: [PATCH 6/9] remove unnecessary test --- .../e2e/detection_rules/rule_snoozing.cy.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts index b896c6b34d957..0b99b9cc9cab3 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts @@ -174,20 +174,6 @@ describe('rule snoozing', () => { deleteConnectors(); }); - it('ensures actions are visible', () => { - createRuleWithActions({ name: 'Snoozed rule with actions' }, createSnoozedRule).then( - ({ body: rule }) => { - visitWithoutDateRange(ruleEditUrl(rule.id)); - // Wait for rule data being loaded - cy.get(RULE_INDICES).should('be.visible'); - goToActionsStepTab(); - } - ); - - // It's enough to check only the first action is visible - cy.get(actionFormSelector(0)).should('be.visible'); - }); - it('adds an action to a snoozed rule', () => { createSnoozedRule(getNewRule({ name: 'Snoozed rule' })).then(({ body: rule }) => { visitWithoutDateRange(ruleEditUrl(rule.id)); From 4f71a9579153e45ee85b8ad134628eb1ae72cd5f Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Thu, 1 Jun 2023 11:32:44 +0200 Subject: [PATCH 7/9] move route urls to a separate file --- .../cypress/e2e/detection_rules/rule_snoozing.cy.ts | 8 ++------ .../security_solution/cypress/urls/navigation.ts | 5 ----- .../plugins/security_solution/cypress/urls/routes.ts | 11 +++++++++++ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/security_solution/cypress/urls/routes.ts diff --git a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts index 0b99b9cc9cab3..f2015a1b4ecb9 100644 --- a/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts +++ b/x-pack/plugins/security_solution/cypress/e2e/detection_rules/rule_snoozing.cy.ts @@ -11,12 +11,8 @@ import { createRule, snoozeRule as snoozeRuleViaAPI } from '../../tasks/api_call import { cleanKibana, deleteAlertsAndRules, deleteConnectors } from '../../tasks/common'; import { login, visitWithoutDateRange } from '../../tasks/login'; import { getNewRule } from '../../objects/rule'; -import { - internalAlertingSnoozeRule, - ruleDetailsUrl, - ruleEditUrl, - SECURITY_DETECTIONS_RULES_URL, -} from '../../urls/navigation'; +import { ruleDetailsUrl, ruleEditUrl, SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation'; +import { internalAlertingSnoozeRule } from '../../urls/routes'; import { RULES_MANAGEMENT_TABLE, RULE_NAME } from '../../screens/alerts_detection_rules'; import { expectRuleSnoozed, diff --git a/x-pack/plugins/security_solution/cypress/urls/navigation.ts b/x-pack/plugins/security_solution/cypress/urls/navigation.ts index bfb523d166438..6048b2d9305c6 100644 --- a/x-pack/plugins/security_solution/cypress/urls/navigation.ts +++ b/x-pack/plugins/security_solution/cypress/urls/navigation.ts @@ -5,8 +5,6 @@ * 2.0. */ -import { INTERNAL_ALERTING_SNOOZE_RULE } from '@kbn/alerting-plugin/common'; - export const KIBANA_HOME = '/app/home#/'; export const KIBANA_SAVED_OBJECTS = '/app/management/kibana/objects'; @@ -71,6 +69,3 @@ export const hostDetailsUrl = (hostName: string) => export const userDetailsUrl = (userName: string) => `/app/security/users/${userName}/allUsers`; export const exceptionsListDetailsUrl = (listId: string) => `${EXCEPTIONS_LIST_URL}/${listId}`; - -export const internalAlertingSnoozeRule = (ruleId: string) => - INTERNAL_ALERTING_SNOOZE_RULE.replace('{id}', ruleId); diff --git a/x-pack/plugins/security_solution/cypress/urls/routes.ts b/x-pack/plugins/security_solution/cypress/urls/routes.ts new file mode 100644 index 0000000000000..63d120d3d8bae --- /dev/null +++ b/x-pack/plugins/security_solution/cypress/urls/routes.ts @@ -0,0 +1,11 @@ +/* + * 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 { INTERNAL_ALERTING_SNOOZE_RULE } from '@kbn/alerting-plugin/common'; + +export const internalAlertingSnoozeRule = (ruleId: string) => + INTERNAL_ALERTING_SNOOZE_RULE.replace('{id}', ruleId); From 4b22ecb44035baa8bd34ed17974da10caa49c451 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 2 Jun 2023 16:59:46 +0200 Subject: [PATCH 8/9] fix lint errors --- .../plugins/security_solution/cypress/tasks/rule_snoozing.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts index 9a58be646b8a1..35b98d9404591 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/rule_snoozing.ts @@ -119,7 +119,8 @@ function snoozeRuleInOpenPopover(duration: SnoozeDuration): void { cy.log(`Snooze a rule for ${value} ${units}`); - cy.get(SNOOZE_POPOVER_INTERVAL_VALUE_INPUT).clear().type(value.toString()); + cy.get(SNOOZE_POPOVER_INTERVAL_VALUE_INPUT).clear(); + cy.get(SNOOZE_POPOVER_INTERVAL_VALUE_INPUT).type(value.toString()); cy.get(SNOOZE_POPOVER_INTERVAL_UNIT_INPUT).select(units); cy.get(SNOOZE_POPOVER_APPLY_BUTTON).click(); } From 2fdbcf2275a232ea2c6a79fc765d41ac99761388 Mon Sep 17 00:00:00 2001 From: Maxim Palenov Date: Fri, 2 Jun 2023 17:02:11 +0200 Subject: [PATCH 9/9] fix improper import --- .../plugins/security_solution/cypress/tasks/api_calls/rules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d4ba1f6c89b0f..5d5de9b7f146a 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 @@ -9,7 +9,7 @@ import moment from 'moment'; import { rootRequest } from '../common'; import { DETECTION_ENGINE_RULES_URL } from '../../../common/constants'; import type { RuleCreateProps, RuleResponse } from '../../../common/detection_engine/rule_schema'; -import { internalAlertingSnoozeRule } from '../../urls/navigation'; +import { internalAlertingSnoozeRule } from '../../urls/routes'; export const createRule = ( rule: RuleCreateProps