Skip to content

Commit

Permalink
[RAM] Add error for action interval shorter than check interval
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacqary committed Jan 13, 2023
1 parent 5c8181b commit 2317be8
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<RawRule, 'notifyWhen' | 'throttle'> & { actions: NormalizedAlertAction[] }
data: Pick<RawRule, 'notifyWhen' | 'throttle' | 'schedule'> & { actions: NormalizedAlertAction[] }
): Promise<void> {
const { actions, notifyWhen, throttle } = data;
const hasRuleLevelNotifyWhen = typeof notifyWhen !== 'undefined';
Expand Down Expand Up @@ -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(', '),
},
})
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export interface ActionAccordionFormProps {
isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean;
hideActionHeader?: boolean;
hideNotifyWhen?: boolean;
minimumThrottleInterval?: [number | undefined, string];
}

interface ActiveActionConnectorState {
Expand All @@ -91,6 +92,7 @@ export const ActionForm = ({
isActionGroupDisabledForActionType,
hideActionHeader,
hideNotifyWhen,
minimumThrottleInterval,
}: ActionAccordionFormProps) => {
const {
http,
Expand Down Expand Up @@ -396,6 +398,7 @@ export const ActionForm = ({
setActiveActionItem(undefined);
}}
hideNotifyWhen={hideNotifyWhen}
minimumThrottleInterval={minimumThrottleInterval}
/>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ({
Expand All @@ -130,6 +132,8 @@ export const ActionNotifyWhen = ({
throttleUnit,
onNotifyWhenChange,
onThrottleChange,
showMinimumThrottleWarning,
showMinimumThrottleUnitWarning,
}: RuleNotifyWhenProps) => {
const [showCustomThrottleOpts, setShowCustomThrottleOpts] = useState<boolean>(false);
const [notifyWhenValue, setNotifyWhenValue] =
Expand Down Expand Up @@ -195,6 +199,7 @@ export const ActionNotifyWhen = ({
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={2}>
<EuiFieldNumber
isInvalid={showMinimumThrottleWarning}
min={1}
value={throttle ?? 1}
name="throttle"
Expand All @@ -220,6 +225,7 @@ export const ActionNotifyWhen = ({
</EuiFlexItem>
<EuiFlexItem grow={3}>
<EuiSelect
isInvalid={showMinimumThrottleUnitWarning}
data-test-subj="throttleUnitInput"
value={throttleUnit}
options={getTimeOptions(throttle ?? 1)}
Expand All @@ -230,6 +236,20 @@ export const ActionNotifyWhen = ({
</EuiFlexItem>
</EuiFlexGroup>
</EuiFormRow>
{(showMinimumThrottleWarning || showMinimumThrottleUnitWarning) && (
<>
<EuiSpacer size="xs" />
<EuiText size="xs" color="danger">
{i18n.translate(
'xpack.triggersActionsUI.sections.actionTypeForm.notifyWhenThrottleWarning',
{
defaultMessage:
"Custom action intervals cannot be shorter than the rule's check interval",
}
)}
</EuiText>
</>
)}
</>
)}
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -65,6 +66,7 @@ export type ActionTypeFormProps = {
recoveryActionGroup?: string;
isActionGroupDisabledForActionType?: (actionGroupId: string, actionTypeId: string) => boolean;
hideNotifyWhen?: boolean;
minimumThrottleInterval?: [number | undefined, string];
} & Pick<
ActionAccordionFormProps,
| 'defaultActionGroupId'
Expand Down Expand Up @@ -102,6 +104,7 @@ export const ActionTypeForm = ({
isActionGroupDisabledForActionType,
recoveryActionGroup,
hideNotifyWhen = false,
minimumThrottleInterval,
}: ActionTypeFormProps) => {
const {
application: { capabilities },
Expand All @@ -123,6 +126,10 @@ export const ActionTypeForm = ({
const [actionThrottleUnit, setActionThrottleUnit] = useState<string>(
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);
Expand All @@ -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(
Expand Down Expand Up @@ -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 = (
Expand Down Expand Up @@ -232,6 +251,8 @@ export const ActionTypeForm = ({
},
[setActionFrequencyProperty, index]
)}
showMinimumThrottleWarning={showMinimumThrottleWarning}
showMinimumThrottleUnitWarning={showMinimumThrottleUnitWarning}
/>
);

Expand All @@ -254,7 +275,6 @@ export const ActionTypeForm = ({
<>
{showSelectActionGroup && (
<>
<EuiSpacer size="xs" />
<EuiSuperSelect
prepend={
<EuiFormLabel htmlFor={`addNewActionConnectorActionGroup-${actionItem.actionTypeId}`}>
Expand All @@ -279,6 +299,7 @@ export const ActionTypeForm = ({
setActionGroup(group);
}}
/>
{!hideNotifyWhen && <EuiSpacer size="xs" />}
</>
)}
{!hideNotifyWhen && actionNotifyWhen}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ export const RuleForm = ({
setActionParamsProperty={setActionParamsProperty}
actionTypeRegistry={actionTypeRegistry}
setActionFrequencyProperty={setActionFrequencyProperty}
minimumThrottleInterval={[ruleInterval, ruleIntervalUnit]}
/>
</>
) : null}
Expand Down

0 comments on commit 2317be8

Please sign in to comment.