Skip to content

Commit

Permalink
Implement "select all" rules feature (#100554)
Browse files Browse the repository at this point in the history
  • Loading branch information
xcrzx authored Jun 9, 2021
1 parent 52d62ce commit 16e66b8
Show file tree
Hide file tree
Showing 81 changed files with 2,100 additions and 1,031 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`;
export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`;
export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`;
export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`;
export const DETECTION_ENGINE_RULES_BULK_ACTION = `${DETECTION_ENGINE_RULES_URL}/_bulk_action`;

export const TIMELINE_URL = '/api/timeline';
export const TIMELINES_URL = '/api/timelines';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export const ruleIdOrUndefined = t.union([rule_id, t.undefined]);
export type RuleIdOrUndefined = t.TypeOf<typeof ruleIdOrUndefined>;

export const id = UUID;
export type Id = t.TypeOf<typeof id>;

export const idOrUndefined = t.union([id, t.undefined]);
export type IdOrUndefined = t.TypeOf<typeof idOrUndefined>;

Expand Down Expand Up @@ -408,3 +410,13 @@ export const privilege = t.type({
});

export type Privilege = t.TypeOf<typeof privilege>;

export enum BulkAction {
'enable' = 'enable',
'disable' = 'disable',
'export' = 'export',
'delete' = 'delete',
'duplicate' = 'duplicate',
}

export const bulkAction = t.keyof(BulkAction);
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ export * from './query_signals_index_schema';
export * from './set_signal_status_schema';
export * from './update_rules_bulk_schema';
export * from './rule_schemas';
export * from './perform_bulk_action_schema';
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import { BulkAction } from '../common/schemas';
import { PerformBulkActionSchema } from './perform_bulk_action_schema';

export const EXPORT_FAILURE = i18n.translate(
'xpack.securitySolution.detectionEngine.rules.components.genericDownloader.exportFailureTitle',
{
defaultMessage: 'Failed to export data…',
}
);
export const getPerformBulkActionSchemaMock = (): PerformBulkActionSchema => ({
query: '',
action: BulkAction.disable,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 { performBulkActionSchema, PerformBulkActionSchema } from './perform_bulk_action_schema';
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
import { left } from 'fp-ts/lib/Either';
import { BulkAction } from '../common/schemas';

describe('perform_bulk_action_schema', () => {
test('query and action is valid', () => {
const payload: PerformBulkActionSchema = {
query: 'name: test',
action: BulkAction.enable,
};

const decoded = performBulkActionSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = foldLeftRight(checked);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('missing query is valid', () => {
const payload: PerformBulkActionSchema = {
query: undefined,
action: BulkAction.enable,
};

const decoded = performBulkActionSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = foldLeftRight(checked);
expect(getPaths(left(message.errors))).toEqual([]);
expect(message.schema).toEqual(payload);
});

test('missing action is invalid', () => {
const payload: Omit<PerformBulkActionSchema, 'action'> = {
query: 'name: test',
};

const decoded = performBulkActionSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = foldLeftRight(checked);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "undefined" supplied to "action"',
]);
expect(message.schema).toEqual({});
});

test('unknown action is invalid', () => {
const payload: Omit<PerformBulkActionSchema, 'action'> & { action: 'unknown' } = {
query: 'name: test',
action: 'unknown',
};

const decoded = performBulkActionSchema.decode(payload);
const checked = exactCheck(payload, decoded);
const message = foldLeftRight(checked);
expect(getPaths(left(message.errors))).toEqual([
'Invalid value "unknown" supplied to "action"',
]);
expect(message.schema).toEqual({});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* 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 * as t from 'io-ts';
import { bulkAction, queryOrUndefined } from '../common/schemas';

export const performBulkActionSchema = t.exact(
t.type({
query: queryOrUndefined,
action: bulkAction,
})
);

export type PerformBulkActionSchema = t.TypeOf<typeof performBulkActionSchema>;
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ import {
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
deleteFirstRule,
deleteSelectedRules,
editFirstRule,
Expand Down Expand Up @@ -159,7 +159,7 @@ describe('Custom detection rules creation', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
filterByCustomRules,
goToCreateNewRule,
goToRuleDetails,
Expand Down Expand Up @@ -113,7 +113,7 @@ describe('Detection rules, EQL', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
Expand Down Expand Up @@ -208,7 +208,7 @@ describe('Detection rules, sequence EQL', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ import {
scrollJsonViewToBottom,
} from '../../tasks/alerts_details';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
duplicateFirstRule,
duplicateSelectedRules,
duplicateRuleFromMenu,
Expand Down Expand Up @@ -424,7 +424,7 @@ describe('indicator match', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
filterByCustomRules,
goToCreateNewRule,
goToRuleDetails,
Expand Down Expand Up @@ -90,7 +90,7 @@ describe('Detection rules, machine learning', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RULES_TABLE).then(($table) => {
cy.wrap($table.find(RULES_ROW).length).should('eql', expectedNumberOfRules);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ import {
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
filterByCustomRules,
goToCreateNewRule,
goToRuleDetails,
Expand Down Expand Up @@ -121,7 +121,7 @@ describe('Detection rules, override', () => {

cy.get(CUSTOM_RULES_BTN).should('have.text', 'Custom rules (1)');

changeRowsPerPageTo300();
changeRowsPerPageTo100();

const expectedNumberOfRules = 1;
cy.get(RULES_TABLE).then(($table) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,29 @@
import {
COLLAPSED_ACTION_BTN,
ELASTIC_RULES_BTN,
pageSelector,
RELOAD_PREBUILT_RULES_BTN,
RULES_ROW,
RULES_TABLE,
RULES_EMPTY_PROMPT,
RULE_SWITCH,
SHOWING_RULES_TEXT,
} from '../../screens/alerts_detection_rules';

import { goToManageAlertsDetectionRules, waitForAlertsIndexToBeCreated } from '../../tasks/alerts';
import {
changeRowsPerPageTo300,
changeRowsPerPageTo100,
deleteFirstRule,
deleteSelectedRules,
loadPrebuiltDetectionRules,
goToNextPage,
reloadDeletedRules,
selectNumberOfRules,
waitForRulesTableToBeLoaded,
waitForPrebuiltDetectionRulesToBeLoaded,
selectAllRules,
confirmRulesDelete,
activateSelectedRules,
waitForRuleToChangeStatus,
deactivateSelectedRules,
changeRowsPerPageTo,
} from '../../tasks/alerts_detection_rules';
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';

Expand All @@ -39,7 +45,9 @@ describe('Alerts rules, prebuilt rules', () => {
});

it('Loads prebuilt rules', () => {
const rowsPerPage = 100;
const expectedNumberOfRules = totalNumberOfPrebuiltRules;
const expectedNumberOfPages = Math.ceil(totalNumberOfPrebuiltRules / rowsPerPage);
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;

loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
Expand All @@ -51,23 +59,14 @@ describe('Alerts rules, prebuilt rules', () => {

cy.get(ELASTIC_RULES_BTN).should('have.text', expectedElasticRulesBtnText);

changeRowsPerPageTo300();
changeRowsPerPageTo(rowsPerPage);

cy.get(SHOWING_RULES_TEXT).should('have.text', `Showing ${expectedNumberOfRules} rules`);
cy.get(RULES_TABLE).then(($table1) => {
const firstScreenRules = $table1.find(RULES_ROW).length;
goToNextPage();
cy.get(RULES_TABLE).then(($table2) => {
const secondScreenRules = $table2.find(RULES_ROW).length;
const totalNumberOfRules = firstScreenRules + secondScreenRules;

expect(totalNumberOfRules).to.eql(expectedNumberOfRules);
});
});
cy.get(pageSelector(expectedNumberOfPages)).should('exist');
});
});

describe('Deleting prebuilt rules', () => {
describe('Actions with prebuilt rules', () => {
beforeEach(() => {
const expectedNumberOfRules = totalNumberOfPrebuiltRules;
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;
Expand All @@ -81,11 +80,30 @@ describe('Deleting prebuilt rules', () => {
waitForPrebuiltDetectionRulesToBeLoaded();

cy.get(ELASTIC_RULES_BTN).should('have.text', expectedElasticRulesBtnText);
});

it('Allows to activate/deactivate all rules at once', () => {
selectAllRules();
activateSelectedRules();
waitForRuleToChangeStatus();
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'true');

changeRowsPerPageTo300();
selectAllRules();
deactivateSelectedRules();
waitForRuleToChangeStatus();
cy.get(RULE_SWITCH).should('have.attr', 'aria-checked', 'false');
});

it('Allows to delete all rules at once', () => {
selectAllRules();
deleteSelectedRules();
confirmRulesDelete();
cy.get(RULES_EMPTY_PROMPT).should('be.visible');
});

it('Does not allow to delete one rule when more than one is selected', () => {
changeRowsPerPageTo100();

const numberOfRulesToBeSelected = 2;
selectNumberOfRules(numberOfRulesToBeSelected);

Expand All @@ -95,12 +113,14 @@ describe('Deleting prebuilt rules', () => {
});

it('Deletes and recovers one rule', () => {
changeRowsPerPageTo100();

const expectedNumberOfRulesAfterDeletion = totalNumberOfPrebuiltRules - 1;
const expectedNumberOfRulesAfterRecovering = totalNumberOfPrebuiltRules;

deleteFirstRule();
cy.reload();
changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(ELASTIC_RULES_BTN).should(
'have.text',
Expand All @@ -114,7 +134,7 @@ describe('Deleting prebuilt rules', () => {
cy.get(RELOAD_PREBUILT_RULES_BTN).should('not.exist');

cy.reload();
changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(ELASTIC_RULES_BTN).should(
'have.text',
Expand All @@ -123,14 +143,16 @@ describe('Deleting prebuilt rules', () => {
});

it('Deletes and recovers more than one rule', () => {
changeRowsPerPageTo100();

const numberOfRulesToBeSelected = 2;
const expectedNumberOfRulesAfterDeletion = totalNumberOfPrebuiltRules - 2;
const expectedNumberOfRulesAfterRecovering = totalNumberOfPrebuiltRules;

selectNumberOfRules(numberOfRulesToBeSelected);
deleteSelectedRules();
cy.reload();
changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(RELOAD_PREBUILT_RULES_BTN).should('exist');
cy.get(RELOAD_PREBUILT_RULES_BTN).should(
Expand All @@ -147,7 +169,7 @@ describe('Deleting prebuilt rules', () => {
cy.get(RELOAD_PREBUILT_RULES_BTN).should('not.exist');

cy.reload();
changeRowsPerPageTo300();
changeRowsPerPageTo100();

cy.get(ELASTIC_RULES_BTN).should(
'have.text',
Expand Down
Loading

0 comments on commit 16e66b8

Please sign in to comment.