mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution][Detections] Adds UI for bulk applying timeline template (#128691)
**Addresses:** https://github.com/elastic/kibana/issues/93083, https://github.com/elastic/security-team/issues/2078 (internal) ## Summary This PR adds a UI for applying a timeline template to multiple rules in bulk. - A new bulk actions menu item to the Rule Management table. - A new form flyout for applying a timeline template. - Some glue code to connect them. There are a few issues that I'd like to address in a follow-up PR after the FF: 1. Resetting already applied templates to `None` doesn't work because of the way the `patchRules` function works. This is a known bug in this implementation. We will need to replace `patchRules` with something else for bulk editing actions. 2. I need to add some test coverage. Other notes: - I changed some copies to hopefully make it a little bit clearer. Let me know if you want to rephrase. ## Screenshots  The template selector doesn't look good on a smaller screen: 
This commit is contained in:
parent
53dde5f44e
commit
17086ad6ce
7 changed files with 187 additions and 11 deletions
|
@ -14,6 +14,7 @@ import {
|
|||
|
||||
import { IndexPatternsForm } from './forms/index_patterns_form';
|
||||
import { TagsForm } from './forms/tags_form';
|
||||
import { TimelineTemplateForm } from './forms/timeline_template_form';
|
||||
|
||||
interface BulkEditFlyoutProps {
|
||||
onClose: () => void;
|
||||
|
@ -35,6 +36,9 @@ const BulkEditFlyoutComponent = ({ editAction, tags, ...props }: BulkEditFlyoutP
|
|||
case BulkActionEditType.set_tags:
|
||||
return <TagsForm {...props} editAction={editAction} tags={tags} />;
|
||||
|
||||
case BulkActionEditType.set_timeline:
|
||||
return <TimelineTemplateForm {...props} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -24,19 +24,21 @@ import { Form, FormHook } from '../../../../../../../shared_imports';
|
|||
import * as i18n from '../../../translations';
|
||||
|
||||
interface BulkEditFormWrapperProps {
|
||||
form: FormHook;
|
||||
title: string;
|
||||
banner?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
title: string;
|
||||
form: FormHook;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const BulkEditFormWrapperComponent: FC<BulkEditFormWrapperProps> = ({
|
||||
form,
|
||||
title,
|
||||
banner,
|
||||
children,
|
||||
onClose,
|
||||
onSubmit,
|
||||
children,
|
||||
title,
|
||||
}) => {
|
||||
const simpleFlyoutTitleId = useGeneratedHtmlId({
|
||||
prefix: 'RulesBulkEditForm',
|
||||
|
@ -50,7 +52,7 @@ const BulkEditFormWrapperComponent: FC<BulkEditFormWrapperProps> = ({
|
|||
<h2 id={simpleFlyoutTitleId}>{title}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlyoutHeader>
|
||||
<EuiFlyoutBody>
|
||||
<EuiFlyoutBody banner={banner}>
|
||||
<Form form={form}>{children}</Form>
|
||||
</EuiFlyoutBody>
|
||||
<EuiFlyoutFooter>
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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, { useCallback } from 'react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { useForm, UseField, FormSchema } from '../../../../../../../shared_imports';
|
||||
import { PickTimeline } from '../../../../../../components/rules/pick_timeline';
|
||||
import {
|
||||
BulkActionEditType,
|
||||
BulkActionEditPayload,
|
||||
} from '../../../../../../../../common/detection_engine/schemas/common/schemas';
|
||||
|
||||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { bulkApplyTimelineTemplate as i18n } from '../translations';
|
||||
|
||||
export interface TimelineTemplateFormData {
|
||||
timeline: {
|
||||
id: string | null;
|
||||
title: string;
|
||||
};
|
||||
}
|
||||
|
||||
const formSchema: FormSchema<TimelineTemplateFormData> = {
|
||||
timeline: {
|
||||
label: i18n.TEMPLATE_SELECTOR_LABEL,
|
||||
helpText: i18n.TEMPLATE_SELECTOR_HELP_TEXT,
|
||||
},
|
||||
};
|
||||
|
||||
const defaultFormData: TimelineTemplateFormData = {
|
||||
timeline: {
|
||||
id: null,
|
||||
title: i18n.TEMPLATE_SELECTOR_DEFAULT_VALUE,
|
||||
},
|
||||
};
|
||||
|
||||
interface TimelineTemplateFormProps {
|
||||
rulesCount: number;
|
||||
onClose: () => void;
|
||||
onConfirm: (bulkActionEditPayload: BulkActionEditPayload) => void;
|
||||
}
|
||||
|
||||
const TimelineTemplateFormComponent = (props: TimelineTemplateFormProps) => {
|
||||
const { rulesCount, onClose, onConfirm } = props;
|
||||
|
||||
const { form } = useForm({
|
||||
schema: formSchema,
|
||||
defaultValue: defaultFormData,
|
||||
});
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
const { data, isValid } = await form.submit();
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const timelineId = data.timeline.id || '';
|
||||
const timelineTitle = timelineId ? data.timeline.title : '';
|
||||
|
||||
onConfirm({
|
||||
type: BulkActionEditType.set_timeline,
|
||||
value: {
|
||||
timeline_id: timelineId,
|
||||
timeline_title: timelineTitle,
|
||||
},
|
||||
});
|
||||
}, [form, onConfirm]);
|
||||
|
||||
const warningCallout = (
|
||||
<EuiCallOut color="warning" data-test-subj="bulkEditRulesTimelineTemplateWarning">
|
||||
{i18n.warningCalloutMessage(rulesCount)}
|
||||
</EuiCallOut>
|
||||
);
|
||||
|
||||
return (
|
||||
<BulkEditFormWrapper
|
||||
form={form}
|
||||
title={i18n.FORM_TITLE}
|
||||
banner={warningCallout}
|
||||
onClose={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
{/* Timeline template selector */}
|
||||
<UseField
|
||||
path="timeline"
|
||||
component={PickTimeline}
|
||||
componentProps={{
|
||||
idAria: 'bulkEditRulesTimelineTemplateSelector',
|
||||
dataTestSubj: 'bulkEditRulesTimelineTemplateSelector',
|
||||
}}
|
||||
/>
|
||||
</BulkEditFormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const TimelineTemplateForm = React.memo(TimelineTemplateFormComponent);
|
||||
TimelineTemplateForm.displayName = 'TimelineTemplateForm';
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
export const bulkApplyTimelineTemplate = {
|
||||
FORM_TITLE: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.formTitle',
|
||||
{
|
||||
defaultMessage: 'Apply timeline template',
|
||||
}
|
||||
),
|
||||
|
||||
TEMPLATE_SELECTOR_LABEL: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorLabel',
|
||||
{
|
||||
defaultMessage: 'Apply timeline template to selected rules',
|
||||
}
|
||||
),
|
||||
|
||||
TEMPLATE_SELECTOR_HELP_TEXT: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorHelpText',
|
||||
{
|
||||
defaultMessage:
|
||||
'Select which timeline to apply to selected rules when investigating generated alerts.',
|
||||
}
|
||||
),
|
||||
|
||||
TEMPLATE_SELECTOR_DEFAULT_VALUE: i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.templateSelectorDefaultValue',
|
||||
{
|
||||
defaultMessage: 'None',
|
||||
}
|
||||
),
|
||||
|
||||
warningCalloutMessage: (rulesCount: number): JSX.Element => (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.applyTimelineTemplate.warningCalloutMessage"
|
||||
defaultMessage="You are about to apply changes to {rulesCount, plural, one {# selected rule} other {# selected rules}}.
|
||||
If you already applied any templates to these rules, they will be overwritten or (if you select 'None') reset to none."
|
||||
values={{ rulesCount }}
|
||||
/>
|
||||
),
|
||||
};
|
|
@ -307,6 +307,16 @@ export const useBulkActions = ({
|
|||
disabled: isEditDisabled,
|
||||
panel: 1,
|
||||
},
|
||||
{
|
||||
key: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE,
|
||||
name: i18n.BULK_ACTION_APPLY_TIMELINE_TEMPLATE,
|
||||
'data-test-subj': 'applyTimelineTemplateBulk',
|
||||
disabled: isEditDisabled,
|
||||
onClick: handleBulkEdit(BulkActionEditType.set_timeline),
|
||||
toolTipContent: missingActionPrivileges ? i18n.EDIT_RULE_SETTINGS_TOOLTIP : undefined,
|
||||
toolTipPosition: 'right',
|
||||
icon: undefined,
|
||||
},
|
||||
{
|
||||
key: i18n.BULK_ACTION_EXPORT,
|
||||
name: i18n.BULK_ACTION_EXPORT,
|
||||
|
|
|
@ -193,6 +193,13 @@ export const BULK_ACTION_DELETE_TAGS = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_APPLY_TIMELINE_TEMPLATE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.applyTimelineTemplateTitle',
|
||||
{
|
||||
defaultMessage: 'Apply timeline template',
|
||||
}
|
||||
);
|
||||
|
||||
export const BULK_ACTION_MENU_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.contextMenuTitle',
|
||||
{
|
||||
|
|
|
@ -78,11 +78,12 @@ export const applyBulkActionEditToRule = (
|
|||
|
||||
// timeline actions
|
||||
case BulkActionEditType.set_timeline:
|
||||
rule.params = {
|
||||
...rule.params,
|
||||
timelineId: action.value.timeline_id,
|
||||
timelineTitle: action.value.timeline_title,
|
||||
};
|
||||
const timelineId = action.value.timeline_id.trim() || undefined;
|
||||
const timelineTitle = timelineId ? action.value.timeline_title : undefined;
|
||||
|
||||
rule.params.timelineId = timelineId;
|
||||
rule.params.timelineTitle = timelineTitle;
|
||||
break;
|
||||
}
|
||||
|
||||
return rule;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue