Skip to content

Commit 044de1e

Browse files
[Security Solutions][Detection Engine] Fixes bug with not being able to duplicate indicator matches (#92565) (#92717)
## Summary Fixes an unreleased regression bug where indicator rules could not be be duplicated. #90356 - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Frank Hassanabad <frank.hassanabad@elastic.co>
1 parent 3abe3a9 commit 044de1e

File tree

6 files changed

+113
-3
lines changed

6 files changed

+113
-3
lines changed

x-pack/plugins/security_solution/cypress/integration/detection_rules/indicator_match_rule.spec.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,15 @@ import {
6060
} from '../../tasks/alerts';
6161
import {
6262
changeRowsPerPageTo300,
63+
duplicateFirstRule,
64+
duplicateRuleFromMenu,
6365
filterByCustomRules,
6466
goToCreateNewRule,
6567
goToRuleDetails,
6668
waitForRulesTableToBeLoaded,
6769
} from '../../tasks/alerts_detection_rules';
68-
import { cleanKibana } from '../../tasks/common';
70+
import { createCustomIndicatorRule } from '../../tasks/api_calls/rules';
71+
import { cleanKibana, reload } from '../../tasks/common';
6972
import {
7073
createAndActivateRule,
7174
fillAboutRuleAndContinue,
@@ -92,8 +95,10 @@ import {
9295
waitForAlertsToPopulate,
9396
waitForTheRuleToBeExecuted,
9497
} from '../../tasks/create_new_rule';
98+
import { waitForKibana } from '../../tasks/edit_rule';
9599
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
96100
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
101+
import { goBackToAllRulesTable } from '../../tasks/rule_details';
97102

98103
import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation';
99104

@@ -465,5 +470,30 @@ describe('indicator match', () => {
465470
cy.get(ALERT_RULE_RISK_SCORE).first().should('have.text', newThreatIndicatorRule.riskScore);
466471
});
467472
});
473+
474+
describe('Duplicates the indicator rule', () => {
475+
beforeEach(() => {
476+
cleanKibana();
477+
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
478+
goToManageAlertsDetectionRules();
479+
createCustomIndicatorRule(newThreatIndicatorRule);
480+
reload();
481+
});
482+
483+
it('Allows the rule to be duplicated from the table', () => {
484+
waitForKibana();
485+
duplicateFirstRule();
486+
cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
487+
});
488+
489+
it('Allows the rule to be duplicated from the edit screen', () => {
490+
waitForKibana();
491+
goToRuleDetails();
492+
duplicateRuleFromMenu();
493+
goBackToAllRulesTable();
494+
reload();
495+
cy.contains(RULE_NAME, `${newThreatIndicatorRule.name} [Duplicate]`);
496+
});
497+
});
468498
});
469499
});

x-pack/plugins/security_solution/cypress/screens/alerts_detection_rules.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]';
1717

1818
export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]';
1919

20+
export const DUPLICATE_RULE_ACTION_BTN = '[data-test-subj="duplicateRuleAction"]';
21+
22+
export const DUPLICATE_RULE_MENU_PANEL_BTN = '[data-test-subj="rules-details-duplicate-rule"]';
23+
24+
export const REFRESH_BTN = '[data-test-subj="refreshRulesAction"] button';
25+
2026
export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]';
2127

2228
export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]';

x-pack/plugins/security_solution/cypress/tasks/alerts_detection_rules.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE,
3232
rowsPerPageSelector,
3333
pageSelector,
34+
DUPLICATE_RULE_ACTION_BTN,
35+
DUPLICATE_RULE_MENU_PANEL_BTN,
3436
} from '../screens/alerts_detection_rules';
3537
import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details';
3638

@@ -45,6 +47,33 @@ export const editFirstRule = () => {
4547
cy.get(EDIT_RULE_ACTION_BTN).click();
4648
};
4749

