Skip to content

Commit

Permalink
[RAM][SECURITYSOLUTION][ALERTS] - Show warning that custom action int…
Browse files Browse the repository at this point in the history
…ervals cannot be shorter than the rule's check interval (elastic#155502)
  • Loading branch information
e40pud committed Apr 25, 2023
1 parent 4e4f408 commit a12b25e
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { getTimeTypeValue } from './time_type_value';

describe('getTimeTypeValue', () => {
[
{ interval: '1ms', value: 1, unit: 'ms' },
{ interval: '0s', value: 0, unit: 's' },
{ interval: '3s', value: 3, unit: 's' },
{ interval: '5m', value: 5, unit: 'm' },
{ interval: '7h', value: 7, unit: 'h' },
{ interval: '10d', value: 10, unit: 'd' },
].forEach(({ interval, value, unit }) => {
it(`should correctly return time duration and time unit when 'interval' is ${interval}`, () => {
const { value: actualValue, unit: actualUnit } = getTimeTypeValue(interval);
expect(actualValue).toEqual(value);
expect(actualUnit).toEqual(unit);
});
});

[
{ interval: '-1ms', value: 1, unit: 'ms' },
{ interval: '-3s', value: 3, unit: 's' },
{ interval: '-5m', value: 5, unit: 'm' },
{ interval: '-7h', value: 7, unit: 'h' },
{ interval: '-10d', value: 10, unit: 'd' },
{ interval: '-1', value: 1, unit: 'ms' },
{ interval: '-3', value: 3, unit: 'ms' },
{ interval: '-5', value: 5, unit: 'ms' },
{ interval: '-7', value: 7, unit: 'ms' },
{ interval: '-10', value: 10, unit: 'ms' },
].forEach(({ interval, value, unit }) => {
it(`should correctly return positive time duration and time unit when 'interval' is negative (${interval})`, () => {
const { value: actualValue, unit: actualUnit } = getTimeTypeValue(interval);
expect(actualValue).toEqual(value);
expect(actualUnit).toEqual(unit);
});
});

[
{ interval: 'ms', value: 0, unit: 'ms' },
{ interval: 's', value: 0, unit: 's' },
{ interval: 'm', value: 0, unit: 'm' },
{ interval: 'h', value: 0, unit: 'h' },
{ interval: 'd', value: 0, unit: 'd' },
{ interval: '-ms', value: 0, unit: 'ms' },
{ interval: '-s', value: 0, unit: 's' },
{ interval: '-m', value: 0, unit: 'm' },
{ interval: '-h', value: 0, unit: 'h' },
{ interval: '-d', value: 0, unit: 'd' },
].forEach(({ interval, value, unit }) => {
it(`should correctly return time duration equal to '0' when 'interval' does not specify time duration (${interval})`, () => {
const { value: actualValue, unit: actualUnit } = getTimeTypeValue(interval);
expect(actualValue).toEqual(value);
expect(actualUnit).toEqual(unit);
});
});

[
{ interval: '0', value: 0, unit: 'ms' },
{ interval: '1', value: 1, unit: 'ms' },
{ interval: '3', value: 3, unit: 'ms' },
{ interval: '5', value: 5, unit: 'ms' },
{ interval: '7', value: 7, unit: 'ms' },
{ interval: '10', value: 10, unit: 'ms' },
].forEach(({ interval, value, unit }) => {
it(`should correctly return time unit set to 'ms' as a default value when 'interval' does not specify it (${interval})`, () => {
const { value: actualValue, unit: actualUnit } = getTimeTypeValue(interval);
expect(actualValue).toEqual(value);
expect(actualUnit).toEqual(unit);
});
});

[
{ interval: '1f', value: 1, unit: 'ms' },
{ interval: '-3r', value: 3, unit: 'ms' },
{ interval: 'p', value: 0, unit: 'ms' },
].forEach(({ interval, value, unit }) => {
it(`should correctly return time unit set to 'ms' as a default value when data is invalid (${interval})`, () => {
const { value: actualValue, unit: actualUnit } = getTimeTypeValue(interval);
expect(actualValue).toEqual(value);
expect(actualUnit).toEqual(unit);
});
});
});
29 changes: 29 additions & 0 deletions x-pack/plugins/security_solution/common/utils/time_type_value.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { isEmpty } from 'lodash/fp';
import type { Unit } from '@kbn/datemath';

