From 0b09ac835a311c031932d1b0d9430c4ad054096c Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Fri, 10 Apr 2020 14:43:08 +0200 Subject: [PATCH 01/16] WIP --- .../components/rule_actions_field/index.tsx | 79 +++++++++++++--- .../components/step_rule_actions/index.tsx | 66 +++++++++----- .../components/step_rule_actions/schema.tsx | 91 ++++++++++++++++++- .../throttle_select_field/index.tsx | 1 + .../rules_notification_alert_type.ts | 4 + .../signals/signal_rule_alert_type.ts | 36 +++++--- .../server/builtin_action_types/slack.ts | 2 + .../builtin_action_types/pagerduty.tsx | 26 ++++++ 8 files changed, 250 insertions(+), 55 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index a746d381c494c..00099cecdf0f6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useCallback, useEffect, useState } from 'react'; +import { isEmpty } from 'lodash/fp'; +import { EuiSpacer, EuiCallOut } from '@elastic/eui'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import deepMerge from 'deepmerge'; +import ReactMarkdown from 'react-markdown'; +import styled from 'styled-components'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { loadActionTypes } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/lib/action_connector_api'; @@ -24,7 +28,15 @@ const DEFAULT_ACTION_GROUP_ID = 'default'; const DEFAULT_ACTION_MESSAGE = 'Rule {{context.rule.name}} generated {{state.signals_count}} signals'; +const FieldErrorsContainer = styled.div` + p { + margin-bottom: 0; + } +`; + export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => { + console.error('error', field); + const [fieldErrors, setFieldErrors] = useState(null); const [supportedActionTypes, setSupportedActionTypes] = useState(); const { http, @@ -66,21 +78,60 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables })(); }, []); + useEffect(() => { + if (field.form.isSubmitting) { + return setFieldErrors(null); + } + if ( + field.form.isSubmitted && + !field.form.isSubmitting && + field.form.isValid === false && + field.errors.length + ) { + const errorsString = field.errors.map(({ message }) => message).join('\n'); + return setFieldErrors(errorsString); + } + }, [ + field.form.isSubmitted, + field.form.isSubmitting, + field.isChangingValue, + field.form.isValid, + field.errors, + setFieldErrors, + ]); + + const actions: AlertAction[] = useMemo( + () => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []), + [field.value] + ); + if (!supportedActionTypes) return <>; return ( - + <> + {fieldErrors ? ( + <> + + + + + + + + ) : null} + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx index aec315938b6ae..6c6435f8d06e5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx @@ -4,7 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton, EuiSpacer } from '@elastic/eui'; +import { + EuiHorizontalRule, + EuiForm, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiSpacer, +} from '@elastic/eui'; import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'; import deepEqual from 'fast-deep-equal'; @@ -16,7 +23,7 @@ import { StepContentWrapper } from '../step_content_wrapper'; import { ThrottleSelectField, THROTTLE_OPTIONS } from '../throttle_select_field'; import { RuleActionsField } from '../rule_actions_field'; import { useKibana } from '../../../../../lib/kibana'; -import { schema } from './schema'; +import { getSchema } from './schema'; import * as I18n from './translations'; interface StepRuleActionsProps extends RuleStepProps { @@ -46,8 +53,12 @@ const StepRuleActionsComponent: FC = ({ }) => { const [myStepData, setMyStepData] = useState(stepActionsDefaultValue); const { - services: { application }, + services: { + application, + triggers_actions_ui: { actionTypeRegistry }, + }, } = useKibana(); + const schema = useMemo(() => getSchema({ actionTypeRegistry }), [actionTypeRegistry]); const { form } = useForm({ defaultValue: myStepData, @@ -110,6 +121,8 @@ const StepRuleActionsComponent: FC = ({ [isLoading, updateThrottle] ); + console.error('step', myStepData, defaultValues, form, form.getErrors()); + return isReadOnlyView && myStepData != null ? ( @@ -118,30 +131,39 @@ const StepRuleActionsComponent: FC = ({ <>
- - {myStepData.throttle !== stepActionsDefaultValue.throttle && ( - <> - + + + {myStepData.throttle !== stepActionsDefaultValue.throttle ? ( + <> + + + + + + ) : ( - - - )} - + )} + +
diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index bc3b0dfe720bc..e63c3b1e36c54 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -4,12 +4,95 @@ * you may not use this file except in compliance with the Elastic License. */ +import mustache from 'mustache'; +import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; -import { FormSchema } from '../../../../../shared_imports'; +import { + ActionTypeModel, + IErrorObject, + AlertAction, +} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; // eslint-disable-line @kbn/eslint/no-restricted-paths +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; +import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; -export const schema: FormSchema = { - actions: {}, +const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + +const isUuidv4 = (id: string) => !!id.match(UUID_V4_REGEX); + +const getActionTypeName = (actionTypeId: string) => startCase(actionTypeId.split('.')[1]); + +const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( + ...data: Parameters +): ReturnType> | undefined => { + console.error('validation', data); + const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; + + const errors = value.reduce((acc, actionItem) => { + const actionTypeName = getActionTypeName(actionItem.actionTypeId); + + const errorsArray = []; + + if (!isUuidv4(actionItem.id)) { + errorsArray.push('No connector selected'); + } else { + const actionErrors: { errors: IErrorObject } = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + + if (Object.values(actionErrors.errors).length) { + // @ts-ignore + const filteredObjects: Array = Object.values(actionErrors.errors).filter( + item => isString(item) || isArray(item) + ); + const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + + if (uniqActionErrors.length) { + errorsArray.push(uniqActionErrors); + } + } + + Object.entries(actionItem.params).forEach(([paramKey, paramValue]) => { + if (!isString(paramValue)) return; + try { + mustache.render(paramValue, {}); + } catch (e) { + errorsArray.push(`${startCase(paramKey)} is not valid mustache template`); + } + }); + } + + if (errorsArray.length) { + const errorsListItems = errorsArray.map(error => `* ${error}`); + + return [...acc, `\n\n**${actionTypeName}:**\n${errorsListItems}`]; + } + + return acc; + }, [] as string[]); + + if (errors.length) { + return { + code: 'ERR_FIELD_FORMAT', + path, + message: `${errors.join('\n')}`, + }; + } +}; + +export const getSchema = ({ + actionTypeRegistry, +}: { + actionTypeRegistry: TypeRegistry; +}): FormSchema => ({ + actions: { + validations: [ + { + validator: validateRuleActionsField(actionTypeRegistry), + }, + ], + }, enabled: {}, kibanaSiemAppUrl: {}, throttle: { @@ -27,4 +110,4 @@ export const schema: FormSchema = { } ), }, -}; +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx index 0cf15c41a0f91..c176e8517d722 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx @@ -15,6 +15,7 @@ import { SelectField } from '../../../../../shared_imports'; export const THROTTLE_OPTIONS = [ { value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' }, { value: NOTIFICATION_THROTTLE_RULE, text: 'On each rule execution' }, + { value: '7m', text: '7m' }, { value: '1h', text: 'Hourly' }, { value: '1d', text: 'Daily' }, { value: '7d', text: 'Weekly' }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index e4ad53de742d6..b3de561ef8f13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -66,6 +66,10 @@ export const rulesNotificationAlertType = ({ kibanaSiemAppUrl: ruleAlertParams.meta?.kibana_siem_app_url, }); + logger.warn('notification'); + logger.warn(`signalsCount ${signalsCount}`); + logger.warn(resultsLink); + logger.info( `Found ${signalsCount} signals using signal rule name: "${ruleParams.name}", id: "${params.ruleAlertId}", rule_id: "${ruleParams.ruleId}" in "${ruleParams.outputIndex}" index` ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 0357f906f8035..e2a09ac3f32c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -250,14 +250,19 @@ export const signalRulesAlertType = ({ result.searchAfterTimes.push(makeFloatString(end - start)); } + logger.warn('rule execution'); + logger.warn(JSON.stringify(result, null, 2)); + logger.warn(`actions ${actions.length}`); + const notificationRuleParams: NotificationRuleTypeParams = { + ...ruleParams, + name, + id: savedObject.id, + }; + + logger.warn(`notificationRuleParams ${JSON.stringify(notificationRuleParams, null, 2)}`); + if (result.success) { if (actions.length) { - const notificationRuleParams: NotificationRuleTypeParams = { - ...ruleParams, - name, - id: savedObject.id, - }; - const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); const toInMs = parseScheduleDates('now')?.format('x'); @@ -267,20 +272,21 @@ export const signalRulesAlertType = ({ id: savedObject.id, kibanaSiemAppUrl: meta?.kibana_siem_app_url, }); + logger.warn(resultsLink); logger.info( buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) ); - if (result.createdSignalsCount) { - const alertInstance = services.alertInstanceFactory(alertId); - scheduleNotificationActions({ - alertInstance, - signalsCount: result.createdSignalsCount, - resultsLink, - ruleParams: notificationRuleParams, - }); - } + // if (result.createdSignalsCount) { + const alertInstance = services.alertInstanceFactory(alertId); + scheduleNotificationActions({ + alertInstance, + signalsCount: result.createdSignalsCount, + resultsLink, + ruleParams: notificationRuleParams, + }); + // } } logger.debug(buildRuleMessage('[+] Signal Rule execution completed.')); diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index e51ef3f67bd65..bf6fff05c79f6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -102,6 +102,8 @@ async function slackExecutor( const { webhookUrl } = secrets; const { message } = params; + console.error('slackExecutor', message); + try { const webhook = new IncomingWebhook(webhookUrl); result = await webhook.send(message); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx index d99362c618356..2fc07421c4761 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx @@ -74,6 +74,32 @@ export function getActionType(): ActionTypeModel { ) ); } + if (actionParams.timestamp?.length) { + let date; + try { + date = Date.parse(actionParams.timestamp); + } catch (err) { + errors.summary.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestampText', + { + defaultMessage: 'Timestamp is not valid Date format.', + } + ) + ); + } + + if (date && isNaN(date)) { + errors.summary.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestampText', + { + defaultMessage: 'Timestamp is not valid Date format.', + } + ) + ); + } + } return validationResult; }, actionConnectorFields: PagerDutyActionConnectorFields, From c19d940001a11947bbee49bbced27ba576cc2a1d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 13 Apr 2020 04:03:08 +0200 Subject: [PATCH 02/16] WIP --- .../components/step_rule_actions/schema.tsx | 86 ++++++++++++------- 1 file changed, 54 insertions(+), 32 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index e63c3b1e36c54..bfafb89b34300 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -8,6 +8,7 @@ import mustache from 'mustache'; import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; +import { SavedObjectAttribute } from 'kibana/public'; import { ActionTypeModel, IErrorObject, @@ -23,6 +24,57 @@ const isUuidv4 = (id: string) => !!id.match(UUID_V4_REGEX); const getActionTypeName = (actionTypeId: string) => startCase(actionTypeId.split('.')[1]); +const validateMustache = (params: Record) => { + const errors: string[] = []; + Object.entries(params).forEach(([paramKey, paramValue]) => { + if (!isString(paramValue)) return; + try { + mustache.render(paramValue, {}); + } catch (e) { + errors.push(`${startCase(paramKey)} is not valid mustache template`); + } + }); + + return errors; +}; + +const validateActionParams = ( + actionItem: AlertAction, + actionTypeRegistry: TypeRegistry +): string[] => { + const actionErrors: { errors: IErrorObject } = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + const actionErrorsValues = Object.values(actionErrors.errors); + + if (actionErrorsValues.length) { + // @ts-ignore + const filteredObjects: Array = actionErrorsValues.filter( + item => isString(item) || isArray(item) + ); + const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + + if (uniqActionErrors.length) { + return uniqActionErrors; + } + } + + return []; +}; + +const validateSingleAction = ( + actionItem: AlertAction, + actionTypeRegistry: TypeRegistry +): string[] => { + if (!isUuidv4(actionItem.id)) { + return ['No connector selected']; + } + const actionParamsErrors = validateActionParams(actionItem, actionTypeRegistry); + const mustacheErrors = validateMustache(actionItem.params); + + return [...actionParamsErrors, ...mustacheErrors]; +}; + const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( ...data: Parameters ): ReturnType> | undefined => { @@ -30,40 +82,10 @@ const validateRuleActionsField = (actionTypeRegistry: TypeRegistry { - const actionTypeName = getActionTypeName(actionItem.actionTypeId); - - const errorsArray = []; - - if (!isUuidv4(actionItem.id)) { - errorsArray.push('No connector selected'); - } else { - const actionErrors: { errors: IErrorObject } = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); - - if (Object.values(actionErrors.errors).length) { - // @ts-ignore - const filteredObjects: Array = Object.values(actionErrors.errors).filter( - item => isString(item) || isArray(item) - ); - const uniqActionErrors = uniq(flattenDeep(filteredObjects)); - - if (uniqActionErrors.length) { - errorsArray.push(uniqActionErrors); - } - } - - Object.entries(actionItem.params).forEach(([paramKey, paramValue]) => { - if (!isString(paramValue)) return; - try { - mustache.render(paramValue, {}); - } catch (e) { - errorsArray.push(`${startCase(paramKey)} is not valid mustache template`); - } - }); - } + const errorsArray = validateSingleAction(actionItem, actionTypeRegistry); if (errorsArray.length) { + const actionTypeName = getActionTypeName(actionItem.actionTypeId); const errorsListItems = errorsArray.map(error => `* ${error}`); return [...acc, `\n\n**${actionTypeName}:**\n${errorsListItems}`]; From cbf97d69031f6b26771607c3c59cb48746183d4e Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 13 Apr 2020 11:14:17 +0200 Subject: [PATCH 03/16] cleanup --- .../components/rule_actions_field/index.tsx | 4 +-- .../rule_actions_field/translations.tsx | 14 ++++++++ .../components/step_rule_actions/index.tsx | 2 -- .../components/step_rule_actions/schema.tsx | 7 ++-- .../step_rule_actions/translations.tsx | 23 ++++++++++-- .../step_schedule_rule/translations.tsx | 21 ----------- .../rules_notification_alert_type.ts | 4 --- .../signals/signal_rule_alert_type.ts | 36 ++++++++----------- .../server/builtin_action_types/slack.ts | 2 -- .../translations/translations/ja-JP.json | 4 +-- .../translations/translations/zh-CN.json | 4 +-- .../builtin_action_types/pagerduty.tsx | 26 -------------- 12 files changed, 60 insertions(+), 87 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/translations.tsx delete mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index 00099cecdf0f6..e7f296a4d8cda 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -21,6 +21,7 @@ import { import { AlertAction } from '../../../../../../../../../plugins/alerting/common'; import { useKibana } from '../../../../../lib/kibana'; import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../../common/constants'; +import { FORM_ERRORS_TITLE } from './translations'; type ThrottleSelectField = typeof SelectField; @@ -35,7 +36,6 @@ const FieldErrorsContainer = styled.div` `; export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }) => { - console.error('error', field); const [fieldErrors, setFieldErrors] = useState(null); const [supportedActionTypes, setSupportedActionTypes] = useState(); const { @@ -112,7 +112,7 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables {fieldErrors ? ( <> - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/translations.tsx new file mode 100644 index 0000000000000..8bf8e39685dfd --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/translations.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const FORM_ERRORS_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.createRule.ruleActionsField.ruleActionsFormErrorsTitle', + { + defaultMessage: 'Please fix issues listed below', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx index 6c6435f8d06e5..fda259f4ad7f6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx @@ -121,8 +121,6 @@ const StepRuleActionsComponent: FC = ({ [isLoading, updateThrottle] ); - console.error('step', myStepData, defaultValues, form, form.getErrors()); - return isReadOnlyView && myStepData != null ? ( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index bfafb89b34300..8e085bcaf35b2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -17,6 +17,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; +import * as I18n from './translations'; const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; @@ -31,7 +32,7 @@ const validateMustache = (params: Record) => { try { mustache.render(paramValue, {}); } catch (e) { - errors.push(`${startCase(paramKey)} is not valid mustache template`); + errors.push(I18n.INVALID_MUSTACHE_TEMPLATE(paramKey)); } }); @@ -67,8 +68,9 @@ const validateSingleAction = ( actionTypeRegistry: TypeRegistry ): string[] => { if (!isUuidv4(actionItem.id)) { - return ['No connector selected']; + return [I18n.NO_CONNECTOR_SELECTED]; } + const actionParamsErrors = validateActionParams(actionItem, actionTypeRegistry); const mustacheErrors = validateMustache(actionItem.params); @@ -78,7 +80,6 @@ const validateSingleAction = ( const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( ...data: Parameters ): ReturnType> | undefined => { - console.error('validation', data); const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; const errors = value.reduce((acc, actionItem) => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx index 67bcc1af8150b..31a9e63c39461 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx @@ -5,17 +5,36 @@ */ import { i18n } from '@kbn/i18n'; +import { startCase } from 'lodash/fp'; export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( - 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle', + 'xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle', { defaultMessage: 'Create rule without activating it', } ); export const COMPLETE_WITH_ACTIVATING = i18n.translate( - 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle', + 'xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle', { defaultMessage: 'Create & activate rule', } ); + +export const NO_CONNECTOR_SELECTED = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepRuleActions.noConnectorSelectedErrorText', + { + defaultMessage: 'No connector selected', + } +); + +export const INVALID_MUSTACHE_TEMPLATE = (paramKey: string) => + i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepRuleActions.invalidMustacheTemplateErrorText', + { + defaultMessage: '{key} is not valid mustache template', + values: { + key: startCase(paramKey), + }, + } + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx deleted file mode 100644 index 67bcc1af8150b..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/translations.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const COMPLETE_WITHOUT_ACTIVATING = i18n.translate( - 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle', - { - defaultMessage: 'Create rule without activating it', - } -); - -export const COMPLETE_WITH_ACTIVATING = i18n.translate( - 'xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle', - { - defaultMessage: 'Create & activate rule', - } -); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts index b3de561ef8f13..e4ad53de742d6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/notifications/rules_notification_alert_type.ts @@ -66,10 +66,6 @@ export const rulesNotificationAlertType = ({ kibanaSiemAppUrl: ruleAlertParams.meta?.kibana_siem_app_url, }); - logger.warn('notification'); - logger.warn(`signalsCount ${signalsCount}`); - logger.warn(resultsLink); - logger.info( `Found ${signalsCount} signals using signal rule name: "${ruleParams.name}", id: "${params.ruleAlertId}", rule_id: "${ruleParams.ruleId}" in "${ruleParams.outputIndex}" index` ); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index e2a09ac3f32c1..0357f906f8035 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -250,19 +250,14 @@ export const signalRulesAlertType = ({ result.searchAfterTimes.push(makeFloatString(end - start)); } - logger.warn('rule execution'); - logger.warn(JSON.stringify(result, null, 2)); - logger.warn(`actions ${actions.length}`); - const notificationRuleParams: NotificationRuleTypeParams = { - ...ruleParams, - name, - id: savedObject.id, - }; - - logger.warn(`notificationRuleParams ${JSON.stringify(notificationRuleParams, null, 2)}`); - if (result.success) { if (actions.length) { + const notificationRuleParams: NotificationRuleTypeParams = { + ...ruleParams, + name, + id: savedObject.id, + }; + const fromInMs = parseScheduleDates(`now-${interval}`)?.format('x'); const toInMs = parseScheduleDates('now')?.format('x'); @@ -272,21 +267,20 @@ export const signalRulesAlertType = ({ id: savedObject.id, kibanaSiemAppUrl: meta?.kibana_siem_app_url, }); - logger.warn(resultsLink); logger.info( buildRuleMessage(`Found ${result.createdSignalsCount} signals for notification.`) ); - // if (result.createdSignalsCount) { - const alertInstance = services.alertInstanceFactory(alertId); - scheduleNotificationActions({ - alertInstance, - signalsCount: result.createdSignalsCount, - resultsLink, - ruleParams: notificationRuleParams, - }); - // } + if (result.createdSignalsCount) { + const alertInstance = services.alertInstanceFactory(alertId); + scheduleNotificationActions({ + alertInstance, + signalsCount: result.createdSignalsCount, + resultsLink, + ruleParams: notificationRuleParams, + }); + } } logger.debug(buildRuleMessage('[+] Signal Rule execution completed.')); diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index bf6fff05c79f6..e51ef3f67bd65 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -102,8 +102,6 @@ async function slackExecutor( const { webhookUrl } = secrets; const { message } = params; - console.error('slackExecutor', message); - try { const webhook = new IncomingWebhook(webhookUrl); result = await webhook.send(message); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fe0c58e83e544..fa562598c6e1b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -13587,8 +13587,8 @@ "xpack.siem.detectionEngine.components.importRuleModal.overwriteDescription": "保存されたオブジェクトを同じルールIDで自動的に上書きします", "xpack.siem.detectionEngine.components.importRuleModal.selectRuleDescription": "インポートする SIEM ルール (検出エンジンビューからエクスポートしたもの) を選択します", "xpack.siem.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle": "{totalRules} {totalRules, plural, =1 {ルール} other {ルール}}を正常にインポートしました", - "xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "ルールの作成と有効化", - "xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "有効化せずにルールを作成", + "xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle": "ルールの作成と有効化", + "xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle": "有効化せずにルールを作成", "xpack.siem.detectionEngine.createRule.backToRulesDescription": "シグナル検出ルールに戻る", "xpack.siem.detectionEngine.createRule.editRuleButton": "編集", "xpack.siem.detectionEngine.createRule.filtersLabel": "フィルター", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index fd2a92c2c402f..b0da75f85ae73 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -13591,8 +13591,8 @@ "xpack.siem.detectionEngine.components.importRuleModal.overwriteDescription": "自动覆盖具有相同规则 ID 的已保存对象", "xpack.siem.detectionEngine.components.importRuleModal.selectRuleDescription": "选择要导入的 SIEM 规则(如从检测引擎视图导出的)", "xpack.siem.detectionEngine.components.importRuleModal.successfullyImportedRulesTitle": "已成功导入 {totalRules} 个{totalRules, plural, =1 {规则} other {规则}}", - "xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithActivatingTitle": "创建并激活规则", - "xpack.siem.detectionEngine.createRule. stepScheduleRule.completeWithoutActivatingTitle": "创建规则但不激活", + "xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithActivatingTitle": "创建并激活规则", + "xpack.siem.detectionEngine.createRule.stepScheduleRule.completeWithoutActivatingTitle": "创建规则但不激活", "xpack.siem.detectionEngine.createRule.backToRulesDescription": "返回到信号检测规则", "xpack.siem.detectionEngine.createRule.editRuleButton": "编辑", "xpack.siem.detectionEngine.createRule.filtersLabel": "筛选", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx index 2fc07421c4761..d99362c618356 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/pagerduty.tsx @@ -74,32 +74,6 @@ export function getActionType(): ActionTypeModel { ) ); } - if (actionParams.timestamp?.length) { - let date; - try { - date = Date.parse(actionParams.timestamp); - } catch (err) { - errors.summary.push( - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestampText', - { - defaultMessage: 'Timestamp is not valid Date format.', - } - ) - ); - } - - if (date && isNaN(date)) { - errors.summary.push( - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.invalidTimestampText', - { - defaultMessage: 'Timestamp is not valid Date format.', - } - ) - ); - } - } return validationResult; }, actionConnectorFields: PagerDutyActionConnectorFields, From 46e828f8516d70c400f92aea63f1d7f13f8ea299 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 13 Apr 2020 11:44:54 +0200 Subject: [PATCH 04/16] add support for custom throttle values --- .../components/step_rule_actions/index.tsx | 18 +++++++++++++++++- .../components/throttle_select_field/index.tsx | 1 - 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx index fda259f4ad7f6..57f843251cb11 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.tsx @@ -12,6 +12,7 @@ import { EuiButton, EuiSpacer, } from '@elastic/eui'; +import { findIndex } from 'lodash/fp'; import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react'; import deepEqual from 'fast-deep-equal'; @@ -41,6 +42,15 @@ const stepActionsDefaultValue = { const GhostFormField = () => <>; +const getThrottleOptions = (throttle?: string | null) => { + // Add support for throttle options set by the API + if (throttle && findIndex(['value', throttle], THROTTLE_OPTIONS) < 0) { + return [...THROTTLE_OPTIONS, { value: throttle, text: throttle }]; + } + + return THROTTLE_OPTIONS; +}; + const StepRuleActionsComponent: FC = ({ addPadding = false, defaultValues, @@ -107,6 +117,12 @@ const StepRuleActionsComponent: FC = ({ setMyStepData, ]); + const throttleOptions = useMemo(() => { + const throttle = myStepData.throttle; + + return getThrottleOptions(throttle); + }, [myStepData]); + const throttleFieldComponentProps = useMemo( () => ({ idAria: 'detectionEngineStepRuleActionsThrottle', @@ -115,7 +131,7 @@ const StepRuleActionsComponent: FC = ({ hasNoInitialSelection: false, handleChange: updateThrottle, euiFieldProps: { - options: THROTTLE_OPTIONS, + options: throttleOptions, }, }), [isLoading, updateThrottle] diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx index c176e8517d722..0cf15c41a0f91 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/throttle_select_field/index.tsx @@ -15,7 +15,6 @@ import { SelectField } from '../../../../../shared_imports'; export const THROTTLE_OPTIONS = [ { value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' }, { value: NOTIFICATION_THROTTLE_RULE, text: 'On each rule execution' }, - { value: '7m', text: '7m' }, { value: '1h', text: 'Hourly' }, { value: '1d', text: 'Daily' }, { value: '7d', text: 'Weekly' }, From 963d74cb6ced361fbc3f5ae2ae34b5a1b9737df3 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 13 Apr 2020 14:38:33 +0200 Subject: [PATCH 05/16] add unit tests --- .../siem/public/mock/test_providers.tsx | 27 ++++ .../rule_actions_field/index.test.tsx | 33 +++++ .../step_rule_actions/schema.test.tsx | 118 +++++++++++++++ .../components/step_rule_actions/schema.tsx | 58 +------- .../step_rule_actions/test_utils.ts | 16 +++ .../step_rule_actions/utils.test.ts | 136 ++++++++++++++++++ .../components/step_rule_actions/utils.ts | 69 +++++++++ 7 files changed, 405 insertions(+), 52 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index c7692755c1330..a7fa11648cda4 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -17,6 +17,7 @@ import { Store } from 'redux'; import { BehaviorSubject } from 'rxjs'; import { ThemeProvider } from 'styled-components'; +import { FieldHook, useForm } from '../shared_imports'; import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; import { createKibanaContextProviderMock } from './kibana_react'; @@ -91,3 +92,29 @@ const TestProviderWithoutDragAndDropComponent: React.FC = ({ ); export const TestProviderWithoutDragAndDrop = React.memo(TestProviderWithoutDragAndDropComponent); + +export const useFormFieldMock = (options?: Partial): FieldHook => { + const { form } = useForm(); + + return { + path: 'path', + type: 'type', + value: [], + isPristine: false, + isValidating: false, + isValidated: false, + isChangingValue: false, + form, + errors: [], + isValid: true, + getErrorsMessages: jest.fn(), + onChange: jest.fn(), + setValue: jest.fn(), + setErrors: jest.fn(), + clearErrors: jest.fn(), + validate: jest.fn(), + reset: jest.fn(), + __serializeOutput: jest.fn(), + ...options, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx new file mode 100644 index 0000000000000..4cfad36b2933f --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { RuleActionsField } from './index'; +import { useKibana } from '../../../../../lib/kibana'; +import { useFormFieldMock } from '../../../../../mock'; +jest.mock('../../../../../lib/kibana'); + +describe('RuleActionsField', () => { + it('should not render ActionForm is no actions are supported', () => { + (useKibana as jest.Mock).mockReturnValue({ + services: { + triggers_actions_ui: { + actionTypeRegistry: {}, + }, + }, + }); + const Component = () => { + const field = useFormFieldMock(); + + return ; + }; + const wrapper = shallow(); + + expect(wrapper.dive().find('ActionForm')).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx new file mode 100644 index 0000000000000..08767fb43cb89 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { validateSingleAction, validateRuleActionsField } from './schema'; +import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; +import { getActionTypeRegistryMock } from './test_utils'; +import { FormHook } from '../../../../../shared_imports'; +jest.mock('./utils'); + +describe('stepRuleActions schema', () => { + const actionTypeRegistryMock = getActionTypeRegistryMock(jest.fn()); + + describe('validateSingleAction', () => { + it('should validate single action', () => { + (isUuidv4 as jest.Mock).mockReturnValue(true); + (validateActionParams as jest.Mock).mockReturnValue([]); + (validateMustache as jest.Mock).mockReturnValue([]); + + expect( + validateSingleAction( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + actionTypeRegistryMock + ) + ).toHaveLength(0); + }); + + it('should validate single action with invalid mustache template', () => { + (isUuidv4 as jest.Mock).mockReturnValue(true); + (validateActionParams as jest.Mock).mockReturnValue([]); + (validateMustache as jest.Mock).mockReturnValue(['Message is not valid mustache template']); + + const errors = validateSingleAction( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: { + message: '{{{mustache}}', + }, + }, + actionTypeRegistryMock + ); + + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual('Message is not valid mustache template'); + }); + + it('should validate single action with incorrect id', () => { + (isUuidv4 as jest.Mock).mockReturnValue(false); + (validateMustache as jest.Mock).mockReturnValue([]); + (validateActionParams as jest.Mock).mockReturnValue([]); + + const errors = validateSingleAction( + { + id: '823d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + actionTypeRegistryMock + ); + expect(errors).toHaveLength(1); + expect(errors[0]).toEqual('No connector selected'); + }); + }); + + describe('validateRuleActionsField', () => { + it('should validate rule actions field', () => { + const validator = validateRuleActionsField(actionTypeRegistryMock); + + const result = validator({ + path: '', + value: [], + form: {} as FormHook, + formData: jest.fn(), + errors: [], + }); + + expect(result).toEqual(undefined); + }); + + it('should validate incorrect rule actions field', () => { + (getActionTypeName as jest.Mock).mockReturnValue('Slack'); + const validator = validateRuleActionsField(actionTypeRegistryMock); + + const result = validator({ + path: '', + value: [ + { + id: '3', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + ], + form: {} as FormHook, + formData: jest.fn(), + errors: [], + }); + + expect(result).toEqual({ + code: 'ERR_FIELD_FORMAT', + message: ` +**Slack:** +* No connector selected`, + path: '', + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index 8e085bcaf35b2..805eaca918ea3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -4,66 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import mustache from 'mustache'; -import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { i18n } from '@kbn/i18n'; -import { SavedObjectAttribute } from 'kibana/public'; import { ActionTypeModel, - IErrorObject, AlertAction, -} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; // eslint-disable-line @kbn/eslint/no-restricted-paths + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; import * as I18n from './translations'; +import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; -const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; - -const isUuidv4 = (id: string) => !!id.match(UUID_V4_REGEX); - -const getActionTypeName = (actionTypeId: string) => startCase(actionTypeId.split('.')[1]); - -const validateMustache = (params: Record) => { - const errors: string[] = []; - Object.entries(params).forEach(([paramKey, paramValue]) => { - if (!isString(paramValue)) return; - try { - mustache.render(paramValue, {}); - } catch (e) { - errors.push(I18n.INVALID_MUSTACHE_TEMPLATE(paramKey)); - } - }); - - return errors; -}; - -const validateActionParams = ( - actionItem: AlertAction, - actionTypeRegistry: TypeRegistry -): string[] => { - const actionErrors: { errors: IErrorObject } = actionTypeRegistry - .get(actionItem.actionTypeId) - ?.validateParams(actionItem.params); - const actionErrorsValues = Object.values(actionErrors.errors); - - if (actionErrorsValues.length) { - // @ts-ignore - const filteredObjects: Array = actionErrorsValues.filter( - item => isString(item) || isArray(item) - ); - const uniqActionErrors = uniq(flattenDeep(filteredObjects)); - - if (uniqActionErrors.length) { - return uniqActionErrors; - } - } - - return []; -}; - -const validateSingleAction = ( +export const validateSingleAction = ( actionItem: AlertAction, actionTypeRegistry: TypeRegistry ): string[] => { @@ -77,7 +31,7 @@ const validateSingleAction = ( return [...actionParamsErrors, ...mustacheErrors]; }; -const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( +export const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( ...data: Parameters ): ReturnType> | undefined => { const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; @@ -89,7 +43,7 @@ const validateRuleActionsField = (actionTypeRegistry: TypeRegistry `* ${error}`); - return [...acc, `\n\n**${actionTypeName}:**\n${errorsListItems}`]; + return [...acc, `\n**${actionTypeName}:**\n${errorsListItems}`]; } return acc; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts new file mode 100644 index 0000000000000..e58d61fb18c38 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ActionTypeModel } from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; + +export const getActionTypeRegistryMock = (validateParamsMock: jest.Mock) => + (({ + get: jest.fn().mockImplementation(() => ({ + validateParams: validateParamsMock, + })), + } as unknown) as TypeRegistry); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts new file mode 100644 index 0000000000000..3458921558e82 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getActionTypeRegistryMock } from './test_utils'; +import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; + +describe('stepRuleActions utils', () => { + describe('isUuidv4', () => { + it('should validate proper uuid v4 value', () => { + expect(isUuidv4('817b8bca-91d1-4729-8ee1-3a83aaafd9d4')).toEqual(true); + }); + + it('should validate incorrect uuid v4 value', () => { + expect(isUuidv4('ad9d4')).toEqual(false); + }); + }); + + describe('getActionTypeName', () => { + it('should return capitalized action type name', () => { + expect(getActionTypeName('.slack')).toEqual('Slack'); + }); + + it('should return empty string actionTypeId had improper format', () => { + expect(getActionTypeName('slack')).toEqual(''); + }); + }); + + describe('validateMustache', () => { + it('should validate mustache template', () => { + expect( + validateMustache({ + message: 'Mustache Template {{variable}}', + }) + ).toHaveLength(0); + }); + + it('should validate incorrect mustache template', () => { + expect( + validateMustache({ + message: 'Mustache Template {{{variable}}', + }) + ).toHaveLength(1); + }); + }); + + describe('validateActionParams', () => { + let validateParamsMock: jest.Mock; + + beforeAll(() => { + validateParamsMock = jest.fn(); + }); + + it('should validate action params', () => { + validateParamsMock.mockReturnValue({ errors: [] }); + const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); + + expect( + validateActionParams( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: { + message: 'Message', + }, + }, + actionTypeRegistryMock + ) + ).toHaveLength(0); + }); + + it('should validate incorrect action params', () => { + validateParamsMock.mockReturnValue({ + errors: ['Message is required'], + }); + const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); + + expect( + validateActionParams( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + actionTypeRegistryMock + ) + ).toHaveLength(1); + }); + + it('should validate incorrect action params and filter error objects', () => { + validateParamsMock.mockReturnValue({ + errors: [ + { + message: 'Message is required', + }, + ], + }); + const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); + + expect( + validateActionParams( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + actionTypeRegistryMock + ) + ).toHaveLength(0); + }); + + it('should validate incorrect action params and filter duplicated errors', () => { + validateParamsMock.mockReturnValue({ + errors: ['Message is required', 'Message is required', 'Message is required'], + }); + const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); + + expect( + validateActionParams( + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + actionTypeRegistryMock + ) + ).toHaveLength(1); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts new file mode 100644 index 0000000000000..ed43f37d1f397 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import mustache from 'mustache'; +import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; + +import { + ActionTypeModel, + AlertAction, + IErrorObject, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; +import * as I18n from './translations'; + +const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; + +export const isUuidv4 = (id: AlertAction['id']) => !!id.match(UUID_V4_REGEX); + +export const getActionTypeName = (actionTypeId: AlertAction['actionTypeId']) => { + if (!actionTypeId) return ''; + const actionType = actionTypeId.split('.')[1]; + + if (!actionType) return ''; + + return startCase(actionType); +}; + +export const validateMustache = (params: AlertAction['params']) => { + const errors: string[] = []; + Object.entries(params).forEach(([paramKey, paramValue]) => { + if (!isString(paramValue)) return; + try { + mustache.render(paramValue, {}); + } catch (e) { + errors.push(I18n.INVALID_MUSTACHE_TEMPLATE(paramKey)); + } + }); + + return errors; +}; + +export const validateActionParams = ( + actionItem: AlertAction, + actionTypeRegistry: TypeRegistry +): string[] => { + const actionErrors: { errors: IErrorObject } = actionTypeRegistry + .get(actionItem.actionTypeId) + ?.validateParams(actionItem.params); + const actionErrorsValues = Object.values(actionErrors.errors); + + if (actionErrorsValues.length) { + // @ts-ignore + const filteredObjects: Array = actionErrorsValues.filter( + item => isString(item) || isArray(item) + ); + const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + + if (uniqActionErrors.length) { + return uniqActionErrors; + } + } + + return []; +}; From d22ff59ecc33b7f25d5272758fca87aa29d39b4a Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 16:17:17 +0200 Subject: [PATCH 06/16] PR comments --- .../rule_actions_field/index.test.tsx | 2 +- .../components/step_rule_actions/schema.tsx | 15 ++++----- .../step_rule_actions/translations.tsx | 4 +-- .../components/step_rule_actions/utils.ts | 32 +++++++++---------- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx index 4cfad36b2933f..f28c1f016e356 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx @@ -13,7 +13,7 @@ import { useFormFieldMock } from '../../../../../mock'; jest.mock('../../../../../lib/kibana'); describe('RuleActionsField', () => { - it('should not render ActionForm is no actions are supported', () => { + it('should not render ActionForm if no actions are supported', () => { (useKibana as jest.Mock).mockReturnValue({ services: { triggers_actions_ui: { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index 7723b04245329..75d5cce67d586 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -9,19 +9,16 @@ import { i18n } from '@kbn/i18n'; import { - ActionTypeModel, AlertAction, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; + TriggersAndActionsUIPublicPluginStart, +} from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; import * as I18n from './translations'; import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; export const validateSingleAction = ( actionItem: AlertAction, - actionTypeRegistry: TypeRegistry + actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] ): string[] => { if (!isUuidv4(actionItem.id)) { return [I18n.NO_CONNECTOR_SELECTED]; @@ -33,7 +30,9 @@ export const validateSingleAction = ( return [...actionParamsErrors, ...mustacheErrors]; }; -export const validateRuleActionsField = (actionTypeRegistry: TypeRegistry) => ( +export const validateRuleActionsField = ( + actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] +) => ( ...data: Parameters ): ReturnType> | undefined => { const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; @@ -63,7 +62,7 @@ export const validateRuleActionsField = (actionTypeRegistry: TypeRegistry; + actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry']; }): FormSchema => ({ actions: { validations: [ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx index 31a9e63c39461..d0c990df81ffe 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/translations.tsx @@ -22,7 +22,7 @@ export const COMPLETE_WITH_ACTIVATING = i18n.translate( ); export const NO_CONNECTOR_SELECTED = i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepRuleActions.noConnectorSelectedErrorText', + 'xpack.siem.detectionEngine.createRule.stepRuleActions.noConnectorSelectedErrorMessage', { defaultMessage: 'No connector selected', } @@ -30,7 +30,7 @@ export const NO_CONNECTOR_SELECTED = i18n.translate( export const INVALID_MUSTACHE_TEMPLATE = (paramKey: string) => i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepRuleActions.invalidMustacheTemplateErrorText', + 'xpack.siem.detectionEngine.createRule.stepRuleActions.invalidMustacheTemplateErrorMessage', { defaultMessage: '{key} is not valid mustache template', values: { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts index ed43f37d1f397..a0459e42050a9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts @@ -8,13 +8,9 @@ import mustache from 'mustache'; import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { - ActionTypeModel, AlertAction, - IErrorObject, - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; + TriggersAndActionsUIPublicPluginStart, +} from '../../../../../../../../../plugins/triggers_actions_ui/public'; import * as I18n from './translations'; const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; @@ -46,22 +42,24 @@ export const validateMustache = (params: AlertAction['params']) => { export const validateActionParams = ( actionItem: AlertAction, - actionTypeRegistry: TypeRegistry + actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] ): string[] => { - const actionErrors: { errors: IErrorObject } = actionTypeRegistry + const actionErrors = actionTypeRegistry .get(actionItem.actionTypeId) ?.validateParams(actionItem.params); - const actionErrorsValues = Object.values(actionErrors.errors); - if (actionErrorsValues.length) { - // @ts-ignore - const filteredObjects: Array = actionErrorsValues.filter( - item => isString(item) || isArray(item) - ); - const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + if (actionErrors) { + const actionErrorsValues = Object.values(actionErrors.errors); - if (uniqActionErrors.length) { - return uniqActionErrors; + if (actionErrorsValues.length) { + const filteredObjects: Array = actionErrorsValues.filter( + item => isString(item) || isArray(item) + ) as Array; + const uniqActionErrors = uniq(flattenDeep(filteredObjects)); + + if (uniqActionErrors.length) { + return uniqActionErrors; + } } } From 6d4b340ff294a142a26faeb5dabbd71e31b7bb35 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 16:28:48 +0200 Subject: [PATCH 07/16] Fix actions value --- .../components/rule_actions_field/index.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index e7f296a4d8cda..e95458d686abe 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -44,13 +44,18 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables notifications, } = useKibana().services; + const actions: AlertAction[] = useMemo( + () => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []), + [field.value] + ); + const setActionIdByIndex = useCallback( (id: string, index: number) => { - const updatedActions = [...(field.value as Array>)]; + const updatedActions = [...(actions as Array>)]; updatedActions[index] = deepMerge(updatedActions[index], { id }); field.setValue(updatedActions); }, - [field] + [field.setValue, actions] ); const setAlertProperty = useCallback( @@ -61,11 +66,11 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables const setActionParamsProperty = useCallback( // eslint-disable-next-line @typescript-eslint/no-explicit-any (key: string, value: any, index: number) => { - const updatedActions = [...(field.value as AlertAction[])]; + const updatedActions = [...actions]; updatedActions[index].params[key] = value; field.setValue(updatedActions); }, - [field] + [field.setValue, actions] ); useEffect(() => { @@ -100,11 +105,6 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables setFieldErrors, ]); - const actions: AlertAction[] = useMemo( - () => (!isEmpty(field.value) ? (field.value as AlertAction[]) : []), - [field.value] - ); - if (!supportedActionTypes) return <>; return ( From d2ee58d0c5b780fd511e044bc4571f1283057625 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 16:38:47 +0200 Subject: [PATCH 08/16] hide errors callout when not needed --- .../rules/components/rule_actions_field/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index e95458d686abe..c9bc8900679bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -84,7 +84,7 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables }, []); useEffect(() => { - if (field.form.isSubmitting) { + if (field.form.isSubmitting || !field.errors.length) { return setFieldErrors(null); } if ( From f85acdd0eeff9a513e0ee34ada40461ebc83e087 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 16:43:03 +0200 Subject: [PATCH 09/16] fix types --- .../rules/components/rule_actions_field/index.tsx | 3 +-- x-pack/plugins/triggers_actions_ui/public/index.ts | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index c9bc8900679bb..c1aa5e5ea93a2 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -11,12 +11,11 @@ import deepMerge from 'deepmerge'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { loadActionTypes } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/lib/action_connector_api'; import { SelectField } from '../../../../../shared_imports'; import { ActionForm, ActionType, + loadActionTypes, } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { AlertAction } from '../../../../../../../../../plugins/alerting/common'; import { useKibana } from '../../../../../lib/kibana'; diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index 96645e856e418..e507b8e25a8ee 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -16,6 +16,7 @@ export { ConnectorAddFlyout, ConnectorEditFlyout, } from './application/sections/action_connector_form'; +export { loadActionTypes } from './application/lib/action_connector_api'; export function plugin(ctx: PluginInitializerContext) { return new Plugin(ctx); From 4c5ce1a27f4f2c43a5e74db6853e3d5f89ca8f62 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 17:53:32 +0200 Subject: [PATCH 10/16] cleanup types --- .../siem/public/mock/test_providers.tsx | 1 - .../step_rule_actions/index.test.tsx | 13 ++++++++- .../step_rule_actions/schema.test.tsx | 14 +++++----- .../components/step_rule_actions/schema.tsx | 10 +++---- .../step_rule_actions/test_utils.ts | 16 ----------- .../step_rule_actions/utils.test.ts | 28 +++++++++++-------- .../components/step_rule_actions/utils.ts | 4 +-- .../triggers_actions_ui/public/index.ts | 10 ++++++- 8 files changed, 51 insertions(+), 45 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index 698683b9683ed..952f7f51b63f2 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -17,7 +17,6 @@ import { Store } from 'redux'; import { BehaviorSubject } from 'rxjs'; import { ThemeProvider } from 'styled-components'; -import { FieldHook, useForm } from '../shared_imports'; import { createStore, State } from '../store'; import { mockGlobalState } from './global_state'; import { createKibanaContextProviderMock } from './kibana_react'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx index 69d118ba9f28e..3338905ea32ae 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/index.test.tsx @@ -9,7 +9,18 @@ import { shallow } from 'enzyme'; import { StepRuleActions } from './index'; -jest.mock('../../../../../lib/kibana'); +jest.mock('../../../../../lib/kibana', () => ({ + useKibana: jest.fn().mockReturnValue({ + services: { + application: { + getUrlForApp: jest.fn(), + }, + triggers_actions_ui: { + actionTypeRegistry: jest.fn(), + }, + }, + }), +})); describe('StepRuleActions', () => { it('renders correctly', () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx index 08767fb43cb89..3cc83133d9291 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx @@ -6,12 +6,12 @@ import { validateSingleAction, validateRuleActionsField } from './schema'; import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; -import { getActionTypeRegistryMock } from './test_utils'; +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { FormHook } from '../../../../../shared_imports'; jest.mock('./utils'); describe('stepRuleActions schema', () => { - const actionTypeRegistryMock = getActionTypeRegistryMock(jest.fn()); + const actionTypeRegistry = actionTypeRegistryMock.create(); describe('validateSingleAction', () => { it('should validate single action', () => { @@ -27,7 +27,7 @@ describe('stepRuleActions schema', () => { actionTypeId: '.slack', params: {}, }, - actionTypeRegistryMock + actionTypeRegistry ) ).toHaveLength(0); }); @@ -46,7 +46,7 @@ describe('stepRuleActions schema', () => { message: '{{{mustache}}', }, }, - actionTypeRegistryMock + actionTypeRegistry ); expect(errors).toHaveLength(1); @@ -65,7 +65,7 @@ describe('stepRuleActions schema', () => { actionTypeId: '.slack', params: {}, }, - actionTypeRegistryMock + actionTypeRegistry ); expect(errors).toHaveLength(1); expect(errors[0]).toEqual('No connector selected'); @@ -74,7 +74,7 @@ describe('stepRuleActions schema', () => { describe('validateRuleActionsField', () => { it('should validate rule actions field', () => { - const validator = validateRuleActionsField(actionTypeRegistryMock); + const validator = validateRuleActionsField(actionTypeRegistry); const result = validator({ path: '', @@ -89,7 +89,7 @@ describe('stepRuleActions schema', () => { it('should validate incorrect rule actions field', () => { (getActionTypeName as jest.Mock).mockReturnValue('Slack'); - const validator = validateRuleActionsField(actionTypeRegistryMock); + const validator = validateRuleActionsField(actionTypeRegistry); const result = validator({ path: '', diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx index 75d5cce67d586..f8dc266dea810 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { AlertAction, - TriggersAndActionsUIPublicPluginStart, + ActionTypeRegistryContract, } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { FormSchema, FormData, ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; import * as I18n from './translations'; @@ -18,7 +18,7 @@ import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } f export const validateSingleAction = ( actionItem: AlertAction, - actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] + actionTypeRegistry: ActionTypeRegistryContract ): string[] => { if (!isUuidv4(actionItem.id)) { return [I18n.NO_CONNECTOR_SELECTED]; @@ -30,9 +30,7 @@ export const validateSingleAction = ( return [...actionParamsErrors, ...mustacheErrors]; }; -export const validateRuleActionsField = ( - actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] -) => ( +export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryContract) => ( ...data: Parameters ): ReturnType> | undefined => { const [{ value, path }] = data as [{ value: AlertAction[]; path: string }]; @@ -62,7 +60,7 @@ export const validateRuleActionsField = ( export const getSchema = ({ actionTypeRegistry, }: { - actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry']; + actionTypeRegistry: ActionTypeRegistryContract; }): FormSchema => ({ actions: { validations: [ diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts deleted file mode 100644 index e58d61fb18c38..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/test_utils.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ActionTypeModel } from '../../../../../../../../../plugins/triggers_actions_ui/public/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { TypeRegistry } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/type_registry'; - -export const getActionTypeRegistryMock = (validateParamsMock: jest.Mock) => - (({ - get: jest.fn().mockImplementation(() => ({ - validateParams: validateParamsMock, - })), - } as unknown) as TypeRegistry); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts index 3458921558e82..cc24ae66dc16c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getActionTypeRegistryMock } from './test_utils'; +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; describe('stepRuleActions utils', () => { @@ -47,15 +47,24 @@ describe('stepRuleActions utils', () => { }); describe('validateActionParams', () => { - let validateParamsMock: jest.Mock; + const validateParamsMock = jest.fn(); + const actionTypeRegistry = actionTypeRegistryMock.create(); beforeAll(() => { - validateParamsMock = jest.fn(); + const actionMock = { + id: 'id', + iconClass: 'iconClass', + validateParams: validateParamsMock, + selectMessage: 'message', + validateConnector: jest.fn(), + actionConnectorFields: () => null, + actionParamsFields: [], + }; + actionTypeRegistry.get.mockReturnValue(actionMock); }); it('should validate action params', () => { validateParamsMock.mockReturnValue({ errors: [] }); - const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); expect( validateActionParams( @@ -67,7 +76,7 @@ describe('stepRuleActions utils', () => { message: 'Message', }, }, - actionTypeRegistryMock + actionTypeRegistry ) ).toHaveLength(0); }); @@ -76,7 +85,6 @@ describe('stepRuleActions utils', () => { validateParamsMock.mockReturnValue({ errors: ['Message is required'], }); - const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); expect( validateActionParams( @@ -86,7 +94,7 @@ describe('stepRuleActions utils', () => { actionTypeId: '.slack', params: {}, }, - actionTypeRegistryMock + actionTypeRegistry ) ).toHaveLength(1); }); @@ -99,7 +107,6 @@ describe('stepRuleActions utils', () => { }, ], }); - const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); expect( validateActionParams( @@ -109,7 +116,7 @@ describe('stepRuleActions utils', () => { actionTypeId: '.slack', params: {}, }, - actionTypeRegistryMock + actionTypeRegistry ) ).toHaveLength(0); }); @@ -118,7 +125,6 @@ describe('stepRuleActions utils', () => { validateParamsMock.mockReturnValue({ errors: ['Message is required', 'Message is required', 'Message is required'], }); - const actionTypeRegistryMock = getActionTypeRegistryMock(validateParamsMock); expect( validateActionParams( @@ -128,7 +134,7 @@ describe('stepRuleActions utils', () => { actionTypeId: '.slack', params: {}, }, - actionTypeRegistryMock + actionTypeRegistry ) ).toHaveLength(1); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts index a0459e42050a9..9cabf9b48a02d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts @@ -9,7 +9,7 @@ import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { AlertAction, - TriggersAndActionsUIPublicPluginStart, + ActionTypeRegistryContract, } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import * as I18n from './translations'; @@ -42,7 +42,7 @@ export const validateMustache = (params: AlertAction['params']) => { export const validateActionParams = ( actionItem: AlertAction, - actionTypeRegistry: TriggersAndActionsUIPublicPluginStart['actionTypeRegistry'] + actionTypeRegistry: ActionTypeRegistryContract ): string[] => { const actionErrors = actionTypeRegistry .get(actionItem.actionTypeId) diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index e507b8e25a8ee..d3409d871f760 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -11,7 +11,13 @@ export { AlertsContextProvider } from './application/context/alerts_context'; export { ActionsConnectorsContextProvider } from './application/context/actions_connectors_context'; export { AlertAdd } from './application/sections/alert_form'; export { ActionForm } from './application/sections/action_connector_form'; -export { AlertAction, Alert, AlertTypeModel, ActionType } from './types'; +export { + AlertAction, + Alert, + AlertTypeModel, + ActionType, + ActionTypeRegistryContract, +} from './types'; export { ConnectorAddFlyout, ConnectorEditFlyout, @@ -28,3 +34,5 @@ export * from './plugin'; export { TIME_UNITS } from './application/constants'; export { getTimeUnitLabel } from './common/lib/get_time_unit_label'; export { ForLastExpression } from './common/expression_items/for_the_last'; + +export { actionTypeRegistryMock } from './application/action_type_registry.mock'; From 36c7b36e9ee5f3c0f101e1fa0f38a1e8669f060d Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 15 Apr 2020 19:58:30 +0200 Subject: [PATCH 11/16] fix build --- .../rules/components/step_rule_actions/schema.test.tsx | 2 +- .../rules/components/step_rule_actions/utils.test.ts | 2 +- x-pack/plugins/triggers_actions_ui/public/index.ts | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx index 3cc83133d9291..2b802e615b4dc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/schema.test.tsx @@ -6,7 +6,7 @@ import { validateSingleAction, validateRuleActionsField } from './schema'; import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; -import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public'; +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock'; import { FormHook } from '../../../../../shared_imports'; jest.mock('./utils'); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts index cc24ae66dc16c..4ba597d784bc8 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public'; +import { actionTypeRegistryMock } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/action_type_registry.mock'; import { isUuidv4, getActionTypeName, validateMustache, validateActionParams } from './utils'; describe('stepRuleActions utils', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index d3409d871f760..5bff9e5acbe13 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -34,5 +34,3 @@ export * from './plugin'; export { TIME_UNITS } from './application/constants'; export { getTimeUnitLabel } from './common/lib/get_time_unit_label'; export { ForLastExpression } from './common/expression_items/for_the_last'; - -export { actionTypeRegistryMock } from './application/action_type_registry.mock'; From 71f0ea9243d590188280eb186b745ebf081342ae Mon Sep 17 00:00:00 2001 From: Garrett Spong Date: Mon, 20 Apr 2020 17:46:12 -0600 Subject: [PATCH 12/16] Fixing lint error --- .../rules/components/rule_actions_field/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index 36869ed5b2129..205a19f386059 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -17,7 +17,7 @@ import { loadActionTypes } from '../../../../../../../../../plugins/triggers_act import { SelectField } from '../../../../../shared_imports'; import { ActionForm, - ActionType + ActionType, } from '../../../../../../../../../plugins/triggers_actions_ui/public'; import { AlertAction } from '../../../../../../../../../plugins/alerting/common'; import { useKibana } from '../../../../../lib/kibana'; From 025fbb3099163082570eac12abc65c45845905bc Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 11 May 2020 18:37:11 +0200 Subject: [PATCH 13/16] merge conflict --- .../rules/components/rule_actions_field/index.test.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx index f28c1f016e356..c7fa4a39ac043 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.test.tsx @@ -19,6 +19,15 @@ describe('RuleActionsField', () => { triggers_actions_ui: { actionTypeRegistry: {}, }, + application: { + capabilities: { + actions: { + delete: true, + save: true, + show: true, + }, + }, + }, }, }); const Component = () => { From 41443bb732ac4ed66369c6c579021922d40fc599 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Mon, 11 May 2020 20:11:48 +0200 Subject: [PATCH 14/16] fix types --- .../rules/components/rule_actions_field/index.tsx | 13 ++++++++----- .../components/step_rule_actions/schema.test.tsx | 2 +- .../rules/components/step_rule_actions/schema.tsx | 2 +- .../components/step_rule_actions/utils.test.ts | 6 +++--- .../rules/components/step_rule_actions/utils.ts | 2 +- 5 files changed, 14 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx index 205a19f386059..240630c4d4d54 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/rule_actions_field/index.tsx @@ -11,15 +11,14 @@ import deepMerge from 'deepmerge'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; -import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../../../../../plugins/siem/common/constants'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { loadActionTypes } from '../../../../../../../../../plugins/triggers_actions_ui/public/application/lib/action_connector_api'; +import { NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS } from '../../../../../../common/constants'; import { SelectField } from '../../../../../shared_imports'; import { ActionForm, ActionType, -} from '../../../../../../../../../plugins/triggers_actions_ui/public'; -import { AlertAction } from '../../../../../../../../../plugins/alerting/common'; + loadActionTypes, +} from '../../../../../../../triggers_actions_ui/public'; +import { AlertAction } from '../../../../../../../alerting/common'; import { useKibana } from '../../../../../lib/kibana'; import { FORM_ERRORS_TITLE } from './translations'; @@ -42,6 +41,8 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables http, triggers_actions_ui: { actionTypeRegistry }, notifications, + docLinks, + application: { capabilities }, } = useKibana().services; const actions: AlertAction[] = useMemo( @@ -121,6 +122,8 @@ export const RuleActionsField: ThrottleSelectField = ({ field, messageVariables ) : null} { @@ -57,8 +57,8 @@ describe('stepRuleActions utils', () => { validateParams: validateParamsMock, selectMessage: 'message', validateConnector: jest.fn(), - actionConnectorFields: () => null, - actionParamsFields: [], + actionConnectorFields: null, + actionParamsFields: null, }; actionTypeRegistry.get.mockReturnValue(actionMock); }); diff --git a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts index 9cabf9b48a02d..d2ff157a7d098 100644 --- a/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts +++ b/x-pack/plugins/siem/public/pages/detection_engine/rules/components/step_rule_actions/utils.ts @@ -10,7 +10,7 @@ import { uniq, startCase, flattenDeep, isArray, isString } from 'lodash/fp'; import { AlertAction, ActionTypeRegistryContract, -} from '../../../../../../../../../plugins/triggers_actions_ui/public'; +} from '../../../../../../../triggers_actions_ui/public'; import * as I18n from './translations'; const UUID_V4_REGEX = /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i; From 5ac991f2ed2918de029203348e1a9e0ee9e55ce8 Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Wed, 13 May 2020 10:21:41 +0200 Subject: [PATCH 15/16] fix import --- .../alerts/components/rules/step_rule_actions/index.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx index 3338905ea32ae..165aa5a30b0f0 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/index.test.tsx @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import { StepRuleActions } from './index'; -jest.mock('../../../../../lib/kibana', () => ({ +jest.mock('../../../../common/lib/kibana', () => ({ useKibana: jest.fn().mockReturnValue({ services: { application: { From f69b6d49ef4a4ab1e6f53c25c12b237dcd9b571e Mon Sep 17 00:00:00 2001 From: Patryk Kopycinski Date: Thu, 14 May 2020 20:32:08 +0200 Subject: [PATCH 16/16] Add more unit tests --- .../rules/step_rule_actions/schema.test.tsx | 50 ++++++++++++++++++- .../rules/step_rule_actions/schema.tsx | 4 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx index c3f1d35e90a3e..d746d42aefe78 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.test.tsx @@ -110,7 +110,55 @@ describe('stepRuleActions schema', () => { code: 'ERR_FIELD_FORMAT', message: ` **Slack:** -* No connector selected`, +* No connector selected +`, + path: '', + }); + }); + + it('should validate multiple incorrect rule actions field', () => { + (isUuidv4 as jest.Mock).mockReturnValueOnce(false); + (getActionTypeName as jest.Mock).mockReturnValueOnce('Slack'); + (isUuidv4 as jest.Mock).mockReturnValueOnce(true); + (getActionTypeName as jest.Mock).mockReturnValueOnce('Pagerduty'); + (validateActionParams as jest.Mock).mockReturnValue(['Summary is required']); + (validateMustache as jest.Mock).mockReturnValue(['Component is not valid mustache template']); + const validator = validateRuleActionsField(actionTypeRegistry); + + const result = validator({ + path: '', + value: [ + { + id: '817b8bca-91d1-4729-8ee1-3a83aaafd9d4', + group: 'default', + actionTypeId: '.slack', + params: {}, + }, + { + id: 'a8d1ef21-dcb9-4ac6-9e52-961f938a4c17', + group: 'default', + actionTypeId: '.pagerduty', + params: { + component: '{{{', + }, + }, + ], + form: {} as FormHook, + formData: jest.fn(), + errors: [], + }); + + expect(result).toEqual({ + code: 'ERR_FIELD_FORMAT', + message: ` +**Slack:** +* No connector selected + + +**Pagerduty:** +* Summary is required +* Component is not valid mustache template +`, path: '', }); }); diff --git a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.tsx b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.tsx index 0eff9de83ce81..189b55ae5622c 100644 --- a/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.tsx +++ b/x-pack/plugins/siem/public/alerts/components/rules/step_rule_actions/schema.tsx @@ -40,9 +40,9 @@ export const validateRuleActionsField = (actionTypeRegistry: ActionTypeRegistryC if (errorsArray.length) { const actionTypeName = getActionTypeName(actionItem.actionTypeId); - const errorsListItems = errorsArray.map(error => `* ${error}`); + const errorsListItems = errorsArray.map(error => `* ${error}\n`); - return [...acc, `\n**${actionTypeName}:**\n${errorsListItems}`]; + return [...acc, `\n**${actionTypeName}:**\n${errorsListItems.join('')}`]; } return acc;