50+
export const duplicateFirstRule = () => {
51+
cy.get(COLLAPSED_ACTION_BTN).should('be.visible');
52+
cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
53+
cy.get(DUPLICATE_RULE_ACTION_BTN).should('be.visible');
54+
cy.get(DUPLICATE_RULE_ACTION_BTN).click();
55+
};
56+
57+
/**
58+
* Duplicates the rule from the menu and does additional
59+
* pipes and checking that the elements are present on the
60+
* page as well as removed when doing the clicks to help reduce
61+
* flake.
62+
*/
63+
export const duplicateRuleFromMenu = () => {
64+
cy.get(ALL_ACTIONS).should('be.visible');
65+
cy.root()
66+
.pipe(($el) => {
67+
$el.find(ALL_ACTIONS).trigger('click');
68+
return $el.find(DUPLICATE_RULE_MENU_PANEL_BTN);
69+
})
70+
.should(($el) => expect($el).to.be.visible);
71+
// Because of a fade effect and fast clicking this can produce more than one click
72+
cy.get(DUPLICATE_RULE_MENU_PANEL_BTN)
73+
.pipe(($el) => $el.trigger('click'))
74+
.should('not.be.visible');
75+
};
76+
4877
export const deleteFirstRule = () => {
4978
cy.get(COLLAPSED_ACTION_BTN).first().click({ force: true });
5079
cy.get(DELETE_RULE_ACTION_BTN).click();

x-pack/plugins/security_solution/cypress/tasks/api_calls/rules.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
import { CustomRule } from '../../objects/rule';
8+
import { CustomRule, ThreatIndicatorRule } from '../../objects/rule';
99

1010
export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
1111
cy.request({
@@ -29,6 +29,44 @@ export const createCustomRule = (rule: CustomRule, ruleId = 'rule_testing') =>
2929
failOnStatusCode: false,
3030
});
3131

32+
export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'rule_testing') =>
33+
cy.request({
34+
method: 'POST',
35+
url: 'api/detection_engine/rules',
36+
body: {
37+
rule_id: ruleId,
38+
risk_score: parseInt(rule.riskScore, 10),
39+
description: rule.description,
40+
interval: '10s',
41+
name: rule.name,
42+
severity: rule.severity.toLocaleLowerCase(),
43+
type: 'threat_match',
44+
threat_mapping: [
45+
{
46+
entries: [
47+
{
48+
field: rule.indicatorMapping,
49+
type: 'mapping',
50+
value: rule.indicatorMapping,
51+
},
52+
],
53+
},
54+
],
55+
threat_query: '*:*',
56+
threat_language: 'kuery',
57+
threat_filters: [],
58+
threat_index: ['mock*'],
59+
threat_indicator_path: '',
60+
from: 'now-17520h',
61+
index: ['exceptions-*'],
62+
query: rule.customQuery || '*:*',
63+
language: 'kuery',
64+
enabled: false,
65+
},
66+
headers: { 'kbn-xsrf': 'cypress-creds' },
67+
failOnStatusCode: false,
68+
});
69+
3270
export const createCustomRuleActivated = (rule: CustomRule, ruleId = '1') =>
3371
cy.request({
3472
method: 'POST',

x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import * as H from 'history';
99
import React, { Dispatch } from 'react';
1010

11+
import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request';
1112
import {
1213
deleteRules,
1314
duplicateRules,
@@ -28,6 +29,7 @@ import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../../../common/lib/t
2829

2930
import * as i18n from '../translations';
3031
import { bucketRulesResponse } from './helpers';
32+
import { transformOutput } from '../../../../containers/detection_engine/rules/transforms';
3133

3234
export const editRuleAction = (rule: Rule, history: H.History) => {
3335
history.push(getEditRuleUrl(rule.id));
@@ -41,7 +43,11 @@ export const duplicateRulesAction = async (
4143
) => {
4244
try {
4345
dispatch({ type: 'loadingRuleIds', ids: ruleIds, actionType: 'duplicate' });
44-
const response = await duplicateRules({ rules });
46+
const response = await duplicateRules({
47+
// We cast this back and forth here as the front end types are not really the right io-ts ones
48+
// and the two types conflict with each other.
49+
rules: rules.map((rule) => transformOutput(rule as CreateRulesSchema) as Rule),
50+
});
4551
const { errors } = bucketRulesResponse(response);
4652
if (errors.length > 0) {
4753
displayErrorToast(

x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const getActions = (
6767
enabled: (rowItem: Rule) => canEditRuleWithActions(rowItem, actionsPrivileges),
6868
},
6969
{
70+
'data-test-subj': 'duplicateRuleAction',
7071
description: i18n.DUPLICATE_RULE,
7172
icon: 'copy',
7273
name: !actionsPrivileges ? (

0 commit comments

Comments
 (0)