Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] [Detection Engine] Fixes duplicate rule action #55252

Merged
merged 2 commits into from
Jan 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -178,41 +178,41 @@ export const deleteRules = async ({ ids }: DeleteRulesProps): Promise<Array<Rule
/**
* Duplicates provided Rules
*
* @param rule to duplicate
* @param rules to duplicate
*/
export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise<Rule[]> => {
const requests = rules.map(rule =>
fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, {
const response = await fetch(
`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_create`,
{
method: 'POST',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
body: JSON.stringify({
...rule,
name: `${rule.name} [${i18n.DUPLICATE}]`,
created_at: undefined,
created_by: undefined,
id: undefined,
rule_id: undefined,
updated_at: undefined,
updated_by: undefined,
enabled: rule.enabled,
immutable: false,
last_success_at: undefined,
last_success_message: undefined,
status: undefined,
status_date: undefined,
}),
})
body: JSON.stringify(
rules.map(rule => ({
...rule,
name: `${rule.name} [${i18n.DUPLICATE}]`,
created_at: undefined,
created_by: undefined,
id: undefined,
rule_id: undefined,
updated_at: undefined,
updated_by: undefined,
enabled: rule.enabled,
immutable: undefined,
last_success_at: undefined,
last_success_message: undefined,
status: undefined,
status_date: undefined,
}))
),
}
);

const responses = await Promise.all(requests);
await responses.map(response => throwIfNotOk(response));
return Promise.all(
responses.map<Promise<Rule>>(response => response.json())
);
await throwIfNotOk(response);
return response.json();
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export interface DeleteRulesProps {
}

export interface DuplicateRulesProps {
rules: Rules;
rules: Rule[];
}

export interface BasicFetchProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,25 @@ export const editRuleAction = (rule: Rule, history: H.History) => {
history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`);
};

export const duplicateRuleAction = async (
rule: Rule,
export const duplicateRulesAction = async (
rules: Rule[],
dispatch: React.Dispatch<Action>,
dispatchToaster: Dispatch<ActionToaster>
) => {
try {
dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true });
const duplicatedRule = await duplicateRules({ rules: [rule] });
dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false });
dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id });
displaySuccessToast(i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRule.length), dispatchToaster);
const ruleIds = rules.map(r => r.id);
dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: true });
const duplicatedRules = await duplicateRules({ rules });
dispatch({ type: 'updateLoading', ids: ruleIds, isLoading: false });
dispatch({
type: 'updateRules',
rules: duplicatedRules,
appendRuleId: rules[rules.length - 1].id,
});
displaySuccessToast(
i18n.SUCCESSFULLY_DUPLICATED_RULES(duplicatedRules.length),
dispatchToaster
);
} catch (e) {
displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import * as H from 'history';
import * as i18n from '../translations';
import { TableData } from '../types';
import { Action } from './reducer';
import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actions';
import {
deleteRulesAction,
duplicateRulesAction,
enableRulesAction,
exportRulesAction,
} from './actions';
import { ActionToaster } from '../../../../components/toasters';
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine';

export const getBatchItems = (
selectedState: TableData[],
Expand All @@ -25,7 +29,6 @@ export const getBatchItems = (
const containsDisabled = selectedState.some(v => !v.activate);
const containsLoading = selectedState.some(v => v.isLoading);
const containsImmutable = selectedState.some(v => v.immutable);
const containsMultipleRules = Array.from(new Set(selectedState.map(v => v.rule_id))).length > 1;

return [
<EuiContextMenuItem
Expand Down Expand Up @@ -67,23 +70,25 @@ export const getBatchItems = (
{i18n.BATCH_ACTION_EXPORT_SELECTED}
</EuiContextMenuItem>,
<EuiContextMenuItem
key={i18n.BATCH_ACTION_EDIT_INDEX_PATTERNS}
icon="indexEdit"
disabled={
containsImmutable || containsLoading || containsMultipleRules || selectedState.length === 0
}
key={i18n.BATCH_ACTION_DUPLICATE_SELECTED}
icon="copy"
disabled={containsLoading || selectedState.length === 0}
onClick={async () => {
closePopover();
history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${selectedState[0].id}/edit`);
await duplicateRulesAction(
selectedState.map(s => s.sourceRule),
dispatch,
dispatchToaster
);
}}
>
{i18n.BATCH_ACTION_EDIT_INDEX_PATTERNS}
{i18n.BATCH_ACTION_DUPLICATE_SELECTED}
</EuiContextMenuItem>,
<EuiContextMenuItem
key={i18n.BATCH_ACTION_DELETE_SELECTED}
icon="trash"
title={containsImmutable ? i18n.BATCH_ACTION_DELETE_SELECTED_IMMUTABLE : undefined}
disabled={containsImmutable || containsLoading || selectedState.length === 0}
disabled={containsLoading || selectedState.length === 0}
onClick={async () => {
closePopover();
await deleteRulesAction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React, { Dispatch } from 'react';
import { getEmptyTagValue } from '../../../../components/empty_value';
import {
deleteRulesAction,
duplicateRuleAction,
duplicateRulesAction,
editRuleAction,
exportRulesAction,
} from './actions';
Expand Down Expand Up @@ -48,7 +48,7 @@ const getActions = (
icon: 'copy',
name: i18n.DUPLICATE_RULE,
onClick: (rowItem: TableData) =>
duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster),
duplicateRulesAction([rowItem.sourceRule], dispatch, dispatchToaster),
},
{
description: i18n.EXPORT_RULE,
Expand All @@ -62,7 +62,6 @@ const getActions = (
icon: 'trash',
name: i18n.DELETE_RULE,
onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster),
enabled: (rowItem: TableData) => !rowItem.immutable,
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { useHistory } from 'react-router-dom';
import { Rule } from '../../../../../containers/detection_engine/rules';
import * as i18n from './translations';
import * as i18nActions from '../../../rules/translations';
import { deleteRulesAction, duplicateRuleAction } from '../../all/actions';
import { deleteRulesAction, duplicateRulesAction } from '../../all/actions';
import { displaySuccessToast, useStateToaster } from '../../../../../components/toasters';
import { RuleDownloader } from '../rule_downloader';
import { DETECTION_ENGINE_PAGE_NAME } from '../../../../../components/link_to/redirect_to_detection_engine';
Expand Down Expand Up @@ -54,7 +54,7 @@ const RuleActionsOverflowComponent = ({
disabled={userHasNoPermissions}
onClick={async () => {
setIsPopoverOpen(false);
await duplicateRuleAction(rule, noop, dispatchToaster);
await duplicateRulesAction([rule], noop, dispatchToaster);
}}
>
{i18nActions.DUPLICATE_RULE}
Expand All @@ -73,7 +73,7 @@ const RuleActionsOverflowComponent = ({
<EuiContextMenuItem
key={i18nActions.DELETE_RULE}
icon="trash"
disabled={userHasNoPermissions || rule.immutable}
disabled={userHasNoPermissions}
onClick={async () => {
setIsPopoverOpen(false);
await deleteRulesAction([rule.id], noop, dispatchToaster, onRuleDeletedCallback);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate(
}
);

export const BATCH_ACTION_EDIT_INDEX_PATTERNS = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.batchActions.editIndexPatternsTitle',
export const BATCH_ACTION_DUPLICATE_SELECTED = i18n.translate(
'xpack.siem.detectionEngine.rules.allRules.batchActions.duplicateSelectedTitle',
{
defaultMessage: 'Edit selected index patterns…',
defaultMessage: 'Duplicate selected…',
}
);

Expand Down