export const getTimeTypeValue = (time: string): { unit: Unit; value: number } => {
const timeObj: { unit: Unit; value: number } = {
unit: 'ms',
value: 0,
};
const filterTimeVal = time.match(/\d+/g);
const filterTimeType = time.match(/[a-zA-Z]+/g);
if (!isEmpty(filterTimeVal) && filterTimeVal != null && !isNaN(Number(filterTimeVal[0]))) {
timeObj.value = Number(filterTimeVal[0]);
}
if (
!isEmpty(filterTimeType) &&
filterTimeType != null &&
['s', 'm', 'h', 'd'].includes(filterTimeType[0])
) {
timeObj.unit = filterTimeType[0] as Unit;
}
return timeObj;
};
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import type {
DefineStepRule,
} from '../../../../detections/pages/detection_engine/rules/types';
import {
getTimeTypeValue,
formatDefineStepData,
formatScheduleStepData,
formatAboutStepData,
Expand All @@ -43,50 +42,6 @@ import { getThreatMock } from '../../../../../common/detection_engine/schemas/ty
import type { Threat, Threats } from '@kbn/securitysolution-io-ts-alerting-types';

describe('helpers', () => {
describe('getTimeTypeValue', () => {
test('returns timeObj with value 0 if no time value found', () => {
const result = getTimeTypeValue('m');

expect(result).toEqual({ unit: 'm', value: 0 });
});

test('returns timeObj with unit set to default unit value of "ms" if no expected time type found', () => {
const result = getTimeTypeValue('5l');

expect(result).toEqual({ unit: 'ms', value: 5 });
});

test('returns timeObj with unit of s and value 5 when time is 5s ', () => {
const result = getTimeTypeValue('5s');

expect(result).toEqual({ unit: 's', value: 5 });
});

test('returns timeObj with unit of m and value 5 when time is 5m ', () => {
const result = getTimeTypeValue('5m');

expect(result).toEqual({ unit: 'm', value: 5 });
});

test('returns timeObj with unit of h and value 5 when time is 5h ', () => {
const result = getTimeTypeValue('5h');

expect(result).toEqual({ unit: 'h', value: 5 });
});

test('returns timeObj with value of 5 when time is float like 5.6m ', () => {
const result = getTimeTypeValue('5m');

expect(result).toEqual({ unit: 'm', value: 5 });
});

test('returns timeObj with value of 0 and unit of "ms" if random string passed in', () => {
const result = getTimeTypeValue('random');

expect(result).toEqual({ unit: 'ms', value: 0 });
});
});

describe('filterEmptyThreats', () => {
let mockThreat: Threat;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
/* eslint-disable complexity */

import { has, isEmpty } from 'lodash/fp';
import type { Unit } from '@kbn/datemath';
import moment from 'moment';
import deepmerge from 'deepmerge';
import omit from 'lodash/omit';
Expand All @@ -25,6 +24,7 @@ import type {
Type,
} from '@kbn/securitysolution-io-ts-alerting-types';
import { ENDPOINT_LIST_ID } from '@kbn/securitysolution-list-constants';
import { getTimeTypeValue } from '../../../../../common/utils/time_type_value';
import { assertUnreachable } from '../../../../../common/utility_types';
import {
transformAlertToRuleAction,
Expand All @@ -50,26 +50,6 @@ import {
import type { RuleCreateProps } from '../../../../../common/detection_engine/rule_schema';
import { stepActionsDefaultValue } from '../../../../detections/components/rules/step_rule_actions';

export const getTimeTypeValue = (time: string): { unit: Unit; value: number } => {
const timeObj: { unit: Unit; value: number } = {
unit: 'ms',
value: 0,
};
const filterTimeVal = time.match(/\d+/g);
const filterTimeType = time.match(/[a-zA-Z]+/g);
if (!isEmpty(filterTimeVal) && filterTimeVal != null && !isNaN(Number(filterTimeVal[0]))) {
timeObj.value = Number(filterTimeVal[0]);
}
if (
!isEmpty(filterTimeType) &&
filterTimeType != null &&
['s', 'm', 'h'].includes(filterTimeType[0])
) {
timeObj.unit = filterTimeType[0] as Unit;
}
return timeObj;
};

export const stepIsValid = <T extends RuleStepsFormData[keyof RuleStepsFormData]>(
formData?: T
): formData is { [K in keyof T]: Exclude<T[K], undefined> } =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ const CreateRulePageComponent: React.FC = () => {
<StepRuleActions
addPadding={true}
defaultValues={stepsData.current[RuleStep.ruleActions].data}
ruleScheduleInterval={scheduleRuleData.interval}
isReadOnlyView={activeStep !== RuleStep.ruleActions}
isLoading={isLoading || loading || isStartingJobs}
setForm={setFormHook}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,12 +301,13 @@ const EditRulePageComponent: FC = () => {
<>
<EuiSpacer />
<StepPanel loading={loading}>
{actionsStep.data != null && (
{actionsStep.data != null && scheduleStep.data != null && (
<StepRuleActions
isReadOnlyView={false}
isLoading={isLoading}
isUpdateView
defaultValues={actionsStep.data}
ruleScheduleInterval={scheduleStep.data.interval}
setForm={setFormHook}
actionMessageParams={actionMessageParams}
ruleType={rule?.type}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
} from '@kbn/alerting-plugin/common';
import { SecurityConnectorFeatureId } from '@kbn/actions-plugin/common';
import { FormattedMessage } from '@kbn/i18n-react';
import { getTimeTypeValue } from '../../../../../common/utils/time_type_value';
import { NOTIFICATION_DEFAULT_FREQUENCY } from '../../../../../common/constants';
import type { FieldHook } from '../../../../shared_imports';
import { useFormContext } from '../../../../shared_imports';
Expand Down Expand Up @@ -77,6 +78,7 @@ const NOTIFY_WHEN_OPTIONS: NotifyWhenSelectOptions[] = [
interface Props {
field: FieldHook;
messageVariables: ActionVariables;
ruleScheduleInterval?: string;
}

const DEFAULT_ACTION_GROUP_ID = 'default';
Expand Down Expand Up @@ -110,7 +112,11 @@ const ContainerActions = styled.div.attrs(
)}
`;

export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) => {
export const RuleActionsField: React.FC<Props> = ({
field,
messageVariables,
ruleScheduleInterval,
}) => {
const [fieldErrors, setFieldErrors] = useState<string | null>(null);
const form = useFormContext();
const { isSubmitted, isSubmitting, isValid } = form;
Expand Down Expand Up @@ -138,6 +144,14 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
[actions]
);

const minimumThrottleInterval = useMemo<[number, string] | undefined>(() => {
if (ruleScheduleInterval != null) {
const { unit: intervalUnit, value: intervalValue } = getTimeTypeValue(ruleScheduleInterval);
return [intervalValue, intervalUnit];
}
return ruleScheduleInterval;
}, [ruleScheduleInterval]);

const setActionIdByIndex = useCallback(
(id: string, index: number) => {
const updatedActions = [...(actions as Array<Partial<RuleAction>>)];
Expand Down Expand Up @@ -244,16 +258,18 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
notifyWhenSelectOptions: NOTIFY_WHEN_OPTIONS,
defaultRuleFrequency: NOTIFICATION_DEFAULT_FREQUENCY,
showActionAlertsFilter: true,
minimumThrottleInterval,
}),
[
actions,
getActionForm,
actions,
messageVariables,
setActionFrequency,
setActionIdByIndex,
setActionParamsProperty,
setAlertActionsProperty,
setActionParamsProperty,
setActionFrequency,
setActionAlertsFilterProperty,
minimumThrottleInterval,
]
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
*/

import moment from 'moment';
import { getTimeTypeValue } from '../../../../../common/utils/time_type_value';

import type { TimeframePreviewOptions } from '../../../pages/detection_engine/rules/types';
import { getTimeTypeValue } from '../../../../detection_engine/rule_creation_ui/pages/rule_creation/helpers';

export const usePreviewInvocationCount = ({
timeframeOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ describe('StepRuleActions', () => {
it('renders correctly', () => {
const wrapper = shallow(
<StepRuleActions
ruleScheduleInterval={'5m'}
actionMessageParams={actionMessageParams}
isReadOnlyView={false}
isLoading={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ interface StepRuleActionsProps extends RuleStepProps {
defaultValues?: ActionsStepRule | null;
actionMessageParams: ActionVariables;
ruleType?: Type;
ruleScheduleInterval: string;
}

export const stepActionsDefaultValue: ActionsStepRule = {
Expand Down Expand Up @@ -77,6 +78,7 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
setForm,
actionMessageParams,
ruleType,
ruleScheduleInterval,
}) => {
const {
services: {
Expand Down Expand Up @@ -145,11 +147,12 @@ const StepRuleActionsComponent: FC<StepRuleActionsProps> = ({
component={RuleActionsField}
componentProps={{
messageVariables: actionMessageParams,
ruleScheduleInterval,
}}
/>
</>
),
[actionMessageParams]
[actionMessageParams, ruleScheduleInterval]
);
const displayResponseActionsOptions = useMemo(() => {
if (isQueryRule(ruleType)) {
Expand Down

0 comments on commit a12b25e

Please sign in to comment.