mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security solution][Detections] increases test coverage for bulk edit (#137165)
## Summary - addresses https://github.com/elastic/kibana/issues/135201, apart from data views - adds tests for scenarios in test plan: [internal document](https://docs.google.com/document/d/116x7ITTTJQ6cTiwaGK831_f6Ox7XB3qyLiHxC3Cmf8w/edit#) - adds data-test-subj attributes to error toasts ### Checklist Delete any items that are not applicable to this PR. - [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
This commit is contained in:
parent
04d1ffea7e
commit
eb525ce0b5
16 changed files with 1146 additions and 405 deletions
|
@ -12,6 +12,7 @@ exports[`renders matching snapshot 1`] = `
|
|||
>
|
||||
<EuiButton
|
||||
color="danger"
|
||||
data-test-subj="errorToastBtn"
|
||||
onClick={[Function]}
|
||||
size="s"
|
||||
>
|
||||
|
|
|
@ -73,7 +73,7 @@ function showErrorDialog({
|
|||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>{title}</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
<EuiModalBody data-test-subj="errorModalBody">
|
||||
<EuiCallOut size="s" color="danger" iconType="alert" title={error.message} />
|
||||
{text && (
|
||||
<React.Fragment>
|
||||
|
@ -112,6 +112,7 @@ export function ErrorToast({
|
|||
<EuiButton
|
||||
size="s"
|
||||
color="danger"
|
||||
data-test-subj="errorToastBtn"
|
||||
onClick={() => showErrorDialog({ title, error, openModal, i18nContext })}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
|
|
@ -6,23 +6,27 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
ELASTIC_RULES_BTN,
|
||||
CUSTOM_RULES_BTN,
|
||||
MODAL_CONFIRMATION_BTN,
|
||||
SELECT_ALL_RULES_ON_PAGE_CHECKBOX,
|
||||
RULES_TAGS_FILTER_BTN,
|
||||
MODAL_CONFIRMATION_BODY,
|
||||
RULE_CHECKBOX,
|
||||
RULES_TAGS_POPOVER_BTN,
|
||||
RULES_TABLE_REFRESH_INDICATOR,
|
||||
TOASTER_BODY,
|
||||
MODAL_ERROR_BODY,
|
||||
} from '../../screens/alerts_detection_rules';
|
||||
|
||||
import {
|
||||
RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX,
|
||||
RULES_BULK_EDIT_INDEX_PATTERNS_WARNING,
|
||||
RULES_BULK_EDIT_TAGS_WARNING,
|
||||
RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING,
|
||||
TAGS_RULE_BULK_MENU_ITEM,
|
||||
INDEX_PATTERNS_RULE_BULK_MENU_ITEM,
|
||||
APPLY_TIMELINE_RULE_BULK_MENU_ITEM,
|
||||
} from '../../screens/rules_bulk_edit';
|
||||
|
||||
import { TIMELINE_TEMPLATE_DETAILS } from '../../screens/rule_details';
|
||||
|
||||
import { EUI_FILTER_SELECT_ITEM } from '../../screens/common/controls';
|
||||
|
||||
import {
|
||||
changeRowsPerPageTo,
|
||||
waitForRulesTableToBeLoaded,
|
||||
|
@ -34,50 +38,86 @@ import {
|
|||
testMultipleSelectedRulesLabel,
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn,
|
||||
switchToElasticRules,
|
||||
clickErrorToastBtn,
|
||||
unselectRuleByName,
|
||||
cancelConfirmationModal,
|
||||
} from '../../tasks/alerts_detection_rules';
|
||||
|
||||
import {
|
||||
openBulkEditAddIndexPatternsForm,
|
||||
openBulkEditDeleteIndexPatternsForm,
|
||||
typeIndexPatterns,
|
||||
waitForBulkEditActionToFinish,
|
||||
confirmBulkEditForm,
|
||||
submitBulkEditForm,
|
||||
clickAddIndexPatternsMenuItem,
|
||||
checkElasticRulesCannotBeModified,
|
||||
checkPrebuiltRulesCannotBeModified,
|
||||
checkMachineLearningRulesCannotBeModified,
|
||||
waitForMixedRulesBulkEditModal,
|
||||
openBulkEditAddTagsForm,
|
||||
openBulkEditDeleteTagsForm,
|
||||
typeTags,
|
||||
openTagsSelect,
|
||||
openBulkActionsMenu,
|
||||
clickApplyTimelineTemplatesMenuItem,
|
||||
clickAddTagsMenuItem,
|
||||
checkOverwriteTagsCheckbox,
|
||||
checkOverwriteIndexPatternsCheckbox,
|
||||
openBulkEditAddIndexPatternsForm,
|
||||
openBulkEditDeleteIndexPatternsForm,
|
||||
selectTimelineTemplate,
|
||||
checkTagsInTagsFilter,
|
||||
} from '../../tasks/rules_bulk_edit';
|
||||
|
||||
import { hasIndexPatterns } from '../../tasks/rule_details';
|
||||
import { hasIndexPatterns, getDetails } from '../../tasks/rule_details';
|
||||
import { login, visitWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { SECURITY_DETECTIONS_RULES_URL } from '../../urls/navigation';
|
||||
import { createCustomRule, createMachineLearningRule } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common';
|
||||
import {
|
||||
getExistingRule,
|
||||
getNewOverrideRule,
|
||||
createCustomRule,
|
||||
createMachineLearningRule,
|
||||
createCustomIndicatorRule,
|
||||
createEventCorrelationRule,
|
||||
createThresholdRule,
|
||||
createNewTermsRule,
|
||||
} from '../../tasks/api_calls/rules';
|
||||
import { loadPrepackagedTimelineTemplates } from '../../tasks/api_calls/timelines';
|
||||
import { cleanKibana, deleteAlertsAndRules } from '../../tasks/common';
|
||||
|
||||
import {
|
||||
getEqlRule,
|
||||
getNewThreatIndicatorRule,
|
||||
getNewRule,
|
||||
getNewThresholdRule,
|
||||
totalNumberOfPrebuiltRules,
|
||||
getMachineLearningRule,
|
||||
getNewTermsRule,
|
||||
} from '../../objects/rule';
|
||||
import { getIndicatorMatchTimelineTemplate } from '../../objects/timeline';
|
||||
|
||||
import { esArchiverResetKibana } from '../../tasks/es_archiver';
|
||||
|
||||
const RULE_NAME = 'Custom rule for bulk actions';
|
||||
|
||||
const CUSTOM_INDEX_PATTERN_1 = 'custom-cypress-test-*';
|
||||
const DEFAULT_INDEX_PATTERNS = ['index-1-*', 'index-2-*'];
|
||||
const TAGS = ['cypress-tag-1', 'cypress-tag-2'];
|
||||
const OVERWRITE_INDEX_PATTERNS = ['overwrite-index-1-*', 'overwrite-index-2-*'];
|
||||
const prePopulatedIndexPatterns = ['index-1-*', 'index-2-*'];
|
||||
const prePopulatedTags = ['test-default-tag-1', 'test-default-tag-2'];
|
||||
|
||||
const expectedNumberOfCustomRulesToBeEdited = 6;
|
||||
const expectedNumberOfMachineLearningRulesToBeEdited = 1;
|
||||
|
||||
const timelineTemplate = getIndicatorMatchTimelineTemplate();
|
||||
/**
|
||||
* total number of custom rules that are not Machine learning
|
||||
*/
|
||||
const expectedNumberOfNotMLRules =
|
||||
expectedNumberOfCustomRulesToBeEdited - expectedNumberOfMachineLearningRulesToBeEdited;
|
||||
const numberOfRulesPerPage = 5;
|
||||
|
||||
const indexDataSource = { index: prePopulatedIndexPatterns, type: 'indexPatterns' } as const;
|
||||
|
||||
const defaultRuleData = {
|
||||
dataSource: indexDataSource,
|
||||
tags: prePopulatedTags,
|
||||
timeline: timelineTemplate,
|
||||
};
|
||||
|
||||
describe('Detection rules, bulk edit', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
|
@ -90,180 +130,358 @@ describe('Detection rules, bulk edit', () => {
|
|||
{
|
||||
...getNewRule(),
|
||||
name: RULE_NAME,
|
||||
dataSource: { index: DEFAULT_INDEX_PATTERNS, type: 'indexPatterns' },
|
||||
...defaultRuleData,
|
||||
},
|
||||
'1'
|
||||
);
|
||||
createCustomRule(getExistingRule(), '2');
|
||||
createCustomRule(getNewOverrideRule(), '3');
|
||||
createCustomRule(getNewThresholdRule(), '4');
|
||||
createCustomRule({ ...getNewRule(), name: 'rule # 5' }, '5');
|
||||
createCustomRule({ ...getNewRule(), name: 'rule # 6' }, '6');
|
||||
createEventCorrelationRule({ ...getEqlRule(), ...defaultRuleData }, '2');
|
||||
createMachineLearningRule({ ...getMachineLearningRule(), ...defaultRuleData });
|
||||
createCustomIndicatorRule({ ...getNewThreatIndicatorRule(), ...defaultRuleData }, '4');
|
||||
createThresholdRule({ ...getNewThresholdRule(), ...defaultRuleData }, '5');
|
||||
createNewTermsRule({ ...getNewTermsRule(), ...defaultRuleData }, '6');
|
||||
|
||||
visitWithoutDateRange(SECURITY_DETECTIONS_RULES_URL);
|
||||
|
||||
waitForRulesTableToBeLoaded();
|
||||
});
|
||||
|
||||
it('should show warning modal windows when some of the selected rules cannot be edited', () => {
|
||||
createMachineLearningRule(getMachineLearningRule(), '7');
|
||||
describe('Prerequisites', () => {
|
||||
it('No rules selected', () => {
|
||||
openBulkActionsMenu();
|
||||
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
// when no rule selected all bulk edit options should be disabled
|
||||
cy.get(TAGS_RULE_BULK_MENU_ITEM).should('be.disabled');
|
||||
cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).should('be.disabled');
|
||||
cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).should('be.disabled');
|
||||
});
|
||||
|
||||
// select few Elastic rules, check if we can't proceed further, as ELastic rules are not editable
|
||||
// filter rules, only Elastic rule to show
|
||||
switchToElasticRules();
|
||||
it('Only prebuilt rules selected', () => {
|
||||
const expectedNumberOfSelectedRules = 10;
|
||||
|
||||
// check modal window for few selected rules
|
||||
selectNumberOfRules(numberOfRulesPerPage);
|
||||
clickAddIndexPatternsMenuItem();
|
||||
checkElasticRulesCannotBeModified(numberOfRulesPerPage);
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
|
||||
// Select all rules(Elastic rules and custom)
|
||||
cy.get(ELASTIC_RULES_BTN).click();
|
||||
selectAllRules();
|
||||
clickAddIndexPatternsMenuItem();
|
||||
waitForMixedRulesBulkEditModal(expectedNumberOfCustomRulesToBeEdited);
|
||||
// select Elastic(prebuilt) rules, check if we can't proceed further, as Elastic rules are not editable
|
||||
switchToElasticRules();
|
||||
selectNumberOfRules(expectedNumberOfSelectedRules);
|
||||
clickApplyTimelineTemplatesMenuItem();
|
||||
|
||||
// check rules that cannot be edited for index patterns: immutable and ML
|
||||
checkElasticRulesCannotBeModified(totalNumberOfPrebuiltRules);
|
||||
checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
|
||||
// check modal window for Elastic rule that can't be edited
|
||||
checkPrebuiltRulesCannotBeModified(expectedNumberOfSelectedRules);
|
||||
|
||||
// proceed with custom rule editing
|
||||
cy.get(MODAL_CONFIRMATION_BTN)
|
||||
.should('have.text', `Edit ${expectedNumberOfCustomRulesToBeEdited} custom rules`)
|
||||
.click();
|
||||
// the confirm button closes modal
|
||||
cy.get(MODAL_CONFIRMATION_BTN).should('have.text', 'Close').click();
|
||||
cy.get(MODAL_CONFIRMATION_BODY).should('not.exist');
|
||||
});
|
||||
|
||||
typeIndexPatterns([CUSTOM_INDEX_PATTERN_1]);
|
||||
confirmBulkEditForm();
|
||||
it('Prebuilt and custom rules selected: user proceeds with custom rules editing', () => {
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
|
||||
// check if rule has been updated
|
||||
cy.get(CUSTOM_RULES_BTN).click();
|
||||
cy.get(RULES_TABLE_REFRESH_INDICATOR).should('exist');
|
||||
cy.get(RULES_TABLE_REFRESH_INDICATOR).should('not.exist');
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns([...DEFAULT_INDEX_PATTERNS, CUSTOM_INDEX_PATTERN_1].join(''));
|
||||
// modal window should show how many rules can be edit, how many not
|
||||
selectAllRules();
|
||||
clickAddTagsMenuItem();
|
||||
waitForMixedRulesBulkEditModal(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
checkPrebuiltRulesCannotBeModified(totalNumberOfPrebuiltRules);
|
||||
|
||||
// user can proceed with custom rule editing
|
||||
cy.get(MODAL_CONFIRMATION_BTN)
|
||||
.should('have.text', `Edit ${expectedNumberOfCustomRulesToBeEdited} custom rules`)
|
||||
.click();
|
||||
|
||||
// action should finish
|
||||
typeTags(['test-tag']);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
});
|
||||
|
||||
it('Prebuilt and custom rules selected: user cancels action', () => {
|
||||
loadPrebuiltDetectionRulesFromHeaderBtn();
|
||||
|
||||
// modal window should show how many rules can be edit, how many not
|
||||
selectAllRules();
|
||||
clickAddTagsMenuItem();
|
||||
waitForMixedRulesBulkEditModal(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
checkPrebuiltRulesCannotBeModified(totalNumberOfPrebuiltRules);
|
||||
|
||||
// user cancels action and modal disappears
|
||||
cancelConfirmationModal();
|
||||
});
|
||||
|
||||
it('should not lose rules selection after edit action', () => {
|
||||
const rulesCount = 4;
|
||||
// Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items)
|
||||
changeRowsPerPageTo(numberOfRulesPerPage);
|
||||
selectNumberOfRules(rulesCount);
|
||||
|
||||
// open add tags form and add 2 new tags
|
||||
openBulkEditAddTagsForm();
|
||||
typeTags(prePopulatedTags);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount });
|
||||
|
||||
testMultipleSelectedRulesLabel(rulesCount);
|
||||
// check if first four(rulesCount) rules still selected and tags are updated
|
||||
for (let i = 0; i < rulesCount; i += 1) {
|
||||
cy.get(RULE_CHECKBOX).eq(i).should('be.checked');
|
||||
cy.get(RULES_TAGS_POPOVER_BTN)
|
||||
.eq(i)
|
||||
.each(($el) => {
|
||||
testTagsBadge($el, prePopulatedTags);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should add/delete/overwrite index patterns in rules', () => {
|
||||
cy.log('Adds index patterns');
|
||||
// Switch to 5(numberOfRulesPerPage) rules per page, so we can edit all existing rules, not only ones on a page
|
||||
// this way we will use underlying bulk edit API with query parameter, which update all rules based on query search results
|
||||
changeRowsPerPageTo(numberOfRulesPerPage);
|
||||
selectAllRules();
|
||||
describe('Tags actions', () => {
|
||||
it('Display list of tags in tags select', () => {
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
openBulkEditAddIndexPatternsForm();
|
||||
typeIndexPatterns([CUSTOM_INDEX_PATTERN_1]);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
openBulkEditAddTagsForm();
|
||||
openTagsSelect();
|
||||
|
||||
// check if rule has been updated
|
||||
changeRowsPerPageTo(20);
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns([...DEFAULT_INDEX_PATTERNS, CUSTOM_INDEX_PATTERN_1].join(''));
|
||||
cy.go('back');
|
||||
|
||||
cy.log('Deletes index patterns');
|
||||
// select all rules on page (as page displays all existing rules).
|
||||
// this way we will use underlying bulk edit API with ids parameter, which updates rules based their ids
|
||||
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
|
||||
openBulkEditDeleteIndexPatternsForm();
|
||||
typeIndexPatterns([CUSTOM_INDEX_PATTERN_1]);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(DEFAULT_INDEX_PATTERNS.join(''));
|
||||
cy.go('back');
|
||||
|
||||
cy.log('Overwrites index patterns');
|
||||
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
|
||||
openBulkEditAddIndexPatternsForm();
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' index patterns")
|
||||
.click();
|
||||
cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should(
|
||||
'have.text',
|
||||
`You’re about to overwrite index patterns for ${expectedNumberOfCustomRulesToBeEdited} selected rules, press Save to apply changes.`
|
||||
);
|
||||
typeIndexPatterns(OVERWRITE_INDEX_PATTERNS);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(OVERWRITE_INDEX_PATTERNS.join(''));
|
||||
});
|
||||
|
||||
it('should add/delete/overwrite tags in rules', () => {
|
||||
cy.log('Add tags to all rules');
|
||||
// Switch to 5(numberOfRulesPerPage) rules per page, so we can edit all existing rules, not only ones on a page
|
||||
// this way we will use underlying bulk edit API with query parameter, which update all rules based on query search results
|
||||
changeRowsPerPageTo(numberOfRulesPerPage);
|
||||
selectAllRules();
|
||||
|
||||
// open add tags form and add 2 new tags
|
||||
openBulkEditAddTagsForm();
|
||||
typeTags(TAGS);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if all rules have been updated with new tags
|
||||
changeRowsPerPageTo(20);
|
||||
testAllTagsBadges(TAGS);
|
||||
// test how many tags exist and displayed in filter button
|
||||
cy.get(RULES_TAGS_FILTER_BTN).contains(/Tags2/);
|
||||
|
||||
cy.log('Remove one tag from all rules');
|
||||
// select all rules on page (as page displays all existing rules).
|
||||
// this way we will use underlying bulk edit API with query parameter, which update all rules based on query search results
|
||||
cy.get(SELECT_ALL_RULES_ON_PAGE_CHECKBOX).click();
|
||||
|
||||
openBulkEditDeleteTagsForm();
|
||||
typeTags([TAGS[0]]);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
testAllTagsBadges(TAGS.slice(1));
|
||||
cy.get(RULES_TAGS_FILTER_BTN).contains(/Tags1/);
|
||||
|
||||
cy.log('Overwrite all tags');
|
||||
openBulkEditAddTagsForm();
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' tags")
|
||||
.click();
|
||||
cy.get(RULES_BULK_EDIT_TAGS_WARNING).should(
|
||||
'have.text',
|
||||
`You’re about to overwrite tags for ${expectedNumberOfCustomRulesToBeEdited} selected rules, press Save to apply changes.`
|
||||
);
|
||||
typeTags(['overwrite-tag']);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
testAllTagsBadges(['overwrite-tag']);
|
||||
});
|
||||
|
||||
it('should not lose rules selection after edit action', () => {
|
||||
const rulesCount = 4;
|
||||
// Switch to 5 rules per page, to have few pages in pagination(ideal way to test auto refresh and selection of few items)
|
||||
changeRowsPerPageTo(numberOfRulesPerPage);
|
||||
selectNumberOfRules(rulesCount);
|
||||
|
||||
// open add tags form and add 2 new tags
|
||||
openBulkEditAddTagsForm();
|
||||
typeTags(TAGS);
|
||||
confirmBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount });
|
||||
|
||||
testMultipleSelectedRulesLabel(rulesCount);
|
||||
// check if first four(rulesCount) rules still selected and tags are updated
|
||||
for (let i = 0; i < rulesCount; i += 1) {
|
||||
cy.get(RULE_CHECKBOX).eq(i).should('be.checked');
|
||||
cy.get(RULES_TAGS_POPOVER_BTN)
|
||||
.eq(i)
|
||||
.each(($el) => {
|
||||
testTagsBadge($el, TAGS);
|
||||
cy.get(EUI_FILTER_SELECT_ITEM)
|
||||
.should('have.length', prePopulatedTags.length)
|
||||
.each(($el, index) => {
|
||||
cy.wrap($el).should('have.text', prePopulatedTags[index]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('Add tags to custom rules', () => {
|
||||
const tagsToBeAdded = ['tag-to-add-1', 'tag-to-add-2'];
|
||||
const resultingTags = [...prePopulatedTags, ...tagsToBeAdded];
|
||||
|
||||
// check if only pre-populated tags exist in the tags filter
|
||||
checkTagsInTagsFilter(prePopulatedTags);
|
||||
|
||||
cy.get(EUI_FILTER_SELECT_ITEM)
|
||||
.should('have.length', prePopulatedTags.length)
|
||||
.each(($el, index) => {
|
||||
cy.wrap($el).should('have.text', prePopulatedTags[index]);
|
||||
});
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
// open add tags form and add 2 new tags
|
||||
openBulkEditAddTagsForm();
|
||||
typeTags(tagsToBeAdded);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if all rules have been updated with new tags
|
||||
testAllTagsBadges(resultingTags);
|
||||
|
||||
// check that new tags were added to tags filter
|
||||
// tags in tags filter sorted alphabetically
|
||||
const resultingTagsInFilter = [...resultingTags].sort();
|
||||
checkTagsInTagsFilter(resultingTagsInFilter);
|
||||
});
|
||||
|
||||
it('Overwrite tags in custom rules', () => {
|
||||
const tagsToOverwrite = ['overwrite-tag-1'];
|
||||
|
||||
// check if only pre-populated tags exist in the tags filter
|
||||
checkTagsInTagsFilter(prePopulatedTags);
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
// open add tags form, check overwrite tags and warning message, type tags
|
||||
openBulkEditAddTagsForm();
|
||||
checkOverwriteTagsCheckbox();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_TAGS_WARNING).should(
|
||||
'have.text',
|
||||
`You’re about to overwrite tags for ${expectedNumberOfCustomRulesToBeEdited} selected rules, press Save to apply changes.`
|
||||
);
|
||||
|
||||
typeTags(tagsToOverwrite);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if all rules have been updated with new tags
|
||||
testAllTagsBadges(tagsToOverwrite);
|
||||
|
||||
// check that only new tags are in the tag filter
|
||||
checkTagsInTagsFilter(tagsToOverwrite);
|
||||
});
|
||||
|
||||
it('Delete tags from custom rules', () => {
|
||||
const tagsToDelete = prePopulatedTags.slice(0, 1);
|
||||
const resultingTags = prePopulatedTags.slice(1);
|
||||
|
||||
// check if only pre-populated tags exist in the tags filter
|
||||
checkTagsInTagsFilter(prePopulatedTags);
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
// open add tags form, check overwrite tags, type tags
|
||||
openBulkEditDeleteTagsForm();
|
||||
typeTags(tagsToDelete);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check tags has been removed from all rules
|
||||
testAllTagsBadges(resultingTags);
|
||||
|
||||
// check that tags were removed from the tag filter
|
||||
checkTagsInTagsFilter(resultingTags);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Index patterns', () => {
|
||||
it('Index pattern action applied to custom rules, including machine learning: user proceeds with edit of custom non machine learning rule', () => {
|
||||
const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
|
||||
const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
clickAddIndexPatternsMenuItem();
|
||||
|
||||
// confirm editing custom rules, that are not Machine Learning
|
||||
checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
|
||||
cy.get(MODAL_CONFIRMATION_BTN).click();
|
||||
|
||||
typeIndexPatterns(indexPattersToBeAdded);
|
||||
submitBulkEditForm();
|
||||
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfNotMLRules });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(resultingIndexPatterns.join(''));
|
||||
});
|
||||
|
||||
it('Index pattern action applied to custom rules, including machine learning: user cancels action', () => {
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
clickAddIndexPatternsMenuItem();
|
||||
|
||||
// confirm editing custom rules, that are not Machine Learning
|
||||
checkMachineLearningRulesCannotBeModified(expectedNumberOfMachineLearningRulesToBeEdited);
|
||||
|
||||
// user cancels action and modal disappears
|
||||
cancelConfirmationModal();
|
||||
});
|
||||
|
||||
it('Add index patterns to custom rules', () => {
|
||||
const indexPattersToBeAdded = ['index-to-add-1-*', 'index-to-add-2-*'];
|
||||
const resultingIndexPatterns = [...prePopulatedIndexPatterns, ...indexPattersToBeAdded];
|
||||
|
||||
// select only rules that are not ML
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
unselectRuleByName(getMachineLearningRule().name);
|
||||
|
||||
openBulkEditAddIndexPatternsForm();
|
||||
typeIndexPatterns(indexPattersToBeAdded);
|
||||
submitBulkEditForm();
|
||||
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfNotMLRules });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(resultingIndexPatterns.join(''));
|
||||
});
|
||||
|
||||
it('Overwrite index patterns in custom rules', () => {
|
||||
const indexPattersToWrite = ['index-to-write-1-*', 'index-to-write-2-*'];
|
||||
|
||||
// select only rules that are not ML
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
unselectRuleByName(getMachineLearningRule().name);
|
||||
|
||||
openBulkEditAddIndexPatternsForm();
|
||||
|
||||
// check overwrite index patterns checkbox, ensure warning message is displayed and type index patterns
|
||||
checkOverwriteIndexPatternsCheckbox();
|
||||
cy.get(RULES_BULK_EDIT_INDEX_PATTERNS_WARNING).should(
|
||||
'have.text',
|
||||
`You’re about to overwrite index patterns for ${expectedNumberOfNotMLRules} selected rules, press Save to apply changes.`
|
||||
);
|
||||
|
||||
typeIndexPatterns(indexPattersToWrite);
|
||||
submitBulkEditForm();
|
||||
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfNotMLRules });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(indexPattersToWrite.join(''));
|
||||
});
|
||||
|
||||
it('Delete index patterns from custom rules', () => {
|
||||
const indexPatternsToDelete = prePopulatedIndexPatterns.slice(0, 1);
|
||||
const resultingIndexPatterns = prePopulatedIndexPatterns.slice(1);
|
||||
|
||||
// select only not ML rules
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
unselectRuleByName(getMachineLearningRule().name);
|
||||
|
||||
openBulkEditDeleteIndexPatternsForm();
|
||||
typeIndexPatterns(indexPatternsToDelete);
|
||||
submitBulkEditForm();
|
||||
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfNotMLRules });
|
||||
|
||||
// check if rule has been updated
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
hasIndexPatterns(resultingIndexPatterns.join(''));
|
||||
});
|
||||
|
||||
it('Delete all index patterns from custom rules', () => {
|
||||
// select only rules that are not ML
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
unselectRuleByName(getMachineLearningRule().name);
|
||||
|
||||
openBulkEditDeleteIndexPatternsForm();
|
||||
typeIndexPatterns(prePopulatedIndexPatterns);
|
||||
submitBulkEditForm();
|
||||
|
||||
// error toast should be displayed that that rules edit failed
|
||||
cy.contains(TOASTER_BODY, `${expectedNumberOfNotMLRules} rules failed to update.`);
|
||||
|
||||
// on error toast button click display error that index patterns can't be empty
|
||||
clickErrorToastBtn();
|
||||
cy.contains(MODAL_ERROR_BODY, "Index patterns can't be empty");
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timeline templates', () => {
|
||||
beforeEach(() => {
|
||||
loadPrepackagedTimelineTemplates();
|
||||
});
|
||||
|
||||
it('Apply timeline template to custom rules', () => {
|
||||
const timelineTemplateName = 'Generic Endpoint Timeline';
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
// open Timeline template form, check warning, select timeline template
|
||||
clickApplyTimelineTemplatesMenuItem();
|
||||
cy.get(RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING).contains(
|
||||
`You're about to apply changes to ${expectedNumberOfCustomRulesToBeEdited} selected rules. If you previously applied Timeline templates to these rules, they will be overwritten or (if you select 'None') reset to none.`
|
||||
);
|
||||
selectTimelineTemplate(timelineTemplateName);
|
||||
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if timeline template has been updated to selected one
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', timelineTemplateName);
|
||||
});
|
||||
|
||||
it('Reset timeline template to None for custom rules', () => {
|
||||
const noneTimelineTemplate = 'None';
|
||||
|
||||
selectNumberOfRules(expectedNumberOfCustomRulesToBeEdited);
|
||||
|
||||
// open Timeline template form, submit form without picking timeline template as None is selected by default
|
||||
clickApplyTimelineTemplatesMenuItem();
|
||||
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ rulesCount: expectedNumberOfCustomRulesToBeEdited });
|
||||
|
||||
// check if timeline template has been updated to selected one, by opening rule that have had timeline prior to editing
|
||||
goToTheRuleDetailsOf(RULE_NAME);
|
||||
getDetails(TIMELINE_TEMPLATE_DETAILS).should('have.text', noneTimelineTemplate);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -499,6 +499,9 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>
|
|||
risk_score: riskScore,
|
||||
severity,
|
||||
query,
|
||||
tags,
|
||||
timeline_id: timelineId,
|
||||
timeline_title: timelineTitle,
|
||||
} = ruleResponse.body;
|
||||
|
||||
// NOTE: Order of the properties in this object matters for the tests to work.
|
||||
|
@ -509,7 +512,7 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>
|
|||
created_at: createdAt,
|
||||
created_by: 'elastic',
|
||||
name,
|
||||
tags: [],
|
||||
tags,
|
||||
interval: '100m',
|
||||
enabled: false,
|
||||
description,
|
||||
|
@ -538,6 +541,8 @@ export const expectedExportedRule = (ruleResponse: Cypress.Response<RulesSchema>
|
|||
query,
|
||||
throttle: 'no_actions',
|
||||
actions: [],
|
||||
timeline_id: timelineId,
|
||||
timeline_title: timelineTitle,
|
||||
};
|
||||
|
||||
// NOTE: Order of the properties in this object matters for the tests to work.
|
||||
|
|
|
@ -105,6 +105,10 @@ export const MODAL_CONFIRMATION_TITLE = '[data-test-subj="confirmModalTitleText"
|
|||
|
||||
export const MODAL_CONFIRMATION_BODY = '[data-test-subj="confirmModalBodyText"]';
|
||||
|
||||
export const MODAL_ERROR_BODY = '[data-test-subj="errorModalBody"]';
|
||||
|
||||
export const MODAL_CONFIRMATION_CANCEL_BTN = '[data-test-subj="confirmModalCancelButton"]';
|
||||
|
||||
export const RULE_DETAILS_DELETE_BTN = '[data-test-subj="rules-details-delete-rule"]';
|
||||
|
||||
export const SERVER_SIDE_EVENT_COUNT = '[data-test-subj="server-side-event-count"]';
|
||||
|
@ -121,6 +125,8 @@ export const TOASTER = '[data-test-subj="euiToastHeader"]';
|
|||
|
||||
export const TOASTER_BODY = '[data-test-subj="globalToastList"] [data-test-subj="euiToastBody"]';
|
||||
|
||||
export const TOASTER_ERROR_BTN = '[data-test-subj="errorToastBtn"]';
|
||||
|
||||
export const RULE_IMPORT_OVERWRITE_CHECKBOX = '[id="import-data-modal-checkbox-label"]';
|
||||
|
||||
export const RULE_IMPORT_OVERWRITE_EXCEPTIONS_CHECKBOX =
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 TIMELINE_SEARCHBOX = '[data-test-subj="timeline-super-select-search-box"]';
|
||||
|
||||
export const EUI_FILTER_SELECT_ITEM = '.euiFilterSelectItem';
|
||||
|
||||
export const EUI_CHECKBOX = '.euiCheckbox__input';
|
|
@ -21,6 +21,4 @@ export const TAGS_INPUT = '[data-test-subj="caseTags"] [data-test-subj="comboBox
|
|||
|
||||
export const TIMELINE = '[data-test-subj="timeline"]';
|
||||
|
||||
export const TIMELINE_SEARCHBOX = '[data-test-subj="timeline-super-select-search-box"]';
|
||||
|
||||
export const TITLE_INPUT = '[data-test-subj="caseTitle"] [data-test-subj="input"]';
|
||||
|
|
|
@ -223,8 +223,6 @@ export const TAGS_INPUT =
|
|||
export const TAGS_CLEAR_BUTTON =
|
||||
'[data-test-subj="detectionEngineStepAboutRuleTags"] [data-test-subj="comboBoxClearButton"]';
|
||||
|
||||
export const EUI_FILTER_SELECT_ITEM = '.euiFilterSelectItem';
|
||||
|
||||
export const THRESHOLD_INPUT_AREA = '[data-test-subj="thresholdInput"]';
|
||||
|
||||
export const THRESHOLD_TYPE = '[data-test-subj="thresholdRuleType"]';
|
||||
|
|
|
@ -13,6 +13,8 @@ export const ADD_INDEX_PATTERNS_RULE_BULK_MENU_ITEM =
|
|||
export const DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM =
|
||||
'[data-test-subj="deleteIndexPatternsBulkEditRule"]';
|
||||
|
||||
export const APPLY_TIMELINE_RULE_BULK_MENU_ITEM = '[data-test-subj="applyTimelineTemplateBulk"]';
|
||||
|
||||
export const TAGS_RULE_BULK_MENU_ITEM = '[data-test-subj="tagsBulkEditRule"]';
|
||||
|
||||
export const ADD_TAGS_RULE_BULK_MENU_ITEM = '[data-test-subj="addTagsBulkEditRule"]';
|
||||
|
@ -37,3 +39,9 @@ export const RULES_BULK_EDIT_INDEX_PATTERNS_WARNING =
|
|||
'[data-test-subj="bulkEditRulesIndexPatternsWarning"]';
|
||||
|
||||
export const RULES_BULK_EDIT_TAGS_WARNING = '[data-test-subj="bulkEditRulesTagsWarning"]';
|
||||
|
||||
export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR =
|
||||
'[data-test-subj="bulkEditRulesTimelineTemplateSelector"]';
|
||||
|
||||
export const RULES_BULK_EDIT_TIMELINE_TEMPLATES_WARNING =
|
||||
'[data-test-subj="bulkEditRulesTimelineTemplateWarning"]';
|
||||
|
|
|
@ -53,7 +53,11 @@ import {
|
|||
REFRESH_SETTINGS_SWITCH,
|
||||
ELASTIC_RULES_BTN,
|
||||
BULK_EXPORT_ACTION_BTN,
|
||||
TOASTER_ERROR_BTN,
|
||||
MODAL_CONFIRMATION_CANCEL_BTN,
|
||||
MODAL_CONFIRMATION_BODY,
|
||||
} from '../screens/alerts_detection_rules';
|
||||
import { EUI_CHECKBOX } from '../screens/common/controls';
|
||||
import { ALL_ACTIONS } from '../screens/rule_details';
|
||||
import { LOADING_INDICATOR } from '../screens/security_header';
|
||||
|
||||
|
@ -171,6 +175,9 @@ export const loadPrebuiltDetectionRules = () => {
|
|||
.should('be.disabled');
|
||||
};
|
||||
|
||||
/**
|
||||
* load prebuilt rules by clicking button on page header
|
||||
*/
|
||||
export const loadPrebuiltDetectionRulesFromHeaderBtn = () => {
|
||||
cy.get(LOAD_PREBUILT_RULES_ON_PAGE_HEADER_BTN)
|
||||
.pipe(($el) => $el.trigger('click'))
|
||||
|
@ -201,6 +208,14 @@ export const selectNumberOfRules = (numberOfRules: number) => {
|
|||
}
|
||||
};
|
||||
|
||||
export const unselectRuleByName = (ruleName: string) => {
|
||||
cy.contains(RULE_NAME, ruleName)
|
||||
.parents(RULES_ROW)
|
||||
.find(EUI_CHECKBOX)
|
||||
.click()
|
||||
.should('not.be.checked');
|
||||
};
|
||||
|
||||
/**
|
||||
* Unselects a passed number of rules. To use together with selectNumberOfRules
|
||||
* as this utility will expect and check the passed number of rules
|
||||
|
@ -363,3 +378,12 @@ export const bulkExportRules = () => {
|
|||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(BULK_EXPORT_ACTION_BTN).click();
|
||||
};
|
||||
|
||||
export const cancelConfirmationModal = () => {
|
||||
cy.get(MODAL_CONFIRMATION_CANCEL_BTN).click();
|
||||
cy.get(MODAL_CONFIRMATION_BODY).should('not.exist');
|
||||
};
|
||||
|
||||
export const clickErrorToastBtn = () => {
|
||||
cy.get(TOASTER_ERROR_BTN).click();
|
||||
};
|
||||
|
|
|
@ -5,7 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { CustomRule, ThreatIndicatorRule, MachineLearningRule } from '../../objects/rule';
|
||||
import type {
|
||||
CustomRule,
|
||||
ThreatIndicatorRule,
|
||||
MachineLearningRule,
|
||||
ThresholdRule,
|
||||
NewTermsRule,
|
||||
} from '../../objects/rule';
|
||||
|
||||
export const createMachineLearningRule = (rule: MachineLearningRule, ruleId = 'ml_rule_testing') =>
|
||||
cy.request({
|
||||
|
@ -23,6 +29,7 @@ export const createMachineLearningRule = (rule: MachineLearningRule, ruleId = 'm
|
|||
enabled: false,
|
||||
machine_learning_job_id: rule.machineLearningJobs,
|
||||
anomaly_threshold: rule.anomalyScoreThreshold,
|
||||
tags: rule.tags,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
|
@ -50,6 +57,13 @@ export const createCustomRule = (
|
|||
language: 'kuery',
|
||||
enabled: false,
|
||||
exceptions_list: rule.exceptionLists ?? [],
|
||||
tags: rule.tags,
|
||||
...(rule.timeline.id ?? rule.timeline.templateTimelineId
|
||||
? {
|
||||
timeline_id: rule.timeline.id ?? rule.timeline.templateTimelineId,
|
||||
timeline_title: rule.timeline.title,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
|
@ -73,6 +87,62 @@ export const createEventCorrelationRule = (rule: CustomRule, ruleId = 'rule_test
|
|||
query: rule.customQuery,
|
||||
language: 'eql',
|
||||
enabled: true,
|
||||
tags: rule.tags,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const createThresholdRule = (rule: ThresholdRule, ruleId = 'rule_testing') => {
|
||||
if (rule.dataSource.type === 'indexPatterns') {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
description: rule.description,
|
||||
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
|
||||
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
type: 'threshold',
|
||||
index: rule.dataSource.index,
|
||||
query: rule.customQuery,
|
||||
threshold: {
|
||||
field: [rule.thresholdField],
|
||||
value: parseInt(rule.threshold, 10),
|
||||
cardinality: [],
|
||||
},
|
||||
enabled: true,
|
||||
tags: rule.tags,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const createNewTermsRule = (rule: NewTermsRule, ruleId = 'rule_testing') => {
|
||||
if (rule.dataSource.type === 'indexPatterns') {
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'api/detection_engine/rules',
|
||||
body: {
|
||||
rule_id: ruleId,
|
||||
risk_score: parseInt(rule.riskScore, 10),
|
||||
description: rule.description,
|
||||
interval: `${rule.runsEvery.interval}${rule.runsEvery.type}`,
|
||||
from: `now-${rule.lookBack.interval}${rule.lookBack.type}`,
|
||||
name: rule.name,
|
||||
severity: rule.severity.toLocaleLowerCase(),
|
||||
type: 'new_terms',
|
||||
index: rule.dataSource.index,
|
||||
query: rule.customQuery,
|
||||
new_terms_fields: rule.newTermsFields,
|
||||
history_window_start: `now-${rule.historyWindowSize.interval}${rule.historyWindowSize.type}`,
|
||||
enabled: true,
|
||||
tags: rule.tags,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
});
|
||||
|
@ -117,6 +187,7 @@ export const createCustomIndicatorRule = (rule: ThreatIndicatorRule, ruleId = 'r
|
|||
query: rule.customQuery || '*:*',
|
||||
language: 'kuery',
|
||||
enabled: true,
|
||||
tags: rule.tags,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds' },
|
||||
failOnStatusCode: false,
|
||||
|
|
|
@ -14,6 +14,7 @@ import type {
|
|||
} from '../objects/case';
|
||||
import { ALL_CASES_OPEN_CASES_COUNT, ALL_CASES_OPEN_FILTER } from '../screens/all_cases';
|
||||
|
||||
import { TIMELINE_SEARCHBOX } from '../screens/common/controls';
|
||||
import {
|
||||
BACK_TO_CASES_BTN,
|
||||
DESCRIPTION_INPUT,
|
||||
|
@ -21,7 +22,6 @@ import {
|
|||
INSERT_TIMELINE_BTN,
|
||||
LOADING_SPINNER,
|
||||
TAGS_INPUT,
|
||||
TIMELINE_SEARCHBOX,
|
||||
TITLE_INPUT,
|
||||
} from '../screens/create_new_case';
|
||||
import {
|
||||
|
|
|
@ -83,7 +83,6 @@ import {
|
|||
THREAT_MATCH_INDICATOR_INDICATOR_INDEX,
|
||||
THREAT_MATCH_OR_BUTTON,
|
||||
THREAT_MATCH_QUERY_INPUT,
|
||||
EUI_FILTER_SELECT_ITEM,
|
||||
THRESHOLD_INPUT_AREA,
|
||||
THRESHOLD_TYPE,
|
||||
CONNECTOR_NAME_INPUT,
|
||||
|
@ -105,6 +104,7 @@ import { TOAST_ERROR } from '../screens/shared';
|
|||
import { SERVER_SIDE_EVENT_COUNT } from '../screens/timeline';
|
||||
import { TIMELINE } from '../screens/timelines';
|
||||
import { refreshPage } from './security_header';
|
||||
import { EUI_FILTER_SELECT_ITEM } from '../screens/common/controls';
|
||||
|
||||
export const createAndEnableRule = () => {
|
||||
cy.get(SCHEDULE_CONTINUE_BUTTON).click({ force: true });
|
||||
|
|
|
@ -5,12 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TIMELINE_SEARCHBOX, EUI_FILTER_SELECT_ITEM } from '../screens/common/controls';
|
||||
|
||||
import {
|
||||
BULK_ACTIONS_BTN,
|
||||
BULK_ACTIONS_PROGRESS_BTN,
|
||||
MODAL_CONFIRMATION_TITLE,
|
||||
MODAL_CONFIRMATION_BODY,
|
||||
TOASTER_BODY,
|
||||
RULES_TAGS_FILTER_BTN,
|
||||
} from '../screens/alerts_detection_rules';
|
||||
|
||||
import {
|
||||
|
@ -24,14 +27,42 @@ import {
|
|||
RULES_BULK_EDIT_INDEX_PATTERNS,
|
||||
RULES_BULK_EDIT_TAGS,
|
||||
RULES_BULK_EDIT_FORM_CONFIRM_BTN,
|
||||
APPLY_TIMELINE_RULE_BULK_MENU_ITEM,
|
||||
RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX,
|
||||
RULES_BULK_EDIT_TIMELINE_TEMPLATES_SELECTOR,
|
||||
} from '../screens/rules_bulk_edit';
|
||||
|
||||
export const clickAddIndexPatternsMenuItem = () => {
|
||||
export const clickApplyTimelineTemplatesMenuItem = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click();
|
||||
cy.get(APPLY_TIMELINE_RULE_BULK_MENU_ITEM).click().should('not.exist');
|
||||
};
|
||||
|
||||
export const clickIndexPatternsMenuItem = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click().should('not.exist');
|
||||
};
|
||||
|
||||
export const clickTagsMenuItem = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
};
|
||||
|
||||
export const clickAddTagsMenuItem = () => {
|
||||
clickTagsMenuItem();
|
||||
cy.get(ADD_TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
};
|
||||
|
||||
export const clickAddIndexPatternsMenuItem = () => {
|
||||
clickIndexPatternsMenuItem();
|
||||
cy.get(ADD_INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click();
|
||||
};
|
||||
|
||||
export const clickDeleteIndexPatternsMenuItem = () => {
|
||||
clickIndexPatternsMenuItem();
|
||||
cy.get(DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM).click().should('not.exist');
|
||||
};
|
||||
|
||||
export const openBulkEditAddIndexPatternsForm = () => {
|
||||
clickAddIndexPatternsMenuItem();
|
||||
|
||||
|
@ -47,21 +78,22 @@ export const openBulkEditDeleteIndexPatternsForm = () => {
|
|||
};
|
||||
|
||||
export const openBulkEditAddTagsForm = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
cy.get(ADD_TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
clickAddTagsMenuItem();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add tags');
|
||||
};
|
||||
|
||||
export const openBulkEditDeleteTagsForm = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
clickTagsMenuItem();
|
||||
cy.get(DELETE_TAGS_RULE_BULK_MENU_ITEM).click();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Delete tags');
|
||||
};
|
||||
|
||||
export const openBulkActionsMenu = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
};
|
||||
|
||||
export const typeIndexPatterns = (indices: string[]) => {
|
||||
cy.get(RULES_BULK_EDIT_INDEX_PATTERNS).find('input').type(indices.join('{enter}'));
|
||||
};
|
||||
|
@ -70,14 +102,18 @@ export const typeTags = (tags: string[]) => {
|
|||
cy.get(RULES_BULK_EDIT_TAGS).find('input').type(tags.join('{enter}'));
|
||||
};
|
||||
|
||||
export const confirmBulkEditForm = () => cy.get(RULES_BULK_EDIT_FORM_CONFIRM_BTN).click();
|
||||
export const openTagsSelect = () => {
|
||||
cy.get(RULES_BULK_EDIT_TAGS).find('input').click();
|
||||
};
|
||||
|
||||
export const submitBulkEditForm = () => cy.get(RULES_BULK_EDIT_FORM_CONFIRM_BTN).click();
|
||||
|
||||
export const waitForBulkEditActionToFinish = ({ rulesCount }: { rulesCount: number }) => {
|
||||
cy.get(BULK_ACTIONS_PROGRESS_BTN).should('be.disabled');
|
||||
cy.contains(TOASTER_BODY, `You've successfully updated ${rulesCount} rule`);
|
||||
};
|
||||
|
||||
export const checkElasticRulesCannotBeModified = (rulesCount: number) => {
|
||||
export const checkPrebuiltRulesCannotBeModified = (rulesCount: number) => {
|
||||
cy.get(MODAL_CONFIRMATION_BODY).contains(
|
||||
`${rulesCount} prebuilt Elastic rules (editing prebuilt rules is not supported)`
|
||||
);
|
||||
|
@ -95,3 +131,38 @@ export const waitForMixedRulesBulkEditModal = (customRulesCount: number) => {
|
|||
`This action can only be applied to ${customRulesCount} custom rules`
|
||||
);
|
||||
};
|
||||
|
||||
export const checkOverwriteTagsCheckbox = () => {
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' tags")
|
||||
.click()
|
||||
.get('input')
|
||||
.should('be.checked');
|
||||
};
|
||||
|
||||
export const checkOverwriteIndexPatternsCheckbox = () => {
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' index patterns")
|
||||
.click()
|
||||
.get('input')
|
||||
.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');
|
||||
};
|
||||
|
||||
/**
|
||||
* check if rule tags filter populated with a list of tags
|
||||
* @param tags
|
||||
*/
|
||||
export const checkTagsInTagsFilter = (tags: string[]) => {
|
||||
cy.get(RULES_TAGS_FILTER_BTN).contains(`Tags${tags.length}`).click();
|
||||
|
||||
cy.get(EUI_FILTER_SELECT_ITEM)
|
||||
.should('have.length', tags.length)
|
||||
.each(($el, index) => {
|
||||
cy.wrap($el).should('have.text', tags[index]);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -38,7 +38,7 @@ describe('deleteItemsFromArray', () => {
|
|||
});
|
||||
|
||||
describe('ruleParamsModifier', () => {
|
||||
const ruleParamsMock = { index: ['initial-index-*'], version: 1 } as RuleAlertType['params'];
|
||||
const ruleParamsMock = { index: ['my-index-*'], version: 1 } as RuleAlertType['params'];
|
||||
|
||||
test('should increment version', () => {
|
||||
const editedRuleParams = ruleParamsModifier(ruleParamsMock, [
|
||||
|
@ -51,26 +51,161 @@ describe('ruleParamsModifier', () => {
|
|||
});
|
||||
|
||||
describe('index_patterns', () => {
|
||||
test('should add new index pattern to rule', () => {
|
||||
const editedRuleParams = ruleParamsModifier(ruleParamsMock, [
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
]);
|
||||
expect(editedRuleParams).toHaveProperty('index', ['initial-index-*', 'my-index-*']);
|
||||
});
|
||||
test('should remove index pattern from rule', () => {
|
||||
const editedRuleParams = ruleParamsModifier(
|
||||
{ index: ['initial-index-*', 'index-2-*'] } as RuleAlertType['params'],
|
||||
describe('add_index_patterns action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing patterns + 2 of them = 3 patterns',
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: ['index-2-*'],
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToAdd: ['index-2-*', 'index-3-*'],
|
||||
resultingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
},
|
||||
]
|
||||
],
|
||||
[
|
||||
'3 existing patterns + 2 other patterns(none of them) = 5 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToAdd: ['index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: [
|
||||
'index-1-*',
|
||||
'index-2-*',
|
||||
'index-3-*',
|
||||
'index-4-*',
|
||||
'index-5-*',
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns + 1 of them + 2 other patterns(none of them) = 5 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToAdd: ['index-3-*', 'index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: [
|
||||
'index-1-*',
|
||||
'index-2-*',
|
||||
'index-3-*',
|
||||
'index-4-*',
|
||||
'index-5-*',
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns + 0 patterns = 3 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToAdd: [],
|
||||
resultingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should add index patterns to rule, case:"%s"',
|
||||
(caseName, { existingIndexPatterns, indexPatternsToAdd, resultingIndexPatterns }) => {
|
||||
const editedRuleParams = ruleParamsModifier(
|
||||
{ ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: indexPatternsToAdd,
|
||||
},
|
||||
]
|
||||
);
|
||||
expect(editedRuleParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('delete_index_patterns action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing patterns - 2 of them = 1 pattern',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToDelete: ['index-2-*', 'index-3-*'],
|
||||
resultingIndexPatterns: ['index-1-*'],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns - 2 other patterns(none of them) = 3 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToDelete: ['index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns - 1 of them - 2 other patterns(none of them) = 2 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToDelete: ['index-3-*', 'index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: ['index-1-*', 'index-2-*'],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns - 0 patterns = 3 patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToDelete: [],
|
||||
resultingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should delete index patterns from rule, case:"%s"',
|
||||
(caseName, { existingIndexPatterns, indexPatternsToDelete, resultingIndexPatterns }) => {
|
||||
const editedRuleParams = ruleParamsModifier(
|
||||
{ ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: indexPatternsToDelete,
|
||||
},
|
||||
]
|
||||
);
|
||||
expect(editedRuleParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('set_index_patterns action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing patterns overwritten with 2 of them = 2 existing patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToOverwrite: ['index-2-*', 'index-3-*'],
|
||||
resultingIndexPatterns: ['index-2-*', 'index-3-*'],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns overwritten with 2 other patterns = 2 other patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToOverwrite: ['index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: ['index-4-*', 'index-5-*'],
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing patterns overwritten with 1 of them + 2 other patterns = 1 existing pattern + 2 other patterns',
|
||||
{
|
||||
existingIndexPatterns: ['index-1-*', 'index-2-*', 'index-3-*'],
|
||||
indexPatternsToOverwrite: ['index-3-*', 'index-4-*', 'index-5-*'],
|
||||
resultingIndexPatterns: ['index-3-*', 'index-4-*', 'index-5-*'],
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should overwrite index patterns in rule, case:"%s"',
|
||||
(caseName, { existingIndexPatterns, indexPatternsToOverwrite, resultingIndexPatterns }) => {
|
||||
const editedRuleParams = ruleParamsModifier(
|
||||
{ ...ruleParamsMock, index: existingIndexPatterns } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditType.set_index_patterns,
|
||||
value: indexPatternsToOverwrite,
|
||||
},
|
||||
]
|
||||
);
|
||||
expect(editedRuleParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
}
|
||||
);
|
||||
expect(editedRuleParams).toHaveProperty('index', ['initial-index-*']);
|
||||
});
|
||||
|
||||
test('should return undefined index patterns on remove action if rule has dataViewId only', () => {
|
||||
|
@ -88,16 +223,6 @@ describe('ruleParamsModifier', () => {
|
|||
expect(editedRuleParams).toHaveProperty('dataViewId', testDataViewId);
|
||||
});
|
||||
|
||||
test('should rewrite index pattern in rule', () => {
|
||||
const editedRuleParams = ruleParamsModifier(ruleParamsMock, [
|
||||
{
|
||||
type: BulkActionEditType.set_index_patterns,
|
||||
value: ['index'],
|
||||
},
|
||||
]);
|
||||
expect(editedRuleParams).toHaveProperty('index', ['index']);
|
||||
});
|
||||
|
||||
test('should set dataViewId to undefined if overwrite_data_views=true on set_index_patterns action', () => {
|
||||
const editedRuleParams = ruleParamsModifier(
|
||||
{ dataViewId: 'test-data-view', index: ['test-*'] } as RuleAlertType['params'],
|
||||
|
|
|
@ -362,73 +362,410 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
describe('edit action', () => {
|
||||
it('should set, add and delete tags in rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const tags = ['tag1', 'tag2'];
|
||||
await createRule(supertest, log, getSimpleRule(ruleId));
|
||||
describe('tags actions', () => {
|
||||
const overwriteTagsCases = [
|
||||
{
|
||||
caseName: '3 existing tags overwritten with 2 of them = 2 existing tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToOverwrite: ['tag1', 'tag2'],
|
||||
resultingTags: ['tag1', 'tag2'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags overwritten with 2 other tags = 2 other tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToOverwrite: ['new-tag1', 'new-tag2'],
|
||||
resultingTags: ['new-tag1', 'new-tag2'],
|
||||
},
|
||||
{
|
||||
caseName:
|
||||
'3 existing tags overwritten with 1 of them + 2 other tags = 1 existing tag + 2 other tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToOverwrite: ['tag1', 'new-tag1', 'new-tag2'],
|
||||
resultingTags: ['tag1', 'new-tag1', 'new-tag2'],
|
||||
},
|
||||
{
|
||||
caseName: '0 existing tags overwritten with 2 tags = 2 tags',
|
||||
existingTags: [],
|
||||
tagsToOverwrite: ['new-tag1', 'new-tag2'],
|
||||
resultingTags: ['new-tag1', 'new-tag2'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags overwritten with 0 tags = 0 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToOverwrite: [],
|
||||
resultingTags: [],
|
||||
},
|
||||
];
|
||||
|
||||
const { body: setTagsBody } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
overwriteTagsCases.forEach(({ caseName, existingTags, tagsToOverwrite, resultingTags }) => {
|
||||
it(`should set tags in rules, case: "${caseName}"`, async () => {
|
||||
const ruleId = 'ruleId';
|
||||
|
||||
await createRule(supertest, log, { ...getSimpleRule(ruleId), tags: existingTags });
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.set_tags,
|
||||
value: tagsToOverwrite,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.tags).to.eql(resultingTags);
|
||||
});
|
||||
});
|
||||
|
||||
const deleteTagsCases = [
|
||||
{
|
||||
caseName: '3 existing tags - 2 of them = 1 tag',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToDelete: ['tag1', 'tag2'],
|
||||
resultingTags: ['tag3'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags - 2 other tags(none of them) = 3 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToDelete: ['tag4', 'tag5'],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags - 1 of them - 2 other tags(none of them) = 2 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToDelete: ['tag3', 'tag4', 'tag5'],
|
||||
resultingTags: ['tag1', 'tag2'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags - 0 tags = 3 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToDelete: [],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3'],
|
||||
},
|
||||
{
|
||||
caseName: '0 existing tags - 2 tags = 0 tags',
|
||||
existingTags: [],
|
||||
tagsToDelete: ['tag4', 'tag5'],
|
||||
resultingTags: [],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags - 3 of them = 0 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
tagsToDelete: ['tag1', 'tag2', 'tag3'],
|
||||
resultingTags: [],
|
||||
},
|
||||
];
|
||||
|
||||
deleteTagsCases.forEach(({ caseName, existingTags, tagsToDelete, resultingTags }) => {
|
||||
it(`should delete tags in rules, case: "${caseName}"`, async () => {
|
||||
const ruleId = 'ruleId';
|
||||
|
||||
await createRule(supertest, log, { ...getSimpleRule(ruleId), tags: existingTags });
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.delete_tags,
|
||||
value: tagsToDelete,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.tags).to.eql(resultingTags);
|
||||
});
|
||||
});
|
||||
|
||||
const addTagsCases = [
|
||||
{
|
||||
caseName: '3 existing tags + 2 of them = 3 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
addedTags: ['tag1', 'tag2'],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags + 2 other tags(none of them) = 5 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
addedTags: ['tag4', 'tag5'],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags + 1 of them + 2 other tags(none of them) = 5 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
addedTags: ['tag4', 'tag5', 'tag1'],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5'],
|
||||
},
|
||||
{
|
||||
caseName: '0 existing tags + 2 tags = 2 tags',
|
||||
existingTags: [],
|
||||
addedTags: ['tag4', 'tag5'],
|
||||
resultingTags: ['tag4', 'tag5'],
|
||||
},
|
||||
{
|
||||
caseName: '3 existing tags + 0 tags = 3 tags',
|
||||
existingTags: ['tag1', 'tag2', 'tag3'],
|
||||
addedTags: [],
|
||||
resultingTags: ['tag1', 'tag2', 'tag3'],
|
||||
},
|
||||
];
|
||||
|
||||
addTagsCases.forEach(({ caseName, existingTags, addedTags, resultingTags }) => {
|
||||
it(`should add tags to rules, case: "${caseName}"`, async () => {
|
||||
const ruleId = 'ruleId';
|
||||
await createRule(supertest, log, { ...getSimpleRule(ruleId), tags: existingTags });
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_tags,
|
||||
value: addedTags,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].tags).to.eql(resultingTags);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.tags).to.eql(resultingTags);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('index patterns actions', () => {
|
||||
it('should set index patterns in rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
await createRule(supertest, log, getSimpleRule(ruleId));
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.set_index_patterns,
|
||||
value: ['initial-index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(['initial-index-*']);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.index).to.eql(['initial-index-*']);
|
||||
});
|
||||
|
||||
it('should add index patterns to rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const indexPatterns = ['index1-*', 'index2-*'];
|
||||
const resultingIndexPatterns = ['index1-*', 'index2-*', 'index3-*'];
|
||||
await createRule(supertest, log, { ...getSimpleRule(ruleId), index: indexPatterns });
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: ['index3-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(
|
||||
resultingIndexPatterns
|
||||
);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.index).to.eql(resultingIndexPatterns);
|
||||
});
|
||||
|
||||
it('should delete index patterns from rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const indexPatterns = ['index1-*', 'index2-*'];
|
||||
const resultingIndexPatterns = ['index1-*'];
|
||||
await createRule(supertest, log, { ...getSimpleRule(ruleId), index: indexPatterns });
|
||||
|
||||
const { body: bulkEditResponse } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: ['index2-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].index).to.eql(
|
||||
resultingIndexPatterns
|
||||
);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.index).to.eql(resultingIndexPatterns);
|
||||
});
|
||||
|
||||
it('should return error if index patterns action is applied to machine learning rule', async () => {
|
||||
const mlRule = await createRule(supertest, log, getSimpleMlRule());
|
||||
|
||||
const { body } = await postBulkAction()
|
||||
.send({
|
||||
ids: [mlRule.id],
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: ['index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
|
||||
expect(body.attributes.errors[0]).to.eql({
|
||||
message:
|
||||
"Index patterns can't be added. Machine learning rule doesn't have index patterns property",
|
||||
status_code: 500,
|
||||
rules: [
|
||||
{
|
||||
type: BulkActionEditType.set_tags,
|
||||
value: ['reset-tag'],
|
||||
id: mlRule.id,
|
||||
name: mlRule.name,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
expect(setTagsBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
it('should return error if all index patterns removed from a rule', async () => {
|
||||
const rule = await createRule(supertest, log, {
|
||||
...getSimpleRule(),
|
||||
index: ['simple-index-*'],
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(setTagsBody.attributes.results.updated[0].tags).to.eql(['reset-tag']);
|
||||
const { body } = await postBulkAction()
|
||||
.send({
|
||||
ids: [rule.id],
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: ['simple-index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: setTagsRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(setTagsRule.tags).to.eql(['reset-tag']);
|
||||
|
||||
const { body: addTagsBody } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
|
||||
expect(body.attributes.errors[0]).to.eql({
|
||||
message: "Mutated params invalid: Index patterns can't be empty",
|
||||
status_code: 500,
|
||||
rules: [
|
||||
{
|
||||
type: BulkActionEditType.add_tags,
|
||||
value: tags,
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
|
||||
expect(addTagsBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
it('should return error if index patterns set to empty list', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const rule = await createRule(supertest, log, {
|
||||
...getSimpleRule(ruleId),
|
||||
index: ['simple-index-*'],
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(addTagsBody.attributes.results.updated[0].tags).to.eql(['reset-tag', ...tags]);
|
||||
const { body } = await postBulkAction()
|
||||
.send({
|
||||
ids: [rule.id],
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.set_index_patterns,
|
||||
value: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: addedTagsRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(addedTagsRule.tags).to.eql(['reset-tag', ...tags]);
|
||||
|
||||
await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
|
||||
expect(body.attributes.errors[0]).to.eql({
|
||||
message: "Mutated params invalid: Index patterns can't be empty",
|
||||
status_code: 500,
|
||||
rules: [
|
||||
{
|
||||
type: BulkActionEditType.delete_tags,
|
||||
value: ['reset-tag', 'tag1'],
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
const { body: deletedTagsRule } = await fetchRule(ruleId).expect(200);
|
||||
// Check that the rule hasn't been updated
|
||||
const { body: reFetchedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(deletedTagsRule.tags).to.eql(['tag2']);
|
||||
expect(reFetchedRule.index).to.eql(['simple-index-*']);
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate legacy actions on edit', async () => {
|
||||
|
@ -490,79 +827,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should set, add and delete index patterns in rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const indices = ['index1-*', 'index2-*'];
|
||||
await createRule(supertest, log, getSimpleRule(ruleId));
|
||||
|
||||
const { body: setIndexBody } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.set_index_patterns,
|
||||
value: ['initial-index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(setIndexBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(setIndexBody.attributes.results.updated[0].index).to.eql(['initial-index-*']);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: setIndexRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(setIndexRule.index).to.eql(['initial-index-*']);
|
||||
|
||||
const { body: addIndexBody } = await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: indices,
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(addIndexBody.attributes.summary).to.eql({ failed: 0, succeeded: 1, total: 1 });
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(addIndexBody.attributes.results.updated[0].index).to.eql([
|
||||
'initial-index-*',
|
||||
...indices,
|
||||
]);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: addIndexRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(addIndexRule.index).to.eql(['initial-index-*', ...indices]);
|
||||
|
||||
await postBulkAction()
|
||||
.send({
|
||||
query: '',
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: ['index1-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const { body: deleteIndexRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(deleteIndexRule.index).to.eql(['initial-index-*', 'index2-*']);
|
||||
});
|
||||
|
||||
it('should set timeline values in rule', async () => {
|
||||
it('should set timeline template values in rule', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const timelineId = '91832785-286d-4ebe-b884-1a208d111a70';
|
||||
const timelineTitle = 'Test timeline';
|
||||
|
@ -597,7 +862,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(rule.timeline_title).to.eql(timelineTitle);
|
||||
});
|
||||
|
||||
it('should correctly remove timeline', async () => {
|
||||
it('should correctly remove timeline template', async () => {
|
||||
const timelineId = 'test-id';
|
||||
const timelineTitle = 'Test timeline template';
|
||||
const ruleId = 'ruleId';
|
||||
|
@ -676,68 +941,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should return error if index patterns action is applied to machine learning rule', async () => {
|
||||
const mlRule = await createRule(supertest, log, getSimpleMlRule());
|
||||
|
||||
const { body } = await postBulkAction()
|
||||
.send({
|
||||
ids: [mlRule.id],
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.add_index_patterns,
|
||||
value: ['index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
|
||||
expect(body.attributes.errors[0]).to.eql({
|
||||
message:
|
||||
"Index patterns can't be added. Machine learning rule doesn't have index patterns property",
|
||||
status_code: 500,
|
||||
rules: [
|
||||
{
|
||||
id: mlRule.id,
|
||||
name: mlRule.name,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error if all index patterns removed from a rule', async () => {
|
||||
const rule = await createRule(supertest, log, {
|
||||
...getSimpleRule(),
|
||||
index: ['simple-index-*'],
|
||||
});
|
||||
|
||||
const { body } = await postBulkAction()
|
||||
.send({
|
||||
ids: [rule.id],
|
||||
action: BulkAction.edit,
|
||||
[BulkAction.edit]: [
|
||||
{
|
||||
type: BulkActionEditType.delete_index_patterns,
|
||||
value: ['simple-index-*'],
|
||||
},
|
||||
],
|
||||
})
|
||||
.expect(500);
|
||||
|
||||
expect(body.attributes.summary).to.eql({ failed: 1, succeeded: 0, total: 1 });
|
||||
expect(body.attributes.errors[0]).to.eql({
|
||||
message: "Mutated params invalid: Index patterns can't be empty",
|
||||
status_code: 500,
|
||||
rules: [
|
||||
{
|
||||
id: rule.id,
|
||||
name: rule.name,
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should increment version on rule bulk edit', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const rule = await createRule(supertest, log, getSimpleRule(ruleId));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue