From 1b6a87b8d7ddc2fae3f27b5e0f8d36165c7f79e3 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 18 Dec 2019 23:31:21 +0000 Subject: [PATCH] Aligns Alerting's interval with TaskManager's generic schedule field (#52873) (#53515) Follow up from the #52727 in Task Manager, we want Alerting and Task Manager to align on their schedule api (in the near future, Alerting will actually use Task manager's schedule system to remove this duplication). --- x-pack/legacy/plugins/alerting/README.md | 12 +- x-pack/legacy/plugins/alerting/mappings.json | 8 +- .../alerting/server/alerts_client.test.ts | 114 +++++++++++------- .../plugins/alerting/server/alerts_client.ts | 25 ++-- .../server/lib/get_next_run_at.test.ts | 4 +- .../alerting/server/lib/get_next_run_at.ts | 5 +- .../server/lib/task_runner_factory.test.ts | 2 +- .../server/lib/task_runner_factory.ts | 11 +- .../alerting/server/routes/create.test.ts | 10 +- .../plugins/alerting/server/routes/create.ts | 9 +- .../alerting/server/routes/get.test.ts | 2 +- .../alerting/server/routes/update.test.ts | 8 +- .../plugins/alerting/server/routes/update.ts | 9 +- .../legacy/plugins/alerting/server/types.ts | 8 +- .../routes/__mocks__/request_responses.ts | 2 +- .../detection_engine/routes/rules/utils.ts | 2 +- .../detection_engine/rules/create_rules.ts | 2 +- .../detection_engine/rules/update_rules.ts | 13 +- .../common/lib/alert_utils.ts | 2 +- .../common/lib/get_test_alert_data.ts | 2 +- .../tests/alerting/alerts.ts | 12 +- .../tests/alerting/create.ts | 32 +++-- .../tests/alerting/find.ts | 4 +- .../security_and_spaces/tests/alerting/get.ts | 2 +- .../tests/alerting/update.ts | 26 ++-- .../spaces_only/tests/alerting/alerts.ts | 2 +- .../spaces_only/tests/alerting/create.ts | 2 +- .../spaces_only/tests/alerting/find.ts | 2 +- .../spaces_only/tests/alerting/get.ts | 2 +- .../spaces_only/tests/alerting/update.ts | 4 +- 30 files changed, 212 insertions(+), 126 deletions(-) diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 85dbd75e14174..0b4024be39548 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -200,7 +200,7 @@ Payload: |name|A name to reference and search in the future.|string| |tags|A list of keywords to reference and search in the future.|string[]| |alertTypeId|The id value of the alert type you want to call when the alert is scheduled to execute.|string| -|interval|The interval in seconds, minutes, hours or days the alert should execute. Example: `10s`, `5m`, `1h`, `1d`.|string| +|schedule|The schedule specifying when this alert should run, using one of the available schedule formats specified under _Schedule Formats_ below|object| |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| |actions|Array of the following:
- `group` (string): We support grouping actions in the scenario of escalations or different types of alert instances. If you don't need this, feel free to use `default` as a value.
- `id` (string): The id of the action saved object to execute.
- `params` (object): The map to the `params` the action type will receive. In order to help apply context to strings, we handle them as mustache templates and pass in a default set of context. (see templating actions).|array| @@ -242,7 +242,7 @@ Payload: |Property|Description|Type| |---|---|---| -|interval|The interval in seconds, minutes, hours or days the alert should execute. Example: `10s`, `5m`, `1h`, `1d`.|string| +|schedule|The schedule specifying when this alert should be run, using one of the available schedule formats specified under _Schedule Formats_ below|object| |name|A name to reference and search in the future.|string| |tags|A list of keywords to reference and search in the future.|string[]| |params|The parameters to pass in to the alert type executor `params` value. This will also validate against the alert type params validator if defined.|object| @@ -304,6 +304,14 @@ Params: |---|---|---| |id|The id of the alert you're trying to update the API key for. System will use user in request context to generate an API key for.|string| +##### Schedule Formats +A schedule is structured such that the key specifies the format you wish to use and its value specifies the schedule. + +We currently support the _Interval format_ which specifies the interval in seconds, minutes, hours or days at which the alert should execute. +Example: `{ interval: "10s" }`, `{ interval: "5m" }`, `{ interval: "1h" }`, `{ interval: "1d" }`. + +There are plans to support multiple other schedule formats in the near fuiture. + ## Alert instance factory **alertInstanceFactory(id)** diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index 7a7446602351d..9536187116031 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -13,8 +13,12 @@ "alertTypeId": { "type": "keyword" }, - "interval": { - "type": "keyword" + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } }, "actions": { "type": "nested", diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index 37eb6a9b21d44..b07dad68da72d 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -47,7 +47,7 @@ function getMockData(overwrites: Record = {}) { name: 'abc', tags: ['foo'], alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, throttle: null, params: { bar: true, @@ -92,7 +92,7 @@ describe('create()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -157,10 +157,12 @@ describe('create()', () => { ], "alertTypeId": "123", "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", } `); @@ -184,13 +186,15 @@ describe('create()', () => { "apiKeyOwner": undefined, "createdBy": "elastic", "enabled": true, - "interval": "10s", "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "tags": Array [ "foo", ], @@ -298,7 +302,7 @@ describe('create()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -399,10 +403,12 @@ describe('create()', () => { ], "alertTypeId": "123", "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", } `); @@ -445,7 +451,7 @@ describe('create()', () => { attributes: { enabled: false, alertTypeId: '123', - interval: 10000, + schedule: { interval: 10000 }, params: { bar: true, }, @@ -484,10 +490,12 @@ describe('create()', () => { "alertTypeId": "123", "enabled": false, "id": "1", - "interval": 10000, "params": Object { "bar": true, }, + "schedule": Object { + "interval": 10000, + }, } `); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); @@ -585,7 +593,7 @@ describe('create()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -648,7 +656,7 @@ describe('create()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -722,7 +730,7 @@ describe('create()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -794,7 +802,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', enabled: true, - interval: '10s', + schedule: { interval: '10s' }, throttle: null, muteAll: false, mutedInstanceIds: [], @@ -820,7 +828,7 @@ describe('enable()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: false, }, @@ -846,7 +854,7 @@ describe('enable()', () => { 'alert', '1', { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -879,7 +887,7 @@ describe('enable()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, }, @@ -897,7 +905,7 @@ describe('enable()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: false, }, @@ -927,7 +935,7 @@ describe('enable()', () => { 'alert', '1', { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -962,7 +970,7 @@ describe('disable()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -976,7 +984,7 @@ describe('disable()', () => { 'alert', '1', { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', apiKey: null, apiKeyOwner: null, @@ -997,7 +1005,7 @@ describe('disable()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: false, scheduledTaskId: 'task-123', @@ -1060,7 +1068,7 @@ describe('muteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1088,7 +1096,7 @@ describe('muteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1107,7 +1115,7 @@ describe('muteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1129,7 +1137,7 @@ describe('unmuteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1157,7 +1165,7 @@ describe('unmuteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1176,7 +1184,7 @@ describe('unmuteInstance()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, scheduledTaskId: 'task-123', @@ -1199,7 +1207,7 @@ describe('get()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1235,10 +1243,12 @@ describe('get()', () => { ], "alertTypeId": "123", "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, } `); expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); @@ -1257,7 +1267,7 @@ describe('get()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1292,7 +1302,7 @@ describe('find()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1332,10 +1342,12 @@ describe('find()', () => { ], "alertTypeId": "123", "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, }, ], "page": 1, @@ -1362,7 +1374,7 @@ describe('delete()', () => { type: 'alert', attributes: { alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1443,7 +1455,7 @@ describe('update()', () => { type: 'alert', attributes: { enabled: true, - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1470,7 +1482,7 @@ describe('update()', () => { const result = await alertsClient.update({ id: '1', data: { - interval: '10s', + schedule: { interval: '10s' }, name: 'abc', tags: ['foo'], params: { @@ -1501,10 +1513,12 @@ describe('update()', () => { ], "enabled": true, "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", } `); @@ -1528,11 +1542,13 @@ describe('update()', () => { "apiKey": null, "apiKeyOwner": null, "enabled": true, - "interval": "10s", "name": "abc", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", "tags": Array [ "foo", @@ -1598,7 +1614,7 @@ describe('update()', () => { type: 'alert', attributes: { enabled: true, - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1651,7 +1667,7 @@ describe('update()', () => { const result = await alertsClient.update({ id: '1', data: { - interval: '10s', + schedule: { interval: '10s' }, name: 'abc', tags: ['foo'], params: { @@ -1712,10 +1728,12 @@ describe('update()', () => { ], "enabled": true, "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", } `); @@ -1771,7 +1789,7 @@ describe('update()', () => { type: 'alert', attributes: { enabled: true, - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, @@ -1799,7 +1817,7 @@ describe('update()', () => { const result = await alertsClient.update({ id: '1', data: { - interval: '10s', + schedule: { interval: '10s' }, name: 'abc', tags: ['foo'], params: { @@ -1831,10 +1849,12 @@ describe('update()', () => { "apiKey": "MTIzOmFiYw==", "enabled": true, "id": "1", - "interval": "10s", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", } `); @@ -1858,11 +1878,13 @@ describe('update()', () => { "apiKey": "MTIzOmFiYw==", "apiKeyOwner": "elastic", "enabled": true, - "interval": "10s", "name": "abc", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "scheduledTaskId": "task-123", "tags": Array [ "foo", @@ -1909,7 +1931,7 @@ describe('update()', () => { alertsClient.update({ id: '1', data: { - interval: '10s', + schedule: { interval: '10s' }, name: 'abc', tags: ['foo'], params: { @@ -1939,7 +1961,7 @@ describe('updateApiKey()', () => { id: '1', type: 'alert', attributes: { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, }, @@ -1956,7 +1978,7 @@ describe('updateApiKey()', () => { 'alert', '1', { - interval: '10s', + schedule: { interval: '10s' }, alertTypeId: '2', enabled: true, apiKey: Buffer.from('123:abc').toString('base64'), diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 27fda9871e685..578daa445b6ff 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -8,7 +8,14 @@ import Boom from 'boom'; import { omit } from 'lodash'; import { i18n } from '@kbn/i18n'; import { Logger, SavedObjectsClientContract, SavedObjectReference } from 'src/core/server'; -import { Alert, RawAlert, AlertTypeRegistry, AlertAction, AlertType } from './types'; +import { + Alert, + RawAlert, + AlertTypeRegistry, + AlertAction, + AlertType, + IntervalSchedule, +} from './types'; import { TaskManagerStartContract } from './shim'; import { validateAlertTypeParams } from './lib'; import { CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult } from '../../../../plugins/security/server'; @@ -82,7 +89,7 @@ interface UpdateOptions { data: { name: string; tags: string[]; - interval: string; + schedule: IntervalSchedule; actions: NormalizedAlertAction[]; params: Record; }; @@ -145,11 +152,7 @@ export class AlertsClient { if (data.enabled) { let scheduledTask; try { - scheduledTask = await this.scheduleAlert( - createdAlert.id, - rawAlert.alertTypeId, - rawAlert.interval - ); + scheduledTask = await this.scheduleAlert(createdAlert.id, rawAlert.alertTypeId); } catch (e) { // Cleanup data, something went wrong scheduling the task try { @@ -259,11 +262,7 @@ export class AlertsClient { const { attributes, version } = await this.savedObjectsClient.get('alert', id); if (attributes.enabled === false) { const apiKey = await this.createAPIKey(); - const scheduledTask = await this.scheduleAlert( - id, - attributes.alertTypeId, - attributes.interval - ); + const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); await this.savedObjectsClient.update( 'alert', @@ -364,7 +363,7 @@ export class AlertsClient { } } - private async scheduleAlert(id: string, alertTypeId: string, interval: string) { + private async scheduleAlert(id: string, alertTypeId: string) { return await this.taskManager.schedule({ taskType: `alerting:${alertTypeId}`, params: { diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts b/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts index 852e412689b35..1c4d8a42d2830 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts @@ -15,12 +15,12 @@ const mockedNow = new Date('2019-06-03T18:55:25.982Z'); test('Adds interface to given date when result is > Date.now()', () => { const currentRunAt = new Date('2019-06-03T18:55:23.982Z'); - const result = getNextRunAt(currentRunAt, '10s'); + const result = getNextRunAt(currentRunAt, { interval: '10s' }); expect(result).toEqual(new Date('2019-06-03T18:55:33.982Z')); }); test('Uses Date.now() when the result would of been a date in the past', () => { const currentRunAt = new Date('2019-06-03T18:55:13.982Z'); - const result = getNextRunAt(currentRunAt, '10s'); + const result = getNextRunAt(currentRunAt, { interval: '10s' }); expect(result).toEqual(mockedNow); }); diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts b/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts index 901b614b4d68c..f9867b5372908 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts @@ -5,9 +5,10 @@ */ import { parseDuration } from './parse_duration'; +import { IntervalSchedule } from '../types'; -export function getNextRunAt(currentRunAt: Date, interval: string) { - let nextRunAt = currentRunAt.getTime() + parseDuration(interval); +export function getNextRunAt(currentRunAt: Date, schedule: IntervalSchedule) { + let nextRunAt = currentRunAt.getTime() + parseDuration(schedule.interval); if (nextRunAt < Date.now()) { // To prevent returning dates in the past, we'll return now instead nextRunAt = Date.now(); diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts index c21c419977bbe..7966f98c749c8 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts @@ -74,7 +74,7 @@ const mockedAlertTypeSavedObject = { attributes: { enabled: true, alertTypeId: '123', - interval: '10s', + schedule: { interval: '10s' }, mutedInstanceIds: [], params: { bar: true, diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts index 051b15fc8dd8f..fe0979538d04e 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts @@ -20,6 +20,7 @@ import { GetServicesFunction, RawAlert, SpaceIdToNamespaceFunction, + IntervalSchedule, } from '../types'; export interface TaskRunnerContext { @@ -94,7 +95,7 @@ export class TaskRunnerFactory { const services = getServices(fakeRequest); // Ensure API key is still valid and user has access const { - attributes: { params, actions, interval, throttle, muteAll, mutedInstanceIds }, + attributes: { params, actions, schedule, throttle, muteAll, mutedInstanceIds }, references, } = await services.savedObjectsClient.get('alert', alertId); @@ -167,7 +168,13 @@ export class TaskRunnerFactory { }) ); - const nextRunAt = getNextRunAt(new Date(taskInstance.startedAt!), interval); + const nextRunAt = getNextRunAt( + new Date(taskInstance.startedAt!), + // we do not currently have a good way of returning the type + // from SavedObjectsClient, and as we currenrtly require a schedule + // and we only support `interval`, we can cast this safely + schedule as IntervalSchedule + ); return { state: { diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 634a797880812..a804aff55ad42 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -13,7 +13,7 @@ server.route(createAlertRoute); const mockedAlert = { alertTypeId: '1', name: 'abc', - interval: '10s', + schedule: { interval: '10s' }, tags: ['foo'], params: { bar: true, @@ -65,11 +65,13 @@ test('creates an alert with proper parameters', async () => { ], "alertTypeId": "1", "id": "123", - "interval": "10s", "name": "abc", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "tags": Array [ "foo", ], @@ -91,11 +93,13 @@ test('creates an alert with proper parameters', async () => { ], "alertTypeId": "1", "enabled": true, - "interval": "10s", "name": "abc", "params": Object { "bar": true, }, + "schedule": Object { + "interval": "10s", + }, "tags": Array [ "foo", ], diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.ts b/x-pack/legacy/plugins/alerting/server/routes/create.ts index cb5277ae19100..417072f978a92 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.ts @@ -7,6 +7,7 @@ import Hapi from 'hapi'; import Joi from 'joi'; import { getDurationSchema } from '../lib'; +import { IntervalSchedule } from '../types'; interface ScheduleRequest extends Hapi.Request { payload: { @@ -14,7 +15,7 @@ interface ScheduleRequest extends Hapi.Request { name: string; tags: string[]; alertTypeId: string; - interval: string; + schedule: IntervalSchedule; actions: Array<{ group: string; id: string; @@ -43,7 +44,11 @@ export const createAlertRoute = { .default([]), alertTypeId: Joi.string().required(), throttle: getDurationSchema().default(null), - interval: getDurationSchema().required(), + schedule: Joi.object() + .keys({ + interval: getDurationSchema().required(), + }) + .required(), params: Joi.object().required(), actions: Joi.array() .items( diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index 4d44ee9dfe6bd..b97762d10c960 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -13,7 +13,7 @@ server.route(getAlertRoute); const mockedAlert = { id: '1', alertTypeId: '1', - interval: '10s', + schedule: { interval: '10s' }, params: { bar: true, }, diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts index 334fb2120319d..8ce9d94140e6d 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts @@ -16,7 +16,7 @@ const mockedResponse = { id: '1', alertTypeId: '1', tags: ['foo'], - interval: '12s', + schedule: { interval: '12s' }, params: { otherField: false, }, @@ -40,7 +40,7 @@ test('calls the update function with proper parameters', async () => { throttle: null, name: 'abc', tags: ['bar'], - interval: '12s', + schedule: { interval: '12s' }, params: { otherField: false, }, @@ -75,11 +75,13 @@ test('calls the update function with proper parameters', async () => { }, }, ], - "interval": "12s", "name": "abc", "params": Object { "otherField": false, }, + "schedule": Object { + "interval": "12s", + }, "tags": Array [ "bar", ], diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.ts b/x-pack/legacy/plugins/alerting/server/routes/update.ts index 6e8f8557fb24a..bc55d48465602 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.ts @@ -7,6 +7,7 @@ import Joi from 'joi'; import Hapi from 'hapi'; import { getDurationSchema } from '../lib'; +import { IntervalSchedule } from '../types'; interface UpdateRequest extends Hapi.Request { params: { @@ -16,7 +17,7 @@ interface UpdateRequest extends Hapi.Request { alertTypeId: string; name: string; tags: string[]; - interval: string; + schedule: IntervalSchedule; actions: Array<{ group: string; id: string; @@ -45,7 +46,11 @@ export const updateAlertRoute = { tags: Joi.array() .items(Joi.string()) .required(), - interval: getDurationSchema().required(), + schedule: Joi.object() + .keys({ + interval: getDurationSchema().required(), + }) + .required(), params: Joi.object().required(), actions: Joi.array() .items( diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index 1bec2632d8082..e06e0c45e20b4 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -60,12 +60,16 @@ export interface RawAlertAction extends SavedObjectAttributes { params: AlertActionParams; } +export interface IntervalSchedule extends SavedObjectAttributes { + interval: string; +} + export interface Alert { enabled: boolean; name: string; tags: string[]; alertTypeId: string; - interval: string; + schedule: IntervalSchedule; actions: AlertAction[]; params: Record; scheduledTaskId?: string; @@ -83,7 +87,7 @@ export interface RawAlert extends SavedObjectAttributes { name: string; tags: string[]; alertTypeId: string; - interval: string; + schedule: SavedObjectAttributes; actions: RawAlertAction[]; params: SavedObjectAttributes; scheduledTaskId?: string; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index ae205a814daae..3c5182b5178b3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -271,7 +271,7 @@ export const getResult = (): RuleAlertType => ({ references: ['http://www.example.com', 'https://ww.example.com'], version: 1, }, - interval: '5m', + schedule: { interval: '5m' }, enabled: true, actions: [], throttle: null, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index 88261d872b0ea..dad22c74398d2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -45,7 +45,7 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial { + it('should handle create alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData(getTestAlertData({ interval: '10x' }))); + .send(getTestAlertData(getTestAlertData({ schedule: { interval: '10x' } }))); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -275,10 +275,15 @@ export default function createAlertTests({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'child "interval" fails because ["interval" with value "10x" fails to match the seconds pattern, "interval" with value "10x" fails to match the minutes pattern, "interval" with value "10x" fails to match the hours pattern, "interval" with value "10x" fails to match the days pattern]', + 'child "schedule" fails because [child "interval" fails because ["interval" with value "10x" fails to match the seconds pattern, "interval" with value "10x" fails to match the minutes pattern, "interval" with value "10x" fails to match the hours pattern, "interval" with value "10x" fails to match the days pattern]]', validation: { source: 'payload', - keys: ['interval', 'interval', 'interval', 'interval'], + keys: [ + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + ], }, }); break; @@ -287,12 +292,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { } }); - it('should handle create alert request appropriately when interval is 0', async () => { + it('should handle create alert request appropriately when interval schedule is 0', async () => { const response = await supertestWithoutAuth .post(`${getUrlPrefix(space.id)}/api/alert`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData(getTestAlertData({ interval: '0s' }))); + .send(getTestAlertData(getTestAlertData({ schedule: { interval: '0s' } }))); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -312,10 +317,15 @@ export default function createAlertTests({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'child "interval" fails because ["interval" with value "0s" fails to match the seconds pattern, "interval" with value "0s" fails to match the minutes pattern, "interval" with value "0s" fails to match the hours pattern, "interval" with value "0s" fails to match the days pattern]', + 'child "schedule" fails because [child "interval" fails because ["interval" with value "0s" fails to match the seconds pattern, "interval" with value "0s" fails to match the minutes pattern, "interval" with value "0s" fails to match the hours pattern, "interval" with value "0s" fails to match the days pattern]]', validation: { source: 'payload', - keys: ['interval', 'interval', 'interval', 'interval'], + keys: [ + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + ], }, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 359058f2ac23a..4da6c059c5a5e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -59,7 +59,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', - interval: '1m', + schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, @@ -138,7 +138,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', - interval: '1m', + schedule: { interval: '1m' }, enabled: false, actions: [ { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index 1a8109f6b6b3c..9c1f7fea93292 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -53,7 +53,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', - interval: '1m', + schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index 1b1bcef9ad23f..0e2ec0f7bc534 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -36,7 +36,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: { foo: true, }, - interval: '12s', + schedule: { interval: '12s' }, actions: [], throttle: '2m', }; @@ -96,7 +96,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: { foo: true, }, - interval: '12s', + schedule: { interval: '12s' }, throttle: '1m', actions: [], }); @@ -145,7 +145,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: { foo: true, }, - interval: '12s', + schedule: { interval: '12s' }, actions: [], }); @@ -203,10 +203,10 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'child "throttle" fails because ["throttle" is required]. child "name" fails because ["name" is required]. child "tags" fails because ["tags" is required]. child "interval" fails because ["interval" is required]. child "params" fails because ["params" is required]. child "actions" fails because ["actions" is required]', + 'child "throttle" fails because ["throttle" is required]. child "name" fails because ["name" is required]. child "tags" fails because ["tags" is required]. child "schedule" fails because ["schedule" is required]. child "params" fails because ["params" is required]. child "actions" fails because ["actions" is required]', validation: { source: 'payload', - keys: ['throttle', 'name', 'tags', 'interval', 'params', 'actions'], + keys: ['throttle', 'name', 'tags', 'schedule', 'params', 'actions'], }, }); break; @@ -237,7 +237,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { .send({ name: 'bcd', tags: ['bar'], - interval: '1m', + schedule: { interval: '1m' }, throttle: '1m', params: {}, actions: [], @@ -269,12 +269,12 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { } }); - it('should handle update alert request appropriately when interval is wrong syntax', async () => { + it('should handle update alert request appropriately when interval schedule is wrong syntax', async () => { const response = await supertestWithoutAuth .put(`${getUrlPrefix(space.id)}/api/alert/1`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) - .send(getTestAlertData({ interval: '10x', enabled: undefined })); + .send(getTestAlertData({ schedule: { interval: '10x' }, enabled: undefined })); switch (scenario.id) { case 'no_kibana_privileges at space1': @@ -294,10 +294,16 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { statusCode: 400, error: 'Bad Request', message: - 'child "interval" fails because ["interval" with value "10x" fails to match the seconds pattern, "interval" with value "10x" fails to match the minutes pattern, "interval" with value "10x" fails to match the hours pattern, "interval" with value "10x" fails to match the days pattern]. "alertTypeId" is not allowed', + 'child "schedule" fails because [child "interval" fails because ["interval" with value "10x" fails to match the seconds pattern, "interval" with value "10x" fails to match the minutes pattern, "interval" with value "10x" fails to match the hours pattern, "interval" with value "10x" fails to match the days pattern]]. "alertTypeId" is not allowed', validation: { source: 'payload', - keys: ['interval', 'interval', 'interval', 'interval', 'alertTypeId'], + keys: [ + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + 'schedule.interval', + 'alertTypeId', + ], }, }); break; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts index 5fafd8b0bfb61..03e973194b4e2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts @@ -123,7 +123,7 @@ export default function alertTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .send( getTestAlertData({ - interval: '1m', + schedule: { interval: '1m' }, alertTypeId: 'test.always-firing', params: { index: ES_TEST_INDEX_NAME, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index 929905a958abb..0e9011729eb3e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -71,7 +71,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { alertTypeId: 'test.noop', params: {}, createdBy: null, - interval: '1m', + schedule: { interval: '1m' }, scheduledTaskId: response.body.scheduledTaskId, updatedBy: null, throttle: '1m', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 0d12af6db79b2..3fdd9168eb5cb 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -42,7 +42,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', - interval: '1m', + schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 9e4797bcbf7ad..a49d3478d336d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -36,7 +36,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { name: 'abc', tags: ['foo'], alertTypeId: 'test.noop', - interval: '1m', + schedule: { interval: '1m' }, enabled: true, actions: [], params: {}, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index a6eccf88d9e26..46822781c0cd3 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -31,7 +31,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: { foo: true, }, - interval: '12s', + schedule: { interval: '12s' }, actions: [], throttle: '1m', }; @@ -71,7 +71,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { params: { foo: true, }, - interval: '12s', + schedule: { interval: '12s' }, actions: [], throttle: '1m', })