mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Security Solution] Bulk editing rule custom highlighted fields (#179312)
**Resolves: https://github.com/elastic/kibana/issues/164301**
**Resolves: https://github.com/elastic/security-team/issues/8958**
## Summary
With these changes we introduce a new feature - Bulk custom highlighted
fields update. It works similarly to bulk tags and indices update.
Here is the overview of the work that has been done:
b1ba6670
-9984-43c9-9f1e-e18a2b7f071f
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] https://github.com/elastic/security-docs/issues/5090
- [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
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] [ESS 100
times](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5834)
- [ ] [Serverless 100
times](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5835)
---------
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Devin W. Hurley <snowmiser111@gmail.com>
This commit is contained in:
parent
887bb7d49b
commit
b4b316a720
31 changed files with 1457 additions and 94 deletions
|
@ -26,6 +26,7 @@ import {
|
|||
RuleActionAlertsFilter,
|
||||
IndexPatternArray,
|
||||
RuleTagArray,
|
||||
InvestigationFields,
|
||||
TimelineTemplateId,
|
||||
TimelineTemplateTitle,
|
||||
} from '../../model/rule_schema/common_attributes.gen';
|
||||
|
@ -52,6 +53,7 @@ export const BulkActionsDryRunErrCode = z.enum([
|
|||
'MACHINE_LEARNING_AUTH',
|
||||
'MACHINE_LEARNING_INDEX_PATTERN',
|
||||
'ESQL_INDEX_PATTERN',
|
||||
'INVESTIGATION_FIELDS_FEATURE',
|
||||
]);
|
||||
export type BulkActionsDryRunErrCodeEnum = typeof BulkActionsDryRunErrCode.enum;
|
||||
export const BulkActionsDryRunErrCodeEnum = BulkActionsDryRunErrCode.enum;
|
||||
|
@ -187,6 +189,9 @@ export const BulkActionEditType = z.enum([
|
|||
'add_rule_actions',
|
||||
'set_rule_actions',
|
||||
'set_schedule',
|
||||
'add_investigation_fields',
|
||||
'delete_investigation_fields',
|
||||
'set_investigation_fields',
|
||||
]);
|
||||
export type BulkActionEditTypeEnum = typeof BulkActionEditType.enum;
|
||||
export const BulkActionEditTypeEnum = BulkActionEditType.enum;
|
||||
|
@ -239,6 +244,18 @@ export const BulkActionEditPayloadTags = z.object({
|
|||
value: RuleTagArray,
|
||||
});
|
||||
|
||||
export type BulkActionEditPayloadInvestigationFields = z.infer<
|
||||
typeof BulkActionEditPayloadInvestigationFields
|
||||
>;
|
||||
export const BulkActionEditPayloadInvestigationFields = z.object({
|
||||
type: z.enum([
|
||||
'add_investigation_fields',
|
||||
'delete_investigation_fields',
|
||||
'set_investigation_fields',
|
||||
]),
|
||||
value: InvestigationFields,
|
||||
});
|
||||
|
||||
export type BulkActionEditPayloadTimeline = z.infer<typeof BulkActionEditPayloadTimeline>;
|
||||
export const BulkActionEditPayloadTimeline = z.object({
|
||||
type: z.literal('set_timeline'),
|
||||
|
@ -252,6 +269,7 @@ export type BulkActionEditPayload = z.infer<typeof BulkActionEditPayload>;
|
|||
export const BulkActionEditPayload = z.union([
|
||||
BulkActionEditPayloadTags,
|
||||
BulkActionEditPayloadIndexPatterns,
|
||||
BulkActionEditPayloadInvestigationFields,
|
||||
BulkActionEditPayloadTimeline,
|
||||
BulkActionEditPayloadRuleActions,
|
||||
BulkActionEditPayloadSchedule,
|
||||
|
|
|
@ -76,6 +76,7 @@ components:
|
|||
- MACHINE_LEARNING_AUTH
|
||||
- MACHINE_LEARNING_INDEX_PATTERN
|
||||
- ESQL_INDEX_PATTERN
|
||||
- INVESTIGATION_FIELDS_FEATURE
|
||||
|
||||
NormalizedRuleError:
|
||||
type: object
|
||||
|
@ -281,6 +282,9 @@ components:
|
|||
- add_rule_actions
|
||||
- set_rule_actions
|
||||
- set_schedule
|
||||
- add_investigation_fields
|
||||
- delete_investigation_fields
|
||||
- set_investigation_fields
|
||||
|
||||
# Per rulesClient.bulkEdit rules actions operation contract (x-pack/plugins/alerting/server/rules_client/rules_client.ts) normalized rule action object is expected (NormalizedAlertAction) as value for the edit operation
|
||||
NormalizedRuleAction:
|
||||
|
@ -381,6 +385,21 @@ components:
|
|||
- type
|
||||
- value
|
||||
|
||||
BulkActionEditPayloadInvestigationFields:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum:
|
||||
- add_investigation_fields
|
||||
- delete_investigation_fields
|
||||
- set_investigation_fields
|
||||
value:
|
||||
$ref: '../../model/rule_schema/common_attributes.schema.yaml#/components/schemas/InvestigationFields'
|
||||
required:
|
||||
- type
|
||||
- value
|
||||
|
||||
BulkActionEditPayloadTimeline:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -406,6 +425,7 @@ components:
|
|||
anyOf:
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadTags'
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadIndexPatterns'
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadInvestigationFields'
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadTimeline'
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadRuleActions'
|
||||
- $ref: '#/components/schemas/BulkActionEditPayloadSchedule'
|
||||
|
|
|
@ -187,7 +187,7 @@ describe('Perform bulk action request schema', () => {
|
|||
expectParseError(result);
|
||||
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 11 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -249,7 +249,7 @@ describe('Perform bulk action request schema', () => {
|
|||
expectParseError(result);
|
||||
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 11 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -299,6 +299,62 @@ describe('Perform bulk action request schema', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('investigation_fields', () => {
|
||||
test('valid request: set_investigation_fields edit action', () => {
|
||||
const payload: PerformBulkActionRequestBody = {
|
||||
query: 'name: test',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_investigation_fields,
|
||||
value: { field_names: ['field-1'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = PerformBulkActionRequestBody.safeParse(payload);
|
||||
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('valid request: add_investigation_fields edit action', () => {
|
||||
const payload: PerformBulkActionRequestBody = {
|
||||
query: 'name: test',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_investigation_fields,
|
||||
value: { field_names: ['field-2'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = PerformBulkActionRequestBody.safeParse(payload);
|
||||
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
|
||||
test('valid request: delete_investigation_fields edit action', () => {
|
||||
const payload: PerformBulkActionRequestBody = {
|
||||
query: 'name: test',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
value: { field_names: ['field-3'] },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const result = PerformBulkActionRequestBody.safeParse(payload);
|
||||
|
||||
expectParseSuccess(result);
|
||||
expect(result.data).toEqual(payload);
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeline', () => {
|
||||
test('invalid request: wrong timeline payload type', () => {
|
||||
const payload = {
|
||||
|
@ -311,7 +367,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -333,7 +389,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 12 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -371,7 +427,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -416,7 +472,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 12 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -438,7 +494,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 10 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 12 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -476,7 +532,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 7 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 9 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -498,7 +554,7 @@ describe('Perform bulk action request schema', () => {
|
|||
|
||||
expectParseError(result);
|
||||
expect(stringifyZodError(result.error)).toMatchInlineSnapshot(
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 11 more"`
|
||||
`"action: Invalid literal value, expected \\"delete\\", action: Invalid literal value, expected \\"disable\\", action: Invalid literal value, expected \\"enable\\", action: Invalid literal value, expected \\"export\\", action: Invalid literal value, expected \\"duplicate\\", and 13 more"`
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type {
|
||||
BulkActionEditPayloadIndexPatterns,
|
||||
BulkActionEditPayloadInvestigationFields,
|
||||
BulkActionEditPayloadRuleActions,
|
||||
BulkActionEditPayloadSchedule,
|
||||
BulkActionEditPayloadTags,
|
||||
|
@ -26,5 +27,6 @@ export type BulkActionEditForRuleAttributes =
|
|||
*/
|
||||
export type BulkActionEditForRuleParams =
|
||||
| BulkActionEditPayloadIndexPatterns
|
||||
| BulkActionEditPayloadInvestigationFields
|
||||
| BulkActionEditPayloadTimeline
|
||||
| BulkActionEditPayloadSchedule;
|
||||
|
|
|
@ -442,6 +442,7 @@ export enum BulkActionsDryRunErrCode {
|
|||
MACHINE_LEARNING_AUTH = 'MACHINE_LEARNING_AUTH',
|
||||
MACHINE_LEARNING_INDEX_PATTERN = 'MACHINE_LEARNING_INDEX_PATTERN',
|
||||
ESQL_INDEX_PATTERN = 'ESQL_INDEX_PATTERN',
|
||||
INVESTIGATION_FIELDS_FEATURE = 'INVESTIGATION_FIELDS_FEATURE',
|
||||
}
|
||||
|
||||
export const MAX_NUMBER_OF_NEW_TERMS_FIELDS = 3;
|
||||
|
|
|
@ -261,6 +261,11 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
* Enables the new modal for the value list items
|
||||
*/
|
||||
valueListItemsModalEnabled: true,
|
||||
|
||||
/**
|
||||
* Enables the new rule's bulk action to manage custom highlighted fields
|
||||
*/
|
||||
bulkCustomHighlightedFieldsEnabled: false,
|
||||
});
|
||||
|
||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||
|
|
|
@ -41,6 +41,11 @@ export enum TELEMETRY_EVENT {
|
|||
DELETE_VALUE_LIST_ITEM = 'delete_value_list_item',
|
||||
EDIT_VALUE_LIST_ITEM = 'edit_value_list_item',
|
||||
ADDITIONAL_UPLOAD_VALUE_LIST_ITEM = 'additinonal_upload_value_list_item',
|
||||
|
||||
// Bulk custom highlighted fields action
|
||||
ADD_INVESTIGATION_FIELDS = 'add_investigation_fields',
|
||||
SET_INVESTIGATION_FIELDS = 'set_investigation_fields',
|
||||
DELETE_INVESTIGATION_FIELDS = 'delete_investigation_fields',
|
||||
}
|
||||
|
||||
export enum TelemetryEventTypes {
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TagsForm } from './forms/tags_form';
|
|||
import { TimelineTemplateForm } from './forms/timeline_template_form';
|
||||
import { RuleActionsForm } from './forms/rule_actions_form';
|
||||
import { ScheduleForm } from './forms/schedule_form';
|
||||
import { InvestigationFieldsForm } from './forms/investigation_fields_form';
|
||||
|
||||
interface BulkEditFlyoutProps {
|
||||
onClose: () => void;
|
||||
|
@ -38,6 +39,11 @@ const BulkEditFlyoutComponent = ({ editAction, ...props }: BulkEditFlyoutProps)
|
|||
case BulkActionEditTypeEnum.set_tags:
|
||||
return <TagsForm {...props} editAction={editAction} />;
|
||||
|
||||
case BulkActionEditTypeEnum.add_investigation_fields:
|
||||
case BulkActionEditTypeEnum.delete_investigation_fields:
|
||||
case BulkActionEditTypeEnum.set_investigation_fields:
|
||||
return <InvestigationFieldsForm {...props} editAction={editAction} />;
|
||||
|
||||
case BulkActionEditTypeEnum.set_timeline:
|
||||
return <TimelineTemplateForm {...props} />;
|
||||
|
||||
|
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiFormRow, EuiCallOut } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
import { useKibana } from '../../../../../../common/lib/kibana';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../../../../../common/constants';
|
||||
import { METRIC_TYPE, TELEMETRY_EVENT, track } from '../../../../../../common/lib/telemetry';
|
||||
import * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations';
|
||||
|
||||
import { useFetchIndex } from '../../../../../../common/containers/source';
|
||||
|
||||
import { BulkActionEditTypeEnum } from '../../../../../../../common/api/detection_engine/rule_management';
|
||||
import type { BulkActionEditPayload } from '../../../../../../../common/api/detection_engine/rule_management';
|
||||
|
||||
import type { FormSchema } from '../../../../../../shared_imports';
|
||||
import {
|
||||
Field,
|
||||
getUseField,
|
||||
useFormData,
|
||||
useForm,
|
||||
FIELD_TYPES,
|
||||
fieldValidators,
|
||||
} from '../../../../../../shared_imports';
|
||||
|
||||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
|
||||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
type InvestigationFieldsEditActions =
|
||||
| BulkActionEditTypeEnum['add_investigation_fields']
|
||||
| BulkActionEditTypeEnum['delete_investigation_fields']
|
||||
| BulkActionEditTypeEnum['set_investigation_fields'];
|
||||
|
||||
interface InvestigationFieldsFormData {
|
||||
investigationFields: string[];
|
||||
overwrite: boolean;
|
||||
}
|
||||
|
||||
const schema: FormSchema<InvestigationFieldsFormData> = {
|
||||
investigationFields: {
|
||||
fieldsToValidateOnChange: ['investigationFields'],
|
||||
type: FIELD_TYPES.COMBO_BOX,
|
||||
validations: [
|
||||
{
|
||||
validator: fieldValidators.emptyField(
|
||||
i18n.BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_REQUIRED_ERROR
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
overwrite: {
|
||||
type: FIELD_TYPES.CHECKBOX,
|
||||
label: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_OVERWRITE_LABEL,
|
||||
},
|
||||
};
|
||||
|
||||
const initialFormData: InvestigationFieldsFormData = {
|
||||
investigationFields: [],
|
||||
overwrite: false,
|
||||
};
|
||||
|
||||
const getFormConfig = (editAction: InvestigationFieldsEditActions) =>
|
||||
editAction === BulkActionEditTypeEnum.add_investigation_fields
|
||||
? {
|
||||
indexLabel: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_LABEL,
|
||||
indexHelpText: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_HELP_TEXT,
|
||||
formTitle: i18n.BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_TITLE,
|
||||
}
|
||||
: {
|
||||
indexLabel: i18n.BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_LABEL,
|
||||
indexHelpText: i18n.BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_HELP_TEXT,
|
||||
formTitle: i18n.BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_TITLE,
|
||||
};
|
||||
|
||||
interface InvestigationFieldsFormProps {
|
||||
editAction: InvestigationFieldsEditActions;
|
||||
rulesCount: number;
|
||||
onClose: () => void;
|
||||
onConfirm: (bulkActionEditPayload: BulkActionEditPayload) => void;
|
||||
}
|
||||
|
||||
const InvestigationFieldsFormComponent = ({
|
||||
editAction,
|
||||
rulesCount,
|
||||
onClose,
|
||||
onConfirm,
|
||||
}: InvestigationFieldsFormProps) => {
|
||||
const { form } = useForm({
|
||||
defaultValue: initialFormData,
|
||||
schema,
|
||||
});
|
||||
|
||||
const { uiSettings } = useKibana().services;
|
||||
const defaultPatterns = uiSettings.get<string[]>(DEFAULT_INDEX_KEY);
|
||||
|
||||
const { indexHelpText, indexLabel, formTitle } = getFormConfig(editAction);
|
||||
|
||||
const [{ overwrite }] = useFormData({
|
||||
form,
|
||||
watch: ['overwrite'],
|
||||
});
|
||||
const [_, { indexPatterns }] = useFetchIndex(defaultPatterns, false);
|
||||
const fieldOptions = indexPatterns.fields.map((field) => ({
|
||||
label: field.name,
|
||||
}));
|
||||
|
||||
const handleSubmit = async () => {
|
||||
const { data, isValid } = await form.submit();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const event = data.overwrite
|
||||
? TELEMETRY_EVENT.SET_INVESTIGATION_FIELDS
|
||||
: editAction === 'delete_investigation_fields'
|
||||
? TELEMETRY_EVENT.DELETE_INVESTIGATION_FIELDS
|
||||
: TELEMETRY_EVENT.ADD_INVESTIGATION_FIELDS;
|
||||
track(METRIC_TYPE.CLICK, event);
|
||||
|
||||
onConfirm({
|
||||
value: { field_names: data.investigationFields },
|
||||
type: data.overwrite ? BulkActionEditTypeEnum.set_investigation_fields : editAction,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<BulkEditFormWrapper form={form} onClose={onClose} onSubmit={handleSubmit} title={formTitle}>
|
||||
<CommonUseField
|
||||
path="investigationFields"
|
||||
config={{ ...schema.investigationFields, label: indexLabel, helpText: indexHelpText }}
|
||||
componentProps={{
|
||||
idAria: 'bulkEditRulesInvestigationFields',
|
||||
'data-test-subj': 'bulkEditRulesInvestigationFields',
|
||||
euiFieldProps: {
|
||||
fullWidth: true,
|
||||
placeholder: '',
|
||||
noSuggestions: false,
|
||||
options: fieldOptions,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{editAction === BulkActionEditTypeEnum.add_investigation_fields && (
|
||||
<CommonUseField
|
||||
path="overwrite"
|
||||
componentProps={{
|
||||
idAria: 'bulkEditRulesOverwriteInvestigationFields',
|
||||
'data-test-subj': 'bulkEditRulesOverwriteInvestigationFields',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{overwrite && (
|
||||
<EuiFormRow fullWidth>
|
||||
<EuiCallOut
|
||||
color="warning"
|
||||
size="s"
|
||||
data-test-subj="bulkEditRulesInvestigationFieldsWarning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.setInvestigationFieldsWarningCallout"
|
||||
defaultMessage="You’re about to overwrite custom highlighted fields for {rulesCount, plural, one {# selected rule} other {# selected rules}}, press Save to
|
||||
apply changes."
|
||||
values={{ rulesCount }}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</BulkEditFormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const InvestigationFieldsForm = React.memo(InvestigationFieldsFormComponent);
|
||||
InvestigationFieldsForm.displayName = 'InvestigationFieldsForm';
|
|
@ -12,6 +12,7 @@ import type { Toast } from '@kbn/core/public';
|
|||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { useKibana } from '../../../../../common/lib/kibana';
|
||||
import { convertRulesFilterToKQL } from '../../../../../../common/detection_engine/rule_management/rule_filtering';
|
||||
import { DuplicateOptions } from '../../../../../../common/detection_engine/rule_management/constants';
|
||||
|
@ -82,6 +83,10 @@ export const useBulkActions = ({
|
|||
actions: { clearRulesSelection, setIsPreflightInProgress },
|
||||
} = rulesTableContext;
|
||||
|
||||
const isBulkCustomHighlightedFieldsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'bulkCustomHighlightedFieldsEnabled'
|
||||
);
|
||||
|
||||
const getBulkItemsPopoverContent = useCallback(
|
||||
(closePopover: () => void): EuiContextMenuPanelDescriptor[] => {
|
||||
const selectedRules = rules.filter(({ id }) => selectedRuleIds.includes(id));
|
||||
|
@ -331,6 +336,17 @@ export const useBulkActions = ({
|
|||
disabled: isEditDisabled,
|
||||
panel: 1,
|
||||
},
|
||||
...(isBulkCustomHighlightedFieldsEnabled
|
||||
? [
|
||||
{
|
||||
key: i18n.BULK_ACTION_INVESTIGATION_FIELDS,
|
||||
name: i18n.BULK_ACTION_INVESTIGATION_FIELDS,
|
||||
'data-test-subj': 'investigationFieldsBulkEditRule',
|
||||
disabled: isEditDisabled,
|
||||
panel: 3,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: i18n.BULK_ACTION_ADD_RULE_ACTIONS,
|
||||
name: i18n.BULK_ACTION_ADD_RULE_ACTIONS,
|
||||
|
@ -461,6 +477,34 @@ export const useBulkActions = ({
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: i18n.BULK_ACTION_MENU_TITLE,
|
||||
items: [
|
||||
{
|
||||
key: i18n.BULK_ACTION_ADD_INVESTIGATION_FIELDS,
|
||||
name: i18n.BULK_ACTION_ADD_INVESTIGATION_FIELDS,
|
||||
'data-test-subj': 'addInvestigationFieldsBulkEditRule',
|
||||
onClick: handleBulkEdit(BulkActionEditTypeEnum.add_investigation_fields),
|
||||
disabled: isEditDisabled,
|
||||
toolTipContent: missingActionPrivileges
|
||||
? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES
|
||||
: undefined,
|
||||
toolTipProps: { position: 'right' },
|
||||
},
|
||||
{
|
||||
key: i18n.BULK_ACTION_DELETE_INVESTIGATION_FIELDS,
|
||||
name: i18n.BULK_ACTION_DELETE_INVESTIGATION_FIELDS,
|
||||
'data-test-subj': 'deleteInvestigationFieldsBulkEditRule',
|
||||
onClick: handleBulkEdit(BulkActionEditTypeEnum.delete_investigation_fields),
|
||||
disabled: isEditDisabled,
|
||||
toolTipContent: missingActionPrivileges
|
||||
? i18n.LACK_OF_KIBANA_ACTIONS_FEATURE_PRIVILEGES
|
||||
: undefined,
|
||||
toolTipProps: { position: 'right' },
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
[
|
||||
|
@ -468,6 +512,7 @@ export const useBulkActions = ({
|
|||
selectedRuleIds,
|
||||
hasActionsPrivileges,
|
||||
isAllSelected,
|
||||
isBulkCustomHighlightedFieldsEnabled,
|
||||
loadingRuleIds,
|
||||
startTransaction,
|
||||
hasMlPermissions,
|
||||
|
|
|
@ -12,6 +12,9 @@ import { computeDryRunEditPayload } from './compute_dry_run_edit_payload';
|
|||
|
||||
describe('computeDryRunEditPayload', () => {
|
||||
test.each<[BulkActionEditType, unknown]>([
|
||||
[BulkActionEditTypeEnum.set_investigation_fields, { field_names: ['@timestamp'] }],
|
||||
[BulkActionEditTypeEnum.delete_investigation_fields, { field_names: ['@timestamp'] }],
|
||||
[BulkActionEditTypeEnum.add_investigation_fields, { field_names: ['@timestamp'] }],
|
||||
[BulkActionEditTypeEnum.set_index_patterns, []],
|
||||
[BulkActionEditTypeEnum.delete_index_patterns, []],
|
||||
[BulkActionEditTypeEnum.add_index_patterns, []],
|
||||
|
|
|
@ -20,6 +20,16 @@ import { assertUnreachable } from '../../../../../../../common/utility_types';
|
|||
*/
|
||||
export function computeDryRunEditPayload(editAction: BulkActionEditType): BulkActionEditPayload[] {
|
||||
switch (editAction) {
|
||||
case BulkActionEditTypeEnum.add_investigation_fields:
|
||||
case BulkActionEditTypeEnum.delete_investigation_fields:
|
||||
case BulkActionEditTypeEnum.set_investigation_fields:
|
||||
return [
|
||||
{
|
||||
type: editAction,
|
||||
value: { field_names: ['@timestamp'] },
|
||||
},
|
||||
];
|
||||
|
||||
case BulkActionEditTypeEnum.add_index_patterns:
|
||||
case BulkActionEditTypeEnum.delete_index_patterns:
|
||||
case BulkActionEditTypeEnum.set_index_patterns:
|
||||
|
|
|
@ -159,6 +159,27 @@ export const BULK_ACTION_DELETE_TAGS = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_INVESTIGATION_FIELDS = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.investigationFieldsTitle',
|
||||
{
|
||||
defaultMessage: 'Custom highlighted fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_ADD_INVESTIGATION_FIELDS = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.addInvestigationFieldsTitle',
|
||||
{
|
||||
defaultMessage: 'Add custom highlighted fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_DELETE_INVESTIGATION_FIELDS = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.deleteInvestigationFieldsTitle',
|
||||
{
|
||||
defaultMessage: 'Delete custom highlighted fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_APPLY_TIMELINE_TEMPLATE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.applyTimelineTemplateTitle',
|
||||
{
|
||||
|
@ -408,6 +429,64 @@ export const BULK_EDIT_FLYOUT_FORM_DELETE_TAGS_TITLE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_REQUIRED_ERROR = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.investigationFieldsRequiredErrorMessage',
|
||||
{
|
||||
defaultMessage: 'A minimum of one custom highlighted field is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_OVERWRITE_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsOverwriteCheckboxLabel',
|
||||
{
|
||||
defaultMessage: "Overwrite all selected rules' custom highlighted fields",
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsComboboxLabel',
|
||||
{
|
||||
defaultMessage: 'Add custom highlighted fields for selected rules',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_HELP_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsComboboxHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enter fields that you would like to add. By default, the dropdown includes fields of the index patterns defined in Security Solution advanced settings.',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_ADD_INVESTIGATION_FIELDS_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.addInvestigationFieldsTitle',
|
||||
{
|
||||
defaultMessage: 'Add custom highlighted fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteInvestigationFieldsComboboxLabel',
|
||||
{
|
||||
defaultMessage: 'Delete custom highlighted fields for selected rules',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_HELP_TEXT = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteInvestigationFieldsComboboxHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Enter fields that you would like to delete. By default, the dropdown includes fields of the index patterns defined in Security Solution advanced settings.',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_EDIT_FLYOUT_FORM_DELETE_INVESTIGATION_FIELDS_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.components.allRules.bulkActions.bulkEditFlyoutForm.deleteInvestigationFieldsTitle',
|
||||
{
|
||||
defaultMessage: 'Delete custom highlighted fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const EXPORT_FILENAME = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.exportFilenameTitle',
|
||||
{
|
||||
|
|
|
@ -47,7 +47,7 @@ export const registerRuleManagementRoutes = (
|
|||
bulkDeleteRulesRoute(router, logger);
|
||||
|
||||
// Rules bulk actions
|
||||
performBulkActionRoute(router, ml, logger);
|
||||
performBulkActionRoute(router, config, ml, logger);
|
||||
|
||||
// Rules export/import
|
||||
exportRulesRoute(router, config, logger);
|
||||
|
|
|
@ -18,7 +18,12 @@ import {
|
|||
getFindResultWithSingleHit,
|
||||
getFindResultWithMultiHits,
|
||||
} from '../../../../routes/__mocks__/request_responses';
|
||||
import { requestContextMock, serverMock, requestMock } from '../../../../routes/__mocks__';
|
||||
import {
|
||||
createMockConfig,
|
||||
requestContextMock,
|
||||
serverMock,
|
||||
requestMock,
|
||||
} from '../../../../routes/__mocks__';
|
||||
import { performBulkActionRoute } from './route';
|
||||
import {
|
||||
getPerformBulkActionEditSchemaMock,
|
||||
|
@ -32,6 +37,7 @@ jest.mock('../../../logic/crud/read_rules', () => ({ readRules: jest.fn() }));
|
|||
|
||||
describe('Perform bulk action route', () => {
|
||||
const readRulesMock = readRules as jest.Mock;
|
||||
let config: ReturnType<typeof createMockConfig>;
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
let ml: ReturnType<typeof mlServicesMock.createSetupContract>;
|
||||
|
@ -42,6 +48,7 @@ describe('Perform bulk action route', () => {
|
|||
server = serverMock.create();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
config = createMockConfig();
|
||||
ml = mlServicesMock.createSetupContract();
|
||||
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
||||
|
@ -50,7 +57,7 @@ describe('Perform bulk action route', () => {
|
|||
errors: [],
|
||||
total: 1,
|
||||
});
|
||||
performBulkActionRoute(server.router, ml, logger);
|
||||
performBulkActionRoute(server.router, config, ml, logger);
|
||||
});
|
||||
|
||||
describe('status codes', () => {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { IKibanaResponse, Logger } from '@kbn/core/server';
|
||||
import { AbortError } from '@kbn/kibana-utils-plugin/common';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { ConfigType } from '../../../../../../config';
|
||||
import type { PerformBulkActionResponse } from '../../../../../../../common/api/detection_engine/rule_management';
|
||||
import {
|
||||
BulkActionTypeEnum,
|
||||
|
@ -47,6 +48,7 @@ const MAX_ROUTE_CONCURRENCY = 5;
|
|||
|
||||
export const performBulkActionRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
config: ConfigType,
|
||||
ml: SetupPlugins['ml'],
|
||||
logger: Logger
|
||||
) => {
|
||||
|
@ -143,6 +145,7 @@ export const performBulkActionRoute = (
|
|||
ids: body.ids,
|
||||
actions: body.edit,
|
||||
mlAuthz,
|
||||
experimentalFeatures: config.experimentalFeatures,
|
||||
});
|
||||
|
||||
return buildBulkResponse(response, {
|
||||
|
@ -303,7 +306,12 @@ export const performBulkActionRoute = (
|
|||
concurrency: MAX_RULES_TO_UPDATE_IN_PARALLEL,
|
||||
items: rules,
|
||||
executor: async (rule) => {
|
||||
await dryRunValidateBulkEditRule({ mlAuthz, rule, edit: body.edit });
|
||||
await dryRunValidateBulkEditRule({
|
||||
mlAuthz,
|
||||
rule,
|
||||
edit: body.edit,
|
||||
experimentalFeatures: config.experimentalFeatures,
|
||||
});
|
||||
|
||||
return rule;
|
||||
},
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import type { BulkActionEditPayload } from '../../../../../../common/api/detection_engine/rule_management';
|
||||
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
|
@ -25,6 +26,7 @@ export interface BulkEditRulesArguments {
|
|||
filter?: string;
|
||||
ids?: string[];
|
||||
mlAuthz: MlAuthz;
|
||||
experimentalFeatures: ExperimentalFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,6 +42,7 @@ export const bulkEditRules = async ({
|
|||
actions,
|
||||
filter,
|
||||
mlAuthz,
|
||||
experimentalFeatures,
|
||||
}: BulkEditRulesArguments) => {
|
||||
const { attributesActions, paramsActions } = splitBulkEditActions(actions);
|
||||
const operations = attributesActions.map(bulkEditActionToRulesClientOperation).flat();
|
||||
|
@ -53,7 +56,7 @@ export const bulkEditRules = async ({
|
|||
edit: actions,
|
||||
immutable: ruleParams.immutable,
|
||||
});
|
||||
return ruleParamsModifier(ruleParams, paramsActions);
|
||||
return ruleParamsModifier(ruleParams, paramsActions, experimentalFeatures);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
import { addItemsToArray, deleteItemsFromArray, ruleParamsModifier } from './rule_params_modifier';
|
||||
import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
|
||||
const mockExperimentalFeatures = {
|
||||
bulkCustomHighlightedFieldsEnabled: true,
|
||||
} as ExperimentalFeatures;
|
||||
|
||||
describe('addItemsToArray', () => {
|
||||
test('should add single item to array', () => {
|
||||
|
@ -45,22 +50,30 @@ describe('ruleParamsModifier', () => {
|
|||
} as RuleAlertType['params'];
|
||||
|
||||
test('should increment version if rule is custom (immutable === false)', () => {
|
||||
const { modifiedParams } = ruleParamsModifier(ruleParamsMock, [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
]);
|
||||
const { modifiedParams } = ruleParamsModifier(
|
||||
ruleParamsMock,
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('version', ruleParamsMock.version + 1);
|
||||
});
|
||||
|
||||
test('should not increment version if rule is prebuilt (immutable === true)', () => {
|
||||
const { modifiedParams } = ruleParamsModifier({ ...ruleParamsMock, immutable: true }, [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
]);
|
||||
const { modifiedParams } = ruleParamsModifier(
|
||||
{ ...ruleParamsMock, immutable: true },
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('version', ruleParamsMock.version);
|
||||
});
|
||||
|
||||
|
@ -133,7 +146,8 @@ describe('ruleParamsModifier', () => {
|
|||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: indexPatternsToAdd,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
|
@ -197,7 +211,8 @@ describe('ruleParamsModifier', () => {
|
|||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: indexPatternsToDelete,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
|
@ -252,7 +267,8 @@ describe('ruleParamsModifier', () => {
|
|||
type: BulkActionEditTypeEnum.set_index_patterns,
|
||||
value: indexPatternsToOverwrite,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('index', resultingIndexPatterns);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
|
@ -270,7 +286,8 @@ describe('ruleParamsModifier', () => {
|
|||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: ['index-2-*'],
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).not.toHaveProperty('index');
|
||||
expect(isParamsUpdateSkipped).toBe(true);
|
||||
|
@ -285,7 +302,8 @@ describe('ruleParamsModifier', () => {
|
|||
value: ['index'],
|
||||
overwrite_data_views: true,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('dataViewId', undefined);
|
||||
expect(isParamsUpdateSkipped).toBe(false);
|
||||
|
@ -300,7 +318,8 @@ describe('ruleParamsModifier', () => {
|
|||
value: ['index'],
|
||||
overwrite_data_views: true,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('dataViewId', undefined);
|
||||
expect(isParamsUpdateSkipped).toBe(false);
|
||||
|
@ -315,7 +334,8 @@ describe('ruleParamsModifier', () => {
|
|||
value: ['index'],
|
||||
overwrite_data_views: true,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('dataViewId', undefined);
|
||||
expect(modifiedParams).toHaveProperty('index', ['test-*']);
|
||||
|
@ -331,7 +351,8 @@ describe('ruleParamsModifier', () => {
|
|||
value: ['index'],
|
||||
overwrite_data_views: true,
|
||||
},
|
||||
]
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty('dataViewId', undefined);
|
||||
expect(modifiedParams).toHaveProperty('index', undefined);
|
||||
|
@ -340,12 +361,16 @@ describe('ruleParamsModifier', () => {
|
|||
|
||||
test('should throw error on adding index pattern if rule is of machine learning type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'machine_learning' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow(
|
||||
"Index patterns can't be added. Machine learning rule doesn't have index patterns property"
|
||||
);
|
||||
|
@ -353,12 +378,16 @@ describe('ruleParamsModifier', () => {
|
|||
|
||||
test('should throw error on deleting index pattern if rule is of machine learning type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'machine_learning' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow(
|
||||
"Index patterns can't be deleted. Machine learning rule doesn't have index patterns property"
|
||||
);
|
||||
|
@ -366,12 +395,16 @@ describe('ruleParamsModifier', () => {
|
|||
|
||||
test('should throw error on overwriting index pattern if rule is of machine learning type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'machine_learning' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'machine_learning' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow(
|
||||
"Index patterns can't be overwritten. Machine learning rule doesn't have index patterns property"
|
||||
);
|
||||
|
@ -379,51 +412,404 @@ describe('ruleParamsModifier', () => {
|
|||
|
||||
test('should throw error on adding index pattern if rule is of ES|QL type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'esql' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow("Index patterns can't be added. ES|QL rule doesn't have index patterns property");
|
||||
});
|
||||
|
||||
test('should throw error on deleting index pattern if rule is of ES|QL type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'esql' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow("Index patterns can't be deleted. ES|QL rule doesn't have index patterns property");
|
||||
});
|
||||
|
||||
test('should throw error on overwriting index pattern if rule is of ES|QL type', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier({ type: 'esql' } as RuleAlertType['params'], [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
])
|
||||
ruleParamsModifier(
|
||||
{ type: 'esql' } as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_index_patterns,
|
||||
value: ['my-index-*'],
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
)
|
||||
).toThrow(
|
||||
"Index patterns can't be overwritten. ES|QL rule doesn't have index patterns property"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('investigation_fields', () => {
|
||||
describe('add_investigation_fields action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing investigation fields + 2 of them = 3 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToAdd: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields + 2 other investigation fields (none of them) = 5 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToAdd: { field_names: ['field-4', 'field-5'] },
|
||||
resultingInvestigationFields: {
|
||||
field_names: ['field-1', 'field-2', 'field-3', 'field-4', 'field-5'],
|
||||
},
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields + 1 of them + 2 other investigation fields (none of them) = 5 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToAdd: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
resultingInvestigationFields: {
|
||||
field_names: ['field-1', 'field-2', 'field-3', 'field-4', 'field-5'],
|
||||
},
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields + 0 investigation fields = 3 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToAdd: { field_names: [] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
'`undefined` existing investigation fields + 1 investigation field = 1 investigation field',
|
||||
{
|
||||
existingInvestigationFields: undefined,
|
||||
investigationFieldsToAdd: { field_names: ['field-1'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'`undefined` existing investigation fields + 1 investigation field = 1 investigation field',
|
||||
{
|
||||
existingInvestigationFields: undefined,
|
||||
investigationFieldsToAdd: { field_names: ['field-1'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing `legacy` investigation fields + 2 other investigation fields (none of them) = 5 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
investigationFieldsToAdd: { field_names: ['field-4', 'field-5'] },
|
||||
resultingInvestigationFields: {
|
||||
field_names: ['field-1', 'field-2', 'field-3', 'field-4', 'field-5'],
|
||||
},
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should add investigation fields to rule, case:"%s"',
|
||||
(
|
||||
caseName,
|
||||
{
|
||||
existingInvestigationFields,
|
||||
investigationFieldsToAdd,
|
||||
resultingInvestigationFields,
|
||||
isParamsUpdateSkipped,
|
||||
}
|
||||
) => {
|
||||
const { modifiedParams, isParamsUpdateSkipped: isUpdateSkipped } = ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: existingInvestigationFields,
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_investigation_fields,
|
||||
value: investigationFieldsToAdd,
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty(
|
||||
'investigationFields',
|
||||
resultingInvestigationFields
|
||||
);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('delete_investigation_fields action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing investigation fields - 2 of them = 1 investigation field',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToDelete: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields - 2 other investigation fields (none of them) = 3 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToDelete: { field_names: ['field-4', 'field-5'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields - 1 of them - 2 other investigation fields (none of them) = 2 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToDelete: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields - 0 investigation fields = 3 investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToDelete: { field_names: [] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
'`undefined` existing investigation fields - 2 of them = `undeinfed` investigation fields',
|
||||
{
|
||||
existingInvestigationFields: undefined,
|
||||
investigationFieldsToDelete: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: undefined,
|
||||
isParamsUpdateSkipped: true,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing `legacy` investigation fields - 2 of them = 1 investigation field',
|
||||
{
|
||||
existingInvestigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
investigationFieldsToDelete: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should delete investigation fields from rule, case:"%s"',
|
||||
(
|
||||
caseName,
|
||||
{
|
||||
existingInvestigationFields,
|
||||
investigationFieldsToDelete,
|
||||
resultingInvestigationFields,
|
||||
isParamsUpdateSkipped,
|
||||
}
|
||||
) => {
|
||||
const { modifiedParams, isParamsUpdateSkipped: isUpdateSkipped } = ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: existingInvestigationFields,
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
value: investigationFieldsToDelete,
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty(
|
||||
'investigationFields',
|
||||
resultingInvestigationFields
|
||||
);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('set_investigation_fields action', () => {
|
||||
test.each([
|
||||
[
|
||||
'3 existing investigation fields overwritten with 2 of them = 2 existing investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToOverwrite: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: { field_names: ['field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields overwritten with 2 other investigation fields = 2 other investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToOverwrite: { field_names: ['field-4', 'field-5'] },
|
||||
resultingInvestigationFields: { field_names: ['field-4', 'field-5'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing investigation fields overwritten with 1 of them + 2 other investigation fields = 1 existing investigation field + 2 other investigation fields',
|
||||
{
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToOverwrite: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
resultingInvestigationFields: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'`undefined` existing investigation fields overwritten with 2 of them = 2 existing investigation fields',
|
||||
{
|
||||
existingInvestigationFields: undefined,
|
||||
investigationFieldsToOverwrite: { field_names: ['field-2', 'field-3'] },
|
||||
resultingInvestigationFields: { field_names: ['field-2', 'field-3'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'3 existing `legacy` investigation fields overwritten with 1 of them + 2 other investigation fields = 1 existing investigation field + 2 other investigation fields',
|
||||
{
|
||||
existingInvestigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
investigationFieldsToOverwrite: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
resultingInvestigationFields: { field_names: ['field-3', 'field-4', 'field-5'] },
|
||||
isParamsUpdateSkipped: false,
|
||||
},
|
||||
],
|
||||
])(
|
||||
'should overwrite investigation fields in rule, case:"%s"',
|
||||
(
|
||||
caseName,
|
||||
{
|
||||
existingInvestigationFields,
|
||||
investigationFieldsToOverwrite,
|
||||
resultingInvestigationFields,
|
||||
isParamsUpdateSkipped,
|
||||
}
|
||||
) => {
|
||||
const { modifiedParams, isParamsUpdateSkipped: isUpdateSkipped } = ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: existingInvestigationFields,
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_investigation_fields,
|
||||
value: investigationFieldsToOverwrite,
|
||||
},
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
expect(modifiedParams).toHaveProperty(
|
||||
'investigationFields',
|
||||
resultingInvestigationFields
|
||||
);
|
||||
expect(isParamsUpdateSkipped).toBe(isUpdateSkipped);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
describe('feature flag disabled state', () => {
|
||||
test('should throw error on adding investigation fields if feature is disabled', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_investigation_fields,
|
||||
value: { field_names: ['field-4'] },
|
||||
},
|
||||
],
|
||||
{
|
||||
bulkCustomHighlightedFieldsEnabled: false,
|
||||
} as ExperimentalFeatures
|
||||
)
|
||||
).toThrow("Custom highlighted fields can't be added. Feature is disabled.");
|
||||
});
|
||||
|
||||
test('should throw error on overwriting investigation fields if feature is disabled', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_investigation_fields,
|
||||
value: { field_names: ['field-4'] },
|
||||
},
|
||||
],
|
||||
{
|
||||
bulkCustomHighlightedFieldsEnabled: false,
|
||||
} as ExperimentalFeatures
|
||||
)
|
||||
).toThrow("Custom highlighted fields can't be overwritten. Feature is disabled.");
|
||||
});
|
||||
|
||||
test('should throw error on deleting investigation fields if feature is disabled', () => {
|
||||
expect(() =>
|
||||
ruleParamsModifier(
|
||||
{
|
||||
...ruleParamsMock,
|
||||
investigationFields: ['field-1', 'field-2', 'field-3'],
|
||||
} as RuleAlertType['params'],
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
value: { field_names: ['field-1'] },
|
||||
},
|
||||
],
|
||||
{
|
||||
bulkCustomHighlightedFieldsEnabled: false,
|
||||
} as ExperimentalFeatures
|
||||
)
|
||||
).toThrow("Custom highlighted fields can't be deleted. Feature is disabled.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('timeline', () => {
|
||||
test('should set timeline', () => {
|
||||
const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_timeline,
|
||||
value: {
|
||||
timeline_id: '91832785-286d-4ebe-b884-1a208d111a70',
|
||||
timeline_title: 'Test timeline',
|
||||
const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(
|
||||
ruleParamsMock,
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_timeline,
|
||||
value: {
|
||||
timeline_id: '91832785-286d-4ebe-b884-1a208d111a70',
|
||||
timeline_title: 'Test timeline',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
|
||||
expect(modifiedParams.timelineId).toBe('91832785-286d-4ebe-b884-1a208d111a70');
|
||||
expect(modifiedParams.timelineTitle).toBe('Test timeline');
|
||||
|
@ -436,15 +822,19 @@ describe('ruleParamsModifier', () => {
|
|||
const INTERVAL_IN_MINUTES = 5;
|
||||
const LOOKBACK_IN_MINUTES = 1;
|
||||
const FROM_IN_SECONDS = (INTERVAL_IN_MINUTES + LOOKBACK_IN_MINUTES) * 60;
|
||||
const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(ruleParamsMock, [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_schedule,
|
||||
value: {
|
||||
interval: `${INTERVAL_IN_MINUTES}m`,
|
||||
lookback: `${LOOKBACK_IN_MINUTES}m`,
|
||||
const { modifiedParams, isParamsUpdateSkipped } = ruleParamsModifier(
|
||||
ruleParamsMock,
|
||||
[
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_schedule,
|
||||
value: {
|
||||
interval: `${INTERVAL_IN_MINUTES}m`,
|
||||
lookback: `${LOOKBACK_IN_MINUTES}m`,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
],
|
||||
mockExperimentalFeatures
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
expect((modifiedParams as any).interval).toBeUndefined();
|
||||
|
|
|
@ -8,10 +8,12 @@
|
|||
import moment from 'moment';
|
||||
import { parseInterval } from '@kbn/data-plugin/common/search/aggs/utils/date_interval_utils';
|
||||
import type { RuleParamsModifierResult } from '@kbn/alerting-plugin/server/rules_client/methods/bulk_edit';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import type { InvestigationFieldsCombined, RuleAlertType } from '../../../rule_schema';
|
||||
import type {
|
||||
BulkActionEditForRuleParams,
|
||||
BulkActionEditPayloadIndexPatterns,
|
||||
BulkActionEditPayloadInvestigationFields,
|
||||
} from '../../../../../../common/api/detection_engine/rule_management';
|
||||
import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management';
|
||||
import { invariant } from '../../../../../../common/utils/invariant';
|
||||
|
@ -63,9 +65,52 @@ const shouldSkipIndexPatternsBulkAction = (
|
|||
return false;
|
||||
};
|
||||
|
||||
// Check if the investigation fields added to the rule already exist in it
|
||||
const hasInvestigationFields = (
|
||||
investigationFields: InvestigationFieldsCombined | undefined,
|
||||
action: BulkActionEditPayloadInvestigationFields
|
||||
) =>
|
||||
action.value.field_names.every((field) =>
|
||||
(Array.isArray(investigationFields)
|
||||
? investigationFields
|
||||
: investigationFields?.field_names ?? []
|
||||
).includes(field)
|
||||
);
|
||||
|
||||
// Check if the investigation fields to be deleted don't exist in the rule
|
||||
const hasNoInvestigationFields = (
|
||||
investigationFields: InvestigationFieldsCombined | undefined,
|
||||
action: BulkActionEditPayloadInvestigationFields
|
||||
) =>
|
||||
action.value.field_names.every(
|
||||
(field) =>
|
||||
!(
|
||||
Array.isArray(investigationFields)
|
||||
? investigationFields
|
||||
: investigationFields?.field_names ?? []
|
||||
).includes(field)
|
||||
);
|
||||
|
||||
const shouldSkipInvestigationFieldsBulkAction = (
|
||||
investigationFields: InvestigationFieldsCombined | undefined,
|
||||
action: BulkActionEditPayloadInvestigationFields
|
||||
) => {
|
||||
if (action.type === BulkActionEditTypeEnum.add_investigation_fields) {
|
||||
return hasInvestigationFields(investigationFields, action);
|
||||
}
|
||||
|
||||
if (action.type === BulkActionEditTypeEnum.delete_investigation_fields) {
|
||||
return hasNoInvestigationFields(investigationFields, action);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
const applyBulkActionEditToRuleParams = (
|
||||
existingRuleParams: RuleAlertType['params'],
|
||||
action: BulkActionEditForRuleParams
|
||||
action: BulkActionEditForRuleParams,
|
||||
experimentalFeatures: ExperimentalFeatures
|
||||
): {
|
||||
ruleParams: RuleAlertType['params'];
|
||||
isActionSkipped: boolean;
|
||||
|
@ -151,6 +196,69 @@ const applyBulkActionEditToRuleParams = (
|
|||
ruleParams.index = action.value;
|
||||
break;
|
||||
}
|
||||
// investigation_fields actions
|
||||
case BulkActionEditTypeEnum.add_investigation_fields: {
|
||||
invariant(
|
||||
experimentalFeatures.bulkCustomHighlightedFieldsEnabled,
|
||||
"Custom highlighted fields can't be added. Feature is disabled."
|
||||
);
|
||||
|
||||
if (shouldSkipInvestigationFieldsBulkAction(ruleParams.investigationFields, action)) {
|
||||
isActionSkipped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ruleParams.investigationFields = {
|
||||
field_names: addItemsToArray(
|
||||
(Array.isArray(ruleParams.investigationFields)
|
||||
? ruleParams.investigationFields
|
||||
: ruleParams.investigationFields?.field_names) ?? [],
|
||||
action.value.field_names
|
||||
),
|
||||
};
|
||||
break;
|
||||
}
|
||||
case BulkActionEditTypeEnum.delete_investigation_fields: {
|
||||
invariant(
|
||||
experimentalFeatures.bulkCustomHighlightedFieldsEnabled,
|
||||
"Custom highlighted fields can't be deleted. Feature is disabled."
|
||||
);
|
||||
|
||||
if (shouldSkipInvestigationFieldsBulkAction(ruleParams.investigationFields, action)) {
|
||||
isActionSkipped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ruleParams.investigationFields) {
|
||||
const fieldNames = deleteItemsFromArray(
|
||||
(Array.isArray(ruleParams.investigationFields)
|
||||
? ruleParams.investigationFields
|
||||
: ruleParams.investigationFields?.field_names) ?? [],
|
||||
action.value.field_names
|
||||
);
|
||||
ruleParams.investigationFields =
|
||||
fieldNames.length > 0
|
||||
? {
|
||||
field_names: fieldNames,
|
||||
}
|
||||
: undefined;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BulkActionEditTypeEnum.set_investigation_fields: {
|
||||
invariant(
|
||||
experimentalFeatures.bulkCustomHighlightedFieldsEnabled,
|
||||
"Custom highlighted fields can't be overwritten. Feature is disabled."
|
||||
);
|
||||
|
||||
if (shouldSkipInvestigationFieldsBulkAction(ruleParams.investigationFields, action)) {
|
||||
isActionSkipped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ruleParams.investigationFields = action.value;
|
||||
break;
|
||||
}
|
||||
// timeline actions
|
||||
case BulkActionEditTypeEnum.set_timeline: {
|
||||
ruleParams = {
|
||||
|
@ -192,12 +300,17 @@ const applyBulkActionEditToRuleParams = (
|
|||
*/
|
||||
export const ruleParamsModifier = (
|
||||
existingRuleParams: RuleAlertType['params'],
|
||||
actions: BulkActionEditForRuleParams[]
|
||||
actions: BulkActionEditForRuleParams[],
|
||||
experimentalFeatures: ExperimentalFeatures
|
||||
): RuleParamsModifierResult<RuleAlertType['params']> => {
|
||||
let isParamsUpdateSkipped = true;
|
||||
|
||||
const modifiedParams = actions.reduce((acc, action) => {
|
||||
const { ruleParams, isActionSkipped } = applyBulkActionEditToRuleParams(acc, action);
|
||||
const { ruleParams, isActionSkipped } = applyBulkActionEditToRuleParams(
|
||||
acc,
|
||||
action,
|
||||
experimentalFeatures
|
||||
);
|
||||
|
||||
// The rule was updated with at least one action, so mark our rule as updated
|
||||
if (!isActionSkipped) {
|
||||
|
|
|
@ -21,3 +21,18 @@ export const isIndexPatternsBulkEditAction = (editAction: BulkActionEditType) =>
|
|||
];
|
||||
return indexPatternsActions.includes(editAction);
|
||||
};
|
||||
|
||||
/**
|
||||
* helper utility that defines whether bulk edit action is related to investigation fields, i.e. one of:
|
||||
* 'add_investigation_fields', 'delete_investigation_fields', 'set_investigation_fields'
|
||||
* @param editAction {@link BulkActionEditType}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export const isInvestigationFieldsBulkEditAction = (editAction: BulkActionEditType) => {
|
||||
const investigationFieldsActions: BulkActionEditType[] = [
|
||||
BulkActionEditTypeEnum.add_investigation_fields,
|
||||
BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
BulkActionEditTypeEnum.set_investigation_fields,
|
||||
];
|
||||
return investigationFieldsActions.includes(editAction);
|
||||
};
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import type { Type as RuleType } from '@kbn/securitysolution-io-ts-alerting-types';
|
||||
import type { ExperimentalFeatures } from '../../../../../../common';
|
||||
import { invariant } from '../../../../../../common/utils/invariant';
|
||||
import { isMlRule } from '../../../../../../common/machine_learning/helpers';
|
||||
import { isEsqlRule } from '../../../../../../common/detection_engine/utils';
|
||||
|
@ -16,7 +17,7 @@ import type {
|
|||
} from '../../../../../../common/api/detection_engine/rule_management';
|
||||
import { BulkActionEditTypeEnum } from '../../../../../../common/api/detection_engine/rule_management';
|
||||
import type { RuleAlertType } from '../../../rule_schema';
|
||||
import { isIndexPatternsBulkEditAction } from './utils';
|
||||
import { isIndexPatternsBulkEditAction, isInvestigationFieldsBulkEditAction } from './utils';
|
||||
import { throwDryRunError } from './dry_run';
|
||||
import type { MlAuthz } from '../../../../machine_learning/authz';
|
||||
import { throwAuthzError } from '../../../../machine_learning/validation';
|
||||
|
@ -37,6 +38,7 @@ interface DryRunBulkEditBulkActionsValidationArgs {
|
|||
rule: RuleAlertType;
|
||||
mlAuthz: MlAuthz;
|
||||
edit: BulkActionEditPayload[];
|
||||
experimentalFeatures: ExperimentalFeatures;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,6 +115,7 @@ export const dryRunValidateBulkEditRule = async ({
|
|||
rule,
|
||||
edit,
|
||||
mlAuthz,
|
||||
experimentalFeatures,
|
||||
}: DryRunBulkEditBulkActionsValidationArgs) => {
|
||||
await validateBulkEditRule({
|
||||
ruleType: rule.params.type,
|
||||
|
@ -142,4 +145,15 @@ export const dryRunValidateBulkEditRule = async ({
|
|||
),
|
||||
BulkActionsDryRunErrCode.ESQL_INDEX_PATTERN
|
||||
);
|
||||
|
||||
// check whether "custom highlighted fields" feature is enabled
|
||||
await throwDryRunError(
|
||||
() =>
|
||||
invariant(
|
||||
experimentalFeatures.bulkCustomHighlightedFieldsEnabled ||
|
||||
!edit.some((action) => isInvestigationFieldsBulkEditAction(action.type)),
|
||||
'Bulk custom highlighted fields action feature is disabled.'
|
||||
),
|
||||
BulkActionsDryRunErrCode.INVESTIGATION_FIELDS_FEATURE
|
||||
);
|
||||
};
|
||||
|
|
|
@ -82,6 +82,7 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s
|
|||
'previewTelemetryUrlEnabled',
|
||||
'riskScoringPersistence',
|
||||
'riskScoringRoutesEnabled',
|
||||
'bulkCustomHighlightedFieldsEnabled',
|
||||
])}`,
|
||||
'--xpack.task_manager.poll_interval=1000',
|
||||
`--xpack.actions.preconfigured=${JSON.stringify(PRECONFIGURED_ACTION_CONNECTORS)}`,
|
||||
|
|
|
@ -17,5 +17,8 @@ export default createTestConfig({
|
|||
'testing_ignored.constant',
|
||||
'/testing_regex*/',
|
||||
])}`, // See tests within the file "ignore_fields.ts" which use these values in "alertIgnoreFields"
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||
'bulkCustomHighlightedFieldsEnabled',
|
||||
])}`,
|
||||
],
|
||||
});
|
||||
|
|
|
@ -1151,6 +1151,210 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('investigation fields actions', () => {
|
||||
it('should set investigation fields in rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
await createRule(supertest, log, getSimpleRule(ruleId));
|
||||
|
||||
const { body: bulkEditResponse } = await securitySolutionApi
|
||||
.performBulkAction({
|
||||
query: {},
|
||||
body: {
|
||||
query: '',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.set_investigation_fields,
|
||||
value: { field_names: ['field-1'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].investigation_fields).to.eql({
|
||||
field_names: ['field-1'],
|
||||
});
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.investigation_fields).to.eql({ field_names: ['field-1'] });
|
||||
});
|
||||
|
||||
it('should add investigation fields to rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const investigationFields = { field_names: ['field-1', 'field-2'] };
|
||||
const resultingFields = { field_names: ['field-1', 'field-2', 'field-3'] };
|
||||
await createRule(supertest, log, {
|
||||
...getSimpleRule(ruleId),
|
||||
investigation_fields: investigationFields,
|
||||
});
|
||||
|
||||
const { body: bulkEditResponse } = await securitySolutionApi
|
||||
.performBulkAction({
|
||||
query: {},
|
||||
body: {
|
||||
query: '',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.add_investigation_fields,
|
||||
value: { field_names: ['field-3'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].investigation_fields).to.eql(
|
||||
resultingFields
|
||||
);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.investigation_fields).to.eql(resultingFields);
|
||||
});
|
||||
|
||||
it('should delete investigation fields from rules', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const investigationFields = { field_names: ['field-1', 'field-2'] };
|
||||
const resultingFields = { field_names: ['field-1'] };
|
||||
await createRule(supertest, log, {
|
||||
...getSimpleRule(ruleId),
|
||||
investigation_fields: investigationFields,
|
||||
});
|
||||
|
||||
const { body: bulkEditResponse } = await securitySolutionApi
|
||||
.performBulkAction({
|
||||
query: {},
|
||||
body: {
|
||||
query: '',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
value: { field_names: ['field-2'] },
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
skipped: 0,
|
||||
succeeded: 1,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the updated rule is returned with the response
|
||||
expect(bulkEditResponse.attributes.results.updated[0].investigation_fields).to.eql(
|
||||
resultingFields
|
||||
);
|
||||
|
||||
// Check that the updates have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.investigation_fields).to.eql(resultingFields);
|
||||
});
|
||||
|
||||
const skipIndexPatternsUpdateCases = [
|
||||
// Delete no-ops
|
||||
{
|
||||
caseName: '0 existing fields - 2 fields = 0 fields',
|
||||
existingInvestigationFields: undefined,
|
||||
investigationFieldsToUpdate: { field_names: ['field-1', 'field-2'] },
|
||||
resultingInvestigationFields: undefined,
|
||||
operation: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
},
|
||||
{
|
||||
caseName: '3 existing fields - 2 other fields (none of them) = 3 fields',
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToUpdate: { field_names: ['field-8', 'field-9'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
operation: BulkActionEditTypeEnum.delete_investigation_fields,
|
||||
},
|
||||
// Add no-ops
|
||||
{
|
||||
caseName: '3 existing fields + 2 exisiting fields= 3 fields',
|
||||
existingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
investigationFieldsToUpdate: { field_names: ['field-1', 'field-2'] },
|
||||
resultingInvestigationFields: { field_names: ['field-1', 'field-2', 'field-3'] },
|
||||
operation: BulkActionEditTypeEnum.add_investigation_fields,
|
||||
},
|
||||
];
|
||||
|
||||
skipIndexPatternsUpdateCases.forEach(
|
||||
({
|
||||
caseName,
|
||||
existingInvestigationFields,
|
||||
investigationFieldsToUpdate,
|
||||
resultingInvestigationFields,
|
||||
operation,
|
||||
}) => {
|
||||
it(`should skip rule updated for investigation fields, case: "${caseName}"`, async () => {
|
||||
const ruleId = 'ruleId';
|
||||
|
||||
await createRule(supertest, log, {
|
||||
...getSimpleRule(ruleId),
|
||||
investigation_fields: existingInvestigationFields,
|
||||
});
|
||||
|
||||
const { body: bulkEditResponse } = await securitySolutionApi
|
||||
.performBulkAction({
|
||||
query: {},
|
||||
body: {
|
||||
query: '',
|
||||
action: BulkActionTypeEnum.edit,
|
||||
[BulkActionTypeEnum.edit]: [
|
||||
{
|
||||
type: operation,
|
||||
value: investigationFieldsToUpdate,
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(bulkEditResponse.attributes.summary).to.eql({
|
||||
failed: 0,
|
||||
skipped: 1,
|
||||
succeeded: 0,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
// Check that the rules is returned as skipped with expected skip reason
|
||||
expect(bulkEditResponse.attributes.results.skipped[0].skip_reason).to.eql(
|
||||
'RULE_NOT_MODIFIED'
|
||||
);
|
||||
|
||||
// Check that the no changes have been persisted
|
||||
const { body: updatedRule } = await fetchRule(ruleId).expect(200);
|
||||
|
||||
expect(updatedRule.investigation_fields).to.eql(resultingInvestigationFields);
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('should set timeline template values in rule', async () => {
|
||||
const ruleId = 'ruleId';
|
||||
const timelineId = '91832785-286d-4ebe-b884-1a208d111a70';
|
||||
|
|
|
@ -44,6 +44,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
// See https://github.com/elastic/kibana/pull/125396 for details
|
||||
'--xpack.alerting.rules.minimumScheduleInterval.value=1s',
|
||||
'--xpack.ruleRegistry.unsafe.legacyMultiTenancy.enabled=true',
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||
'bulkCustomHighlightedFieldsEnabled',
|
||||
])}`,
|
||||
// mock cloud to enable the guided onboarding tour in e2e tests
|
||||
'--xpack.cloud.id=test',
|
||||
`--home.disableWelcomeScreen=true`,
|
||||
|
|
|
@ -20,9 +20,13 @@ import {
|
|||
TAGS_RULE_BULK_MENU_ITEM,
|
||||
INDEX_PATTERNS_RULE_BULK_MENU_ITEM,
|
||||
APPLY_TIMELINE_RULE_BULK_MENU_ITEM,
|
||||
RULES_BULK_EDIT_INVESTIGATION_FIELDS_WARNING,
|
||||
} from '../../../../../screens/rules_bulk_actions';
|
||||
|
||||
import { TIMELINE_TEMPLATE_DETAILS } from '../../../../../screens/rule_details';
|
||||
import {
|
||||
INVESTIGATION_FIELDS_DETAILS,
|
||||
TIMELINE_TEMPLATE_DETAILS,
|
||||
} from '../../../../../screens/rule_details';
|
||||
|
||||
import { EUI_CHECKBOX, EUI_FILTER_SELECT_ITEM } from '../../../../../screens/common/controls';
|
||||
|
||||
|
@ -72,10 +76,19 @@ import {
|
|||
assertRuleScheduleValues,
|
||||
assertUpdateScheduleWarningExists,
|
||||
assertDefaultValuesAreAppliedToScheduleFields,
|
||||
openBulkEditAddInvestigationFieldsForm,
|
||||
typeInvestigationFields,
|
||||
checkOverwriteInvestigationFieldsCheckbox,
|
||||
openBulkEditDeleteInvestigationFieldsForm,
|
||||
} from '../../../../../tasks/rules_bulk_actions';
|
||||
|
||||
import { createRuleAssetSavedObject } from '../../../../../helpers/rules';
|
||||
import { hasIndexPatterns, getDetails } from '../../../../../tasks/rule_details';
|
||||
import {
|
||||
hasIndexPatterns,
|
||||
getDetails,
|
||||
hasInvestigationFields,
|
||||
assertDetailsNotExist,
|
||||
} from '../../../../../tasks/rule_details';
|
||||
import { login } from '../../../../../tasks/login';
|
||||
import { visitRulesManagementTable } from '../../../../../tasks/rules_management';
|
||||
import { createRule } from '../../../../../tasks/api_calls/rules';
|
||||
|
@ -102,14 +115,16 @@ import { setRowsPerPageTo, sortByTableColumn } from '../../../../../tasks/table_
|
|||
const RULE_NAME = 'Custom rule for bulk actions';
|
||||
const EUI_SELECTABLE_LIST_ITEM_SR_TEXT = '. To check this option, press Enter.';
|
||||
|
||||
const prePopulatedIndexPatterns = ['index-1-*', 'index-2-*'];
|
||||
const prePopulatedIndexPatterns = ['index-1-*', 'index-2-*', 'auditbeat-*'];
|
||||
const prePopulatedTags = ['test-default-tag-1', 'test-default-tag-2'];
|
||||
const prePopulatedInvestigationFields = ['agent.version', 'host.name'];
|
||||
|
||||
const expectedNumberOfMachineLearningRulesToBeEdited = 1;
|
||||
|
||||
const defaultRuleData = {
|
||||
index: prePopulatedIndexPatterns,
|
||||
tags: prePopulatedTags,
|
||||
investigation_fields: { field_names: prePopulatedInvestigationFields },
|
||||
timeline_title: 'Generic Threat Match Timeline',
|
||||
timeline_id: '495ad7a7-316e-4544-8a0f-9c098daee76e',
|
||||
};
|
||||
|
@ -129,6 +144,7 @@ describe('Detection rules, bulk edit', { tags: ['@ess', '@serverless'] }, () =>
|
|||
getMachineLearningRule({
|
||||
name: 'New ML Rule Test',
|
||||
tags: ['test-default-tag-1', 'test-default-tag-2'],
|
||||
investigation_fields: { field_names: prePopulatedInvestigationFields },
|
||||
enabled: false,
|
||||
})
|
||||
);
|
||||
|
@ -562,6 +578,86 @@ describe('Detection rules, bulk edit', { tags: ['@ess', '@serverless'] }, () =>
|
|||
});
|
||||
});
|
||||
|
||||
describe('Investigation fields actions', () => {
|
||||
it('Add investigation fields to custom rules', () => {
|
||||
getRulesManagementTableRows().then((rows) => {
|
||||
const fieldsToBeAdded = ['source.ip', 'destination.ip'];
|
||||
const resultingFields = [...prePopulatedInvestigationFields, ...fieldsToBeAdded];
|
||||
|
||||
selectAllRules();
|
||||
|
||||
// open add custom highlighted fields form and add 2 new fields
|
||||
openBulkEditAddInvestigationFieldsForm();
|
||||
typeInvestigationFields(fieldsToBeAdded);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: rows.length });
|
||||
|
||||
// check if rule has been updated
|
||||
goToRuleDetailsOf(RULE_NAME);
|
||||
hasInvestigationFields(resultingFields.join(''));
|
||||
});
|
||||
});
|
||||
|
||||
it('Overwrite investigation fields in custom rules', () => {
|
||||
getRulesManagementTableRows().then((rows) => {
|
||||
const fieldsToOverwrite = ['source.ip'];
|
||||
|
||||
selectAllRules();
|
||||
|
||||
// open add tags form, check overwrite tags and warning message, type tags
|
||||
openBulkEditAddInvestigationFieldsForm();
|
||||
checkOverwriteInvestigationFieldsCheckbox();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_INVESTIGATION_FIELDS_WARNING).should(
|
||||
'have.text',
|
||||
`You’re about to overwrite custom highlighted fields for ${rows.length} selected rules, press Save to apply changes.`
|
||||
);
|
||||
|
||||
typeInvestigationFields(fieldsToOverwrite);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: rows.length });
|
||||
|
||||
// check if rule has been updated
|
||||
goToRuleDetailsOf(RULE_NAME);
|
||||
hasInvestigationFields(fieldsToOverwrite.join(''));
|
||||
});
|
||||
});
|
||||
|
||||
it('Delete investigation fields from custom rules', () => {
|
||||
getRulesManagementTableRows().then((rows) => {
|
||||
const fieldsToDelete = prePopulatedInvestigationFields.slice(0, 1);
|
||||
const resultingFields = prePopulatedInvestigationFields.slice(1);
|
||||
|
||||
selectAllRules();
|
||||
|
||||
// open add tags form, check overwrite tags, type tags
|
||||
openBulkEditDeleteInvestigationFieldsForm();
|
||||
typeInvestigationFields(fieldsToDelete);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: rows.length });
|
||||
|
||||
// check if rule has been updated
|
||||
goToRuleDetailsOf(RULE_NAME);
|
||||
hasInvestigationFields(resultingFields.join(''));
|
||||
});
|
||||
});
|
||||
|
||||
it('Delete all investigation fields from custom rules', () => {
|
||||
getRulesManagementTableRows().then((rows) => {
|
||||
selectAllRules();
|
||||
|
||||
openBulkEditDeleteInvestigationFieldsForm();
|
||||
typeInvestigationFields(prePopulatedInvestigationFields);
|
||||
submitBulkEditForm();
|
||||
waitForBulkEditActionToFinish({ updatedCount: rows.length });
|
||||
|
||||
// check if rule has been updated
|
||||
goToRuleDetailsOf(RULE_NAME);
|
||||
assertDetailsNotExist(INVESTIGATION_FIELDS_DETAILS);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Timeline templates', () => {
|
||||
beforeEach(() => {
|
||||
loadPrepackagedTimelineTemplates();
|
||||
|
|
|
@ -54,6 +54,8 @@ export const FALSE_POSITIVES_DETAILS = 'False positive examples';
|
|||
|
||||
export const INDEX_PATTERNS_DETAILS = 'Index patterns';
|
||||
|
||||
export const INVESTIGATION_FIELDS_DETAILS = 'Custom highlighted fields';
|
||||
|
||||
export const ENDPOINT_EXCEPTIONS_TAB = 'a[data-test-subj="navigation-endpoint_exceptions"]';
|
||||
|
||||
export const INDICATOR_INDEX_PATTERNS = 'Indicator index patterns';
|
||||
|
|
|
@ -83,6 +83,25 @@ export const RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX =
|
|||
|
||||
export const RULES_BULK_EDIT_TAGS_WARNING = '[data-test-subj="bulkEditRulesTagsWarning"]';
|
||||
|
||||
// INVESTIGATION FIELDS
|
||||
export const INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM =
|
||||
'[data-test-subj="investigationFieldsBulkEditRule"]';
|
||||
|
||||
export const ADD_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM =
|
||||
'[data-test-subj="addInvestigationFieldsBulkEditRule"]';
|
||||
|
||||
export const DELETE_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM =
|
||||
'[data-test-subj="deleteInvestigationFieldsBulkEditRule"]';
|
||||
|
||||
export const RULES_BULK_EDIT_INVESTIGATION_FIELDS =
|
||||
'[data-test-subj="bulkEditRulesInvestigationFields"]';
|
||||
|
||||
export const RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX =
|
||||
'[data-test-subj="bulkEditRulesOverwriteInvestigationFields"]';
|
||||
|
||||
export const RULES_BULK_EDIT_INVESTIGATION_FIELDS_WARNING =
|
||||
'[data-test-subj="bulkEditRulesInvestigationFieldsWarning"]';
|
||||
|
||||
// ENABLE/DISABLE
|
||||
export const ENABLE_RULE_BULK_BTN = '[data-test-subj="enableRuleBulk"]';
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ import {
|
|||
EXCEPTIONS_TAB_EXPIRED_FILTER,
|
||||
EXCEPTIONS_TAB_ACTIVE_FILTER,
|
||||
RULE_NAME_HEADER,
|
||||
INVESTIGATION_FIELDS_DETAILS,
|
||||
ABOUT_DETAILS,
|
||||
} from '../screens/rule_details';
|
||||
import { RuleDetailsTabs, ruleDetailsUrl } from '../urls/rule_details';
|
||||
import {
|
||||
|
@ -179,6 +181,12 @@ export const hasIndexPatterns = (indexPatterns: string) => {
|
|||
});
|
||||
};
|
||||
|
||||
export const hasInvestigationFields = (fields: string) => {
|
||||
cy.get(ABOUT_DETAILS).within(() => {
|
||||
getDetails(INVESTIGATION_FIELDS_DETAILS).should('have.text', fields);
|
||||
});
|
||||
};
|
||||
|
||||
export const goToRuleEditSettings = () => {
|
||||
cy.get(EDIT_RULE_SETTINGS_LINK).click();
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { EUI_SELECTABLE_LIST_ITEM, TIMELINE_SEARCHBOX } from '../screens/common/controls';
|
||||
import {
|
||||
ADD_INDEX_PATTERNS_RULE_BULK_MENU_ITEM,
|
||||
ADD_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM,
|
||||
ADD_RULE_ACTIONS_MENU_ITEM,
|
||||
ADD_TAGS_RULE_BULK_MENU_ITEM,
|
||||
APPLY_TIMELINE_RULE_BULK_MENU_ITEM,
|
||||
|
@ -28,18 +29,22 @@ import {
|
|||
BULK_ACTIONS_PROGRESS_BTN,
|
||||
BULK_EXPORT_ACTION_BTN,
|
||||
DELETE_INDEX_PATTERNS_RULE_BULK_MENU_ITEM,
|
||||
DELETE_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM,
|
||||
DELETE_RULE_BULK_BTN,
|
||||
DELETE_TAGS_RULE_BULK_MENU_ITEM,
|
||||
DISABLE_RULE_BULK_BTN,
|
||||
DUPLICATE_RULE_BULK_BTN,
|
||||
ENABLE_RULE_BULK_BTN,
|
||||
INDEX_PATTERNS_RULE_BULK_MENU_ITEM,
|
||||
INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM,
|
||||
RULES_BULK_EDIT_FORM_CONFIRM_BTN,
|
||||
RULES_BULK_EDIT_FORM_TITLE,
|
||||
RULES_BULK_EDIT_INDEX_PATTERNS,
|
||||
RULES_BULK_EDIT_INVESTIGATION_FIELDS,
|
||||
RULES_BULK_EDIT_OVERWRITE_ACTIONS_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_DATA_VIEW_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_INDEX_PATTERNS_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX,
|
||||
RULES_BULK_EDIT_OVERWRITE_TAGS_CHECKBOX,
|
||||
RULES_BULK_EDIT_SCHEDULES_WARNING,
|
||||
RULES_BULK_EDIT_TAGS,
|
||||
|
@ -232,6 +237,46 @@ export const checkTagsInTagsFilter = (tags: string[], srOnlyText: string = '') =
|
|||
});
|
||||
};
|
||||
|
||||
// EDIT-INVESTIGATION FIELDS
|
||||
const clickInvestigationFieldsMenuItem = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
cy.get(INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click();
|
||||
};
|
||||
|
||||
export const clickAddInvestigationFieldsMenuItem = () => {
|
||||
clickInvestigationFieldsMenuItem();
|
||||
cy.get(ADD_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click();
|
||||
};
|
||||
|
||||
export const openBulkEditAddInvestigationFieldsForm = () => {
|
||||
clickAddInvestigationFieldsMenuItem();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Add custom highlighted fields');
|
||||
};
|
||||
|
||||
export const openBulkEditDeleteInvestigationFieldsForm = () => {
|
||||
clickInvestigationFieldsMenuItem();
|
||||
cy.get(DELETE_INVESTIGATION_FIELDS_RULE_BULK_MENU_ITEM).click();
|
||||
|
||||
cy.get(RULES_BULK_EDIT_FORM_TITLE).should('have.text', 'Delete custom highlighted fields');
|
||||
};
|
||||
|
||||
export const typeInvestigationFields = (fields: string[]) => {
|
||||
cy.get(RULES_BULK_EDIT_INVESTIGATION_FIELDS)
|
||||
.find('input')
|
||||
.type(fields.join('{enter}') + '{enter}');
|
||||
};
|
||||
|
||||
export const checkOverwriteInvestigationFieldsCheckbox = () => {
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' custom highlighted fields")
|
||||
.click();
|
||||
cy.get(RULES_BULK_EDIT_OVERWRITE_INVESTIGATION_FIELDS_CHECKBOX)
|
||||
.should('have.text', "Overwrite all selected rules' custom highlighted fields")
|
||||
.get('input')
|
||||
.should('be.checked');
|
||||
};
|
||||
|
||||
// EDIT-SCHEDULE
|
||||
export const clickUpdateScheduleMenuItem = () => {
|
||||
cy.get(BULK_ACTIONS_BTN).click();
|
||||
|
|
|
@ -34,6 +34,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
{ product_line: 'endpoint', product_tier: 'complete' },
|
||||
{ product_line: 'cloud', product_tier: 'complete' },
|
||||
])}`,
|
||||
`--xpack.securitySolution.enableExperimental=${JSON.stringify([
|
||||
'bulkCustomHighlightedFieldsEnabled',
|
||||
])}`,
|
||||
],
|
||||
},
|
||||
testRunner: SecuritySolutionConfigurableCypressTestRunner,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue