mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] Don't mute rules when bulk editing rule actions (#140626)
## Intro This PR modifies the logic of bulk updating rule actions, in preparation for https://github.com/elastic/kibana/pull/137430 ## Summary - Removes the mute logic for bulk updating rule actions - Remove option for “Perform no actions” from the bulk update rule actions dropdown options ONLY (option still available when creating or editing rules individually) - Also corrects bulk update rule actions flyout, so that: - available actions are always displayed - copy referring to using "Perform No Actions" to mute all selected rules is no longer displayed. ## Screenshots **Removed unwanted copy and "On each rule execution" selected as default**  **"Perform No Action" option no longer available**  ### Checklist Delete any items that are not applicable to this PR. - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) - [ ] Any UI touched in this PR does not create any new axe failures (run axe in browser: [FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/), [Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US)) - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server)) - [ ] This was checked for [cross-browser compatibility](https://www.elastic.co/support/matrix#matrix_browsers) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
This commit is contained in:
parent
bde62fd5d8
commit
7aa5428597
15 changed files with 414 additions and 287 deletions
|
@ -18,7 +18,6 @@ export * from './src/default_per_page';
|
|||
export * from './src/default_risk_score_mapping_array';
|
||||
export * from './src/default_severity_mapping_array';
|
||||
export * from './src/default_threat_array';
|
||||
export * from './src/default_throttle_null';
|
||||
export * from './src/default_to_string';
|
||||
export * from './src/default_uuid';
|
||||
export * from './src/from';
|
||||
|
|
|
@ -1,44 +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
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { Throttle } from '../throttle';
|
||||
import { DefaultThrottleNull } from '.';
|
||||
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
|
||||
describe('default_throttle_null', () => {
|
||||
test('it should validate a throttle string', () => {
|
||||
const payload: Throttle = 'some string';
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an array with a number', () => {
|
||||
const payload = 5;
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "5" supplied to "DefaultThreatNull"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should return a default "null" if not provided a value', () => {
|
||||
const payload = undefined;
|
||||
const decoded = DefaultThrottleNull.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(null);
|
||||
});
|
||||
});
|
|
@ -1,23 +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
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as t from 'io-ts';
|
||||
import { Either } from 'fp-ts/lib/Either';
|
||||
import { throttle, ThrottleOrNull } from '../throttle';
|
||||
|
||||
/**
|
||||
* Types the DefaultThrottleNull as:
|
||||
* - If null or undefined, then a null will be set
|
||||
*/
|
||||
export const DefaultThrottleNull = new t.Type<ThrottleOrNull, ThrottleOrNull | undefined, unknown>(
|
||||
'DefaultThreatNull',
|
||||
throttle.is,
|
||||
(input, context): Either<t.Errors, ThrottleOrNull> =>
|
||||
input == null ? t.success(null) : throttle.validate(input, context),
|
||||
t.identity
|
||||
);
|
|
@ -6,13 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { TimeDuration } from '@kbn/securitysolution-io-ts-types';
|
||||
import * as t from 'io-ts';
|
||||
|
||||
export const throttle = t.string;
|
||||
export const throttle = t.union([
|
||||
t.literal('no_actions'),
|
||||
t.literal('rule'),
|
||||
TimeDuration({ allowedUnits: ['h', 'd'] }),
|
||||
]);
|
||||
export type Throttle = t.TypeOf<typeof throttle>;
|
||||
|
||||
export const throttleOrNull = t.union([throttle, t.null]);
|
||||
export type ThrottleOrNull = t.TypeOf<typeof throttleOrNull>;
|
||||
|
||||
export const throttleOrNullOrUndefined = t.union([throttle, t.null, t.undefined]);
|
||||
export type ThrottleOrUndefinedOrNull = t.TypeOf<typeof throttleOrNullOrUndefined>;
|
||||
|
|
|
@ -6,122 +6,315 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { left } from 'fp-ts/lib/Either';
|
||||
import { TimeDuration } from '.';
|
||||
import { foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
|
||||
describe('TimeDuration', () => {
|
||||
test('it should validate a correctly formed TimeDuration with time unit of seconds', () => {
|
||||
const payload = '1s';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
describe('with allowedDurations', () => {
|
||||
test('it should validate a correctly formed TimeDuration with an allowed duration of 1s', () => {
|
||||
const payload = '1s';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 's'],
|
||||
[2, 'h'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate a correctly formed TimeDuration with an allowed duration of 7d', () => {
|
||||
const payload = '1s';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 's'],
|
||||
[2, 'h'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should NOT validate a time duration if the allowed durations does not include it', () => {
|
||||
const payload = '24h';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 's'],
|
||||
[2, 'h'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "24h" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a an allowed duration with a negative number', () => {
|
||||
const payload = '10s';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 's'],
|
||||
[-7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[[1,"s"],[-7,"d"]]" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an allowed duration with a fractional number', () => {
|
||||
const payload = '1.5s';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 's'],
|
||||
[-7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1.5s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a an allowed duration with a duration of 0', () => {
|
||||
const payload = '10s';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[0, 's'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "[[0,"s"],[7,"d"]]" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a TimeDuration with an invalid time unit', () => {
|
||||
const payload = '10000000days';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "10000000days" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => {
|
||||
const payload = '100ff0000w';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100ff0000w" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an number', () => {
|
||||
const payload = 100;
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => {
|
||||
const payload = `${Math.pow(2, 53)}h`;
|
||||
const decoded = TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
describe('with allowedUnits', () => {
|
||||
test('it should validate a correctly formed TimeDuration with time unit of seconds', () => {
|
||||
const payload = '1s';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should validate a correctly formed TimeDuration with time unit of minutes', () => {
|
||||
const payload = '100m';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
test('it should validate a correctly formed TimeDuration with time unit of minutes', () => {
|
||||
const payload = '100m';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should validate a correctly formed TimeDuration with time unit of hours', () => {
|
||||
const payload = '10000000h';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
test('it should validate a correctly formed TimeDuration with time unit of hours', () => {
|
||||
const payload = '10000000h';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate a TimeDuration of 0 length', () => {
|
||||
const payload = '0s';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "0s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should validate a correctly formed TimeDuration with time unit of days', () => {
|
||||
const payload = '7d';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate a negative TimeDuration', () => {
|
||||
const payload = '-10s';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-10s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate a correctly formed TimeDuration with time unit of seconds if it is not an allowed unit', () => {
|
||||
const payload = '30s';
|
||||
const decoded = TimeDuration({ allowedUnits: ['m', 'h', 'd'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate a decimal TimeDuration', () => {
|
||||
const payload = '1.5s';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "30s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1.5s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate a negative TimeDuration', () => {
|
||||
const payload = '-10s';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate a TimeDuration with some other time unit', () => {
|
||||
const payload = '10000000w';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-10s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "10000000w" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate a fractional number', () => {
|
||||
const payload = '1.5s';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => {
|
||||
const payload = '100ff0000w';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1.5s" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100ff0000w" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate a TimeDuration with an invalid time unit', () => {
|
||||
const payload = '10000000days';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h', 'd'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "10000000days" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['Invalid value "" supplied to "TimeDuration"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate a TimeDuration with a time interval with incorrect format', () => {
|
||||
const payload = '100ff0000w';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate an number', () => {
|
||||
const payload = 100;
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100ff0000w" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
test('it should NOT validate an empty string', () => {
|
||||
const payload = '';
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => {
|
||||
const payload = `${Math.pow(2, 53)}h`;
|
||||
const decoded = TimeDuration.decode(payload);
|
||||
const message = pipe(decoded, foldLeftRight);
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
test('it should NOT validate an number', () => {
|
||||
const payload = 100;
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "100" supplied to "TimeDuration"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an TimeDuration with a valid time unit but unsafe integer', () => {
|
||||
const payload = `${Math.pow(2, 53)}h`;
|
||||
const decoded = TimeDuration({ allowedUnits: ['s', 'm', 'h'] }).decode(payload);
|
||||
const message = foldLeftRight(decoded);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
`Invalid value "${Math.pow(2, 53)}h" supplied to "TimeDuration"`,
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,38 +12,60 @@ import { Either } from 'fp-ts/lib/Either';
|
|||
/**
|
||||
* Types the TimeDuration as:
|
||||
* - A string that is not empty, and composed of a positive integer greater than 0 followed by a unit of time
|
||||
* - in the format {safe_integer}{timeUnit}, e.g. "30s", "1m", "2h"
|
||||
* - in the format {safe_integer}{timeUnit}, e.g. "30s", "1m", "2h", "7d"
|
||||
*/
|
||||
export const TimeDuration = new t.Type<string, string, unknown>(
|
||||
'TimeDuration',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> => {
|
||||
if (typeof input === 'string' && input.trim() !== '') {
|
||||
try {
|
||||
const inputLength = input.length;
|
||||
const unit = input.trim().at(-1);
|
||||
const time = parseFloat(input.trim().substring(0, inputLength - 1));
|
||||
|
||||
if (!Number.isInteger(time)) {
|
||||
type TimeUnits = 's' | 'm' | 'h' | 'd' | 'w' | 'y';
|
||||
interface TimeDurationWithAllowedDurations {
|
||||
allowedDurations: Array<[number, TimeUnits]>;
|
||||
allowedUnits?: never;
|
||||
}
|
||||
interface TimeDurationWithAllowedUnits {
|
||||
allowedUnits: TimeUnits[];
|
||||
allowedDurations?: never;
|
||||
}
|
||||
|
||||
type TimeDurationType = TimeDurationWithAllowedDurations | TimeDurationWithAllowedUnits;
|
||||
|
||||
const isTimeSafe = (time: number) => time >= 1 && Number.isSafeInteger(time);
|
||||
|
||||
export const TimeDuration = ({ allowedUnits, allowedDurations }: TimeDurationType) => {
|
||||
return new t.Type<string, string, unknown>(
|
||||
'TimeDuration',
|
||||
t.string.is,
|
||||
(input, context): Either<t.Errors, string> => {
|
||||
if (typeof input === 'string' && input.trim() !== '') {
|
||||
try {
|
||||
const inputLength = input.length;
|
||||
const time = Number(input.trim().substring(0, inputLength - 1));
|
||||
const unit = input.trim().at(-1);
|
||||
if (!isTimeSafe(time)) {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
if (allowedDurations) {
|
||||
for (const [allowedTime, allowedUnit] of allowedDurations) {
|
||||
if (!isTimeSafe(allowedTime)) {
|
||||
return t.failure(allowedDurations, context);
|
||||
}
|
||||
if (allowedTime === time && allowedUnit === unit) {
|
||||
return t.success(input);
|
||||
}
|
||||
}
|
||||
return t.failure(input, context);
|
||||
} else if (allowedUnits.includes(unit as TimeUnits)) {
|
||||
return t.success(input);
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
} catch (error) {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
if (
|
||||
time >= 1 &&
|
||||
Number.isSafeInteger(time) &&
|
||||
(unit === 's' || unit === 'm' || unit === 'h')
|
||||
) {
|
||||
return t.success(input);
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
} catch (error) {
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
} else {
|
||||
return t.failure(input, context);
|
||||
}
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
},
|
||||
t.identity
|
||||
);
|
||||
};
|
||||
|
||||
export type TimeDurationC = typeof TimeDuration;
|
||||
|
|
|
@ -9,7 +9,6 @@ import * as t from 'io-ts';
|
|||
import { NonEmptyArray, TimeDuration, enumeration } from '@kbn/securitysolution-io-ts-types';
|
||||
|
||||
import {
|
||||
throttle,
|
||||
action_group as actionGroup,
|
||||
action_params as actionParams,
|
||||
action_id as actionId,
|
||||
|
@ -41,6 +40,18 @@ export enum BulkActionEditType {
|
|||
'set_schedule' = 'set_schedule',
|
||||
}
|
||||
|
||||
export const throttleForBulkActions = t.union([
|
||||
t.literal('rule'),
|
||||
TimeDuration({
|
||||
allowedDurations: [
|
||||
[1, 'h'],
|
||||
[1, 'd'],
|
||||
[7, 'd'],
|
||||
],
|
||||
}),
|
||||
]);
|
||||
export type ThrottleForBulkActions = t.TypeOf<typeof throttleForBulkActions>;
|
||||
|
||||
const bulkActionEditPayloadTags = t.type({
|
||||
type: t.union([
|
||||
t.literal(BulkActionEditType.add_tags),
|
||||
|
@ -96,7 +107,7 @@ const bulkActionEditPayloadRuleActions = t.type({
|
|||
t.literal(BulkActionEditType.set_rule_actions),
|
||||
]),
|
||||
value: t.type({
|
||||
throttle,
|
||||
throttle: throttleForBulkActions,
|
||||
actions: t.array(normalizedRuleAction),
|
||||
}),
|
||||
});
|
||||
|
@ -106,8 +117,8 @@ export type BulkActionEditPayloadRuleActions = t.TypeOf<typeof bulkActionEditPay
|
|||
const bulkActionEditPayloadSchedule = t.type({
|
||||
type: t.literal(BulkActionEditType.set_schedule),
|
||||
value: t.type({
|
||||
interval: TimeDuration,
|
||||
lookback: TimeDuration,
|
||||
interval: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }),
|
||||
lookback: TimeDuration({ allowedUnits: ['s', 'm', 'h'] }),
|
||||
}),
|
||||
});
|
||||
export type BulkActionEditPayloadSchedule = t.TypeOf<typeof bulkActionEditPayloadSchedule>;
|
||||
|
|
|
@ -6,10 +6,13 @@
|
|||
*/
|
||||
|
||||
import { find } from 'lodash/fp';
|
||||
import { THROTTLE_OPTIONS, DEFAULT_THROTTLE_OPTION } from '../throttle_select_field';
|
||||
import {
|
||||
THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING,
|
||||
DEFAULT_THROTTLE_OPTION,
|
||||
} from '../throttle_select_field';
|
||||
|
||||
export const buildThrottleDescription = (value = DEFAULT_THROTTLE_OPTION.value, title: string) => {
|
||||
const throttleOption = find(['value', value], THROTTLE_OPTIONS);
|
||||
const throttleOption = find(['value', value], THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING);
|
||||
|
||||
return {
|
||||
title,
|
||||
|
|
|
@ -33,7 +33,7 @@ import { Form, UseField, useForm, useFormData } from '../../../../shared_imports
|
|||
import { StepContentWrapper } from '../step_content_wrapper';
|
||||
import {
|
||||
ThrottleSelectField,
|
||||
THROTTLE_OPTIONS,
|
||||
THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING,
|
||||
DEFAULT_THROTTLE_OPTION,
|
||||
} from '../throttle_select_field';
|
||||
import { RuleActionsField } from '../rule_actions_field';
|
||||
|
@ -62,11 +62,14 @@ 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 }];
|
||||
if (
|
||||
throttle &&
|
||||
findIndex(['value', throttle], THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING) < 0
|
||||
) {
|
||||
return [...THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING, { value: throttle, text: throttle }];
|
||||
}
|
||||
|
||||
return THROTTLE_OPTIONS;
|
||||
return THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING;
|
||||
};
|
||||
|
||||
const DisplayActionsHeader = () => {
|
||||
|
|
|
@ -13,15 +13,19 @@ import {
|
|||
} from '../../../../../common/constants';
|
||||
import { SelectField } from '../../../../shared_imports';
|
||||
|
||||
export const THROTTLE_OPTIONS = [
|
||||
{ value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' },
|
||||
export const THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS = [
|
||||
{ value: NOTIFICATION_THROTTLE_RULE, text: 'On each rule execution' },
|
||||
{ value: '1h', text: 'Hourly' },
|
||||
{ value: '1d', text: 'Daily' },
|
||||
{ value: '7d', text: 'Weekly' },
|
||||
];
|
||||
|
||||
export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS[0];
|
||||
export const THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING = [
|
||||
{ value: NOTIFICATION_THROTTLE_NO_ACTIONS, text: 'Perform no actions' },
|
||||
...THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS,
|
||||
];
|
||||
|
||||
export const DEFAULT_THROTTLE_OPTION = THROTTLE_OPTIONS_FOR_RULE_CREATION_AND_EDITING[0];
|
||||
|
||||
type ThrottleSelectField = typeof SelectField;
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import type {
|
|||
RuleAction,
|
||||
ActionTypeRegistryContract,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
import type { FormSchema } from '../../../../../../../shared_imports';
|
||||
import {
|
||||
useForm,
|
||||
|
@ -21,9 +22,12 @@ import {
|
|||
getUseField,
|
||||
Field,
|
||||
} from '../../../../../../../shared_imports';
|
||||
import type { BulkActionEditPayload } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { BulkActionEditType } from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { NOTIFICATION_THROTTLE_NO_ACTIONS } from '../../../../../../../../common/constants';
|
||||
import type {
|
||||
BulkActionEditPayload,
|
||||
ThrottleForBulkActions,
|
||||
} from '../../../../../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { NOTIFICATION_THROTTLE_RULE } from '../../../../../../../../common/constants';
|
||||
|
||||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { bulkAddRuleActions as i18n } from '../translations';
|
||||
|
@ -32,7 +36,7 @@ import { useKibana } from '../../../../../../../common/lib/kibana';
|
|||
|
||||
import {
|
||||
ThrottleSelectField,
|
||||
THROTTLE_OPTIONS,
|
||||
THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS,
|
||||
} from '../../../../../../components/rules/throttle_select_field';
|
||||
import { getAllActionMessageParams } from '../../../helpers';
|
||||
|
||||
|
@ -42,7 +46,7 @@ import { validateRuleActionsField } from '../../../../../../containers/detection
|
|||
const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
export interface RuleActionsFormData {
|
||||
throttle: string;
|
||||
throttle: ThrottleForBulkActions;
|
||||
actions: RuleAction[];
|
||||
overwrite: boolean;
|
||||
}
|
||||
|
@ -68,7 +72,7 @@ const getFormSchema = (
|
|||
});
|
||||
|
||||
const defaultFormData: RuleActionsFormData = {
|
||||
throttle: NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
throttle: NOTIFICATION_THROTTLE_RULE,
|
||||
actions: [],
|
||||
overwrite: false,
|
||||
};
|
||||
|
@ -93,7 +97,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
defaultValue: defaultFormData,
|
||||
});
|
||||
|
||||
const [{ overwrite, throttle }] = useFormData({ form, watch: ['overwrite', 'throttle'] });
|
||||
const [{ overwrite }] = useFormData({ form, watch: ['overwrite', 'throttle'] });
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
const { data, isValid } = await form.submit();
|
||||
|
@ -121,7 +125,7 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
dataTestSubj: 'bulkEditRulesRuleActionThrottle',
|
||||
hasNoInitialSelection: false,
|
||||
euiFieldProps: {
|
||||
options: THROTTLE_OPTIONS,
|
||||
options: THROTTLE_OPTIONS_FOR_BULK_RULE_ACTIONS,
|
||||
},
|
||||
}),
|
||||
[]
|
||||
|
@ -129,8 +133,6 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
|
||||
const messageVariables = useMemo(() => getAllActionMessageParams(), []);
|
||||
|
||||
const showActionsSelect = throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS;
|
||||
|
||||
return (
|
||||
<BulkEditFormWrapper
|
||||
form={form}
|
||||
|
@ -157,30 +159,6 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
defaultMessage="The actions frequency you select below is applied to all actions (both new and existing) for all selected rules."
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.deleteActionsDetail"
|
||||
defaultMessage="To delete actions for all selected rules, select {noActionsOption} in the menu and check {overwriteActionsCheckbox}."
|
||||
values={{
|
||||
noActionsOption: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.deleteActionsDetail.menuOptionLabel"
|
||||
defaultMessage="Perform no actions"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
overwriteActionsCheckbox: (
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.detectionEngine.rules.allRules.bulkActions.edit.addRuleActions.deleteActionsDetail.overwriteActionsCheckboxLabel"
|
||||
defaultMessage="Overwrite all selected rule actions"
|
||||
/>
|
||||
</strong>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</li>
|
||||
<li>{i18n.RULE_VARIABLES_DETAIL}</li>
|
||||
</ul>
|
||||
</EuiCallOut>
|
||||
|
@ -193,18 +171,14 @@ const RuleActionsFormComponent = ({ rulesCount, onClose, onConfirm }: RuleAction
|
|||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
{showActionsSelect && (
|
||||
<>
|
||||
<UseField
|
||||
path="actions"
|
||||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
</>
|
||||
)}
|
||||
<UseField
|
||||
path="actions"
|
||||
component={RuleActionsField}
|
||||
componentProps={{
|
||||
messageVariables,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<CommonUseField
|
||||
path="overwrite"
|
||||
|
|
|
@ -5,26 +5,26 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { BulkEditError, RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import pMap from 'p-map';
|
||||
import type { RulesClient, BulkEditError } from '@kbn/alerting-plugin/server';
|
||||
import type {
|
||||
BulkActionEditPayload,
|
||||
BulkActionEditPayloadRuleActions,
|
||||
} from '../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { BulkActionEditType } from '../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { enrichFilterWithRuleTypeMapping } from './enrich_filter_with_rule_type_mappings';
|
||||
import type { MlAuthz } from '../../machine_learning/authz';
|
||||
import { ruleParamsModifier } from './bulk_actions/rule_params_modifier';
|
||||
import { splitBulkEditActions } from './bulk_actions/split_bulk_edit_actions';
|
||||
import { validateBulkEditRule } from './bulk_actions/validations';
|
||||
import { bulkEditActionToRulesClientOperation } from './bulk_actions/action_to_rules_client_operation';
|
||||
import {
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
MAX_RULES_TO_UPDATE_IN_PARALLEL,
|
||||
} from '../../../../common/constants';
|
||||
import { BulkActionEditType } from '../../../../common/detection_engine/schemas/request/perform_bulk_action_schema';
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
import type { RuleAlertType } from './types';
|
||||
import {
|
||||
MAX_RULES_TO_UPDATE_IN_PARALLEL,
|
||||
NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
} from '../../../../common/constants';
|
||||
import { readRules } from './read_rules';
|
||||
|
||||
export interface BulkEditRulesArguments {
|
||||
rulesClient: RulesClient;
|
||||
|
@ -67,39 +67,30 @@ export const bulkEditRules = async ({
|
|||
// rulesClient bulkEdit currently doesn't support bulk mute/unmute.
|
||||
// this is a workaround to mitigate this,
|
||||
// until https://github.com/elastic/kibana/issues/139084 is resolved
|
||||
// if rule actions has been applied:
|
||||
// - we go through each rule
|
||||
// - mute/unmute if needed, refetch rule
|
||||
// calling mute for rule needed only when rule was unmuted before and throttle value is NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
// if rule actions has been applied, we go through each rule, unmute it if necessary and refetch it
|
||||
// calling unmute needed only if rule was muted and throttle value is not NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
const ruleActions = attributesActions.filter((rule): rule is BulkActionEditPayloadRuleActions =>
|
||||
[BulkActionEditType.set_rule_actions, BulkActionEditType.add_rule_actions].includes(rule.type)
|
||||
);
|
||||
|
||||
// bulk edit actions are applying in a historical order.
|
||||
// bulk edit actions are applied in historical order.
|
||||
// So, we need to find a rule action that will be applied the last, to be able to check if rule should be muted/unmuted
|
||||
const rulesAction = ruleActions.pop();
|
||||
|
||||
if (rulesAction) {
|
||||
const muteOrUnmuteErrors: BulkEditError[] = [];
|
||||
const rulesToMuteOrUnmute = await pMap(
|
||||
const unmuteErrors: BulkEditError[] = [];
|
||||
const rulesToUnmute = await pMap(
|
||||
result.rules,
|
||||
async (rule) => {
|
||||
try {
|
||||
if (rule.muteAll && rulesAction.value.throttle !== NOTIFICATION_THROTTLE_NO_ACTIONS) {
|
||||
await rulesClient.unmuteAll({ id: rule.id });
|
||||
return (await readRules({ rulesClient, id: rule.id, ruleId: undefined })) ?? rule;
|
||||
} else if (
|
||||
!rule.muteAll &&
|
||||
rulesAction.value.throttle === NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
) {
|
||||
await rulesClient.muteAll({ id: rule.id });
|
||||
return (await readRules({ rulesClient, id: rule.id, ruleId: undefined })) ?? rule;
|
||||
}
|
||||
|
||||
return rule;
|
||||
} catch (err) {
|
||||
muteOrUnmuteErrors.push({
|
||||
unmuteErrors.push({
|
||||
message: err.message,
|
||||
rule: {
|
||||
id: rule.id,
|
||||
|
@ -115,8 +106,8 @@ export const bulkEditRules = async ({
|
|||
|
||||
return {
|
||||
...result,
|
||||
rules: rulesToMuteOrUnmute.filter((rule): rule is RuleAlertType => rule != null),
|
||||
errors: [...result.errors, ...muteOrUnmuteErrors],
|
||||
rules: rulesToUnmute.filter((rule): rule is RuleAlertType => rule != null),
|
||||
errors: [...result.errors, ...unmuteErrors],
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1513,10 +1513,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
describe('throttle', () => {
|
||||
// For bulk editing of rule actions, NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
// is not available as payload, because "Perform No Actions" is not a valid option
|
||||
const casesForEmptyActions = [
|
||||
{
|
||||
payloadThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
},
|
||||
{
|
||||
payloadThrottle: NOTIFICATION_THROTTLE_RULE,
|
||||
},
|
||||
|
@ -1561,10 +1560,6 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
const casesForNonEmptyActions = [
|
||||
{
|
||||
payloadThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
expectedThrottle: NOTIFICATION_THROTTLE_NO_ACTIONS,
|
||||
},
|
||||
{
|
||||
payloadThrottle: NOTIFICATION_THROTTLE_RULE,
|
||||
expectedThrottle: NOTIFICATION_THROTTLE_RULE,
|
||||
|
@ -1616,12 +1611,9 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
});
|
||||
|
||||
describe('notifyWhen', () => {
|
||||
// For bulk editing of rule actions, NOTIFICATION_THROTTLE_NO_ACTIONS
|
||||
// is not available as payload, because "Perform No Actions" is not a valid option
|
||||
const cases = [
|
||||
{
|
||||
payload: { throttle: NOTIFICATION_THROTTLE_NO_ACTIONS },
|
||||
// keeps existing default value which is onActiveAlert
|
||||
expected: { notifyWhen: 'onActiveAlert' },
|
||||
},
|
||||
{
|
||||
payload: { throttle: '1d' },
|
||||
expected: { notifyWhen: 'onThrottleInterval' },
|
||||
|
|
|
@ -220,7 +220,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
updatedRule.rule_id = createRuleBody.rule_id;
|
||||
updatedRule.name = 'some other name';
|
||||
updatedRule.actions = [action1];
|
||||
updatedRule.throttle = '1m';
|
||||
updatedRule.throttle = '1d';
|
||||
delete updatedRule.id;
|
||||
|
||||
const { body } = await supertest
|
||||
|
@ -243,7 +243,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
},
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1m';
|
||||
outputRule.throttle = '1d';
|
||||
const bodyToCompare = removeServerGeneratedPropertiesIncludingRuleId(body);
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
|
|
@ -150,12 +150,12 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
const updatedRule1 = getSimpleRuleUpdate('rule-1');
|
||||
updatedRule1.name = 'some other name';
|
||||
updatedRule1.actions = [action1];
|
||||
updatedRule1.throttle = '1m';
|
||||
updatedRule1.throttle = '1d';
|
||||
|
||||
const updatedRule2 = getSimpleRuleUpdate('rule-2');
|
||||
updatedRule2.name = 'some other name';
|
||||
updatedRule2.actions = [action1];
|
||||
updatedRule2.throttle = '1m';
|
||||
updatedRule2.throttle = '1d';
|
||||
|
||||
// update both rule names
|
||||
const { body }: { body: FullResponseSchema[] } = await supertest
|
||||
|
@ -179,7 +179,7 @@ export default ({ getService }: FtrProviderContext) => {
|
|||
},
|
||||
},
|
||||
];
|
||||
outputRule.throttle = '1m';
|
||||
outputRule.throttle = '1d';
|
||||
const bodyToCompare = removeServerGeneratedProperties(response);
|
||||
expect(bodyToCompare).to.eql(outputRule);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue