diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts index c2ed7dd7061b5..f6178e639f717 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.gen.ts @@ -26,6 +26,7 @@ import { RuleActionAlertsFilter, IndexPatternArray, RuleTagArray, + InvestigationFields, TimelineTemplateId, TimelineTemplateTitle, } from '../../model/rule_schema/common_attributes.gen'; @@ -187,6 +188,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 +243,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; export const BulkActionEditPayloadTimeline = z.object({ type: z.literal('set_timeline'), @@ -252,6 +268,7 @@ export type BulkActionEditPayload = z.infer; export const BulkActionEditPayload = z.union([ BulkActionEditPayloadTags, BulkActionEditPayloadIndexPatterns, + BulkActionEditPayloadInvestigationFields, BulkActionEditPayloadTimeline, BulkActionEditPayloadRuleActions, BulkActionEditPayloadSchedule, diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml index 10422772785e3..26f993e015c11 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_route.schema.yaml @@ -281,6 +281,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 +384,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 +424,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' diff --git a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts index 6e57e5abe2410..c160b6fb21c27 100644 --- a/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts +++ b/x-pack/plugins/security_solution/common/api/detection_engine/rule_management/bulk_actions/bulk_actions_types.ts @@ -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; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx index 8ef1f9723cd39..d4b01000233df 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/bulk_edit_flyout.tsx @@ -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 ; + case BulkActionEditTypeEnum.add_investigation_fields: + case BulkActionEditTypeEnum.delete_investigation_fields: + case BulkActionEditTypeEnum.set_investigation_fields: + return ; + case BulkActionEditTypeEnum.set_timeline: return ; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx new file mode 100644 index 0000000000000..98de5d069ad67 --- /dev/null +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/investigation_fields_form.tsx @@ -0,0 +1,169 @@ +/* + * 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 * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations'; + +import { DEFAULT_INDEX_KEY } from '../../../../../../../common/constants'; +import { useFetchIndex } from '../../../../../../common/containers/source'; +import { useKibana } from '../../../../../../common/lib/kibana'; + +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 = { + 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 { indexHelpText, indexLabel, formTitle } = getFormConfig(editAction); + + const [{ overwrite }] = useFormData({ + form, + watch: ['overwrite'], + }); + const { uiSettings } = useKibana().services; + const defaultPatterns = uiSettings.get(DEFAULT_INDEX_KEY); + 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; + } + + onConfirm({ + value: { field_names: data.investigationFields }, + type: data.overwrite ? BulkActionEditTypeEnum.set_investigation_fields : editAction, + }); + }; + + return ( + + + {editAction === BulkActionEditTypeEnum.add_investigation_fields && ( + + )} + {overwrite && ( + + + + + + )} + + ); +}; + +export const InvestigationFieldsForm = React.memo(InvestigationFieldsFormComponent); +InvestigationFieldsForm.displayName = 'InvestigationFieldsForm'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx index d66f1b45feb9a..f8bf35a45c872 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/use_bulk_actions.tsx @@ -328,6 +328,13 @@ export const useBulkActions = ({ disabled: isEditDisabled, panel: 1, }, + { + 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, @@ -458,6 +465,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' }, + }, + ], + }, ]; }, [ diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts index ba5d565e393d0..340e2345b33db 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_management_ui/components/rules_table/bulk_actions/utils/compute_dry_run_edit_payload.ts @@ -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: diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts index a7823f39abb70..5cc40f5c68c74 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/translations.ts @@ -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,62 @@ 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.', + } +); + +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.', + } +); + +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', { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts index 6bfdfcf394aac..6402ce82b0ccc 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rule_management/logic/bulk_actions/rule_params_modifier.ts @@ -63,6 +63,7 @@ const shouldSkipIndexPatternsBulkAction = ( return false; }; +// eslint-disable-next-line complexity const applyBulkActionEditToRuleParams = ( existingRuleParams: RuleAlertType['params'], action: BulkActionEditForRuleParams @@ -151,6 +152,33 @@ const applyBulkActionEditToRuleParams = ( ruleParams.index = action.value; break; } + // investigation_fields actions + case BulkActionEditTypeEnum.add_investigation_fields: { + 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: { + if (ruleParams.investigationFields) { + ruleParams.investigationFields = deleteItemsFromArray( + (Array.isArray(ruleParams.investigationFields) + ? ruleParams.investigationFields + : ruleParams.investigationFields?.field_names) ?? [], + action.value.field_names + ); + } + break; + } + case BulkActionEditTypeEnum.set_investigation_fields: { + ruleParams.investigationFields = action.value; + break; + } // timeline actions case BulkActionEditTypeEnum.set_timeline: { ruleParams = {