From 2317be88749e4193d4b225b59f3aeda62f3d48a1 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 19 Dec 2022 15:14:03 -0600 Subject: [PATCH] [RAM] Add error for action interval shorter than check interval --- .../rules_client/lib/validate_actions.ts | 28 ++++++++++++- .../action_connector_form/action_form.tsx | 3 ++ .../action_notify_when.tsx | 20 ++++++++++ .../action_type_form.tsx | 39 ++++++++++++++----- .../sections/rule_form/rule_errors.ts | 19 +++++++++ .../sections/rule_form/rule_form.tsx | 1 + 6 files changed, 99 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts index 2924755ec3ba5..185d08056d9db 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.ts @@ -8,15 +8,16 @@ import Boom from '@hapi/boom'; import { map } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { RawRule } from '../../types'; +import { RawRule, RuleNotifyWhen } from '../../types'; import { UntypedNormalizedRuleType } from '../../rule_type_registry'; import { NormalizedAlertAction } from '../types'; import { RulesClientContext } from '../types'; +import { parseDuration } from '../../lib'; export async function validateActions( context: RulesClientContext, alertType: UntypedNormalizedRuleType, - data: Pick & { actions: NormalizedAlertAction[] } + data: Pick & { actions: NormalizedAlertAction[] } ): Promise { const { actions, notifyWhen, throttle } = data; const hasRuleLevelNotifyWhen = typeof notifyWhen !== 'undefined'; @@ -91,4 +92,27 @@ export async function validateActions( ); } } + + // check for actions throttled shorter than the rule schedule + const scheduleInterval = parseDuration(data.schedule.interval); + const actionsWithInvalidThrottles = actions.filter( + (action) => + !action.frequency || + (action.frequency.notifyWhen === RuleNotifyWhen.THROTTLE && + parseDuration(action.frequency.throttle!) < scheduleInterval) + ); + if (actionsWithInvalidThrottles.length) { + throw Boom.badRequest( + i18n.translate('xpack.alerting.rulesClient.validateActions.notAllActionsWithFreq', { + defaultMessage: + 'Action throttle cannot be shorter than the schedule interval of {scheduleIntervalText}: {groups}', + values: { + scheduleIntervalText: data.schedule.interval, + groups: actionsWithInvalidThrottles + .map((a) => `${a.group} (${a.frequency?.throttle})`) + .join(', '), + }, + }) + ); + } } 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 1edf372334757..17b27bf509d3b 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 @@ -65,6 +65,7 @@ export interface ActionAccordionFormProps { isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean; hideActionHeader?: boolean; hideNotifyWhen?: boolean; + minimumThrottleInterval?: [number | undefined, string]; } interface ActiveActionConnectorState { @@ -91,6 +92,7 @@ export const ActionForm = ({ isActionGroupDisabledForActionType, hideActionHeader, hideNotifyWhen, + minimumThrottleInterval, }: ActionAccordionFormProps) => { const { http, @@ -396,6 +398,7 @@ export const ActionForm = ({ setActiveActionItem(undefined); }} hideNotifyWhen={hideNotifyWhen} + minimumThrottleInterval={minimumThrottleInterval} /> ); })} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_notify_when.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_notify_when.tsx index 17113f7e1c40d..624ced0843bd4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_notify_when.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_notify_when.tsx @@ -122,6 +122,8 @@ interface RuleNotifyWhenProps { throttleUnit: string; onNotifyWhenChange: (notifyWhen: RuleNotifyWhenType) => void; onThrottleChange: (throttle: number | null, throttleUnit: string) => void; + showMinimumThrottleWarning?: boolean; + showMinimumThrottleUnitWarning?: boolean; } export const ActionNotifyWhen = ({ @@ -130,6 +132,8 @@ export const ActionNotifyWhen = ({ throttleUnit, onNotifyWhenChange, onThrottleChange, + showMinimumThrottleWarning, + showMinimumThrottleUnitWarning, }: RuleNotifyWhenProps) => { const [showCustomThrottleOpts, setShowCustomThrottleOpts] = useState(false); const [notifyWhenValue, setNotifyWhenValue] = @@ -195,6 +199,7 @@ export const ActionNotifyWhen = ({ + {(showMinimumThrottleWarning || showMinimumThrottleUnitWarning) && ( + <> + + + {i18n.translate( + 'xpack.triggersActionsUI.sections.actionTypeForm.notifyWhenThrottleWarning', + { + defaultMessage: + "Custom action intervals cannot be shorter than the rule's check interval", + } + )} + + + )} )} 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 4853abb756ab6..ebc985526e7fb 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 @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Suspense, useEffect, useState, useCallback } from 'react'; +import React, { Suspense, useEffect, useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -31,6 +31,7 @@ import { ActionVariable, RuleActionParam } from '@kbn/alerting-plugin/common'; import { getDurationNumberInItsUnit, getDurationUnitValue, + parseDuration, } from '@kbn/alerting-plugin/common/parse_duration'; import { betaBadgeProps } from './beta_badge_props'; import { @@ -65,6 +66,7 @@ export type ActionTypeFormProps = { recoveryActionGroup?: string; isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean; hideNotifyWhen?: boolean; + minimumThrottleInterval?: [number | undefined, string]; } & Pick< ActionAccordionFormProps, | 'defaultActionGroupId' @@ -102,6 +104,7 @@ export const ActionTypeForm = ({ isActionGroupDisabledForActionType, recoveryActionGroup, hideNotifyWhen = false, + minimumThrottleInterval, }: ActionTypeFormProps) => { const { application: { capabilities }, @@ -123,6 +126,10 @@ export const ActionTypeForm = ({ const [actionThrottleUnit, setActionThrottleUnit] = useState( actionItem.frequency?.throttle ? getDurationUnitValue(actionItem.frequency?.throttle) : 'h' ); + const [minimumActionThrottle = -1, minimumActionThrottleUnit] = minimumThrottleInterval ?? [ + -1, + 's', + ]; const getDefaultParams = async () => { const connectorType = await actionTypeRegistry.get(actionItem.actionTypeId); @@ -138,6 +145,25 @@ export const ActionTypeForm = ({ return defaultParams; }; + const [showMinimumThrottleWarning, showMinimumThrottleUnitWarning] = useMemo(() => { + try { + if (!actionThrottle) return [false, false]; + const throttleUnitDuration = parseDuration(`1${actionThrottleUnit}`); + const minThrottleUnitDuration = parseDuration(`1${minimumActionThrottleUnit}`); + const boundedThrottle = + throttleUnitDuration > minThrottleUnitDuration + ? actionThrottle + : Math.max(actionThrottle, minimumActionThrottle); + const boundedThrottleUnit = + throttleUnitDuration >= minThrottleUnitDuration + ? actionThrottleUnit + : minimumActionThrottleUnit; + return [boundedThrottle !== actionThrottle, boundedThrottleUnit !== actionThrottleUnit]; + } catch (e) { + return [false, false]; + } + }, [minimumActionThrottle, minimumActionThrottleUnit, actionThrottle, actionThrottleUnit]); + useEffect(() => { (async () => { setAvailableActionVariables( @@ -178,13 +204,6 @@ export const ActionTypeForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionItem]); - // useEffect(() => { - // if (!actionItem.frequency) { - // setActionFrequency(DEFAULT_FREQUENCY, index); - // } - // // eslint-disable-next-line react-hooks/exhaustive-deps - // }, [actionItem.frequency]); - const canSave = hasSaveActionsCapability(capabilities); const actionGroupDisplay = ( @@ -232,6 +251,8 @@ export const ActionTypeForm = ({ }, [setActionFrequencyProperty, index] )} + showMinimumThrottleWarning={showMinimumThrottleWarning} + showMinimumThrottleUnitWarning={showMinimumThrottleUnitWarning} /> ); @@ -254,7 +275,6 @@ export const ActionTypeForm = ({ <> {showSelectActionGroup && ( <> - @@ -279,6 +299,7 @@ export const ActionTypeForm = ({ setActionGroup(group); }} /> + {!hideNotifyWhen && } )} {!hideNotifyWhen && actionNotifyWhen} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts index b57d6b8ab9a4c..cf747ab60be01 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts @@ -6,6 +6,7 @@ */ import { isObject } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; import { formatDuration, parseDuration } from '@kbn/alerting-plugin/common/parse_duration'; import { RuleTypeModel, @@ -58,6 +59,24 @@ export function validateBaseProperties( } } + const invalidThrottleActions = ruleObject.actions.filter( + (a) => + a.frequency?.notifyWhen === RuleNotifyWhen.THROTTLE && + parseDuration(a.frequency.throttle ?? '0m') < + parseDuration(ruleObject.schedule.interval ?? '0m') + ); + if (invalidThrottleActions.length) { + errors['schedule.interval'].push( + i18n.translate( + 'xpack.triggersActionsUI.sections.ruleForm.error.actionThrottleBelowSchedule', + { + defaultMessage: + "Custom action intervals cannot be shorter than the rule's check interval", + } + ) + ); + } + if (!ruleObject.ruleTypeId) { errors.ruleTypeId.push( i18n.translate('xpack.triggersActionsUI.sections.ruleForm.error.requiredRuleTypeIdText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 94a7cc5bba513..8a0ad49e936f4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -672,6 +672,7 @@ export const RuleForm = ({ setActionParamsProperty={setActionParamsProperty} actionTypeRegistry={actionTypeRegistry} setActionFrequencyProperty={setActionFrequencyProperty} + minimumThrottleInterval={[ruleInterval, ruleIntervalUnit]} /> ) : null}