diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx index a524ad3929ea8..bfdc407fc551e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_field/index.tsx @@ -119,7 +119,7 @@ export const RuleActionsField: React.FC = ({ }) => { const [fieldErrors, setFieldErrors] = useState(null); const form = useFormContext(); - const { isSubmitted, isSubmitting, isValid } = form; + const { isValid } = form; const { triggersActionsUi: { getActionForm }, } = useKibana().services; @@ -231,6 +231,7 @@ export const RuleActionsField: React.FC = ({ [field] ); + const isFormValidated = isValid !== undefined; const actionForm = useMemo( () => getActionForm({ @@ -251,6 +252,7 @@ export const RuleActionsField: React.FC = ({ hasSummary: true, notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS, defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY, + disableErrorMessages: !isFormValidated, }), [ actions, @@ -262,18 +264,17 @@ export const RuleActionsField: React.FC = ({ setActionParamsProperty, setAlertActionsProperty, setActionAlertsFilterProperty, + isFormValidated, ] ); useEffect(() => { - if (isSubmitting || !field.errors.length) { - return setFieldErrors(null); - } - if (isSubmitted && !isSubmitting && isValid === false && field.errors.length) { + if (isValid === false) { const errorsString = field.errors.map(({ message }) => message).join('\n'); return setFieldErrors(errorsString); } - }, [isSubmitted, isSubmitting, field.isChangingValue, isValid, field.errors, setFieldErrors]); + return setFieldErrors(null); + }, [field.errors, isValid]); return ( diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts index 8d04ed43e0538..b881fd805e597 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/validate_rule_actions_field/validate_rule_actions_field.ts @@ -16,6 +16,7 @@ import type { RuleAction, ActionTypeRegistryContract, } from '@kbn/triggers-actions-ui-plugin/public'; +import { validateActionFilterQuery } from '@kbn/triggers-actions-ui-plugin/public'; import type { RuleActionsFormData } from '../../../../../detection_engine/rule_management_ui/components/rules_table/bulk_actions/forms/rule_actions_form'; import type { ActionsStepRule } from '../../../../pages/detection_engine/rules/types'; import type { ValidationFunc, ERROR_CODE } from '../../../../../shared_imports'; @@ -29,8 +30,9 @@ export const validateSingleAction = async ( ): Promise => { const actionParamsErrors = await validateActionParams(actionItem, actionTypeRegistry); const mustacheErrors = validateMustache(actionItem.params); + const queryErrors = validateActionFilterQuery(actionItem); - return [...actionParamsErrors, ...mustacheErrors]; + return [...actionParamsErrors, ...mustacheErrors, ...(queryErrors ? [queryErrors] : [])]; }; export const validateRuleActionsField = diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts index e7b799519e1d0..96c01d201b0a0 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/value_validators.ts @@ -7,7 +7,23 @@ import { set } from '@kbn/safer-lodash-set'; import { constant, get } from 'lodash'; -import { UserConfiguredActionConnector, IErrorObject, Rule } from '../../types'; +import { i18n } from '@kbn/i18n'; +import { UserConfiguredActionConnector, IErrorObject, Rule, RuleAction } from '../../types'; + +const filterQueryRequiredError = i18n.translate( + 'xpack.triggersActionsUI.sections.actionTypeForm.error.requiredFilterQuery', + { + defaultMessage: 'A custom query is required.', + } +); + +export const validateActionFilterQuery = (actionItem: RuleAction): string | null => { + const query = actionItem.alertsFilter?.query; + if (query && !query.kql) { + return filterQueryRequiredError; + } + return null; +}; export function throwIfAbsent(message: string) { return (value: T | undefined): T => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index b7ff043d9727a..2bb623d423429 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -86,6 +86,7 @@ export interface ActionAccordionFormProps { defaultRuleFrequency?: RuleActionFrequency; ruleTypeId?: string; hasFieldsForAAD?: boolean; + disableErrorMessages?: boolean; } interface ActiveActionConnectorState { @@ -122,6 +123,7 @@ export const ActionForm = ({ ruleTypeId, producerId, hasFieldsForAAD, + disableErrorMessages, }: ActionAccordionFormProps) => { const { http, @@ -499,6 +501,7 @@ export const ActionForm = ({ producerId={producerId} ruleTypeId={ruleTypeId} hasFieldsForAAD={hasFieldsForAAD} + disableErrorMessages={disableErrorMessages} /> ); })} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx index d7fbb7f20169e..a506a6fc0ef27 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_type_form.tsx @@ -63,6 +63,7 @@ import { ActionNotifyWhen } from './action_notify_when'; import { validateParamsForWarnings } from '../../lib/validate_params_for_warnings'; import { ActionAlertsFilterTimeframe } from './action_alerts_filter_timeframe'; import { ActionAlertsFilterQuery } from './action_alerts_filter_query'; +import { validateActionFilterQuery } from '../../lib/value_validators'; export type ActionTypeFormProps = { actionItem: RuleAction; @@ -92,6 +93,7 @@ export type ActionTypeFormProps = { producerId: string; ruleTypeId?: string; hasFieldsForAAD?: boolean; + disableErrorMessages?: boolean; } & Pick< ActionAccordionFormProps, | 'defaultActionGroupId' @@ -142,6 +144,7 @@ export const ActionTypeForm = ({ featureId, ruleTypeId, hasFieldsForAAD, + disableErrorMessages, }: ActionTypeFormProps) => { const { application: { capabilities }, @@ -247,13 +250,28 @@ export const ActionTypeForm = ({ useEffect(() => { (async () => { + if (disableErrorMessages) { + setActionParamsErrors({ errors: {} }); + return; + } const res: { errors: IErrorObject } = await actionTypeRegistry .get(actionItem.actionTypeId) ?.validateParams(actionItem.params); setActionParamsErrors(res); })(); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [actionItem]); + }, [actionItem, disableErrorMessages]); + + const [queryError, setQueryError] = useState(null); + useEffect(() => { + (async () => { + if (disableErrorMessages) { + setQueryError(null); + return; + } + setQueryError(validateActionFilterQuery(actionItem)); + })(); + }, [actionItem, disableErrorMessages]); const canSave = hasSaveActionsCapability(capabilities); @@ -424,13 +442,15 @@ export const ActionTypeForm = ({ {showActionAlertsFilter && ( <> {!hideNotifyWhen && } - setActionAlertsFilterProperty('query', query, index)} - featureIds={[producerId as ValidFeatureId]} - appName={featureId!} - ruleTypeId={ruleTypeId} - /> + + setActionAlertsFilterProperty('query', query, index)} + featureIds={[producerId as ValidFeatureId]} + appName={featureId!} + ruleTypeId={ruleTypeId} + /> + { }; export { transformRule } from './application/lib/rule_api/common_transformations'; + +export { validateActionFilterQuery } from './application/lib/value_validators';