From a98faba00aaa720bb7fe0f00dd4cb350e3b302b6 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 15:16:25 -0500 Subject: [PATCH 01/45] [RAM] Add data model for scheduled and recurring snoozes --- x-pack/plugins/alerting/common/index.ts | 2 + .../alerting/common/is_rule_snoozed.test.ts | 188 ++++++++++++++++++ .../alerting/common/is_rule_snoozed.ts | 81 ++++++++ x-pack/plugins/alerting/common/rule.ts | 5 +- .../alerting/common/rule_snooze_type.ts | 15 ++ .../alerting/server/routes/find_rules.ts | 10 +- .../alerting/server/routes/get_rule.ts | 10 +- .../server/rules_client/rules_client.ts | 56 ++++-- .../rules_client/tests/aggregate.test.ts | 2 +- .../server/rules_client/tests/create.test.ts | 84 ++++---- .../rules_client/tests/mute_all.test.ts | 12 +- .../rules_client/tests/unmute_all.test.ts | 12 +- .../server/saved_objects/mappings.json | 32 ++- .../server/saved_objects/migrations.ts | 32 +++ .../server/task_runner/task_runner.test.ts | 23 ++- .../server/task_runner/task_runner.ts | 15 +- x-pack/plugins/alerting/server/types.ts | 5 +- .../lib/rule_api/common_transformations.ts | 8 +- .../components/rule_status_dropdown.tsx | 22 +- 19 files changed, 480 insertions(+), 134 deletions(-) create mode 100644 x-pack/plugins/alerting/common/is_rule_snoozed.test.ts create mode 100644 x-pack/plugins/alerting/common/is_rule_snoozed.ts create mode 100644 x-pack/plugins/alerting/common/rule_snooze_type.ts diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index f056ad7e0e4b7..b62b34eb058e6 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -21,6 +21,8 @@ export * from './disabled_action_groups'; export * from './rule_notify_when_type'; export * from './parse_duration'; export * from './execution_log_types'; +export * from './rule_snooze_type'; +export * from './is_rule_snoozed'; export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts new file mode 100644 index 0000000000000..a4b5f796a61b7 --- /dev/null +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -0,0 +1,188 @@ +/* + * 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 moment from 'moment'; +import sinon from 'sinon'; +import { isRuleSnoozed } from './is_rule_snoozed'; + +const LOCAL_OFFSET = moment('2020-01-01T00:00:00.000').format('Z'); +const DATE_9999 = '9999-12-31T12:34:56.789' + LOCAL_OFFSET; +const DATE_1970 = '1970-01-01T00:00:00.000' + LOCAL_OFFSET; +const DATE_1970_PLUS_6_HOURS = '1970-01-01T06:00:00.000' + LOCAL_OFFSET; +const DATE_2020 = '2020-01-01T00:00:00.000' + LOCAL_OFFSET; +const DATE_2020_MINUS_1_HOUR = '2019-12-31T23:00:00.000' + LOCAL_OFFSET; +const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000' + LOCAL_OFFSET; + +const NOW = DATE_2020; + +let fakeTimer: sinon.SinonFakeTimers; + +describe('isRuleSnoozed', () => { + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(new Date(NOW)); + }); + + afterAll(() => fakeTimer.restore()); + + test('returns false when snooze has not yet started', () => { + const snoozeSchedule = [ + { + startTime: DATE_9999, + duration: 100000000, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule })).toBe(false); + }); + + test('returns true when snooze has started', () => { + const snoozeSchedule = [ + { + startTime: NOW, + duration: 100000000, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule })).toBe(true); + }); + + test('returns false when snooze has ended', () => { + const snoozeSchedule = [ + { + startTime: DATE_1970, + duration: 100000000, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule })).toBe(false); + }); + + test('returns true when snooze is indefinite', () => { + const snoozeSchedule = [ + { + startTime: DATE_9999, + duration: 100000000, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: true })).toBe(true); + }); + + test('returns as expected for an indefinitely recurring snooze', () => { + const snoozeScheduleA = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: '1d', + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + const snoozeScheduleB = [ + { + startTime: DATE_1970_PLUS_6_HOURS, + duration: 60 * 1000, + repeatInterval: '1d', + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + const snoozeScheduleC = [ + { + startTime: DATE_2020_MINUS_1_HOUR, + duration: 60 * 1000, + repeatInterval: '1h', + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(true); + }); + + test('returns as expected for a recurring snooze with limited occurrences', () => { + const snoozeScheduleA = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: '1h', + occurrences: 600000, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + const snoozeScheduleB = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: '1h', + occurrences: 25, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + + // FIXME: THIS FAILS due to not compensating for leap years. Also 1M intervals exhibit confusing behavior + // Either we should add something to compensate for this, or disable the use of M and y, and only allow for explicit day lengths + // const snoozeScheduleC = [ + // { + // startTime: DATE_1970, + // duration: 60 * 1000, + // repeatInterval: '1y', + // occurrences: 60, + // }, + // ]; + // expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(true); + }); + + test('returns as expected for a recurring snooze with an end date', () => { + const snoozeScheduleA = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: '1h', + repeatEndTime: DATE_9999, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + const snoozeScheduleB = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: '1h', + repeatEndTime: DATE_2020_MINUS_1_HOUR, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + }); + + test('returns as expected for a recurring snooze on a day of the week', () => { + const snoozeScheduleA = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: 'DOW:135', // Monday Wednesday Friday; Jan 1 2020 was a Wednesday + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + const snoozeScheduleB = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + repeatInterval: 'DOW:2467', // Tue, Thu, Sat, Sun + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + const snoozeScheduleC = [ + { + startTime: DATE_2020_MINUS_1_MONTH, + duration: 60 * 1000, + repeatInterval: 'DOW:135', + occurrences: 12, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(false); + const snoozeScheduleD = [ + { + startTime: DATE_2020_MINUS_1_MONTH, + duration: 60 * 1000, + repeatInterval: 'DOW:135', + occurrences: 15, + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD })).toBe(true); + }); +}); diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.ts new file mode 100644 index 0000000000000..c6df412c8efad --- /dev/null +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.ts @@ -0,0 +1,81 @@ +/* + * 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 moment from 'moment'; +import { parseInterval } from '@kbn/data-plugin/common'; +import { SanitizedRule, RuleTypeParams } from './rule'; + +type RuleSnoozeProps = Pick, 'snoozeIndefinitely' | 'snoozeSchedule'>; + +export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { + if (rule.snoozeSchedule == null) { + return null; + } + + const now = Date.now(); + for (const snooze of rule.snoozeSchedule) { + const { startTime, duration, repeatInterval, occurrences, repeatEndTime } = snooze; + const startTimeMS = Date.parse(startTime); + const initialEndTime = startTimeMS + duration; + // If now is during the first occurrence of the snooze + + if (now >= startTimeMS && now < initialEndTime) return new Date(initialEndTime); + + // Check to see if now is during a recurrence of the snooze + if (repeatInterval) { + let occurrence; + let occurrenceStartTime; + if (repeatEndTime) { + const repeatEndTimeMS = Date.parse(repeatEndTime); + if (now >= repeatEndTimeMS) continue; + } + const timeFromInitialStart = now - startTimeMS; + + // Handle day-of-week recurrences + if (repeatInterval.startsWith('DOW:')) { + const [, daysOfWeekString] = repeatInterval.split(':'); + const repeatDays = daysOfWeekString + .split('') + .map((d) => Number(d)) + .sort(); + const today = moment(now).isoWeekday(); + + if (!repeatDays.includes(today)) continue; + + const weeksFromInitialStart = moment(now).diff(moment(startTime), 'weeks'); + const occurrencesPerWeek = repeatDays.length; + occurrence = weeksFromInitialStart * occurrencesPerWeek + repeatDays.indexOf(today); + const nowMoment = moment(now); + occurrenceStartTime = moment(startTime) + .year(nowMoment.year()) + .dayOfYear(nowMoment.dayOfYear()) + .valueOf(); + } else { + const interval = parseInterval(repeatInterval)?.asMilliseconds(); + if (!interval) continue; + + occurrence = Math.floor(timeFromInitialStart / interval); + occurrenceStartTime = interval * occurrence + startTimeMS; + } + + if (occurrences && occurrence > occurrences) continue; + + const occurrenceEndTime = occurrenceStartTime + duration; + + if (now >= occurrenceStartTime && now < occurrenceEndTime) return new Date(occurrenceEndTime); + } + } + + return null; +} + +export function isRuleSnoozed(rule: RuleSnoozeProps) { + if (rule.snoozeIndefinitely) { + return true; + } + return Boolean(getRuleSnoozeEndTime(rule)); +} diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 3d3b66f18a436..c19d2644348f6 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -13,6 +13,7 @@ import { } from '@kbn/core/server'; import { RuleExecutionMetrics } from '.'; import { RuleNotifyWhenType } from './rule_notify_when_type'; +import { RuleSnooze } from './rule_snooze_type'; export type RuleTypeState = Record; export type RuleTypeParams = Record; @@ -108,11 +109,11 @@ export interface Rule { apiKeyOwner: string | null; throttle: string | null; notifyWhen: RuleNotifyWhenType | null; - muteAll: boolean; mutedInstanceIds: string[]; executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeEndTime?: Date | null; // Remove ? when this parameter is made available in the public API + snoozeIndefinitely?: boolean; + snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } export type SanitizedRule = Omit, 'apiKey'>; diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts new file mode 100644 index 0000000000000..373568d4b4c3b --- /dev/null +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -0,0 +1,15 @@ +/* + * 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. + */ + +export type RuleSnooze = Array<{ + startTime: string; + duration: number; + id?: string; + repeatInterval?: string; + occurrences?: number; + repeatEndTime?: string; +}>; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index ef8b8b29057c0..86537b08c8655 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -79,12 +79,12 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - muteAll, mutedInstanceIds, executionStatus, actions, scheduledTaskId, - snoozeEndTime, + snoozeIndefinitely, + snoozeSchedule, ...rest }) => ({ ...rest, @@ -95,11 +95,11 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, - // Remove this object spread boolean check after snoozeEndTime is added to the public API - ...(snoozeEndTime !== undefined ? { snooze_end_time: snoozeEndTime } : {}), + // Remove these object spread boolean check after snooze props is added to the public API + ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), + ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), last_execution_date: executionStatus.lastExecutionDate, diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index f4414b0364dcb..bb8d6ce5b929f 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -30,12 +30,12 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - muteAll, mutedInstanceIds, executionStatus, actions, scheduledTaskId, - snoozeEndTime, + snoozeIndefinitely, + snoozeSchedule, ...rest }) => ({ ...rest, @@ -46,10 +46,10 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, muted_alert_ids: mutedInstanceIds, - // Remove this object spread boolean check after snoozeEndTime is added to the public API - ...(snoozeEndTime !== undefined ? { snooze_end_time: snoozeEndTime } : {}), + // Remove these object spread boolean check after snooze props is added to the public API + ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), + ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), scheduled_task_id: scheduledTaskId, execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d925315928f2c..d29cecd3c9345 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -51,6 +51,7 @@ import { RuleWithLegacyId, SanitizedRuleWithLegacyId, PartialRuleWithLegacyId, + RuleSnooze, RawAlertInstance as RawAlert, } from '../types'; import { validateRuleTypeParams, ruleExecutionStatusFromRaw, getRuleNotifyWhenType } from '../lib'; @@ -219,7 +220,7 @@ export interface CreateOptions { | 'updatedAt' | 'apiKey' | 'apiKeyOwner' - | 'muteAll' + | 'snoozeIndefinitely' | 'mutedInstanceIds' | 'actions' | 'executionStatus' @@ -300,7 +301,8 @@ export class RulesClient { private readonly fieldsToExcludeFromPublicApi: Array = [ 'monitoring', 'mapped_params', - 'snoozeEndTime', + 'snoozeIndefinitely', + 'snoozeSchedule', ]; constructor({ @@ -415,7 +417,7 @@ export class RulesClient { updatedAt: new Date(createTime).toISOString(), snoozeEndTime: null, params: updatedParams as RawRule['params'], - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], notifyWhen, executionStatus: getRuleExecutionStatusPending(new Date().toISOString()), @@ -919,7 +921,7 @@ export class RulesClient { terms: { field: 'alert.attributes.enabled' }, }, muted: { - terms: { field: 'alert.attributes.muteAll' }, + terms: { field: 'alert.attributes.snoozeIndefinitely' }, }, snoozed: { date_range: { @@ -1700,8 +1702,17 @@ export class RulesClient { // If snoozeEndTime is -1, instead mute all const newAttrs = snoozeEndTime === -1 - ? { muteAll: true, snoozeEndTime: null } - : { snoozeEndTime: new Date(snoozeEndTime).toISOString(), muteAll: false }; + ? { + snoozeIndefinitely: true, + snoozeSchedule: clearUnscheduledSnooze(attributes), + } + : { + snoozeSchedule: (attributes.snoozeSchedule ?? []).concat({ + startTime: new Date().toISOString(), + duration: Date.parse(snoozeEndTime) - Date.now(), + }), + snoozeIndefinitely: false, + }; const updateAttributes = this.updateMeta({ ...newAttrs, @@ -1765,8 +1776,8 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - snoozeEndTime: null, - muteAll: false, + snoozeEndTime: clearUnscheduledSnooze(attributes), + snoozeIndefinitely: false, updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -1827,9 +1838,9 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - muteAll: true, + snoozeIndefinitely: true, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeEndTime: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -1890,9 +1901,9 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeEndTime: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -1953,7 +1964,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { + if (!attributes.snoozeIndefinitely && !mutedInstanceIds.includes(alertInstanceId)) { mutedInstanceIds.push(alertInstanceId); await this.unsecuredSavedObjectsClient.update( 'alert', @@ -2020,7 +2031,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { + if (!attributes.snoozeIndefinitely && mutedInstanceIds.includes(alertInstanceId)) { await this.unsecuredSavedObjectsClient.update( 'alert', alertId, @@ -2140,15 +2151,18 @@ export class RulesClient { executionStatus, schedule, actions, - snoozeEndTime, + snoozeSchedule, ...partialRawRule }: Partial, references: SavedObjectReference[] | undefined, includeLegacyId: boolean = false, excludeFromPublicApi: boolean = false ): PartialRule | PartialRuleWithLegacyId { - const snoozeEndTimeDate = snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime; - const includeSnoozeEndTime = snoozeEndTimeDate !== undefined && !excludeFromPublicApi; + const snoozeScheduleDates = snoozeSchedule?.map((s) => ({ + ...s, + startTime: new Date(s.startTime), + })); + const includeSnoozeSchedule = snoozeSchedule !== undefined && !excludeFromPublicApi; const rule = { id, notifyWhen, @@ -2158,7 +2172,7 @@ export class RulesClient { schedule: schedule as IntervalSchedule, actions: actions ? this.injectReferencesIntoActions(id, actions, references || []) : [], params: this.injectReferencesIntoParams(id, ruleType, params, references || []) as Params, - ...(includeSnoozeEndTime ? { snoozeEndTime: snoozeEndTimeDate } : {}), + ...(includeSnoozeSchedule ? { snoozeEndTime: snoozeScheduleDates } : {}), ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), ...(createdAt ? { createdAt: new Date(createdAt) } : {}), ...(scheduledTaskId ? { scheduledTaskId } : {}), @@ -2375,3 +2389,9 @@ function parseDate(dateString: string | undefined, propertyName: string, default return parsedDate; } + +function clearUnscheduledSnooze(attributes: { snoozeSchedule?: RuleSnooze }) { + return attributes.snoozeSchedule + ? attributes.snoozeSchedule.filter((s) => typeof s.id === 'undefined') + : []; +} diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index b74059e4be3d6..f3338c6b66b42 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -178,7 +178,7 @@ describe('aggregate()', () => { terms: { field: 'alert.attributes.enabled' }, }, muted: { - terms: { field: 'alert.attributes.muteAll' }, + terms: { field: 'alert.attributes.snoozeIndefinitely' }, }, snoozed: { date_range: { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index d695acf574aeb..b418d9e3900ee 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -293,8 +293,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], actions: [ { @@ -359,7 +359,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": null, @@ -418,7 +418,7 @@ describe('create()', () => { "history": Array [], }, }, - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -428,7 +428,7 @@ describe('create()', () => { "schedule": Object { "interval": "1m", }, - "snoozeEndTime": null, + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -499,8 +499,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], actions: [ { @@ -559,8 +559,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], actions: [ { @@ -624,7 +624,7 @@ describe('create()', () => { "history": Array [], }, }, - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -634,7 +634,7 @@ describe('create()', () => { "schedule": Object { "interval": "1m", }, - "snoozeEndTime": null, + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -1042,8 +1042,8 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', notifyWhen: 'onActiveAlert', @@ -1246,8 +1246,8 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', notifyWhen: 'onActiveAlert', @@ -1419,8 +1419,8 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', notifyWhen: 'onActiveAlert', @@ -1528,8 +1528,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onActionGroupChange', actions: [ @@ -1585,8 +1585,8 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: '10m', notifyWhen: 'onActionGroupChange', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], executionStatus: { @@ -1626,7 +1626,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActionGroupChange", @@ -1660,8 +1660,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onThrottleInterval', actions: [ @@ -1717,8 +1717,8 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: '10m', notifyWhen: 'onThrottleInterval', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], executionStatus: { @@ -1758,7 +1758,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onThrottleInterval", @@ -1792,8 +1792,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onActiveAlert', actions: [ @@ -1849,8 +1849,8 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], executionStatus: { @@ -1890,7 +1890,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -1933,8 +1933,8 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], actions: [ { @@ -1997,8 +1997,8 @@ describe('create()', () => { updatedBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], executionStatus: { status: 'pending', @@ -2052,7 +2052,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "123", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": null, @@ -2372,8 +2372,8 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], executionStatus: { @@ -2474,8 +2474,8 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - muteAll: false, - snoozeEndTime: null, + snoozeIndefinitely: false, + snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], executionStatus: { @@ -2575,7 +2575,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], actions: [ { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 7f8ae28a20c6e..40f25c1a5711c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -69,7 +69,7 @@ describe('muteAll()', () => { }, }, ], - muteAll: false, + snoozeIndefinitely: false, }, references: [], version: '123', @@ -80,9 +80,9 @@ describe('muteAll()', () => { 'alert', '1', { - muteAll: true, + snoozeIndefinitely: true, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeSchedule: [], updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, @@ -117,7 +117,7 @@ describe('muteAll()', () => { enabled: false, scheduledTaskId: null, updatedBy: 'elastic', - muteAll: false, + snoozeIndefinitely: false, }, references: [], }); @@ -173,7 +173,7 @@ describe('muteAll()', () => { }, }, ], - muteAll: false, + snoozeIndefinitely: false, }, references: [], version: '123', @@ -207,7 +207,7 @@ describe('muteAll()', () => { }, }, ], - muteAll: false, + snoozeIndefinitely: false, }, references: [], version: '123', diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index cf063eea07862..acb9dc86c9aa3 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -69,7 +69,7 @@ describe('unmuteAll()', () => { }, }, ], - muteAll: true, + snoozeIndefinitely: true, }, references: [], version: '123', @@ -80,9 +80,9 @@ describe('unmuteAll()', () => { 'alert', '1', { - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeSchedule: [], updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, @@ -117,7 +117,7 @@ describe('unmuteAll()', () => { enabled: false, scheduledTaskId: null, updatedBy: 'elastic', - muteAll: false, + snoozeIndefinitely: false, }, references: [], }); @@ -173,7 +173,7 @@ describe('unmuteAll()', () => { }, }, ], - muteAll: false, + snoozeIndefinitely: false, }, references: [], version: '123', @@ -207,7 +207,7 @@ describe('unmuteAll()', () => { }, }, ], - muteAll: false, + snoozeIndefinitely: false, }, references: [], version: '123', diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerting/server/saved_objects/mappings.json index a027dd389575e..cd67714ae9688 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.json @@ -90,9 +90,6 @@ "notifyWhen": { "type": "keyword" }, - "muteAll": { - "type": "boolean" - }, "mutedInstanceIds": { "type": "keyword" }, @@ -176,9 +173,32 @@ } } }, - "snoozeEndTime": { - "type": "date", - "format": "strict_date_time" + "snoozeIndefinitely": { + "type": "boolean" + }, + "snoozeSchedule": { + "properties": { + "id": { + "type": "keyword" + }, + "startTime": { + "type": "date", + "format": "strict_date_time" + }, + "duration": { + "type": "long" + }, + "repeatInterval": { + "type": "keyword" + }, + "occurrences": { + "type": "long" + }, + "repeatEndTime": { + "type": "date", + "format": "strict_date_time" + } + } } } } diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 18de27eb6919e..a01d4607671eb 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -7,6 +7,7 @@ import { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; import { isString } from 'lodash/fp'; +import { omit } from 'lodash'; import { LogMeta, SavedObjectMigrationMap, @@ -152,6 +153,12 @@ export function getMigrations( pipeMigrations(addMappedParams) ); + const migrationRules830 = createEsoMigration( + encryptedSavedObjects, + (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, + pipeMigrations(convertMutesAndSnoozes) + ); + return { '7.10.0': executeMigrationWithErrorHandling(migrationWhenRBACWasIntroduced, '7.10.0'), '7.11.0': executeMigrationWithErrorHandling(migrationAlertUpdatedAtAndNotifyWhen, '7.11.0'), @@ -163,6 +170,7 @@ export function getMigrations( '8.0.0': executeMigrationWithErrorHandling(migrationRules800, '8.0.0'), '8.0.1': executeMigrationWithErrorHandling(migrationRules801, '8.0.1'), '8.2.0': executeMigrationWithErrorHandling(migrationRules820, '8.2.0'), + '8.3.0': executeMigrationWithErrorHandling(migrationRules830, '8.3.0'), }; } @@ -852,6 +860,30 @@ function addMappedParams( return doc; } +function convertMutesAndSnoozes( + doc: SavedObjectUnsanitizedDoc +): SavedObjectUnsanitizedDoc { + const { + attributes: { muteAll, snoozeEndTime }, + } = doc; + + return { + ...doc, + attributes: { + ...(omit(doc.attributes, ['snoozeEndTime', 'muteAll']) as RawRule), + snoozeIndefinitely: Boolean(muteAll), + snoozeSchedule: snoozeEndTime + ? [ + { + startTime: new Date().toISOString(), + duration: Date.parse(snoozeEndTime as string) - Date.now(), + }, + ] + : [], + }, + }; +} + function getCorrespondingAction( actions: SavedObjectAttribute, connectorRef: string diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 5ed33093cd8bc..b8f44f6b29155 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -404,7 +404,7 @@ describe('Task Runner', () => { ); rulesClient.get.mockResolvedValue({ ...mockedRuleTypeSavedObject, - muteAll: true, + snoozeIndefinitely: true, }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); await taskRunner.run(); @@ -475,25 +475,26 @@ describe('Task Runner', () => { }); type SnoozeTestParams = [ - muteAll: boolean, - snoozeEndTime: string | undefined | null, + snoozeIndefinitely: boolean, + snoozeSchedule: string | undefined | null, shouldBeSnoozed: boolean ]; const snoozeTestParams: SnoozeTestParams[] = [ [false, null, false], [false, undefined, false], - [false, DATE_1970, false], - [false, DATE_9999, true], + // Stringify the snooze schedules for better failure reporting + [false, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), false], + [false, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], [true, null, true], [true, undefined, true], - [true, DATE_1970, true], - [true, DATE_9999, true], + [true, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), true], + [true, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], ]; test.each(snoozeTestParams)( - 'snoozing works as expected with muteAll: %s; snoozeEndTime: %s', - async (muteAll, snoozeEndTime, shouldBeSnoozed) => { + 'snoozing works as expected with snoozeIndefinitely: %s; snoozeSchedule: %s', + async (snoozeIndefinitely, snoozeSchedule, shouldBeSnoozed) => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); ruleType.executor.mockImplementation( @@ -517,8 +518,8 @@ describe('Task Runner', () => { ); rulesClient.get.mockResolvedValue({ ...mockedRuleTypeSavedObject, - muteAll, - snoozeEndTime: snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime, + snoozeIndefinitely, + snoozeSchedule: snoozeSchedule != null ? JSON.parse(snoozeSchedule) : [], }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); await taskRunner.run(); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index 36703e7c28dd7..248ceada7b2e7 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -56,6 +56,7 @@ import { MONITORING_HISTORY_LIMIT, parseDuration, WithoutReservedActionGroups, + isRuleSnoozed, } from '../../common'; import { NormalizedRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { getEsErrorMessage } from '../lib/errors'; @@ -252,18 +253,6 @@ export class TaskRunner< } } - private isRuleSnoozed(rule: SanitizedRule): boolean { - if (rule.muteAll) { - return true; - } - - if (rule.snoozeEndTime == null) { - return false; - } - - return Date.now() < rule.snoozeEndTime.getTime(); - } - private shouldLogAndScheduleActionsForAlerts() { // if execution hasn't been cancelled, return true if (!this.cancelled) { @@ -497,7 +486,7 @@ export class TaskRunner< triggeredActionsStatus: ActionsCompletion.COMPLETE, }; - const ruleIsSnoozed = this.isRuleSnoozed(rule); + const ruleIsSnoozed = isRuleSnoozed(rule); if (!ruleIsSnoozed && this.shouldLogAndScheduleActionsForAlerts()) { const mutedAlertIdsSet = new Set(mutedInstanceIds); diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 32c6e41c63268..3d1a79e4f0896 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -40,6 +40,7 @@ import { SanitizedRuleConfig, RuleMonitoring, MappedParams, + RuleSnooze, } from '../common'; import { RuleTypeConfig } from './config'; export type WithoutQueryAndParams = Pick>; @@ -245,12 +246,12 @@ export interface RawRule extends SavedObjectAttributes { apiKeyOwner: string | null; throttle: string | null; notifyWhen: RuleNotifyWhenType | null; - muteAll: boolean; mutedInstanceIds: string[]; meta?: RuleMeta; executionStatus: RawRuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeEndTime?: string | null; // Remove ? when this parameter is made available in the public API + snoozeIndefinitely?: boolean; // Remove ? when this parameter is made available in the public API + snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } export interface AlertingPlugin { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index 67838f4f84881..df8e0186abe2a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -38,12 +38,12 @@ export const transformRule: RewriteRequestCase = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatus, actions: actions, - snooze_end_time: snoozeEndTime, + snooze_indefinitely: snoozeIndefinitely, + snooze_schedule: snoozeSchedule, ...rest }: any) => ({ ruleTypeId, @@ -53,9 +53,9 @@ export const transformRule: RewriteRequestCase = ({ updatedAt, apiKeyOwner, notifyWhen, - muteAll, mutedInstanceIds, - snoozeEndTime, + snoozeIndefinitely, + snoozeSchedule, executionStatus: executionStatus ? transformExecutionStatus(executionStatus) : undefined, actions: actions ? actions.map((action: AsApiContract) => transformAction(action)) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 40658ae282e16..ea72f9fc6f432 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -8,6 +8,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; +import { isRuleSnoozed, getRuleSnoozeEndTime } from '@kbn/alerting-plugin/common'; import { useGeneratedHtmlId, EuiLoadingSpinner, @@ -36,7 +37,7 @@ import { Rule } from '../../../../types'; type SnoozeUnit = 'm' | 'h' | 'd' | 'w' | 'M'; const SNOOZE_END_TIME_FORMAT = 'LL @ LT'; -type DropdownRuleRecord = Pick; +type DropdownRuleRecord = Pick; export interface ComponentOpts { rule: DropdownRuleRecord; @@ -158,11 +159,15 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ isEnabled && isSnoozed ? ( - {rule.muteAll ? INDEFINITELY : moment(rule.snoozeEndTime).fromNow(true)} + {rule.snoozeSchedule + ? INDEFINITELY + : moment(getRuleSnoozeEndTime(rule) as Date).fromNow(true)} ) : null; @@ -215,7 +220,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ onChangeSnooze={onChangeSnooze} isEnabled={isEnabled} isSnoozed={isSnoozed} - snoozeEndTime={rule.snoozeEndTime} + snoozeEndTime={getRuleSnoozeEndTime(rule)} previousSnoozeInterval={previousSnoozeInterval} /> @@ -474,15 +479,6 @@ const SnoozePanel: React.FunctionComponent = ({ ); }; -const isRuleSnoozed = (rule: DropdownRuleRecord) => { - const { snoozeEndTime, muteAll } = rule; - if (muteAll) return true; - if (!snoozeEndTime) { - return false; - } - return moment(Date.now()).isBefore(snoozeEndTime); -}; - const futureTimeToInterval = (time?: Date | null) => { if (!time) return; const relativeTime = moment(time).locale('en').fromNow(true); From bc204436dab518b07f27b7a13f682137cf9abd4d Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 15:35:40 -0500 Subject: [PATCH 02/45] Update migration tests --- .../server/saved_objects/migrations.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 921412d4e79e8..77cc0784586a9 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -5,6 +5,7 @@ * 2.0. */ +import sinon from 'sinon'; import uuid from 'uuid'; import { getMigrations, isAnyActionSupportIncidents } from './migrations'; import { RawRule } from '../types'; @@ -2254,6 +2255,38 @@ describe('successful migrations', () => { }); describe('8.3.0', () => { + test('migrates muted rules to the new data model', () => { + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const mutedAlert = getMockData( + { + muteAll: true, + }, + true + ); + const migratedMutedAlert830 = migration830(mutedAlert, migrationContext); + + expect(migratedMutedAlert830.attributes.snoozeIndefinitely).toEqual(true); + }); + + test('migrates snoozed rules to the new data model', () => { + const fakeTimer = sinon.useFakeTimers(); + const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; + const mutedAlert = getMockData( + { + snoozeEndTime: '1970-01-02T00:00:00.000Z', + }, + true + ); + const migratedMutedAlert830 = migration830(mutedAlert, migrationContext); + + expect(migratedMutedAlert830.attributes.snoozeSchedule.length).toEqual(1); + expect(migratedMutedAlert830.attributes.snoozeSchedule[0].startTime).toEqual( + '1970-01-01T00:00:00.000Z' + ); + expect(migratedMutedAlert830.attributes.snoozeSchedule[0].duration).toEqual(86400000); + fakeTimer.restore(); + }); + test('removes internal tags', () => { const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; const alert = getMockData( From de7a37f00bbb400d0b8d5a17bdbcab9013bfec1a Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 16:15:49 -0500 Subject: [PATCH 03/45] Make snoozeIndefinitely required --- x-pack/plugins/alerting/common/alert_summary.ts | 2 +- x-pack/plugins/alerting/common/rule.ts | 2 +- .../alerting/server/lib/alert_summary_from_event_log.ts | 2 +- x-pack/plugins/alerting/server/routes/find_rules.ts | 4 ++-- x-pack/plugins/alerting/server/routes/get_rule.ts | 4 ++-- x-pack/plugins/alerting/server/rules_client/rules_client.ts | 1 - x-pack/plugins/alerting/server/types.ts | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/alerting/common/alert_summary.ts b/x-pack/plugins/alerting/common/alert_summary.ts index b04ce59eed1bd..6e91202cea74c 100644 --- a/x-pack/plugins/alerting/common/alert_summary.ts +++ b/x-pack/plugins/alerting/common/alert_summary.ts @@ -19,7 +19,7 @@ export interface AlertSummary { tags: string[]; ruleTypeId: string; consumer: string; - muteAll: boolean; + snoozeIndefinitely: boolean; throttle: string | null; enabled: boolean; statusStartDate: string; diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 97108e965e629..bfd7bb918bb86 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -108,7 +108,7 @@ export interface Rule { mutedInstanceIds: string[]; executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeIndefinitely?: boolean; + snoozeIndefinitely: boolean; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index ff9bc776cfb95..72cf6b6d5bfae 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -32,7 +32,7 @@ export function alertSummaryFromEventLog(params: AlertSummaryFromEventLogParams) statusStartDate: dateStart, statusEndDate: dateEnd, status: 'OK', - muteAll: rule.muteAll, + snoozeIndefinitely: rule.snoozeIndefinitely, throttle: rule.throttle, enabled: rule.enabled, lastRun: undefined, diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index 86537b08c8655..7764122ed48ce 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -97,8 +97,8 @@ const rewriteBodyRes: RewriteResponseCase> = ({ notify_when: notifyWhen, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, - // Remove these object spread boolean check after snooze props is added to the public API - ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), + snooze_indefinitely: snoozeIndefinitely, + // Remove this object spread boolean check after snoozeSchedule is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index bb8d6ce5b929f..09335f5347f52 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -47,8 +47,8 @@ const rewriteBodyRes: RewriteResponseCase> = ({ api_key_owner: apiKeyOwner, notify_when: notifyWhen, muted_alert_ids: mutedInstanceIds, - // Remove these object spread boolean check after snooze props is added to the public API - ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), + snooze_indefinitely: snoozeIndefinitely, + // Remove this object spread boolean check after snooze props is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), scheduled_task_id: scheduledTaskId, execution_status: executionStatus && { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d29cecd3c9345..06348a7b76242 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -301,7 +301,6 @@ export class RulesClient { private readonly fieldsToExcludeFromPublicApi: Array = [ 'monitoring', 'mapped_params', - 'snoozeIndefinitely', 'snoozeSchedule', ]; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index be8068736ba80..7312b5109e748 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -248,7 +248,7 @@ export interface RawRule extends SavedObjectAttributes { meta?: RuleMeta; executionStatus: RawRuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeIndefinitely?: boolean; // Remove ? when this parameter is made available in the public API + snoozeIndefinitely: boolean; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } From 7fec109c265dba6cf7c45dc104f6d82e083c537d Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 16:21:32 -0500 Subject: [PATCH 04/45] Fix remaining muteAlls --- x-pack/plugins/alerting/public/alert_api.test.ts | 4 ++-- .../alerting/public/lib/common_transformations.test.ts | 8 ++++---- .../plugins/alerting/public/lib/common_transformations.ts | 4 ++-- .../server/lib/alert_summary_from_event_log.test.ts | 8 ++++---- x-pack/plugins/alerting/server/routes/create_rule.test.ts | 4 ++-- x-pack/plugins/alerting/server/routes/create_rule.ts | 4 ++-- x-pack/plugins/alerting/server/routes/get_rule.test.ts | 4 ++-- .../plugins/alerting/server/routes/legacy/create.test.ts | 2 +- x-pack/plugins/alerting/server/routes/legacy/get.test.ts | 2 +- .../plugins/alerting/server/routes/resolve_rule.test.ts | 4 ++-- x-pack/plugins/alerting/server/routes/resolve_rule.ts | 4 ++-- x-pack/plugins/alerting/server/routes/update_rule.ts | 4 ++-- .../server/task_runner/alert_task_instance.test.ts | 2 +- .../server/task_runner/task_runner_cancel.test.ts | 2 +- 14 files changed, 28 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerting/public/alert_api.test.ts index 2f09643e499e3..8a09d0b7fea40 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerting/public/alert_api.test.ts @@ -124,7 +124,7 @@ describe('loadRule', () => { "status": "ok", }, "id": "3d534c70-582b-11ec-8995-2b1578a3bc5d", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [], "name": "stressing index-threshold 37/200", "notifyWhen": "onActiveAlert", @@ -306,7 +306,7 @@ function getRule(): Rule<{ x: number }> { apiKeyOwner: '2889684073', createdBy: 'elastic', updatedBy: '2889684073', - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], schedule: { interval: '1s', diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts index 51d24538b449e..c6a0362782656 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts @@ -43,7 +43,7 @@ describe('common_transformations', () => { api_key_owner: 'api-key-user', throttle: '2s', notify_when: 'onActiveAlert', - mute_all: false, + snooze_indefinitely: false, muted_alert_ids: ['bob', 'jim'], execution_status: { last_execution_date: dateExecuted.toISOString(), @@ -89,7 +89,7 @@ describe('common_transformations', () => { "status": "error", }, "id": "some-id", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [ "bob", "jim", @@ -146,7 +146,7 @@ describe('common_transformations', () => { api_key_owner: 'api-key-user', throttle: '2s', notify_when: 'onActiveAlert', - mute_all: false, + snooze_indefinitely: false, muted_alert_ids: ['bob', 'jim'], execution_status: { last_execution_date: dateExecuted.toISOString(), @@ -176,7 +176,7 @@ describe('common_transformations', () => { "status": "error", }, "id": "some-id", - "muteAll": false, + "snoozeIndefinitely": false, "mutedInstanceIds": Array [ "bob", "jim", diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.ts b/x-pack/plugins/alerting/public/lib/common_transformations.ts index 1b306aae0ae2f..ba39e5a98f951 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.ts @@ -56,7 +56,7 @@ export function transformRule(input: ApiRule): Rule { api_key: apiKey, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, + snooze_indefinitely: snoozeIndefinitely, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatusAPI, @@ -73,7 +73,7 @@ export function transformRule(input: ApiRule): Rule { apiKey, apiKeyOwner, notifyWhen, - muteAll, + snoozeIndefinitely, mutedInstanceIds, executionStatus: transformExecutionStatus(executionStatusAPI), actions: actionsAPI ? actionsAPI.map((action) => transformAction(action)) : [], diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts index 70ec1524fec16..da72376ef0411 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts @@ -40,7 +40,7 @@ describe('alertSummaryFromEventLog', () => { }, "id": "rule-123", "lastRun": undefined, - "muteAll": false, + "snoozeIndefinitely": false, "name": "rule-name", "ruleTypeId": "123", "status": "OK", @@ -62,7 +62,7 @@ describe('alertSummaryFromEventLog', () => { tags: ['tag-1', 'tag-2'], consumer: 'rule-consumer-2', throttle: '1h', - muteAll: true, + snoozeIndefinitely: true, }); const events: IValidatedEvent[] = []; const executionEvents: IValidatedEvent[] = []; @@ -86,7 +86,7 @@ describe('alertSummaryFromEventLog', () => { }, "id": "rule-456", "lastRun": undefined, - "muteAll": true, + "snoozeIndefinitely": true, "name": "rule-name-2", "ruleTypeId": "456", "status": "OK", @@ -730,7 +730,7 @@ const BaseRule: SanitizedRule<{ bar: boolean }> = { consumer: 'rule-consumer', throttle: null, notifyWhen: null, - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], params: { bar: true }, actions: [], diff --git a/x-pack/plugins/alerting/server/routes/create_rule.test.ts b/x-pack/plugins/alerting/server/routes/create_rule.test.ts index cbb7965cab081..66d7bd36a08a0 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.test.ts @@ -54,7 +54,7 @@ describe('createRuleRoute', () => { }, ], enabled: true, - muteAll: false, + snoozeIndefinitely: false, createdBy: '', updatedBy: '', apiKeyOwner: '', @@ -84,7 +84,7 @@ describe('createRuleRoute', () => { const createResult: AsApiContract> = { ...ruleToCreate, - mute_all: mockedAlert.muteAll, + snooze_indefinitely: mockedAlert.snoozeIndefinitely, created_by: mockedAlert.createdBy, updated_by: mockedAlert.updatedBy, api_key_owner: mockedAlert.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index cf044c94f2529..91dcfc7313870 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -65,7 +65,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - muteAll, + snoozeIndefinitely, mutedInstanceIds, executionStatus: { lastExecutionDate, lastDuration, ...executionStatus }, ...rest @@ -79,7 +79,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, + snooze_indefinitely: snoozeIndefinitely, muted_alert_ids: mutedInstanceIds, execution_status: { ...executionStatus, diff --git a/x-pack/plugins/alerting/server/routes/get_rule.test.ts b/x-pack/plugins/alerting/server/routes/get_rule.test.ts index 065cd567f1f69..a8e9ccfe9356e 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.test.ts @@ -50,7 +50,7 @@ describe('getRuleRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - muteAll: false, + snoozeIndefinitely: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', @@ -67,7 +67,7 @@ describe('getRuleRoute', () => { ...pick(mockedAlert, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), rule_type_id: mockedAlert.alertTypeId, notify_when: mockedAlert.notifyWhen, - mute_all: mockedAlert.muteAll, + snooze_indefinitely: mockedAlert.snoozeIndefinitely, created_by: mockedAlert.createdBy, updated_by: mockedAlert.updatedBy, api_key_owner: mockedAlert.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts index 60b299be6db97..7b8e3aebcdb1f 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -60,7 +60,7 @@ describe('createAlertRoute', () => { const createResult: Rule<{ bar: boolean }> = { ...mockedAlert, enabled: true, - muteAll: false, + snoozeIndefinitely: false, createdBy: '', updatedBy: '', apiKey: '', diff --git a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts index 6860140ce2540..406d22245ca0d 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts @@ -54,7 +54,7 @@ describe('getAlertRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - muteAll: false, + snoozeIndefinitely: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts index 656874b5cf332..864c0cbf5c1da 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts @@ -50,7 +50,7 @@ describe('resolveRuleRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - muteAll: false, + snoozeIndefinitely: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', @@ -79,7 +79,7 @@ describe('resolveRuleRoute', () => { ), rule_type_id: mockedRule.alertTypeId, notify_when: mockedRule.notifyWhen, - mute_all: mockedRule.muteAll, + snooze_indefinitely: mockedRule.snoozeIndefinitely, created_by: mockedRule.createdBy, updated_by: mockedRule.updatedBy, api_key_owner: mockedRule.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index cde747f9272fe..7dd21ea9c5d77 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -29,7 +29,7 @@ const rewriteBodyRes: RewriteResponseCase> updatedAt, apiKeyOwner, notifyWhen, - muteAll, + snoozeIndefinitely, mutedInstanceIds, executionStatus, actions, @@ -44,7 +44,7 @@ const rewriteBodyRes: RewriteResponseCase> updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - mute_all: muteAll, + snooze_indefinitely: snoozeIndefinitely, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatus && { diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index d2130e1f33541..b7c8452f94908 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -67,7 +67,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - muteAll, + snoozeIndefinitely, mutedInstanceIds, executionStatus, ...rest @@ -81,7 +81,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(createdAt ? { created_at: createdAt } : {}), ...(updatedAt ? { updated_at: updatedAt } : {}), ...(notifyWhen ? { notify_when: notifyWhen } : {}), - ...(muteAll !== undefined ? { mute_all: muteAll } : {}), + ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), ...(mutedInstanceIds ? { muted_alert_ids: mutedInstanceIds } : {}), ...(executionStatus ? { diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts index 3faea1a4d2261..e3ac325ba7e73 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts @@ -31,7 +31,7 @@ const alert: SanitizedRule<{ apiKeyOwner: null, throttle: null, notifyWhen: null, - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 4986359115377..69c997c323484 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -144,7 +144,7 @@ describe('Task Runner Cancel', () => { createdAt: mockDate, updatedAt: mockDate, throttle: null, - muteAll: false, + snoozeIndefinitely: false, notifyWhen: 'onActiveAlert', enabled: true, alertTypeId: ruleType.id, From ab4f21bddb31d9f2c64c7bb9308d7efdfa950210 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 16:25:04 -0500 Subject: [PATCH 05/45] Replace snoozeEndTime with snoozeSchedule --- .../alerting/server/rules_client/rules_client.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 06348a7b76242..a4672e85a41ae 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -414,7 +414,7 @@ export class RulesClient { updatedBy: username, createdAt: new Date(createTime).toISOString(), updatedAt: new Date(createTime).toISOString(), - snoozeEndTime: null, + snoozeSchedule: [], params: updatedParams as RawRule['params'], snoozeIndefinitely: false, mutedInstanceIds: [], @@ -924,7 +924,7 @@ export class RulesClient { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeEndTime', + field: 'alert.attributes.snoozeSchedule.startTime', format: 'strict_date_time', ranges: [{ from: 'now' }], }, @@ -1775,7 +1775,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - snoozeEndTime: clearUnscheduledSnooze(attributes), + snoozeSchedule: clearUnscheduledSnooze(attributes), snoozeIndefinitely: false, updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), @@ -1839,7 +1839,7 @@ export class RulesClient { const updateAttributes = this.updateMeta({ snoozeIndefinitely: true, mutedInstanceIds: [], - snoozeEndTime: clearUnscheduledSnooze(attributes), + snoozeSchedule: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -1902,7 +1902,7 @@ export class RulesClient { const updateAttributes = this.updateMeta({ snoozeIndefinitely: false, mutedInstanceIds: [], - snoozeEndTime: clearUnscheduledSnooze(attributes), + snoozeSchedule: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -2171,7 +2171,7 @@ export class RulesClient { schedule: schedule as IntervalSchedule, actions: actions ? this.injectReferencesIntoActions(id, actions, references || []) : [], params: this.injectReferencesIntoParams(id, ruleType, params, references || []) as Params, - ...(includeSnoozeSchedule ? { snoozeEndTime: snoozeScheduleDates } : {}), + ...(includeSnoozeSchedule ? { snoozeSchedule: snoozeScheduleDates } : {}), ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), ...(createdAt ? { createdAt: new Date(createdAt) } : {}), ...(scheduledTaskId ? { scheduledTaskId } : {}), From 29173d81931ed67b7c40fda5f556333b2e7b79ea Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 16:41:49 -0500 Subject: [PATCH 06/45] Fix typecheck --- .../alerting/common/is_rule_snoozed.test.ts | 50 +++++++++++++------ .../routes/get_rule_alert_summary.test.ts | 2 +- .../server/routes/get_rule_alert_summary.ts | 4 +- .../legacy/get_alert_instance_summary.test.ts | 2 +- .../tests/get_execution_log.test.ts | 2 +- .../alerting/server/task_runner/fixtures.ts | 2 +- 6 files changed, 42 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts index a4b5f796a61b7..74215efdc0edb 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -35,7 +35,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(false); }); test('returns true when snooze has started', () => { @@ -45,7 +45,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(true); }); test('returns false when snooze has ended', () => { @@ -55,7 +55,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(false); }); test('returns true when snooze is indefinite', () => { @@ -76,7 +76,9 @@ describe('isRuleSnoozed', () => { repeatInterval: '1d', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( + true + ); const snoozeScheduleB = [ { startTime: DATE_1970_PLUS_6_HOURS, @@ -84,7 +86,9 @@ describe('isRuleSnoozed', () => { repeatInterval: '1d', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( + false + ); const snoozeScheduleC = [ { startTime: DATE_2020_MINUS_1_HOUR, @@ -92,7 +96,9 @@ describe('isRuleSnoozed', () => { repeatInterval: '1h', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, snoozeIndefinitely: false })).toBe( + true + ); }); test('returns as expected for a recurring snooze with limited occurrences', () => { @@ -104,7 +110,9 @@ describe('isRuleSnoozed', () => { occurrences: 600000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( + true + ); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -113,7 +121,9 @@ describe('isRuleSnoozed', () => { occurrences: 25, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( + false + ); // FIXME: THIS FAILS due to not compensating for leap years. Also 1M intervals exhibit confusing behavior // Either we should add something to compensate for this, or disable the use of M and y, and only allow for explicit day lengths @@ -137,7 +147,9 @@ describe('isRuleSnoozed', () => { repeatEndTime: DATE_9999, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( + true + ); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -146,7 +158,9 @@ describe('isRuleSnoozed', () => { repeatEndTime: DATE_2020_MINUS_1_HOUR, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( + false + ); }); test('returns as expected for a recurring snooze on a day of the week', () => { @@ -157,7 +171,9 @@ describe('isRuleSnoozed', () => { repeatInterval: 'DOW:135', // Monday Wednesday Friday; Jan 1 2020 was a Wednesday }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( + true + ); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -165,7 +181,9 @@ describe('isRuleSnoozed', () => { repeatInterval: 'DOW:2467', // Tue, Thu, Sat, Sun }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( + false + ); const snoozeScheduleC = [ { startTime: DATE_2020_MINUS_1_MONTH, @@ -174,7 +192,9 @@ describe('isRuleSnoozed', () => { occurrences: 12, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, snoozeIndefinitely: false })).toBe( + false + ); const snoozeScheduleD = [ { startTime: DATE_2020_MINUS_1_MONTH, @@ -183,6 +203,8 @@ describe('isRuleSnoozed', () => { occurrences: 15, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD, snoozeIndefinitely: false })).toBe( + true + ); }); }); diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts index a6b0e88bbf5af..c6e718c1e1bff 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts @@ -30,7 +30,7 @@ describe('getRuleAlertSummaryRoute', () => { tags: [], ruleTypeId: '', consumer: '', - muteAll: false, + snoozeIndefinitely: false, throttle: null, enabled: false, statusStartDate: dateString, diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts index c0d31d1ccbfac..3ec370fcf5f18 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts @@ -37,7 +37,7 @@ const rewriteReq: RewriteRequestCase = ({ const rewriteBodyRes: RewriteResponseCase = ({ ruleTypeId, - muteAll, + snoozeIndefinitely, statusStartDate, statusEndDate, errorMessages, @@ -47,7 +47,7 @@ const rewriteBodyRes: RewriteResponseCase = ({ }) => ({ ...rest, rule_type_id: ruleTypeId, - mute_all: muteAll, + snooze_indefinitely: snoozeIndefinitely, status_start_date: statusStartDate, status_end_date: statusEndDate, error_messages: errorMessages, diff --git a/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts index 672ccee369aac..89cd4ed7be269 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts @@ -35,7 +35,7 @@ describe('getAlertInstanceSummaryRoute', () => { tags: [], ruleTypeId: '', consumer: '', - muteAll: false, + snoozeIndefinitely: false, throttle: null, enabled: false, statusStartDate: dateString, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 0eee41402000f..2b92da152957e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -82,7 +82,7 @@ const BaseRuleSavedObject: SavedObject = { apiKeyOwner: null, throttle: null, notifyWhen: null, - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 74a121e9c8026..6e27f65ebf9b9 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -122,7 +122,7 @@ export const mockedRuleTypeSavedObject: Rule = { createdAt: mockDate, updatedAt: mockDate, throttle: null, - muteAll: false, + snoozeIndefinitely: false, notifyWhen: 'onActiveAlert', enabled: true, alertTypeId: ruleType.id, From 274c0c0fc5618dfc308b4a0beddbb476fc5d0619 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 26 Apr 2022 17:06:01 -0500 Subject: [PATCH 07/45] Fix typecheck --- .../server/rules_client/tests/get_alert_summary.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index 6dcd64565c562..1a7267d630615 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -86,7 +86,7 @@ const BaseRuleSavedObject: SavedObject = { apiKeyOwner: null, throttle: null, notifyWhen: null, - muteAll: false, + snoozeIndefinitely: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', From 7c918a587d3ea47eb585059676c989268cebe8fb Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 27 Apr 2022 13:01:02 -0500 Subject: [PATCH 08/45] Revert muteAll => snoozeIndefinitely rename --- .../plugins/alerting/common/alert_summary.ts | 2 +- .../alerting/common/is_rule_snoozed.ts | 4 +- x-pack/plugins/alerting/common/rule.ts | 2 +- .../plugins/alerting/public/alert_api.test.ts | 4 +- .../public/lib/common_transformations.test.ts | 8 +- .../public/lib/common_transformations.ts | 4 +- .../lib/alert_summary_from_event_log.test.ts | 8 +- .../lib/alert_summary_from_event_log.ts | 2 +- .../server/routes/create_rule.test.ts | 4 +- .../alerting/server/routes/create_rule.ts | 4 +- .../alerting/server/routes/find_rules.ts | 4 +- .../alerting/server/routes/get_rule.test.ts | 4 +- .../alerting/server/routes/get_rule.ts | 4 +- .../routes/get_rule_alert_summary.test.ts | 2 +- .../server/routes/get_rule_alert_summary.ts | 4 +- .../server/routes/legacy/create.test.ts | 2 +- .../alerting/server/routes/legacy/get.test.ts | 2 +- .../legacy/get_alert_instance_summary.test.ts | 2 +- .../server/routes/resolve_rule.test.ts | 4 +- .../alerting/server/routes/resolve_rule.ts | 4 +- .../alerting/server/routes/update_rule.ts | 4 +- .../server/rules_client/rules_client.ts | 22 +- .../tests/get_alert_summary.test.ts | 2 +- .../tests/get_execution_log.test.ts | 2 +- .../rules_client/tests/mute_all.test.ts | 12 +- .../rules_client/tests/unmute_all.test.ts | 12 +- .../server/saved_objects/mappings.json | 6 +- .../server/saved_objects/migrations.test.ts | 13 - .../server/saved_objects/migrations.ts | 9 +- .../task_runner/alert_task_instance.test.ts | 2 +- .../alerting/server/task_runner/fixtures.ts | 2 +- .../server/task_runner/task_runner.test.ts | 23 +- .../task_runner/task_runner_cancel.test.ts | 2 +- x-pack/plugins/alerting/server/types.ts | 2 +- x-pack/plugins/triggers_actions_ui/README.md | 390 ++++++++++-------- .../lib/rule_api/common_transformations.ts | 4 +- .../components/rule_quick_edit_buttons.tsx | 2 +- .../with_bulk_rule_api_operations.tsx | 2 +- .../components/rule_status_dropdown.tsx | 2 +- 39 files changed, 308 insertions(+), 279 deletions(-) diff --git a/x-pack/plugins/alerting/common/alert_summary.ts b/x-pack/plugins/alerting/common/alert_summary.ts index 6e91202cea74c..b04ce59eed1bd 100644 --- a/x-pack/plugins/alerting/common/alert_summary.ts +++ b/x-pack/plugins/alerting/common/alert_summary.ts @@ -19,7 +19,7 @@ export interface AlertSummary { tags: string[]; ruleTypeId: string; consumer: string; - snoozeIndefinitely: boolean; + muteAll: boolean; throttle: string | null; enabled: boolean; statusStartDate: string; diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.ts index c6df412c8efad..ed484dc4d6a7a 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { parseInterval } from '@kbn/data-plugin/common'; import { SanitizedRule, RuleTypeParams } from './rule'; -type RuleSnoozeProps = Pick, 'snoozeIndefinitely' | 'snoozeSchedule'>; +type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { if (rule.snoozeSchedule == null) { @@ -74,7 +74,7 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { } export function isRuleSnoozed(rule: RuleSnoozeProps) { - if (rule.snoozeIndefinitely) { + if (rule.muteAll) { return true; } return Boolean(getRuleSnoozeEndTime(rule)); diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index bfd7bb918bb86..6ed8f89bacc8e 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -104,11 +104,11 @@ export interface Rule { apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; + muteAll: boolean; notifyWhen: RuleNotifyWhenType | null; mutedInstanceIds: string[]; executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeIndefinitely: boolean; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } diff --git a/x-pack/plugins/alerting/public/alert_api.test.ts b/x-pack/plugins/alerting/public/alert_api.test.ts index 8a09d0b7fea40..2f09643e499e3 100644 --- a/x-pack/plugins/alerting/public/alert_api.test.ts +++ b/x-pack/plugins/alerting/public/alert_api.test.ts @@ -124,7 +124,7 @@ describe('loadRule', () => { "status": "ok", }, "id": "3d534c70-582b-11ec-8995-2b1578a3bc5d", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "stressing index-threshold 37/200", "notifyWhen": "onActiveAlert", @@ -306,7 +306,7 @@ function getRule(): Rule<{ x: number }> { apiKeyOwner: '2889684073', createdBy: 'elastic', updatedBy: '2889684073', - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], schedule: { interval: '1s', diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts index c6a0362782656..51d24538b449e 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.test.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.test.ts @@ -43,7 +43,7 @@ describe('common_transformations', () => { api_key_owner: 'api-key-user', throttle: '2s', notify_when: 'onActiveAlert', - snooze_indefinitely: false, + mute_all: false, muted_alert_ids: ['bob', 'jim'], execution_status: { last_execution_date: dateExecuted.toISOString(), @@ -89,7 +89,7 @@ describe('common_transformations', () => { "status": "error", }, "id": "some-id", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [ "bob", "jim", @@ -146,7 +146,7 @@ describe('common_transformations', () => { api_key_owner: 'api-key-user', throttle: '2s', notify_when: 'onActiveAlert', - snooze_indefinitely: false, + mute_all: false, muted_alert_ids: ['bob', 'jim'], execution_status: { last_execution_date: dateExecuted.toISOString(), @@ -176,7 +176,7 @@ describe('common_transformations', () => { "status": "error", }, "id": "some-id", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [ "bob", "jim", diff --git a/x-pack/plugins/alerting/public/lib/common_transformations.ts b/x-pack/plugins/alerting/public/lib/common_transformations.ts index ba39e5a98f951..1b306aae0ae2f 100644 --- a/x-pack/plugins/alerting/public/lib/common_transformations.ts +++ b/x-pack/plugins/alerting/public/lib/common_transformations.ts @@ -56,7 +56,7 @@ export function transformRule(input: ApiRule): Rule { api_key: apiKey, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatusAPI, @@ -73,7 +73,7 @@ export function transformRule(input: ApiRule): Rule { apiKey, apiKeyOwner, notifyWhen, - snoozeIndefinitely, + muteAll, mutedInstanceIds, executionStatus: transformExecutionStatus(executionStatusAPI), actions: actionsAPI ? actionsAPI.map((action) => transformAction(action)) : [], diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts index da72376ef0411..70ec1524fec16 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.test.ts @@ -40,7 +40,7 @@ describe('alertSummaryFromEventLog', () => { }, "id": "rule-123", "lastRun": undefined, - "snoozeIndefinitely": false, + "muteAll": false, "name": "rule-name", "ruleTypeId": "123", "status": "OK", @@ -62,7 +62,7 @@ describe('alertSummaryFromEventLog', () => { tags: ['tag-1', 'tag-2'], consumer: 'rule-consumer-2', throttle: '1h', - snoozeIndefinitely: true, + muteAll: true, }); const events: IValidatedEvent[] = []; const executionEvents: IValidatedEvent[] = []; @@ -86,7 +86,7 @@ describe('alertSummaryFromEventLog', () => { }, "id": "rule-456", "lastRun": undefined, - "snoozeIndefinitely": true, + "muteAll": true, "name": "rule-name-2", "ruleTypeId": "456", "status": "OK", @@ -730,7 +730,7 @@ const BaseRule: SanitizedRule<{ bar: boolean }> = { consumer: 'rule-consumer', throttle: null, notifyWhen: null, - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], params: { bar: true }, actions: [], diff --git a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts index 72cf6b6d5bfae..ff9bc776cfb95 100644 --- a/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts +++ b/x-pack/plugins/alerting/server/lib/alert_summary_from_event_log.ts @@ -32,7 +32,7 @@ export function alertSummaryFromEventLog(params: AlertSummaryFromEventLogParams) statusStartDate: dateStart, statusEndDate: dateEnd, status: 'OK', - snoozeIndefinitely: rule.snoozeIndefinitely, + muteAll: rule.muteAll, throttle: rule.throttle, enabled: rule.enabled, lastRun: undefined, diff --git a/x-pack/plugins/alerting/server/routes/create_rule.test.ts b/x-pack/plugins/alerting/server/routes/create_rule.test.ts index 66d7bd36a08a0..cbb7965cab081 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.test.ts @@ -54,7 +54,7 @@ describe('createRuleRoute', () => { }, ], enabled: true, - snoozeIndefinitely: false, + muteAll: false, createdBy: '', updatedBy: '', apiKeyOwner: '', @@ -84,7 +84,7 @@ describe('createRuleRoute', () => { const createResult: AsApiContract> = { ...ruleToCreate, - snooze_indefinitely: mockedAlert.snoozeIndefinitely, + mute_all: mockedAlert.muteAll, created_by: mockedAlert.createdBy, updated_by: mockedAlert.updatedBy, api_key_owner: mockedAlert.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index 91dcfc7313870..cf044c94f2529 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -65,7 +65,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - snoozeIndefinitely, + muteAll, mutedInstanceIds, executionStatus: { lastExecutionDate, lastDuration, ...executionStatus }, ...rest @@ -79,7 +79,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, muted_alert_ids: mutedInstanceIds, execution_status: { ...executionStatus, diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index 7764122ed48ce..69c6b8206a528 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -79,11 +79,11 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, + muteAll, mutedInstanceIds, executionStatus, actions, scheduledTaskId, - snoozeIndefinitely, snoozeSchedule, ...rest }) => ({ @@ -97,7 +97,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ notify_when: notifyWhen, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, // Remove this object spread boolean check after snoozeSchedule is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), execution_status: executionStatus && { diff --git a/x-pack/plugins/alerting/server/routes/get_rule.test.ts b/x-pack/plugins/alerting/server/routes/get_rule.test.ts index a8e9ccfe9356e..065cd567f1f69 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.test.ts @@ -50,7 +50,7 @@ describe('getRuleRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - snoozeIndefinitely: false, + muteAll: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', @@ -67,7 +67,7 @@ describe('getRuleRoute', () => { ...pick(mockedAlert, 'consumer', 'name', 'schedule', 'tags', 'params', 'throttle', 'enabled'), rule_type_id: mockedAlert.alertTypeId, notify_when: mockedAlert.notifyWhen, - snooze_indefinitely: mockedAlert.snoozeIndefinitely, + mute_all: mockedAlert.muteAll, created_by: mockedAlert.createdBy, updated_by: mockedAlert.updatedBy, api_key_owner: mockedAlert.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 09335f5347f52..12ecd61fd0a20 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -30,11 +30,11 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, + muteAll, mutedInstanceIds, executionStatus, actions, scheduledTaskId, - snoozeIndefinitely, snoozeSchedule, ...rest }) => ({ @@ -47,7 +47,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ api_key_owner: apiKeyOwner, notify_when: notifyWhen, muted_alert_ids: mutedInstanceIds, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, // Remove this object spread boolean check after snooze props is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), scheduled_task_id: scheduledTaskId, diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts index c6e718c1e1bff..a6b0e88bbf5af 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.test.ts @@ -30,7 +30,7 @@ describe('getRuleAlertSummaryRoute', () => { tags: [], ruleTypeId: '', consumer: '', - snoozeIndefinitely: false, + muteAll: false, throttle: null, enabled: false, statusStartDate: dateString, diff --git a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts index 3ec370fcf5f18..c0d31d1ccbfac 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule_alert_summary.ts @@ -37,7 +37,7 @@ const rewriteReq: RewriteRequestCase = ({ const rewriteBodyRes: RewriteResponseCase = ({ ruleTypeId, - snoozeIndefinitely, + muteAll, statusStartDate, statusEndDate, errorMessages, @@ -47,7 +47,7 @@ const rewriteBodyRes: RewriteResponseCase = ({ }) => ({ ...rest, rule_type_id: ruleTypeId, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, status_start_date: statusStartDate, status_end_date: statusEndDate, error_messages: errorMessages, diff --git a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts index 7b8e3aebcdb1f..60b299be6db97 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/create.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/create.test.ts @@ -60,7 +60,7 @@ describe('createAlertRoute', () => { const createResult: Rule<{ bar: boolean }> = { ...mockedAlert, enabled: true, - snoozeIndefinitely: false, + muteAll: false, createdBy: '', updatedBy: '', apiKey: '', diff --git a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts index 406d22245ca0d..6860140ce2540 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get.test.ts @@ -54,7 +54,7 @@ describe('getAlertRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - snoozeIndefinitely: false, + muteAll: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', diff --git a/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts index 89cd4ed7be269..672ccee369aac 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/get_alert_instance_summary.test.ts @@ -35,7 +35,7 @@ describe('getAlertInstanceSummaryRoute', () => { tags: [], ruleTypeId: '', consumer: '', - snoozeIndefinitely: false, + muteAll: false, throttle: null, enabled: false, statusStartDate: dateString, diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts index 864c0cbf5c1da..656874b5cf332 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.test.ts @@ -50,7 +50,7 @@ describe('resolveRuleRoute', () => { name: 'abc', tags: ['foo'], enabled: true, - snoozeIndefinitely: false, + muteAll: false, notifyWhen: 'onActionGroupChange', createdBy: '', updatedBy: '', @@ -79,7 +79,7 @@ describe('resolveRuleRoute', () => { ), rule_type_id: mockedRule.alertTypeId, notify_when: mockedRule.notifyWhen, - snooze_indefinitely: mockedRule.snoozeIndefinitely, + mute_all: mockedRule.muteAll, created_by: mockedRule.createdBy, updated_by: mockedRule.updatedBy, api_key_owner: mockedRule.apiKeyOwner, diff --git a/x-pack/plugins/alerting/server/routes/resolve_rule.ts b/x-pack/plugins/alerting/server/routes/resolve_rule.ts index 7dd21ea9c5d77..cde747f9272fe 100644 --- a/x-pack/plugins/alerting/server/routes/resolve_rule.ts +++ b/x-pack/plugins/alerting/server/routes/resolve_rule.ts @@ -29,7 +29,7 @@ const rewriteBodyRes: RewriteResponseCase> updatedAt, apiKeyOwner, notifyWhen, - snoozeIndefinitely, + muteAll, mutedInstanceIds, executionStatus, actions, @@ -44,7 +44,7 @@ const rewriteBodyRes: RewriteResponseCase> updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, - snooze_indefinitely: snoozeIndefinitely, + mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatus && { diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index b7c8452f94908..d2130e1f33541 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -67,7 +67,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ updatedAt, apiKeyOwner, notifyWhen, - snoozeIndefinitely, + muteAll, mutedInstanceIds, executionStatus, ...rest @@ -81,7 +81,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(createdAt ? { created_at: createdAt } : {}), ...(updatedAt ? { updated_at: updatedAt } : {}), ...(notifyWhen ? { notify_when: notifyWhen } : {}), - ...(snoozeIndefinitely !== undefined ? { snooze_indefinitely: snoozeIndefinitely } : {}), + ...(muteAll !== undefined ? { mute_all: muteAll } : {}), ...(mutedInstanceIds ? { muted_alert_ids: mutedInstanceIds } : {}), ...(executionStatus ? { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index a4672e85a41ae..275647e19918d 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -220,11 +220,11 @@ export interface CreateOptions { | 'updatedAt' | 'apiKey' | 'apiKeyOwner' - | 'snoozeIndefinitely' + | 'muteAll' | 'mutedInstanceIds' | 'actions' | 'executionStatus' - | 'snoozeEndTime' + | 'snoozeSchedule' > & { actions: NormalizedAlertAction[] }; options?: { id?: string; @@ -416,7 +416,7 @@ export class RulesClient { updatedAt: new Date(createTime).toISOString(), snoozeSchedule: [], params: updatedParams as RawRule['params'], - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], notifyWhen, executionStatus: getRuleExecutionStatusPending(new Date().toISOString()), @@ -920,7 +920,7 @@ export class RulesClient { terms: { field: 'alert.attributes.enabled' }, }, muted: { - terms: { field: 'alert.attributes.snoozeIndefinitely' }, + terms: { field: 'alert.attributes.muteAll' }, }, snoozed: { date_range: { @@ -1702,7 +1702,7 @@ export class RulesClient { const newAttrs = snoozeEndTime === -1 ? { - snoozeIndefinitely: true, + muteAll: true, snoozeSchedule: clearUnscheduledSnooze(attributes), } : { @@ -1710,7 +1710,7 @@ export class RulesClient { startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime) - Date.now(), }), - snoozeIndefinitely: false, + muteAll: false, }; const updateAttributes = this.updateMeta({ @@ -1776,7 +1776,7 @@ export class RulesClient { const updateAttributes = this.updateMeta({ snoozeSchedule: clearUnscheduledSnooze(attributes), - snoozeIndefinitely: false, + muteAll: false, updatedBy: await this.getUserName(), updatedAt: new Date().toISOString(), }); @@ -1837,7 +1837,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - snoozeIndefinitely: true, + muteAll: true, mutedInstanceIds: [], snoozeSchedule: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), @@ -1900,7 +1900,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const updateAttributes = this.updateMeta({ - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], snoozeSchedule: clearUnscheduledSnooze(attributes), updatedBy: await this.getUserName(), @@ -1963,7 +1963,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.snoozeIndefinitely && !mutedInstanceIds.includes(alertInstanceId)) { + if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { mutedInstanceIds.push(alertInstanceId); await this.unsecuredSavedObjectsClient.update( 'alert', @@ -2030,7 +2030,7 @@ export class RulesClient { this.ruleTypeRegistry.ensureRuleTypeEnabled(attributes.alertTypeId); const mutedInstanceIds = attributes.mutedInstanceIds || []; - if (!attributes.snoozeIndefinitely && mutedInstanceIds.includes(alertInstanceId)) { + if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { await this.unsecuredSavedObjectsClient.update( 'alert', alertId, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts index 1a7267d630615..6dcd64565c562 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_alert_summary.test.ts @@ -86,7 +86,7 @@ const BaseRuleSavedObject: SavedObject = { apiKeyOwner: null, throttle: null, notifyWhen: null, - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts index 2b92da152957e..0eee41402000f 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_execution_log.test.ts @@ -82,7 +82,7 @@ const BaseRuleSavedObject: SavedObject = { apiKeyOwner: null, throttle: null, notifyWhen: null, - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 40f25c1a5711c..7f8ae28a20c6e 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -69,7 +69,7 @@ describe('muteAll()', () => { }, }, ], - snoozeIndefinitely: false, + muteAll: false, }, references: [], version: '123', @@ -80,9 +80,9 @@ describe('muteAll()', () => { 'alert', '1', { - snoozeIndefinitely: true, + muteAll: true, mutedInstanceIds: [], - snoozeSchedule: [], + snoozeEndTime: null, updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, @@ -117,7 +117,7 @@ describe('muteAll()', () => { enabled: false, scheduledTaskId: null, updatedBy: 'elastic', - snoozeIndefinitely: false, + muteAll: false, }, references: [], }); @@ -173,7 +173,7 @@ describe('muteAll()', () => { }, }, ], - snoozeIndefinitely: false, + muteAll: false, }, references: [], version: '123', @@ -207,7 +207,7 @@ describe('muteAll()', () => { }, }, ], - snoozeIndefinitely: false, + muteAll: false, }, references: [], version: '123', diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index acb9dc86c9aa3..cf063eea07862 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -69,7 +69,7 @@ describe('unmuteAll()', () => { }, }, ], - snoozeIndefinitely: true, + muteAll: true, }, references: [], version: '123', @@ -80,9 +80,9 @@ describe('unmuteAll()', () => { 'alert', '1', { - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], - snoozeSchedule: [], + snoozeEndTime: null, updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, @@ -117,7 +117,7 @@ describe('unmuteAll()', () => { enabled: false, scheduledTaskId: null, updatedBy: 'elastic', - snoozeIndefinitely: false, + muteAll: false, }, references: [], }); @@ -173,7 +173,7 @@ describe('unmuteAll()', () => { }, }, ], - snoozeIndefinitely: false, + muteAll: false, }, references: [], version: '123', @@ -207,7 +207,7 @@ describe('unmuteAll()', () => { }, }, ], - snoozeIndefinitely: false, + muteAll: false, }, references: [], version: '123', diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerting/server/saved_objects/mappings.json index cd67714ae9688..a5e5ab2c48bb4 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.json @@ -90,6 +90,9 @@ "notifyWhen": { "type": "keyword" }, + "muteAll": { + "type": "boolean" + }, "mutedInstanceIds": { "type": "keyword" }, @@ -173,9 +176,6 @@ } } }, - "snoozeIndefinitely": { - "type": "boolean" - }, "snoozeSchedule": { "properties": { "id": { diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index 77cc0784586a9..d8c77e4c71854 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -2255,19 +2255,6 @@ describe('successful migrations', () => { }); describe('8.3.0', () => { - test('migrates muted rules to the new data model', () => { - const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; - const mutedAlert = getMockData( - { - muteAll: true, - }, - true - ); - const migratedMutedAlert830 = migration830(mutedAlert, migrationContext); - - expect(migratedMutedAlert830.attributes.snoozeIndefinitely).toEqual(true); - }); - test('migrates snoozed rules to the new data model', () => { const fakeTimer = sinon.useFakeTimers(); const migration830 = getMigrations(encryptedSavedObjectsSetup, isPreconfigured)['8.3.0']; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index c00d0897c3632..27d50cd2d4dd2 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -156,7 +156,7 @@ export function getMigrations( const migrationRules830 = createEsoMigration( encryptedSavedObjects, (doc: SavedObjectUnsanitizedDoc): doc is SavedObjectUnsanitizedDoc => true, - pipeMigrations(removeInternalTags, convertMutesAndSnoozes) + pipeMigrations(removeInternalTags, convertSnoozes) ); return { @@ -860,18 +860,17 @@ function addMappedParams( return doc; } -function convertMutesAndSnoozes( +function convertSnoozes( doc: SavedObjectUnsanitizedDoc ): SavedObjectUnsanitizedDoc { const { - attributes: { muteAll, snoozeEndTime }, + attributes: { snoozeEndTime }, } = doc; return { ...doc, attributes: { - ...(omit(doc.attributes, ['snoozeEndTime', 'muteAll']) as RawRule), - snoozeIndefinitely: Boolean(muteAll), + ...(omit(doc.attributes, ['snoozeEndTime']) as RawRule), snoozeSchedule: snoozeEndTime ? [ { diff --git a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts index e3ac325ba7e73..3faea1a4d2261 100644 --- a/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/alert_task_instance.test.ts @@ -31,7 +31,7 @@ const alert: SanitizedRule<{ apiKeyOwner: null, throttle: null, notifyWhen: null, - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], executionStatus: { status: 'unknown', diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 6e27f65ebf9b9..74a121e9c8026 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -122,7 +122,7 @@ export const mockedRuleTypeSavedObject: Rule = { createdAt: mockDate, updatedAt: mockDate, throttle: null, - snoozeIndefinitely: false, + muteAll: false, notifyWhen: 'onActiveAlert', enabled: true, alertTypeId: ruleType.id, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index dab64d85e9f77..111c0a7689504 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -419,7 +419,7 @@ describe('Task Runner', () => { ); rulesClient.get.mockResolvedValue({ ...mockedRuleTypeSavedObject, - snoozeIndefinitely: true, + muteAll: true, }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); await taskRunner.run(); @@ -496,26 +496,25 @@ describe('Task Runner', () => { }); type SnoozeTestParams = [ - snoozeIndefinitely: boolean, - snoozeSchedule: string | undefined | null, + muteAll: boolean, + snoozeEndTime: string | undefined | null, shouldBeSnoozed: boolean ]; const snoozeTestParams: SnoozeTestParams[] = [ [false, null, false], [false, undefined, false], - // Stringify the snooze schedules for better failure reporting - [false, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), false], - [false, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], + [false, DATE_1970, false], + [false, DATE_9999, true], [true, null, true], [true, undefined, true], - [true, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), true], - [true, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], + [true, DATE_1970, true], + [true, DATE_9999, true], ]; test.each(snoozeTestParams)( - 'snoozing works as expected with snoozeIndefinitely: %s; snoozeSchedule: %s', - async (snoozeIndefinitely, snoozeSchedule, shouldBeSnoozed) => { + 'snoozing works as expected with muteAll: %s; snoozeEndTime: %s', + async (muteAll, snoozeEndTime, shouldBeSnoozed) => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); ruleType.executor.mockImplementation( @@ -539,8 +538,8 @@ describe('Task Runner', () => { ); rulesClient.get.mockResolvedValue({ ...mockedRuleTypeSavedObject, - snoozeIndefinitely, - snoozeSchedule: snoozeSchedule != null ? JSON.parse(snoozeSchedule) : [], + muteAll, + snoozeEndTime: snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime, }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); await taskRunner.run(); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts index 69c997c323484..4986359115377 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_cancel.test.ts @@ -144,7 +144,7 @@ describe('Task Runner Cancel', () => { createdAt: mockDate, updatedAt: mockDate, throttle: null, - snoozeIndefinitely: false, + muteAll: false, notifyWhen: 'onActiveAlert', enabled: true, alertTypeId: ruleType.id, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 7312b5109e748..9ff1e33fc84e2 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -244,11 +244,11 @@ export interface RawRule extends SavedObjectAttributes { apiKeyOwner: string | null; throttle: string | null; notifyWhen: RuleNotifyWhenType | null; + muteAll: boolean; mutedInstanceIds: string[]; meta?: RuleMeta; executionStatus: RawRuleExecutionStatus; monitoring?: RuleMonitoring; - snoozeIndefinitely: boolean; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index f4a1f79da1546..1e21dbd278808 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -1,14 +1,13 @@ # Kibana Alerts and Actions UI -The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. +The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. As a developer you can reuse and extend built-in alerts and actions UI functionality: - Create and register a new Alert Type. - Create and register a new Action Type. - Embed the Create Alert flyout within any Kibana plugin. ------ - +--- Table of Contents @@ -46,9 +45,9 @@ Table of Contents Kibana ships with several built-in alert types: -|Type|Id|Description| -|---|---|---| -|[Index Threshold](#index-threshold-alert)|`threshold`|Index Threshold Alert| +| Type | Id | Description | +| ----------------------------------------- | ----------- | --------------------- | +| [Index Threshold](#index-threshold-alert) | `threshold` | Index Threshold Alert | Every alert type must be registered server side, and can optionally be registered client side. Only alert types registered on both client and server will be displayed in the Create Alert flyout, as a part of the UI. @@ -89,13 +88,12 @@ interface IndexThresholdProps { } ``` -|Property|Description| -|---|---| -|ruleParams|Set of Alert params relevant for the index threshold alert type.| -|setRuleParams|Alert reducer method, which is used to create a new copy of alert object with the changed params property any subproperty value.| -|setRuleProperty|Alert reducer method, which is used to create a new copy of alert object with the changed any direct alert property value.| -|errors|Alert level errors tracking object.| - +| Property | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| ruleParams | Set of Alert params relevant for the index threshold alert type. | +| setRuleParams | Alert reducer method, which is used to create a new copy of alert object with the changed params property any subproperty value. | +| setRuleProperty | Alert reducer method, which is used to create a new copy of alert object with the changed any direct alert property value. | +| errors | Alert level errors tracking object. | Alert reducer is defined on the AlertAdd functional component level and passed down to the subcomponents to provide a new state of Alert object: @@ -127,6 +125,7 @@ const setRuleProperty = (key: string, value: any) => { ``` 'x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.ts' define the methods for changing different type of alert properties: + ``` export const alertReducer = (state: any, action: AlertReducerAction) => { const { command, payload } = action; @@ -235,6 +234,7 @@ Index Threshold Alert form with validation: ## Alert type model definition Each alert type should be defined as `RuleTypeModel` object with the these properties: + ``` id: string; name: string; @@ -245,19 +245,21 @@ Each alert type should be defined as `RuleTypeModel` object with the these prope >; defaultActionMessage?: string; ``` -|Property|Description| -|---|---| -|id|Alert type id. Should be the same as on the server side.| -|name|Name of the alert type that will be displayed on the select card in the UI.| -|iconClass|Icon of the alert type that will be displayed on the select card in the UI.| -|validate|Validation function for the alert params.| -|ruleParamsExpression| A lazy loaded React component for building UI of the current alert type params.| -|defaultActionMessage|Optional property for providing default message for all added actions with `message` property.| -|requiresAppContext|Define if alert type is enabled for create and edit in the alerting management UI.| - -IMPORTANT: The current UI supports a single action group only. + +| Property | Description | +| -------------------- | ---------------------------------------------------------------------------------------------- | +| id | Alert type id. Should be the same as on the server side. | +| name | Name of the alert type that will be displayed on the select card in the UI. | +| iconClass | Icon of the alert type that will be displayed on the select card in the UI. | +| validate | Validation function for the alert params. | +| ruleParamsExpression | A lazy loaded React component for building UI of the current alert type params. | +| defaultActionMessage | Optional property for providing default message for all added actions with `message` property. | +| requiresAppContext | Define if alert type is enabled for create and edit in the alerting management UI. | + +IMPORTANT: The current UI supports a single action group only. Action groups are mapped from the server API result for [GET /api/alerts/list_alert_types: List alert types](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#get-apialerttypes-list-alert-types). Server side alert type model: + ``` export interface RuleType { id: string; @@ -270,6 +272,7 @@ export interface RuleType { requiresAppContext: boolean; } ``` + Only the default (which means first item of the array) action group is displayed in the current UI. Design of user interface and server API for multiple action groups is under discussion and development. @@ -278,11 +281,11 @@ Design of user interface and server API for multiple action groups is under disc There are two ways of registering a new alert type: 1. Directly in the `triggers_actions_ui` plugin. In this case, the alert type will be available in the Create Alert flyout of the Alerts and Actions management section. -Registration code for a new alert type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` -Only registered alert types are available in UI. + Registration code for a new alert type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` + Only registered alert types are available in UI. -2. Register the alert type in another plugin. In this case, the alert type will be available only in the current plugin UI. -It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: +2. Register the alert type in another plugin. In this case, the alert type will be available only in the current plugin UI. + It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: ``` function getSomeNewAlertType() { @@ -294,13 +297,14 @@ triggersActionsUi.ruleTypeRegistry.register(getSomeNewAlertType()); ## Create and register new alert type UI example -Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#example +Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#example Alert type UI is expected to be defined as `RuleTypeModel` object. Below is a list of steps that should be done to build and register a new alert type with the name `Example Alert Type`: 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [RuleTypeModel](https://github.com/elastic/kibana/blob/55b7905fb5265b73806006e7265739545d7521d0/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts#L83). Example: + ``` import { lazy } from 'react'; import { RuleTypeModel } from '../../../../types'; @@ -318,9 +322,11 @@ export function getAlertType(): RuleTypeModel { }; } ``` + Fields of this object `RuleTypeModel` will be mapped properly in the UI below. 2. Define `ruleParamsExpression` as `React.FunctionComponent` - this is the form for filling Alert params based on the current Alert type. + ``` import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; @@ -373,11 +379,13 @@ export const ExampleExpression: React.FunctionComponent = ({ export {ExampleExpression as default}; ``` + This alert type form becomes available, when the card of `Example Alert Type` is selected. Each expression word here is `EuiExpression` component and implements the basic aggregation, grouping and comparison methods. Expression components, which can be embedded to different alert types, are described here [Common expression components](#common-expression-components). -3. Define alert type params validation using the property of `RuleTypeModel` `validate`: +3. Define alert type params validation using the property of `RuleTypeModel` `validate`: + ``` import { i18n } from '@kbn/i18n'; import { ValidationResult } from '../../../../types'; @@ -404,6 +412,7 @@ export function validateExampleAlertType({ ``` 4. Extend registration code with the new alert type register in the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` + ``` import { getAlertType as getExampledAlertType } from './example'; ... @@ -434,6 +443,7 @@ Click on the select card for `Example Alert Type` to open the expression form th ``` Props definition: + ``` interface WhenExpressionProps { aggType: string; @@ -444,12 +454,12 @@ interface WhenExpressionProps { } ``` -|Property|Description| -|---|---| -|aggType|Selected aggregation type that will be set as the alert type property.| -|customAggTypesOptions|(Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`.| -|onChangeSelectedAggType|event handler that will be executed when selected aggregation type is changed.| -|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| +| Property | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| aggType | Selected aggregation type that will be set as the alert type property. | +| customAggTypesOptions | (Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`. | +| onChangeSelectedAggType | event handler that will be executed when selected aggregation type is changed. | +| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | ### OF expression component @@ -470,6 +480,7 @@ OF expression is available, if aggregation type requires selecting data fields f ``` Props definition: + ``` interface OfExpressionProps { aggType: string; @@ -485,15 +496,15 @@ interface OfExpressionProps { } ``` -|Property|Description| -|---|---| -|aggType|Selected aggregation type that will be set as the alert type property.| -|aggField|Selected aggregation field that will be set as the alert type property.| -|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `aggField`.| -|onChangeSelectedAggField|Event handler that will be excuted if selected aggregation field is changed.| -|fields|Fields list that will be available in the OF `Select a field` dropdown.| -|customAggTypesOptions|(Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`.| -|popupPosition|(Optional) expression popup position. Default is `downRight`. Recommend changing it for a small parent window space.| +| Property | Description | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| aggType | Selected aggregation type that will be set as the alert type property. | +| aggField | Selected aggregation field that will be set as the alert type property. | +| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `aggField`. | +| onChangeSelectedAggField | Event handler that will be excuted if selected aggregation field is changed. | +| fields | Fields list that will be available in the OF `Select a field` dropdown. | +| customAggTypesOptions | (Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`. | +| popupPosition | (Optional) expression popup position. Default is `downRight`. Recommend changing it for a small parent window space. | ### GROUPED BY expression component @@ -517,6 +528,7 @@ interface OfExpressionProps { ``` Props definition: + ``` interface GroupByExpressionProps { groupBy: string; @@ -535,18 +547,18 @@ interface GroupByExpressionProps { } ``` -|Property|Description| -|---|---| -|groupBy|Selected group by type that will be set as the alert type property.| -|termSize|Selected term size that will be set as the alert type property.| -|termField|Selected term field that will be set as the alert type property.| -|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`.| -|onChangeSelectedTermSize|Event handler that will be excuted if selected term size is changed.| -|onChangeSelectedTermField|Event handler that will be excuted if selected term field is changed.| -|onChangeSelectedGroupBy|Event handler that will be excuted if selected group by is changed.| -|fields|Fields list with options for the `termField` dropdown.| -|customGroupByTypes|(Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`.| -|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| +| Property | Description | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| groupBy | Selected group by type that will be set as the alert type property. | +| termSize | Selected term size that will be set as the alert type property. | +| termField | Selected term field that will be set as the alert type property. | +| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`. | +| onChangeSelectedTermSize | Event handler that will be excuted if selected term size is changed. | +| onChangeSelectedTermField | Event handler that will be excuted if selected term field is changed. | +| onChangeSelectedGroupBy | Event handler that will be excuted if selected group by is changed. | +| fields | Fields list with options for the `termField` dropdown. | +| customGroupByTypes | (Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`. | +| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | ### FOR THE LAST expression component @@ -567,6 +579,7 @@ interface GroupByExpressionProps { ``` Props definition: + ``` interface ForLastExpressionProps { timeWindowSize?: number; @@ -579,14 +592,14 @@ interface ForLastExpressionProps { } ``` -|Property|Description| -|---|---| -|timeWindowSize|Selected time window size that will be set as the alert type property.| -|timeWindowUnit|Selected time window unit that will be set as the alert type property.| -|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `termWindowSize`.| -|onChangeWindowSize|Event handler that will be excuted if selected window size is changed.| -|onChangeWindowUnit|Event handler that will be excuted if selected window unit is changed.| -|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| +| Property | Description | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | +| timeWindowSize | Selected time window size that will be set as the alert type property. | +| timeWindowUnit | Selected time window unit that will be set as the alert type property. | +| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termWindowSize`. | +| onChangeWindowSize | Event handler that will be excuted if selected window size is changed. | +| onChangeWindowUnit | Event handler that will be excuted if selected window unit is changed. | +| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | ### THRESHOLD expression component @@ -607,6 +620,7 @@ interface ForLastExpressionProps { ``` Props definition: + ``` interface ThresholdExpressionProps { thresholdComparator: string; @@ -622,18 +636,20 @@ interface ThresholdExpressionProps { } ``` -|Property|Description| -|---|---| -|thresholdComparator|Selected time window size that will be set as the alert type property.| -|threshold|Selected time window size that will be set as the alert type property.| -|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `threshold0` and `threshold1`.| -|onChangeSelectedThresholdComparator|Event handler that will be excuted if selected threshold comparator is changed.| -|onChangeSelectedThreshold|Event handler that will be excuted if selected threshold is changed.| -|customComparators|(Optional) List of comparators that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/comparators.ts`.| -|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| +| Property | Description | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| thresholdComparator | Selected time window size that will be set as the alert type property. | +| threshold | Selected time window size that will be set as the alert type property. | +| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `threshold0` and `threshold1`. | +| onChangeSelectedThresholdComparator | Event handler that will be excuted if selected threshold comparator is changed. | +| onChangeSelectedThreshold | Event handler that will be excuted if selected threshold is changed. | +| customComparators | (Optional) List of comparators that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/comparators.ts`. | +| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | ## Alert Conditions Components + To aid in creating a uniform UX across Alert Types, we provide two components for specifying the conditions for detection of a certain alert under within any specific Action Groups: + 1. `AlertConditions`: A component that generates a container which renders custom component for each Action Group which has had its _conditions_ specified. 2. `AlertConditionsGroup`: A component that provides a unified container for the Action Group with its name and a button for resetting its condition. @@ -703,6 +719,7 @@ The `isRequired` field specifies whether this specific action group is _required Using this `ThresholdSpecifier` component, we can now use `AlertConditionsGroup` & `AlertConditions` to enable the user to specify these thresholds for each action group in the alert type. Like so: + ``` interface ThresholdAlertTypeParams { thresholds?: { @@ -761,37 +778,39 @@ const DEFAULT_THRESHOLDS: ThresholdAlertTypeParams['threshold] = { ``` -### The AlertConditions component +### The AlertConditions component This component will render the `Conditions` header & headline, along with the selectors for adding every Action Group you specity. Additionally it will clone its `children` for _each_ action group which has a `condition` specified for it, passing in the appropriate `actionGroup` prop for each one. -|Property|Description| -|---|---| -|headline|The headline title displayed above the fields | -|actionGroups|A list of `ActionGroupWithCondition` which includes all the action group you wish to offer the user and what conditions they are already configured to follow| -|onInitializeConditionsFor|A callback which is called when the user ask for a certain actionGroup to be initialized with an initial default condition. If you have no specific default, that's fine, as the component will render the action group's field even if the condition is empty (using a `null` or an `undefined`) and determines whether to render these fields by _the very presence_ of a `condition` field| +| Property | Description | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| headline | The headline title displayed above the fields | +| actionGroups | A list of `ActionGroupWithCondition` which includes all the action group you wish to offer the user and what conditions they are already configured to follow | +| onInitializeConditionsFor | A callback which is called when the user ask for a certain actionGroup to be initialized with an initial default condition. If you have no specific default, that's fine, as the component will render the action group's field even if the condition is empty (using a `null` or an `undefined`) and determines whether to render these fields by _the very presence_ of a `condition` field | -### The AlertConditionsGroup component +### The AlertConditionsGroup component This component renders a standard EuiTitle foe each action group, wrapping the Alert Type specific component, in addition to a "reset" button which allows the user to reset the condition for that action group. The definition of what a _reset_ actually means is Alert Type specific, and up to the implementor to decide. In some case it might mean removing the condition, in others it might mean to reset it to some default value on the server side. In either case, it should _delete_ the `condition` field from the appropriate `actionGroup` as per the above example. -|Property|Description| -|---|---| -|onResetConditionsFor|A callback which is called when the user clicks the _reset_ button besides the action group's title. The implementor should use this to remove the `condition` from the specified actionGroup| - +| Property | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| onResetConditionsFor | A callback which is called when the user clicks the _reset_ button besides the action group's title. The implementor should use this to remove the `condition` from the specified actionGroup | ## Embed the Create Alert flyout within any Kibana plugin Follow the instructions bellow to embed the Create Alert flyout within any Kibana plugin: + 1. Add TriggersAndActionsUIPublicPluginStart to Kibana plugin setup dependencies: ``` triggersActionsUi: TriggersAndActionsUIPublicPluginStart; ``` + Then this dependency will be used to embed Create Alert flyout. 2. Add Create Alert flyout to React component using triggersActionsUi start contract: + ``` // in the component state definition section const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); @@ -825,6 +844,7 @@ const AddAlertFlyout = useMemo( ``` getAddAlertFlyout variables definition: + ``` interface AlertAddProps { consumer: string; @@ -838,39 +858,39 @@ interface AlertAddProps { } ``` -|Property|Description| -|---|---| -|consumer|Name of the plugin that creates an alert.| -|addFlyoutVisible|Visibility state of the Create Alert flyout.| -|setAddFlyoutVisibility|Function for changing visibility state of the Create Alert flyout.| -|alertTypeId|Optional property to preselect alert type.| -|canChangeTrigger|Optional property, that hides change alert type possibility.| -|onSave|Optional function, which will be executed if alert was saved sucsessfuly.| -|initialValues|Default values for Alert properties.| -|metadata|Optional generic property, which allows to define component specific metadata. This metadata can be used for passing down preloaded data for Alert type expression component.| +| Property | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| consumer | Name of the plugin that creates an alert. | +| addFlyoutVisible | Visibility state of the Create Alert flyout. | +| setAddFlyoutVisibility | Function for changing visibility state of the Create Alert flyout. | +| alertTypeId | Optional property to preselect alert type. | +| canChangeTrigger | Optional property, that hides change alert type possibility. | +| onSave | Optional function, which will be executed if alert was saved sucsessfuly. | +| initialValues | Default values for Alert properties. | +| metadata | Optional generic property, which allows to define component specific metadata. This metadata can be used for passing down preloaded data for Alert type expression component. | ## Build and register Action Types Kibana ships with a set of built-in action types UI: -|Type|Id|Description| -|---|---|---| -|[Server log](#server-log)|`.log`|Logs messages to the Kibana log using `server.log()`| -|[Email](#email)|`.email`|Sends an email using SMTP| -|[Slack](#slack)|`.slack`|Posts a message to a Slack channel| -|[Index](#index)|`.index`|Indexes document(s) into Elasticsearch| -|[Webhook](#webhook)|`.webhook`|Sends a payload to a web service using HTTP POST or PUT| -|[PagerDuty](#pagerduty)|`.pagerduty`|Triggers, resolves, or acknowledges an incident to a PagerDuty service| +| Type | Id | Description | +| ------------------------- | ------------ | ---------------------------------------------------------------------- | +| [Server log](#server-log) | `.log` | Logs messages to the Kibana log using `server.log()` | +| [Email](#email) | `.email` | Sends an email using SMTP | +| [Slack](#slack) | `.slack` | Posts a message to a Slack channel | +| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch | +| [Webhook](#webhook) | `.webhook` | Sends a payload to a web service using HTTP POST or PUT | +| [PagerDuty](#pagerduty) | `.pagerduty` | Triggers, resolves, or acknowledges an incident to a PagerDuty service | -Every action type should be registered server side, and can be optionally registered client side. +Every action type should be registered server side, and can be optionally registered client side. Only action types registered on both client and server will be displayed in the Alerts and Actions UI. Built-in action types UI is located under the folder `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types` and this is a file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` for client side registration. - ### Server log Action type model definition: + ``` export function getActionType(): ActionTypeModel { return { @@ -899,6 +919,7 @@ export function getActionType(): ActionTypeModel { }; } ``` + Server log has a connector UI: ![Server log connector card](https://i.imgur.com/ZIWhV89.png) @@ -911,6 +932,7 @@ and action params form available in Create Alert form: ### Email Action type model definition: + ``` export function getActionType(): ActionTypeModel { const mailformat = /^[^@\s]+@[^@\s]+$/; @@ -940,6 +962,7 @@ export function getActionType(): ActionTypeModel { }; } ``` + ![Email connector card](https://i.imgur.com/d8kCbjQ.png) ![Email connector form](https://i.imgur.com/Uf6HU7X.png) @@ -950,6 +973,7 @@ and action params form available in Create Alert form: ### Slack Action type model definition: + ``` export function getActionType(): ActionTypeModel { return { @@ -971,7 +995,7 @@ export function getActionType(): ActionTypeModel { // validation of connector properties implementation }, validateParams: (actionParams: SlackActionParams): Promise => { - // validation of action params implementation + // validation of action params implementation }, actionConnectorFields: SlackActionFields, actionParamsFields: SlackParamsFields, @@ -989,6 +1013,7 @@ and action params form available in Create Alert form: ### Index Action type model definition: + ``` export function getActionType(): ActionTypeModel { return { @@ -1029,12 +1054,13 @@ Example of the index document for Index Threshold alert: "context_title": "{{context.title}}", "context_value": "{{context.value}}", "context_message": "{{context.message}}" -} +} ``` ### Webhook Action type model definition: + ``` export function getActionType(): ActionTypeModel { return { @@ -1065,10 +1091,10 @@ export function getActionType(): ActionTypeModel { and action params form available in Create Alert form: ![Webhook action form](https://i.imgur.com/mBGfeuC.png) - ### PagerDuty Action type model definition: + ``` export function getActionType(): ActionTypeModel { return { @@ -1108,6 +1134,7 @@ and action params form available in Create Alert form: ## Action type model definition Each action type should be defined as an `ActionTypeModel` object with the following properties: + ``` id: string; iconClass: IconType; @@ -1119,16 +1146,17 @@ Each action type should be defined as an `ActionTypeModel` object with the follo actionParamsFields: React.LazyExoticComponent>>; customConnectorSelectItem?: CustomConnectorSelectionItem; ``` -|Property|Description| -|---|---| -|id|Action type id. Should be the same as on server side.| -|iconClass|Setting for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or a lazy loaded React component, ReactElement.| -|selectMessage|Short description of action type responsibility, that will be displayed on the select card in UI.| -|validateConnector|Validation function for action connector.| -|validateParams|Validation function for action params.| -|actionConnectorFields|A lazy loaded React component for building UI of current action type connector.| -|actionParamsFields|A lazy loaded React component for building UI of current action type params. Displayed as a part of Create Alert flyout.| -|customConnectorSelectItem|Optional, an object for customizing the selection row of the action connector form.| + +| Property | Description | +| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | +| id | Action type id. Should be the same as on server side. | +| iconClass | Setting for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or a lazy loaded React component, ReactElement. | +| selectMessage | Short description of action type responsibility, that will be displayed on the select card in UI. | +| validateConnector | Validation function for action connector. | +| validateParams | Validation function for action params. | +| actionConnectorFields | A lazy loaded React component for building UI of current action type connector. | +| actionParamsFields | A lazy loaded React component for building UI of current action type params. Displayed as a part of Create Alert flyout. | +| customConnectorSelectItem | Optional, an object for customizing the selection row of the action connector form. | ### CustomConnectorSelectionItem Properties @@ -1138,21 +1166,21 @@ Each action type should be defined as an `ActionTypeModel` object with the follo LazyExoticComponent | undefined; ``` -|Property|Description| -|---|---| -|getText|Function for returning the text to display for the row.| -|getComponent|Function for returning a lazy loaded React component for customizing the selection row of the action connector form. Or undefined if if no customization is needed.| +| Property | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| getText | Function for returning the text to display for the row. | +| getComponent | Function for returning a lazy loaded React component for customizing the selection row of the action connector form. Or undefined if if no customization is needed. | ## Register action type model There are two ways to register a new action type UI: 1. Directly in `triggers_actions_ui` plugin. In this case, the action type will be available in the Alerts and Actions management section. -Registration code for a new action type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` -Only registered action types are available in UI. + Registration code for a new action type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` + Only registered action types are available in UI. 2. Register action type in another plugin. In this case, the action type will be available only in the current plugin UI. -It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: + It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: ``` function getSomeNewActionType() { @@ -1171,6 +1199,7 @@ Action type UI is expected to be defined as `ActionTypeModel` object. Below is a list of steps that should be done to build and register a new action type with the name `Example Action Type`: 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [ActionTypeModel]: + ``` import React, { lazy } from 'react'; import { i18n } from '@kbn/i18n'; @@ -1244,6 +1273,7 @@ export function getActionType(): ActionTypeModel { ``` 2. Define `actionConnectorFields` as `React.FunctionComponent` - this is the form for action connector. + ``` import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -1290,7 +1320,8 @@ const ExampleConnectorFields: React.FunctionComponent = ({ updated_at: updatedAt, api_key_owner: apiKeyOwner, notify_when: notifyWhen, + mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, execution_status: executionStatus, actions: actions, - snooze_indefinitely: snoozeIndefinitely, snooze_schedule: snoozeSchedule, ...rest }: any) => ({ @@ -53,8 +53,8 @@ export const transformRule: RewriteRequestCase = ({ updatedAt, apiKeyOwner, notifyWhen, + muteAll, mutedInstanceIds, - snoozeIndefinitely, snoozeSchedule, executionStatus: executionStatus ? transformExecutionStatus(executionStatus) : undefined, actions: actions diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx index 208fbcde88143..813a8180cca94 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx @@ -243,7 +243,7 @@ function isRuleDisabled(alert: RuleTableItem) { } function isRuleMuted(alert: RuleTableItem) { - return alert.muteAll === true; + return alert.snoozeIndefinitely === true; } function noop() {} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index e82bbaad26743..801de3d8c6781 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -165,7 +165,7 @@ function isRuleDisabled(rule: Rule) { } function isRuleMuted(rule: Rule) { - return rule.muteAll === true; + return rule.snoozeIndefinitely === true; } function isAlertInstanceMuted(rule: Rule, instanceId: string) { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index ea72f9fc6f432..6319928e932e1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -37,7 +37,7 @@ import { Rule } from '../../../../types'; type SnoozeUnit = 'm' | 'h' | 'd' | 'w' | 'M'; const SNOOZE_END_TIME_FORMAT = 'LL @ LT'; -type DropdownRuleRecord = Pick; +type DropdownRuleRecord = Pick; export interface ComponentOpts { rule: DropdownRuleRecord; From 5b57cb8fa5eef080b1e5819d72291c286e7b78d7 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 27 Apr 2022 13:03:37 -0500 Subject: [PATCH 09/45] Revert more snoozeIndefinitely refs --- .../alerting/common/is_rule_snoozed.test.ts | 52 ++++++------------- .../rules_client/tests/aggregate.test.ts | 2 +- .../server/rules_client/tests/create.test.ts | 48 ++++++++--------- .../components/rule_quick_edit_buttons.tsx | 2 +- .../with_bulk_rule_api_operations.tsx | 2 +- 5 files changed, 42 insertions(+), 64 deletions(-) diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts index 74215efdc0edb..1040aa3f08d4d 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -35,7 +35,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); }); test('returns true when snooze has started', () => { @@ -45,7 +45,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(true); }); test('returns false when snooze has ended', () => { @@ -55,7 +55,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: false })).toBe(false); + expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); }); test('returns true when snooze is indefinite', () => { @@ -65,7 +65,7 @@ describe('isRuleSnoozed', () => { duration: 100000000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule, snoozeIndefinitely: true })).toBe(true); + expect(isRuleSnoozed({ snoozeSchedule, muteAll: true })).toBe(true); }); test('returns as expected for an indefinitely recurring snooze', () => { @@ -76,9 +76,7 @@ describe('isRuleSnoozed', () => { repeatInterval: '1d', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { startTime: DATE_1970_PLUS_6_HOURS, @@ -86,9 +84,7 @@ describe('isRuleSnoozed', () => { repeatInterval: '1d', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( - false - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); const snoozeScheduleC = [ { startTime: DATE_2020_MINUS_1_HOUR, @@ -96,9 +92,7 @@ describe('isRuleSnoozed', () => { repeatInterval: '1h', }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(true); }); test('returns as expected for a recurring snooze with limited occurrences', () => { @@ -110,9 +104,7 @@ describe('isRuleSnoozed', () => { occurrences: 600000, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -121,9 +113,7 @@ describe('isRuleSnoozed', () => { occurrences: 25, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( - false - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); // FIXME: THIS FAILS due to not compensating for leap years. Also 1M intervals exhibit confusing behavior // Either we should add something to compensate for this, or disable the use of M and y, and only allow for explicit day lengths @@ -147,9 +137,7 @@ describe('isRuleSnoozed', () => { repeatEndTime: DATE_9999, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -158,9 +146,7 @@ describe('isRuleSnoozed', () => { repeatEndTime: DATE_2020_MINUS_1_HOUR, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( - false - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); }); test('returns as expected for a recurring snooze on a day of the week', () => { @@ -171,9 +157,7 @@ describe('isRuleSnoozed', () => { repeatInterval: 'DOW:135', // Monday Wednesday Friday; Jan 1 2020 was a Wednesday }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { startTime: DATE_1970, @@ -181,9 +165,7 @@ describe('isRuleSnoozed', () => { repeatInterval: 'DOW:2467', // Tue, Thu, Sat, Sun }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, snoozeIndefinitely: false })).toBe( - false - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); const snoozeScheduleC = [ { startTime: DATE_2020_MINUS_1_MONTH, @@ -192,9 +174,7 @@ describe('isRuleSnoozed', () => { occurrences: 12, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, snoozeIndefinitely: false })).toBe( - false - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(false); const snoozeScheduleD = [ { startTime: DATE_2020_MINUS_1_MONTH, @@ -203,8 +183,6 @@ describe('isRuleSnoozed', () => { occurrences: 15, }, ]; - expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD, snoozeIndefinitely: false })).toBe( - true - ); + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD, muteAll: false })).toBe(true); }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index f3338c6b66b42..b74059e4be3d6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -178,7 +178,7 @@ describe('aggregate()', () => { terms: { field: 'alert.attributes.enabled' }, }, muted: { - terms: { field: 'alert.attributes.snoozeIndefinitely' }, + terms: { field: 'alert.attributes.muteAll' }, }, snoozed: { date_range: { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index b047e0846faa2..faed4d1edff3d 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -294,7 +294,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], actions: [ @@ -360,7 +360,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": null, @@ -419,7 +419,7 @@ describe('create()', () => { "history": Array [], }, }, - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -500,7 +500,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], actions: [ @@ -560,7 +560,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], actions: [ @@ -625,7 +625,7 @@ describe('create()', () => { "history": Array [], }, }, - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -1048,7 +1048,7 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', @@ -1247,7 +1247,7 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', @@ -1415,7 +1415,7 @@ describe('create()', () => { }, monitoring: getDefaultRuleMonitoring(), meta: { versionApiKeyLastmodified: kibanaVersion }, - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], name: 'abc', @@ -1524,7 +1524,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onActionGroupChange', @@ -1581,7 +1581,7 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: '10m', notifyWhen: 'onActionGroupChange', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], @@ -1622,7 +1622,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActionGroupChange", @@ -1656,7 +1656,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onThrottleInterval', @@ -1713,7 +1713,7 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: '10m', notifyWhen: 'onThrottleInterval', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], @@ -1754,7 +1754,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onThrottleInterval", @@ -1788,7 +1788,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], notifyWhen: 'onActiveAlert', @@ -1845,7 +1845,7 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], @@ -1886,7 +1886,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "1", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": "onActiveAlert", @@ -1929,7 +1929,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], actions: [ @@ -1993,7 +1993,7 @@ describe('create()', () => { updatedBy: 'elastic', createdAt: '2019-02-12T21:01:22.479Z', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], executionStatus: { @@ -2048,7 +2048,7 @@ describe('create()', () => { "createdBy": "elastic", "enabled": true, "id": "123", - "snoozeIndefinitely": false, + "muteAll": false, "mutedInstanceIds": Array [], "name": "abc", "notifyWhen": null, @@ -2363,7 +2363,7 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], @@ -2465,7 +2465,7 @@ describe('create()', () => { schedule: { interval: '1m' }, throttle: null, notifyWhen: 'onActiveAlert', - snoozeIndefinitely: false, + muteAll: false, snoozeSchedule: [], mutedInstanceIds: [], tags: ['foo'], @@ -2567,7 +2567,7 @@ describe('create()', () => { createdBy: 'elastic', updatedBy: 'elastic', updatedAt: '2019-02-12T21:01:22.479Z', - snoozeIndefinitely: false, + muteAll: false, mutedInstanceIds: [], actions: [ { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx index 813a8180cca94..208fbcde88143 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/rule_quick_edit_buttons.tsx @@ -243,7 +243,7 @@ function isRuleDisabled(alert: RuleTableItem) { } function isRuleMuted(alert: RuleTableItem) { - return alert.snoozeIndefinitely === true; + return alert.muteAll === true; } function noop() {} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx index 801de3d8c6781..e82bbaad26743 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/common/components/with_bulk_rule_api_operations.tsx @@ -165,7 +165,7 @@ function isRuleDisabled(rule: Rule) { } function isRuleMuted(rule: Rule) { - return rule.snoozeIndefinitely === true; + return rule.muteAll === true; } function isAlertInstanceMuted(rule: Rule, instanceId: string) { From 5df3868b6bfc2ea5e7cd1ca0ddbf574b658ddd28 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 27 Apr 2022 13:04:17 -0500 Subject: [PATCH 10/45] Revert README --- x-pack/plugins/triggers_actions_ui/README.md | 391 +++++++++---------- 1 file changed, 174 insertions(+), 217 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/README.md b/x-pack/plugins/triggers_actions_ui/README.md index 1e21dbd278808..7fde1f1512302 100644 --- a/x-pack/plugins/triggers_actions_ui/README.md +++ b/x-pack/plugins/triggers_actions_ui/README.md @@ -1,13 +1,14 @@ # Kibana Alerts and Actions UI -The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. +The Kibana alerts and actions UI plugin provides a user interface for managing alerts and actions. As a developer you can reuse and extend built-in alerts and actions UI functionality: - Create and register a new Alert Type. - Create and register a new Action Type. - Embed the Create Alert flyout within any Kibana plugin. ---- +----- + Table of Contents @@ -45,9 +46,9 @@ Table of Contents Kibana ships with several built-in alert types: -| Type | Id | Description | -| ----------------------------------------- | ----------- | --------------------- | -| [Index Threshold](#index-threshold-alert) | `threshold` | Index Threshold Alert | +|Type|Id|Description| +|---|---|---| +|[Index Threshold](#index-threshold-alert)|`threshold`|Index Threshold Alert| Every alert type must be registered server side, and can optionally be registered client side. Only alert types registered on both client and server will be displayed in the Create Alert flyout, as a part of the UI. @@ -88,12 +89,13 @@ interface IndexThresholdProps { } ``` -| Property | Description | -| --------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| ruleParams | Set of Alert params relevant for the index threshold alert type. | -| setRuleParams | Alert reducer method, which is used to create a new copy of alert object with the changed params property any subproperty value. | -| setRuleProperty | Alert reducer method, which is used to create a new copy of alert object with the changed any direct alert property value. | -| errors | Alert level errors tracking object. | +|Property|Description| +|---|---| +|ruleParams|Set of Alert params relevant for the index threshold alert type.| +|setRuleParams|Alert reducer method, which is used to create a new copy of alert object with the changed params property any subproperty value.| +|setRuleProperty|Alert reducer method, which is used to create a new copy of alert object with the changed any direct alert property value.| +|errors|Alert level errors tracking object.| + Alert reducer is defined on the AlertAdd functional component level and passed down to the subcomponents to provide a new state of Alert object: @@ -125,7 +127,6 @@ const setRuleProperty = (key: string, value: any) => { ``` 'x-pack/plugins/triggers_actions_ui/public/application/sections/alert_add/alert_reducer.ts' define the methods for changing different type of alert properties: - ``` export const alertReducer = (state: any, action: AlertReducerAction) => { const { command, payload } = action; @@ -234,7 +235,6 @@ Index Threshold Alert form with validation: ## Alert type model definition Each alert type should be defined as `RuleTypeModel` object with the these properties: - ``` id: string; name: string; @@ -245,21 +245,20 @@ Each alert type should be defined as `RuleTypeModel` object with the these prope >; defaultActionMessage?: string; ``` - -| Property | Description | -| -------------------- | ---------------------------------------------------------------------------------------------- | -| id | Alert type id. Should be the same as on the server side. | -| name | Name of the alert type that will be displayed on the select card in the UI. | -| iconClass | Icon of the alert type that will be displayed on the select card in the UI. | -| validate | Validation function for the alert params. | -| ruleParamsExpression | A lazy loaded React component for building UI of the current alert type params. | -| defaultActionMessage | Optional property for providing default message for all added actions with `message` property. | -| requiresAppContext | Define if alert type is enabled for create and edit in the alerting management UI. | - -IMPORTANT: The current UI supports a single action group only. +|Property|Description| +|---|---| +|id|Alert type id. Should be the same as on the server side.| +|name|Name of the alert type that will be displayed on the select card in the UI.| +|iconClass|Icon of the alert type that will be displayed on the select card in the UI.| +|validate|Validation function for the alert params.| +|ruleParamsExpression| A lazy loaded React component for building UI of the current alert type params.| +|defaultActionMessage|Optional property for providing default messages for all added actions, excluding the Recovery action group, with `message` property. | +|defaultRecoveryMessage|Optional property for providing a default message for all added actions with `message` property for the Recovery action group.| +|requiresAppContext|Define if alert type is enabled for create and edit in the alerting management UI.| + +IMPORTANT: The current UI supports a single action group only. Action groups are mapped from the server API result for [GET /api/alerts/list_alert_types: List alert types](https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#get-apialerttypes-list-alert-types). Server side alert type model: - ``` export interface RuleType { id: string; @@ -272,7 +271,6 @@ export interface RuleType { requiresAppContext: boolean; } ``` - Only the default (which means first item of the array) action group is displayed in the current UI. Design of user interface and server API for multiple action groups is under discussion and development. @@ -281,11 +279,11 @@ Design of user interface and server API for multiple action groups is under disc There are two ways of registering a new alert type: 1. Directly in the `triggers_actions_ui` plugin. In this case, the alert type will be available in the Create Alert flyout of the Alerts and Actions management section. - Registration code for a new alert type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` - Only registered alert types are available in UI. +Registration code for a new alert type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` +Only registered alert types are available in UI. -2. Register the alert type in another plugin. In this case, the alert type will be available only in the current plugin UI. - It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: +2. Register the alert type in another plugin. In this case, the alert type will be available only in the current plugin UI. +It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: ``` function getSomeNewAlertType() { @@ -297,14 +295,13 @@ triggersActionsUi.ruleTypeRegistry.register(getSomeNewAlertType()); ## Create and register new alert type UI example -Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#example +Before registering a UI for a new Alert Type, you should first register the type on the server-side by following the Alerting guide: https://github.com/elastic/kibana/tree/main/x-pack/plugins/alerting#example Alert type UI is expected to be defined as `RuleTypeModel` object. Below is a list of steps that should be done to build and register a new alert type with the name `Example Alert Type`: 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [RuleTypeModel](https://github.com/elastic/kibana/blob/55b7905fb5265b73806006e7265739545d7521d0/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts#L83). Example: - ``` import { lazy } from 'react'; import { RuleTypeModel } from '../../../../types'; @@ -322,11 +319,9 @@ export function getAlertType(): RuleTypeModel { }; } ``` - Fields of this object `RuleTypeModel` will be mapped properly in the UI below. 2. Define `ruleParamsExpression` as `React.FunctionComponent` - this is the form for filling Alert params based on the current Alert type. - ``` import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; @@ -379,13 +374,11 @@ export const ExampleExpression: React.FunctionComponent = ({ export {ExampleExpression as default}; ``` - This alert type form becomes available, when the card of `Example Alert Type` is selected. Each expression word here is `EuiExpression` component and implements the basic aggregation, grouping and comparison methods. Expression components, which can be embedded to different alert types, are described here [Common expression components](#common-expression-components). -3. Define alert type params validation using the property of `RuleTypeModel` `validate`: - +3. Define alert type params validation using the property of `RuleTypeModel` `validate`: ``` import { i18n } from '@kbn/i18n'; import { ValidationResult } from '../../../../types'; @@ -412,7 +405,6 @@ export function validateExampleAlertType({ ``` 4. Extend registration code with the new alert type register in the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_alert_types/index.ts` - ``` import { getAlertType as getExampledAlertType } from './example'; ... @@ -443,7 +435,6 @@ Click on the select card for `Example Alert Type` to open the expression form th ``` Props definition: - ``` interface WhenExpressionProps { aggType: string; @@ -454,12 +445,12 @@ interface WhenExpressionProps { } ``` -| Property | Description | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| aggType | Selected aggregation type that will be set as the alert type property. | -| customAggTypesOptions | (Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`. | -| onChangeSelectedAggType | event handler that will be executed when selected aggregation type is changed. | -| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | +|Property|Description| +|---|---| +|aggType|Selected aggregation type that will be set as the alert type property.| +|customAggTypesOptions|(Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`.| +|onChangeSelectedAggType|event handler that will be executed when selected aggregation type is changed.| +|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| ### OF expression component @@ -480,7 +471,6 @@ OF expression is available, if aggregation type requires selecting data fields f ``` Props definition: - ``` interface OfExpressionProps { aggType: string; @@ -496,15 +486,15 @@ interface OfExpressionProps { } ``` -| Property | Description | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| aggType | Selected aggregation type that will be set as the alert type property. | -| aggField | Selected aggregation field that will be set as the alert type property. | -| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `aggField`. | -| onChangeSelectedAggField | Event handler that will be excuted if selected aggregation field is changed. | -| fields | Fields list that will be available in the OF `Select a field` dropdown. | -| customAggTypesOptions | (Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`. | -| popupPosition | (Optional) expression popup position. Default is `downRight`. Recommend changing it for a small parent window space. | +|Property|Description| +|---|---| +|aggType|Selected aggregation type that will be set as the alert type property.| +|aggField|Selected aggregation field that will be set as the alert type property.| +|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `aggField`.| +|onChangeSelectedAggField|Event handler that will be excuted if selected aggregation field is changed.| +|fields|Fields list that will be available in the OF `Select a field` dropdown.| +|customAggTypesOptions|(Optional) List of aggregation types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/aggregation_types.ts`.| +|popupPosition|(Optional) expression popup position. Default is `downRight`. Recommend changing it for a small parent window space.| ### GROUPED BY expression component @@ -528,7 +518,6 @@ interface OfExpressionProps { ``` Props definition: - ``` interface GroupByExpressionProps { groupBy: string; @@ -547,18 +536,18 @@ interface GroupByExpressionProps { } ``` -| Property | Description | -| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| groupBy | Selected group by type that will be set as the alert type property. | -| termSize | Selected term size that will be set as the alert type property. | -| termField | Selected term field that will be set as the alert type property. | -| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`. | -| onChangeSelectedTermSize | Event handler that will be excuted if selected term size is changed. | -| onChangeSelectedTermField | Event handler that will be excuted if selected term field is changed. | -| onChangeSelectedGroupBy | Event handler that will be excuted if selected group by is changed. | -| fields | Fields list with options for the `termField` dropdown. | -| customGroupByTypes | (Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`. | -| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | +|Property|Description| +|---|---| +|groupBy|Selected group by type that will be set as the alert type property.| +|termSize|Selected term size that will be set as the alert type property.| +|termField|Selected term field that will be set as the alert type property.| +|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `termSize` and `termField`.| +|onChangeSelectedTermSize|Event handler that will be excuted if selected term size is changed.| +|onChangeSelectedTermField|Event handler that will be excuted if selected term field is changed.| +|onChangeSelectedGroupBy|Event handler that will be excuted if selected group by is changed.| +|fields|Fields list with options for the `termField` dropdown.| +|customGroupByTypes|(Optional) List of group by types that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/group_by_types.ts`.| +|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| ### FOR THE LAST expression component @@ -579,7 +568,6 @@ interface GroupByExpressionProps { ``` Props definition: - ``` interface ForLastExpressionProps { timeWindowSize?: number; @@ -592,14 +580,14 @@ interface ForLastExpressionProps { } ``` -| Property | Description | -| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------- | -| timeWindowSize | Selected time window size that will be set as the alert type property. | -| timeWindowUnit | Selected time window unit that will be set as the alert type property. | -| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `termWindowSize`. | -| onChangeWindowSize | Event handler that will be excuted if selected window size is changed. | -| onChangeWindowUnit | Event handler that will be excuted if selected window unit is changed. | -| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | +|Property|Description| +|---|---| +|timeWindowSize|Selected time window size that will be set as the alert type property.| +|timeWindowUnit|Selected time window unit that will be set as the alert type property.| +|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `termWindowSize`.| +|onChangeWindowSize|Event handler that will be excuted if selected window size is changed.| +|onChangeWindowUnit|Event handler that will be excuted if selected window unit is changed.| +|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| ### THRESHOLD expression component @@ -620,7 +608,6 @@ interface ForLastExpressionProps { ``` Props definition: - ``` interface ThresholdExpressionProps { thresholdComparator: string; @@ -636,20 +623,18 @@ interface ThresholdExpressionProps { } ``` -| Property | Description | -| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| thresholdComparator | Selected time window size that will be set as the alert type property. | -| threshold | Selected time window size that will be set as the alert type property. | -| errors | List of errors with proper messages for the alert params that should be validated. In current component is validated `threshold0` and `threshold1`. | -| onChangeSelectedThresholdComparator | Event handler that will be excuted if selected threshold comparator is changed. | -| onChangeSelectedThreshold | Event handler that will be excuted if selected threshold is changed. | -| customComparators | (Optional) List of comparators that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/comparators.ts`. | -| popupPosition | (Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space. | +|Property|Description| +|---|---| +|thresholdComparator|Selected time window size that will be set as the alert type property.| +|threshold|Selected time window size that will be set as the alert type property.| +|errors|List of errors with proper messages for the alert params that should be validated. In current component is validated `threshold0` and `threshold1`.| +|onChangeSelectedThresholdComparator|Event handler that will be excuted if selected threshold comparator is changed.| +|onChangeSelectedThreshold|Event handler that will be excuted if selected threshold is changed.| +|customComparators|(Optional) List of comparators that replaces the default options defined in constants `x-pack/plugins/triggers_actions_ui/public/common/constants/comparators.ts`.| +|popupPosition|(Optional) expression popup position. Default is `downLeft`. Recommend changing it for a small parent window space.| ## Alert Conditions Components - To aid in creating a uniform UX across Alert Types, we provide two components for specifying the conditions for detection of a certain alert under within any specific Action Groups: - 1. `AlertConditions`: A component that generates a container which renders custom component for each Action Group which has had its _conditions_ specified. 2. `AlertConditionsGroup`: A component that provides a unified container for the Action Group with its name and a button for resetting its condition. @@ -719,7 +704,6 @@ The `isRequired` field specifies whether this specific action group is _required Using this `ThresholdSpecifier` component, we can now use `AlertConditionsGroup` & `AlertConditions` to enable the user to specify these thresholds for each action group in the alert type. Like so: - ``` interface ThresholdAlertTypeParams { thresholds?: { @@ -778,39 +762,37 @@ const DEFAULT_THRESHOLDS: ThresholdAlertTypeParams['threshold] = { ``` -### The AlertConditions component +### The AlertConditions component This component will render the `Conditions` header & headline, along with the selectors for adding every Action Group you specity. Additionally it will clone its `children` for _each_ action group which has a `condition` specified for it, passing in the appropriate `actionGroup` prop for each one. -| Property | Description | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| headline | The headline title displayed above the fields | -| actionGroups | A list of `ActionGroupWithCondition` which includes all the action group you wish to offer the user and what conditions they are already configured to follow | -| onInitializeConditionsFor | A callback which is called when the user ask for a certain actionGroup to be initialized with an initial default condition. If you have no specific default, that's fine, as the component will render the action group's field even if the condition is empty (using a `null` or an `undefined`) and determines whether to render these fields by _the very presence_ of a `condition` field | +|Property|Description| +|---|---| +|headline|The headline title displayed above the fields | +|actionGroups|A list of `ActionGroupWithCondition` which includes all the action group you wish to offer the user and what conditions they are already configured to follow| +|onInitializeConditionsFor|A callback which is called when the user ask for a certain actionGroup to be initialized with an initial default condition. If you have no specific default, that's fine, as the component will render the action group's field even if the condition is empty (using a `null` or an `undefined`) and determines whether to render these fields by _the very presence_ of a `condition` field| -### The AlertConditionsGroup component +### The AlertConditionsGroup component This component renders a standard EuiTitle foe each action group, wrapping the Alert Type specific component, in addition to a "reset" button which allows the user to reset the condition for that action group. The definition of what a _reset_ actually means is Alert Type specific, and up to the implementor to decide. In some case it might mean removing the condition, in others it might mean to reset it to some default value on the server side. In either case, it should _delete_ the `condition` field from the appropriate `actionGroup` as per the above example. -| Property | Description | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| onResetConditionsFor | A callback which is called when the user clicks the _reset_ button besides the action group's title. The implementor should use this to remove the `condition` from the specified actionGroup | +|Property|Description| +|---|---| +|onResetConditionsFor|A callback which is called when the user clicks the _reset_ button besides the action group's title. The implementor should use this to remove the `condition` from the specified actionGroup| + ## Embed the Create Alert flyout within any Kibana plugin Follow the instructions bellow to embed the Create Alert flyout within any Kibana plugin: - 1. Add TriggersAndActionsUIPublicPluginStart to Kibana plugin setup dependencies: ``` triggersActionsUi: TriggersAndActionsUIPublicPluginStart; ``` - Then this dependency will be used to embed Create Alert flyout. 2. Add Create Alert flyout to React component using triggersActionsUi start contract: - ``` // in the component state definition section const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); @@ -844,7 +826,6 @@ const AddAlertFlyout = useMemo( ``` getAddAlertFlyout variables definition: - ``` interface AlertAddProps { consumer: string; @@ -858,39 +839,39 @@ interface AlertAddProps { } ``` -| Property | Description | -| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| consumer | Name of the plugin that creates an alert. | -| addFlyoutVisible | Visibility state of the Create Alert flyout. | -| setAddFlyoutVisibility | Function for changing visibility state of the Create Alert flyout. | -| alertTypeId | Optional property to preselect alert type. | -| canChangeTrigger | Optional property, that hides change alert type possibility. | -| onSave | Optional function, which will be executed if alert was saved sucsessfuly. | -| initialValues | Default values for Alert properties. | -| metadata | Optional generic property, which allows to define component specific metadata. This metadata can be used for passing down preloaded data for Alert type expression component. | +|Property|Description| +|---|---| +|consumer|Name of the plugin that creates an alert.| +|addFlyoutVisible|Visibility state of the Create Alert flyout.| +|setAddFlyoutVisibility|Function for changing visibility state of the Create Alert flyout.| +|alertTypeId|Optional property to preselect alert type.| +|canChangeTrigger|Optional property, that hides change alert type possibility.| +|onSave|Optional function, which will be executed if alert was saved sucsessfuly.| +|initialValues|Default values for Alert properties.| +|metadata|Optional generic property, which allows to define component specific metadata. This metadata can be used for passing down preloaded data for Alert type expression component.| ## Build and register Action Types Kibana ships with a set of built-in action types UI: -| Type | Id | Description | -| ------------------------- | ------------ | ---------------------------------------------------------------------- | -| [Server log](#server-log) | `.log` | Logs messages to the Kibana log using `server.log()` | -| [Email](#email) | `.email` | Sends an email using SMTP | -| [Slack](#slack) | `.slack` | Posts a message to a Slack channel | -| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch | -| [Webhook](#webhook) | `.webhook` | Sends a payload to a web service using HTTP POST or PUT | -| [PagerDuty](#pagerduty) | `.pagerduty` | Triggers, resolves, or acknowledges an incident to a PagerDuty service | +|Type|Id|Description| +|---|---|---| +|[Server log](#server-log)|`.log`|Logs messages to the Kibana log using `server.log()`| +|[Email](#email)|`.email`|Sends an email using SMTP| +|[Slack](#slack)|`.slack`|Posts a message to a Slack channel| +|[Index](#index)|`.index`|Indexes document(s) into Elasticsearch| +|[Webhook](#webhook)|`.webhook`|Sends a payload to a web service using HTTP POST or PUT| +|[PagerDuty](#pagerduty)|`.pagerduty`|Triggers, resolves, or acknowledges an incident to a PagerDuty service| -Every action type should be registered server side, and can be optionally registered client side. +Every action type should be registered server side, and can be optionally registered client side. Only action types registered on both client and server will be displayed in the Alerts and Actions UI. Built-in action types UI is located under the folder `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types` and this is a file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` for client side registration. + ### Server log Action type model definition: - ``` export function getActionType(): ActionTypeModel { return { @@ -919,7 +900,6 @@ export function getActionType(): ActionTypeModel { }; } ``` - Server log has a connector UI: ![Server log connector card](https://i.imgur.com/ZIWhV89.png) @@ -932,7 +912,6 @@ and action params form available in Create Alert form: ### Email Action type model definition: - ``` export function getActionType(): ActionTypeModel { const mailformat = /^[^@\s]+@[^@\s]+$/; @@ -962,7 +941,6 @@ export function getActionType(): ActionTypeModel { }; } ``` - ![Email connector card](https://i.imgur.com/d8kCbjQ.png) ![Email connector form](https://i.imgur.com/Uf6HU7X.png) @@ -973,7 +951,6 @@ and action params form available in Create Alert form: ### Slack Action type model definition: - ``` export function getActionType(): ActionTypeModel { return { @@ -995,7 +972,7 @@ export function getActionType(): ActionTypeModel { // validation of connector properties implementation }, validateParams: (actionParams: SlackActionParams): Promise => { - // validation of action params implementation + // validation of action params implementation }, actionConnectorFields: SlackActionFields, actionParamsFields: SlackParamsFields, @@ -1013,7 +990,6 @@ and action params form available in Create Alert form: ### Index Action type model definition: - ``` export function getActionType(): ActionTypeModel { return { @@ -1054,13 +1030,12 @@ Example of the index document for Index Threshold alert: "context_title": "{{context.title}}", "context_value": "{{context.value}}", "context_message": "{{context.message}}" -} +} ``` ### Webhook Action type model definition: - ``` export function getActionType(): ActionTypeModel { return { @@ -1091,10 +1066,10 @@ export function getActionType(): ActionTypeModel { and action params form available in Create Alert form: ![Webhook action form](https://i.imgur.com/mBGfeuC.png) + ### PagerDuty Action type model definition: - ``` export function getActionType(): ActionTypeModel { return { @@ -1134,7 +1109,6 @@ and action params form available in Create Alert form: ## Action type model definition Each action type should be defined as an `ActionTypeModel` object with the following properties: - ``` id: string; iconClass: IconType; @@ -1146,17 +1120,16 @@ Each action type should be defined as an `ActionTypeModel` object with the follo actionParamsFields: React.LazyExoticComponent>>; customConnectorSelectItem?: CustomConnectorSelectionItem; ``` - -| Property | Description | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| id | Action type id. Should be the same as on server side. | -| iconClass | Setting for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or a lazy loaded React component, ReactElement. | -| selectMessage | Short description of action type responsibility, that will be displayed on the select card in UI. | -| validateConnector | Validation function for action connector. | -| validateParams | Validation function for action params. | -| actionConnectorFields | A lazy loaded React component for building UI of current action type connector. | -| actionParamsFields | A lazy loaded React component for building UI of current action type params. Displayed as a part of Create Alert flyout. | -| customConnectorSelectItem | Optional, an object for customizing the selection row of the action connector form. | +|Property|Description| +|---|---| +|id|Action type id. Should be the same as on server side.| +|iconClass|Setting for icon to be displayed to the user. EUI supports any known EUI icon, SVG URL, or a lazy loaded React component, ReactElement.| +|selectMessage|Short description of action type responsibility, that will be displayed on the select card in UI.| +|validateConnector|Validation function for action connector.| +|validateParams|Validation function for action params.| +|actionConnectorFields|A lazy loaded React component for building UI of current action type connector.| +|actionParamsFields|A lazy loaded React component for building UI of current action type params. Displayed as a part of Create Alert flyout.| +|customConnectorSelectItem|Optional, an object for customizing the selection row of the action connector form.| ### CustomConnectorSelectionItem Properties @@ -1166,21 +1139,21 @@ Each action type should be defined as an `ActionTypeModel` object with the follo LazyExoticComponent | undefined; ``` -| Property | Description | -| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| getText | Function for returning the text to display for the row. | -| getComponent | Function for returning a lazy loaded React component for customizing the selection row of the action connector form. Or undefined if if no customization is needed. | +|Property|Description| +|---|---| +|getText|Function for returning the text to display for the row.| +|getComponent|Function for returning a lazy loaded React component for customizing the selection row of the action connector form. Or undefined if if no customization is needed.| ## Register action type model There are two ways to register a new action type UI: 1. Directly in `triggers_actions_ui` plugin. In this case, the action type will be available in the Alerts and Actions management section. - Registration code for a new action type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` - Only registered action types are available in UI. +Registration code for a new action type model should be added to the file `x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts` +Only registered action types are available in UI. 2. Register action type in another plugin. In this case, the action type will be available only in the current plugin UI. - It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: +It should be done by importing dependency `TriggersAndActionsUIPublicPluginSetup` and adding the next code on plugin setup: ``` function getSomeNewActionType() { @@ -1199,7 +1172,6 @@ Action type UI is expected to be defined as `ActionTypeModel` object. Below is a list of steps that should be done to build and register a new action type with the name `Example Action Type`: 1. At any suitable place in Kibana, create a file, which will expose an object implementing interface [ActionTypeModel]: - ``` import React, { lazy } from 'react'; import { i18n } from '@kbn/i18n'; @@ -1273,7 +1245,6 @@ export function getActionType(): ActionTypeModel { ``` 2. Define `actionConnectorFields` as `React.FunctionComponent` - this is the form for action connector. - ``` import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -1320,8 +1291,7 @@ const ExampleConnectorFields: React.FunctionComponent Date: Wed, 27 Apr 2022 13:53:18 -0500 Subject: [PATCH 11/45] Restore updated taskrunner test --- .../server/task_runner/task_runner.test.ts | 264 +++--------------- 1 file changed, 38 insertions(+), 226 deletions(-) diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 111c0a7689504..d56fe52a5d32e 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -236,15 +236,11 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(4); + expect(logger.debug).toHaveBeenCalledTimes(3); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' - ); - expect(logger.debug).nthCalledWith( - 3, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -316,7 +312,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledWith(generateEnqueueFunctionInput()); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(4); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -324,11 +320,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 3, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' - ); - expect(logger.debug).nthCalledWith( - 4, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -385,8 +377,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, - numberOfActiveAlerts: 1, - numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -426,7 +416,7 @@ describe('Task Runner', () => { expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -438,11 +428,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' - ); - expect(logger.debug).nthCalledWith( - 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -486,8 +472,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfActiveAlerts: 1, - numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -497,24 +481,25 @@ describe('Task Runner', () => { type SnoozeTestParams = [ muteAll: boolean, - snoozeEndTime: string | undefined | null, + snoozeSchedule: string | undefined | null, shouldBeSnoozed: boolean ]; const snoozeTestParams: SnoozeTestParams[] = [ [false, null, false], [false, undefined, false], - [false, DATE_1970, false], - [false, DATE_9999, true], + // Stringify the snooze schedules for better failure reporting + [false, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), false], + [false, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], [true, null, true], [true, undefined, true], - [true, DATE_1970, true], - [true, DATE_9999, true], + [true, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), true], + [true, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], ]; test.each(snoozeTestParams)( - 'snoozing works as expected with muteAll: %s; snoozeEndTime: %s', - async (muteAll, snoozeEndTime, shouldBeSnoozed) => { + 'snoozing works as expected with muteAll: %s; snoozeSchedule: %s', + async (muteAll, snoozeSchedule, shouldBeSnoozed) => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); ruleType.executor.mockImplementation( @@ -539,7 +524,7 @@ describe('Task Runner', () => { rulesClient.get.mockResolvedValue({ ...mockedRuleTypeSavedObject, muteAll, - snoozeEndTime: snoozeEndTime != null ? new Date(snoozeEndTime) : snoozeEndTime, + snoozeSchedule: snoozeSchedule != null ? JSON.parse(snoozeSchedule) : [], }); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue(SAVED_OBJECT); await taskRunner.run(); @@ -597,7 +582,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -609,11 +594,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' - ); - expect(logger.debug).nthCalledWith( - 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' ); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -718,7 +699,7 @@ describe('Task Runner', () => { await taskRunner.run(); expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith( 3, `skipping scheduling of actions for '2' in rule test:1: '${RULE_NAME}': rule is muted` @@ -803,7 +784,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -869,7 +849,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, - numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -944,7 +923,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, - numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -1063,8 +1041,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, - numberOfActiveAlerts: 1, - numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -1134,7 +1110,7 @@ describe('Task Runner', () => { ); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -1146,11 +1122,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' - ); - expect(logger.debug).nthCalledWith( - 5, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1218,8 +1190,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 2, numberOfGeneratedActions: 2, - numberOfActiveAlerts: 1, - numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1293,11 +1263,7 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - `ruleRunStatus for test:${alertId}: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` - ); - expect(logger.debug).nthCalledWith( - 5, - `ruleRunMetrics for test:${alertId}: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}` + `ruleExecutionStatus for test:${alertId}: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1486,8 +1452,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 2, - numberOfActiveAlerts: 1, - numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1495,7 +1459,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('validates params before running the rule type', async () => { + test('validates params before executing the alert type', async () => { const taskRunner = new TaskRunner( { ...ruleType, @@ -1583,7 +1547,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('rescheduled the rule if the schedule has update during a task run', async () => { + test('rescheduled the Alert if the schedule has update during a task run', async () => { const taskRunner = new TaskRunner( ruleType, mockedTaskInstance, @@ -2094,8 +2058,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfActiveAlerts: 2, - numberOfNewAlerts: 2, task: true, consumer: 'bar', }) @@ -2199,7 +2161,6 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfActiveAlerts: 2, task: true, consumer: 'bar', }) @@ -2292,7 +2253,6 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfActiveAlerts: 2, task: true, }) ); @@ -2383,7 +2343,6 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2470,7 +2429,6 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, - numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2528,15 +2486,11 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(4); + expect(logger.debug).toHaveBeenCalledTimes(3); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' - ); - expect(logger.debug).nthCalledWith( - 3, - 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' + 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -2693,11 +2647,13 @@ describe('Task Runner', () => { const runnerResult = await taskRunner.run(); expect(runnerResult.monitoring?.execution.history.length).toBe(200); }); - test('Actions circuit breaker kicked in, should set status as warning and log a message in event log', async () => { - const actionsConfigMap = { - default: { - max: 3, + const ruleTypeWithConfig = { + ...ruleType, + config: { + run: { + actions: { max: 3 }, + }, }, }; @@ -2755,22 +2711,21 @@ describe('Task Runner', () => { ...mockedRuleTypeSavedObject, actions: mockActions, } as jest.ResolvedValue); - ruleTypeRegistry.get.mockReturnValue(ruleType); + ruleTypeRegistry.get.mockReturnValue(ruleTypeWithConfig); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); const taskRunner = new TaskRunner( - ruleType, + ruleTypeWithConfig, mockedTaskInstance, - { - ...taskRunnerFactoryInitializerParams, - actionsConfigMap, - }, + taskRunnerFactoryInitializerParams, inMemoryMetrics ); const runnerResult = await taskRunner.run(); - expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(actionsConfigMap.default.max); + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes( + ruleTypeWithConfig.config.run.actions.max + ); expect( taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update @@ -2796,15 +2751,6 @@ describe('Task Runner', () => { }, }) ); - - const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); - - expect(logger.debug).nthCalledWith( - 3, - 'Rule "1" skipped scheduling action "4" because the maximum number of allowed actions has been reached.' - ); - const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(7); @@ -2878,10 +2824,8 @@ describe('Task Runner', () => { action: EVENT_LOG_ACTIONS.execute, outcome: 'success', status: 'warning', - numberOfTriggeredActions: actionsConfigMap.default.max, + numberOfTriggeredActions: ruleTypeWithConfig.config.run.actions.max, numberOfGeneratedActions: mockActions.length, - numberOfActiveAlerts: 1, - numberOfNewAlerts: 1, reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, task: true, consumer: 'bar', @@ -2889,138 +2833,6 @@ describe('Task Runner', () => { ); }); - test('Actions circuit breaker kicked in with connectorType specific config and multiple alerts', async () => { - const actionsConfigMap = { - default: { - max: 30, - }, - '.server-log': { - max: 1, - }, - }; - - const warning = { - reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, - message: translations.taskRunner.warning.maxExecutableActions, - }; - - taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); - taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); - - ruleType.executor.mockImplementation( - async ({ - services: executorServices, - }: RuleExecutorOptions< - RuleTypeParams, - RuleTypeState, - AlertInstanceState, - AlertInstanceContext, - string - >) => { - executorServices.alertFactory.create('1').scheduleActions('default'); - executorServices.alertFactory.create('2').scheduleActions('default'); - } - ); - - rulesClient.get.mockResolvedValue({ - ...mockedRuleTypeSavedObject, - actions: [ - { - group: 'default', - id: '1', - actionTypeId: '.server-log', - }, - { - group: 'default', - id: '2', - actionTypeId: '.server-log', - }, - { - group: 'default', - id: '3', - actionTypeId: '.server-log', - }, - { - group: 'default', - id: '4', - actionTypeId: 'any-action', - }, - { - group: 'default', - id: '5', - actionTypeId: 'any-action', - }, - ], - } as jest.ResolvedValue); - - ruleTypeRegistry.get.mockReturnValue(ruleType); - encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); - encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); - - const taskRunner = new TaskRunner( - ruleType, - mockedTaskInstance, - { - ...taskRunnerFactoryInitializerParams, - actionsConfigMap, - }, - inMemoryMetrics - ); - - const runnerResult = await taskRunner.run(); - - // 1x(.server-log) and 2x(any-action) per alert - expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(5); - - expect( - taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update - ).toHaveBeenCalledWith(...generateSavedObjectParams({ status: 'warning', warning })); - - expect(runnerResult).toEqual( - generateRunnerResult({ - state: true, - history: [true], - alertInstances: { - '1': { - meta: { - lastScheduledActions: { - date: new Date(DATE_1970), - group: 'default', - }, - }, - state: { - duration: 0, - start: '1970-01-01T00:00:00.000Z', - }, - }, - '2': { - meta: { - lastScheduledActions: { - date: new Date(DATE_1970), - group: 'default', - }, - }, - state: { - duration: 0, - start: '1970-01-01T00:00:00.000Z', - }, - }, - }, - }) - ); - - const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(6); - - expect(logger.debug).nthCalledWith( - 3, - 'Rule "1" skipped scheduling action "1" because the maximum number of allowed actions for connector type .server-log has been reached.' - ); - - const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; - expect(eventLogger.logEvent).toHaveBeenCalledTimes(11); - }); - test('increments monitoring metrics after execution', async () => { const taskRunner = new TaskRunner( ruleType, From 59c5a1b5f7cfa5f40849f3d694d7c77e541d7df5 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 27 Apr 2022 13:56:42 -0500 Subject: [PATCH 12/45] Fix RuleStatusDropdown test --- .../components/rule_status_dropdown.test.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx index 15086518124b4..b900d110e0ba9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx @@ -10,7 +10,12 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RuleStatusDropdown, ComponentOpts } from './rule_status_dropdown'; const NOW_STRING = '2020-03-01T00:00:00.000Z'; -const SNOOZE_END_TIME = new Date('2020-03-04T00:00:00.000Z'); +const SNOOZED_SCHEDULE = [ + { + startTime: NOW_STRING, + duration: Date.parse('2020-03-04T00:00:00.000Z') - Date.parse(NOW_STRING), + }, +]; describe('RuleStatusDropdown', () => { const enableRule = jest.fn(); @@ -51,7 +56,7 @@ describe('RuleStatusDropdown', () => { notifyWhen: null, index: 0, updatedAt: new Date('2020-08-20T19:23:38Z'), - snoozeEndTime: null, + snoozeSchedule: [], } as ComponentOpts['rule'], onRuleChanged: jest.fn(), }; @@ -86,7 +91,7 @@ describe('RuleStatusDropdown', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe('Snoozed'); @@ -108,7 +113,7 @@ describe('RuleStatusDropdown', () => { test('renders status control as disabled when rule is snoozed but also disabled', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe( @@ -121,7 +126,7 @@ describe('RuleStatusDropdown', () => { From 03d2d29d8991ff93ba7c874901d732148c0bb7f4 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 28 Apr 2022 11:04:41 -0500 Subject: [PATCH 13/45] Add timeZone to SO --- .../alerting/server/saved_objects/mappings.json | 3 +++ .../rule_status_dropdown_sandbox.tsx | 15 +++++++++------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.json b/x-pack/plugins/alerting/server/saved_objects/mappings.json index a5e5ab2c48bb4..37a9d7caa1905 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.json +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.json @@ -181,6 +181,9 @@ "id": { "type": "keyword" }, + "timeZone": { + "type": "keyword" + }, "startTime": { "type": "date", "format": "strict_date_time" diff --git a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx index 46b7fed8e14d4..1e3f9765a0ffa 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx @@ -6,37 +6,40 @@ */ import React, { useState } from 'react'; +import { RuleSnooze } from '@kbn/alerting-plugin/common'; import { getRuleStatusDropdownLazy } from '../../../common/get_rule_status_dropdown'; export const RuleStatusDropdownSandbox: React.FC<{}> = () => { const [enabled, setEnabled] = useState(true); - const [snoozeEndTime, setSnoozeEndTime] = useState(null); + const [snoozeSchedule, setSnoozeSchedule] = useState([]); const [muteAll, setMuteAll] = useState(false); return getRuleStatusDropdownLazy({ rule: { enabled, - snoozeEndTime, + snoozeSchedule, muteAll, }, enableRule: async () => { setEnabled(true); setMuteAll(false); - setSnoozeEndTime(null); + setSnoozeSchedule([]); }, disableRule: async () => setEnabled(false), snoozeRule: async (time) => { if (time === -1) { - setSnoozeEndTime(null); + setSnoozeSchedule([]); setMuteAll(true); } else { - setSnoozeEndTime(new Date(time)); + setSnoozeSchedule([ + { startTime: new Date().toISOString(), duration: Date.parse(time) - Date.now() }, + ]); setMuteAll(false); } }, unsnoozeRule: async () => { setMuteAll(false); - setSnoozeEndTime(null); + setSnoozeSchedule([]); }, onRuleChanged: () => {}, isEditable: true, From e3c28e83a311a89f5221a3d7f8930360a548344d Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 28 Apr 2022 12:31:51 -0500 Subject: [PATCH 14/45] Update timezone usage --- .../alerting/common/is_rule_snoozed.test.ts | 28 +++++++++++++------ .../alerting/common/is_rule_snoozed.ts | 6 ++-- .../alerting/common/rule_snooze_type.ts | 1 + .../server/saved_objects/migrations.ts | 2 ++ 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts index 1040aa3f08d4d..f5b8c2d3da0a1 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -5,17 +5,15 @@ * 2.0. */ -import moment from 'moment'; import sinon from 'sinon'; import { isRuleSnoozed } from './is_rule_snoozed'; -const LOCAL_OFFSET = moment('2020-01-01T00:00:00.000').format('Z'); -const DATE_9999 = '9999-12-31T12:34:56.789' + LOCAL_OFFSET; -const DATE_1970 = '1970-01-01T00:00:00.000' + LOCAL_OFFSET; -const DATE_1970_PLUS_6_HOURS = '1970-01-01T06:00:00.000' + LOCAL_OFFSET; -const DATE_2020 = '2020-01-01T00:00:00.000' + LOCAL_OFFSET; -const DATE_2020_MINUS_1_HOUR = '2019-12-31T23:00:00.000' + LOCAL_OFFSET; -const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000' + LOCAL_OFFSET; +const DATE_9999 = '9999-12-31T12:34:56.789Z'; +const DATE_1970 = '1970-01-01T00:00:00.000Z'; +const DATE_1970_PLUS_6_HOURS = '1970-01-01T06:00:00.000Z'; +const DATE_2020 = '2020-01-01T00:00:00.000Z'; +const DATE_2020_MINUS_1_HOUR = '2019-12-31T23:00:00.000Z'; +const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000Z'; const NOW = DATE_2020; @@ -33,6 +31,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_9999, duration: 100000000, + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); @@ -43,6 +42,7 @@ describe('isRuleSnoozed', () => { { startTime: NOW, duration: 100000000, + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(true); @@ -53,6 +53,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 100000000, + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); @@ -63,6 +64,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_9999, duration: 100000000, + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: true })).toBe(true); @@ -73,6 +75,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: '1d', }, ]; @@ -82,6 +85,7 @@ describe('isRuleSnoozed', () => { startTime: DATE_1970_PLUS_6_HOURS, duration: 60 * 1000, repeatInterval: '1d', + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); @@ -90,6 +94,7 @@ describe('isRuleSnoozed', () => { startTime: DATE_2020_MINUS_1_HOUR, duration: 60 * 1000, repeatInterval: '1h', + timeZone: 'UTC', }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(true); @@ -109,6 +114,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: '1h', occurrences: 25, }, @@ -133,6 +139,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: '1h', repeatEndTime: DATE_9999, }, @@ -142,6 +149,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: '1h', repeatEndTime: DATE_2020_MINUS_1_HOUR, }, @@ -154,6 +162,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: 'DOW:135', // Monday Wednesday Friday; Jan 1 2020 was a Wednesday }, ]; @@ -162,6 +171,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_1970, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: 'DOW:2467', // Tue, Thu, Sat, Sun }, ]; @@ -170,6 +180,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: 'DOW:135', occurrences: 12, }, @@ -179,6 +190,7 @@ describe('isRuleSnoozed', () => { { startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, + timeZone: 'UTC', repeatInterval: 'DOW:135', occurrences: 15, }, diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.ts index ed484dc4d6a7a..e8a52c68af960 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.ts @@ -5,7 +5,7 @@ * 2.0. */ -import moment from 'moment'; +import moment from 'moment-timezone'; import { parseInterval } from '@kbn/data-plugin/common'; import { SanitizedRule, RuleTypeParams } from './rule'; @@ -18,7 +18,7 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { const now = Date.now(); for (const snooze of rule.snoozeSchedule) { - const { startTime, duration, repeatInterval, occurrences, repeatEndTime } = snooze; + const { startTime, duration, repeatInterval, occurrences, repeatEndTime, timeZone } = snooze; const startTimeMS = Date.parse(startTime); const initialEndTime = startTimeMS + duration; // If now is during the first occurrence of the snooze @@ -42,7 +42,7 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { .split('') .map((d) => Number(d)) .sort(); - const today = moment(now).isoWeekday(); + const today = moment(now).tz(timeZone).isoWeekday(); if (!repeatDays.includes(today)) continue; diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index 373568d4b4c3b..0225ef9f8e578 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -8,6 +8,7 @@ export type RuleSnooze = Array<{ startTime: string; duration: number; + timeZone: string; id?: string; repeatInterval?: string; occurrences?: number; diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 27d50cd2d4dd2..c3e51cd743399 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -8,6 +8,7 @@ import { isRuleType, ruleTypeMappings } from '@kbn/securitysolution-rules'; import { isString } from 'lodash/fp'; import { omit } from 'lodash'; +import moment from 'moment-timezone'; import { LogMeta, SavedObjectMigrationMap, @@ -876,6 +877,7 @@ function convertSnoozes( { startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime as string) - Date.now(), + timeZone: moment.tz.guess(), }, ] : [], From b93d4603aa9769586534404979ce1d3ef1076404 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 28 Apr 2022 18:06:25 -0500 Subject: [PATCH 15/45] Implement RRule --- package.json | 4 +- .../alerting/common/is_rule_snoozed.test.ts | 213 ++++++++++++++---- .../alerting/common/is_rule_snoozed.ts | 52 +---- .../alerting/common/rule_snooze_type.ts | 4 +- .../alerting/server/saved_objects/mappings.ts | 9 +- yarn.lock | 45 ++-- 6 files changed, 208 insertions(+), 119 deletions(-) diff --git a/package.json b/package.json index 620e22de5ec1f..c0d8da4082046 100644 --- a/package.json +++ b/package.json @@ -221,7 +221,7 @@ "base64-js": "^1.3.1", "bitmap-sdf": "^1.0.3", "brace": "0.11.1", - "broadcast-channel": "^4.11.0", + "broadcast-channel": "^4.10.0", "canvg": "^3.0.9", "chalk": "^4.1.0", "cheerio": "^1.0.0-rc.10", @@ -306,6 +306,7 @@ "loader-utils": "^1.2.3", "lodash": "^4.17.21", "lru-cache": "^4.1.5", + "luxon": "^2.3.2", "lz-string": "^1.4.4", "mapbox-gl-draw-rectangle-mode": "1.0.4", "maplibre-gl": "2.1.9", @@ -401,6 +402,7 @@ "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.1", "rison-node": "1.0.2", + "rrule": "2.6.4", "rxjs": "^7.5.5", "safe-squel": "^5.12.5", "seedrandom": "^3.0.5", diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts index f5b8c2d3da0a1..db72df0c6876d 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -6,11 +6,14 @@ */ import sinon from 'sinon'; +import { DateTime } from 'luxon'; +import { RRule } from 'rrule'; import { isRuleSnoozed } from './is_rule_snoozed'; const DATE_9999 = '9999-12-31T12:34:56.789Z'; const DATE_1970 = '1970-01-01T00:00:00.000Z'; -const DATE_1970_PLUS_6_HOURS = '1970-01-01T06:00:00.000Z'; +const DATE_2019 = '2019-01-01T00:00:00.000Z'; +const DATE_2019_PLUS_6_HOURS = '2019-01-01T06:00:00.000Z'; const DATE_2020 = '2020-01-01T00:00:00.000Z'; const DATE_2020_MINUS_1_HOUR = '2019-12-31T23:00:00.000Z'; const DATE_2020_MINUS_1_MONTH = '2019-12-01T00:00:00.000Z'; @@ -51,7 +54,7 @@ describe('isRuleSnoozed', () => { test('returns false when snooze has ended', () => { const snoozeSchedule = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 100000000, timeZone: 'UTC', }, @@ -73,18 +76,28 @@ describe('isRuleSnoozed', () => { test('returns as expected for an indefinitely recurring snooze', () => { const snoozeScheduleA = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: '1d', + rRule: new RRule({ + freq: RRule.DAILY, + interval: 1, + tzid: 'UTC', + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_1970_PLUS_6_HOURS, + startTime: DATE_2019_PLUS_6_HOURS, duration: 60 * 1000, - repeatInterval: '1d', + rRule: new RRule({ + freq: RRule.DAILY, + interval: 1, + tzid: 'UTC', + dtstart: new Date(DATE_2019_PLUS_6_HOURS), + }).toString(), timeZone: 'UTC', }, ]; @@ -93,7 +106,12 @@ describe('isRuleSnoozed', () => { { startTime: DATE_2020_MINUS_1_HOUR, duration: 60 * 1000, - repeatInterval: '1h', + rRule: new RRule({ + freq: RRule.HOURLY, + interval: 1, + tzid: 'UTC', + dtstart: new Date(DATE_2020_MINUS_1_HOUR), + }).toString(), timeZone: 'UTC', }, ]; @@ -103,55 +121,79 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze with limited occurrences', () => { const snoozeScheduleA = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, - repeatInterval: '1h', - occurrences: 600000, + timeZone: 'UTC', + rRule: new RRule({ + freq: RRule.HOURLY, + interval: 1, + tzid: 'UTC', + count: 8761, + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: '1h', - occurrences: 25, + rRule: new RRule({ + freq: RRule.HOURLY, + interval: 1, + tzid: 'UTC', + count: 25, + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); - - // FIXME: THIS FAILS due to not compensating for leap years. Also 1M intervals exhibit confusing behavior - // Either we should add something to compensate for this, or disable the use of M and y, and only allow for explicit day lengths - // const snoozeScheduleC = [ - // { - // startTime: DATE_1970, - // duration: 60 * 1000, - // repeatInterval: '1y', - // occurrences: 60, - // }, - // ]; - // expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC })).toBe(true); + const snoozeScheduleC = [ + { + startTime: DATE_1970, + duration: 60 * 1000, + timeZone: 'UTC', + rRule: new RRule({ + freq: RRule.YEARLY, + interval: 1, + tzid: 'UTC', + count: 60, + dtstart: new Date(DATE_1970), + }).toString(), + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(true); }); test('returns as expected for a recurring snooze with an end date', () => { const snoozeScheduleA = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: '1h', - repeatEndTime: DATE_9999, + rRule: new RRule({ + freq: RRule.HOURLY, + interval: 1, + tzid: 'UTC', + until: new Date(DATE_9999), + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: '1h', - repeatEndTime: DATE_2020_MINUS_1_HOUR, + rRule: new RRule({ + freq: RRule.HOURLY, + interval: 1, + tzid: 'UTC', + until: new Date(DATE_2020_MINUS_1_HOUR), + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); @@ -160,19 +202,31 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze on a day of the week', () => { const snoozeScheduleA = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: 'DOW:135', // Monday Wednesday Friday; Jan 1 2020 was a Wednesday + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.MO, RRule.WE, RRule.FR], + dtstart: new Date(DATE_2019), + }).toString(), // Monday Wednesday Friday; Jan 1 2020 was a Wednesday }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_1970, + startTime: DATE_2019, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: 'DOW:2467', // Tue, Thu, Sat, Sun + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.TU, RRule.TH, RRule.SA, RRule.SU], + dtstart: new Date(DATE_2019), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); @@ -181,8 +235,14 @@ describe('isRuleSnoozed', () => { startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: 'DOW:135', - occurrences: 12, + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.MO, RRule.WE, RRule.FR], + count: 12, + dtstart: new Date(DATE_2020_MINUS_1_MONTH), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(false); @@ -191,10 +251,85 @@ describe('isRuleSnoozed', () => { startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, timeZone: 'UTC', - repeatInterval: 'DOW:135', - occurrences: 15, + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.MO, RRule.WE, RRule.FR], + count: 15, + dtstart: new Date(DATE_2020_MINUS_1_MONTH), + }).toString(), }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD, muteAll: false })).toBe(true); }); + + test('returns as expected for a recurring snooze on an nth day of the week of a month', () => { + const snoozeScheduleA = [ + { + startTime: DATE_2019, + duration: 60 * 1000, + timeZone: 'UTC', + rRule: new RRule({ + freq: RRule.MONTHLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.WE.nth(1)], // Jan 1 2020 was the first Wednesday of the month + dtstart: new Date(DATE_2019), + }).toString(), + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); + const snoozeScheduleB = [ + { + startTime: DATE_2019, + duration: 60 * 1000, + timeZone: 'UTC', + rRule: new RRule({ + freq: RRule.MONTHLY, + interval: 1, + tzid: 'UTC', + byweekday: [RRule.WE.nth(2)], + dtstart: new Date(DATE_2019), + }).toString(), + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); + }); + + test('using a timezone, returns as expected for a recurring snooze on a day of the week', () => { + const snoozeScheduleA = [ + { + startTime: DATE_2019, + duration: 60 * 1000, + timeZone: 'Asia/Taipei', + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + byweekday: [RRule.WE], + tzid: 'Asia/Taipei', + dtstart: new Date(DATE_2019), + }).toString(), + }, + ]; + + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(false); + const snoozeScheduleB = [ + { + startTime: DATE_2019, + duration: 60 * 1000, + timeZone: 'UTC', + rRule: new RRule({ + freq: RRule.WEEKLY, + interval: 1, + byweekday: [RRule.WE], + byhour: [0], + byminute: [0], + tzid: 'UTC', + dtstart: new Date(DATE_2019), + }).toString(), + }, + ]; + expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(true); + }); }); diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.ts index e8a52c68af960..30a95d588a406 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.ts @@ -5,8 +5,7 @@ * 2.0. */ -import moment from 'moment-timezone'; -import { parseInterval } from '@kbn/data-plugin/common'; +import { rrulestr } from 'rrule'; import { SanitizedRule, RuleTypeParams } from './rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; @@ -18,7 +17,7 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { const now = Date.now(); for (const snooze of rule.snoozeSchedule) { - const { startTime, duration, repeatInterval, occurrences, repeatEndTime, timeZone } = snooze; + const { startTime, duration, rRule } = snooze; const startTimeMS = Date.parse(startTime); const initialEndTime = startTimeMS + duration; // If now is during the first occurrence of the snooze @@ -26,47 +25,12 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { if (now >= startTimeMS && now < initialEndTime) return new Date(initialEndTime); // Check to see if now is during a recurrence of the snooze - if (repeatInterval) { - let occurrence; - let occurrenceStartTime; - if (repeatEndTime) { - const repeatEndTimeMS = Date.parse(repeatEndTime); - if (now >= repeatEndTimeMS) continue; - } - const timeFromInitialStart = now - startTimeMS; - - // Handle day-of-week recurrences - if (repeatInterval.startsWith('DOW:')) { - const [, daysOfWeekString] = repeatInterval.split(':'); - const repeatDays = daysOfWeekString - .split('') - .map((d) => Number(d)) - .sort(); - const today = moment(now).tz(timeZone).isoWeekday(); - - if (!repeatDays.includes(today)) continue; - - const weeksFromInitialStart = moment(now).diff(moment(startTime), 'weeks'); - const occurrencesPerWeek = repeatDays.length; - occurrence = weeksFromInitialStart * occurrencesPerWeek + repeatDays.indexOf(today); - const nowMoment = moment(now); - occurrenceStartTime = moment(startTime) - .year(nowMoment.year()) - .dayOfYear(nowMoment.dayOfYear()) - .valueOf(); - } else { - const interval = parseInterval(repeatInterval)?.asMilliseconds(); - if (!interval) continue; - - occurrence = Math.floor(timeFromInitialStart / interval); - occurrenceStartTime = interval * occurrence + startTimeMS; - } - - if (occurrences && occurrence > occurrences) continue; - - const occurrenceEndTime = occurrenceStartTime + duration; - - if (now >= occurrenceStartTime && now < occurrenceEndTime) return new Date(occurrenceEndTime); + if (rRule) { + const recurrenceRule = rrulestr(rRule); + const lastOccurrence = recurrenceRule.before(new Date(now), true); + if (!lastOccurrence) continue; + const lastOccurrenceEndTime = lastOccurrence.getTime() + duration; + if (lastOccurrenceEndTime > now) return new Date(lastOccurrenceEndTime); } } diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index 0225ef9f8e578..e64b9e5b663cd 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -10,7 +10,5 @@ export type RuleSnooze = Array<{ duration: number; timeZone: string; id?: string; - repeatInterval?: string; - occurrences?: number; - repeatEndTime?: string; + rRule?: string; }>; diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index f8ca97a61532c..8d788cee6a17f 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -200,16 +200,9 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { duration: { type: 'long', }, - repeatInterval: { + rRule: { type: 'keyword', }, - occurrences: { - type: 'long', - }, - repeatEndTime: { - type: 'date', - format: 'strict_date_time', - }, }, }, }, diff --git a/yarn.lock b/yarn.lock index 10ec99fd95892..d1a750ea130dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9294,14 +9294,15 @@ broadcast-channel@^3.4.1: rimraf "3.0.2" unload "2.2.0" -broadcast-channel@^4.11.0: - version "4.11.0" - resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.11.0.tgz#b9ebc7ce1326120088e61d2197477496908a1a9e" - integrity sha512-4FS1Zk+ttekfXHq5I2R7KhN9AsnZUFVV5SczrTtnZPuf5w+jw+fqM1PJHuHzwEXJezJeCbJxoZMDcFqsIN2c1Q== +broadcast-channel@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.10.0.tgz#d19fb902df227df40b1b580351713d30c302d198" + integrity sha512-hOUh312XyHk6JTVyX9cyXaH1UYs+2gHVtnW16oQAu9FL7ALcXGXc/YoJWqlkV8vUn14URQPMmRi4A9q4UrwVEQ== dependencies: "@babel/runtime" "^7.16.0" detect-node "^2.1.0" - microtime "3.0.0" + microseconds "0.2.0" + nano-time "1.0.0" oblivious-set "1.0.0" p-queue "6.6.2" rimraf "3.0.2" @@ -19602,11 +19603,16 @@ lru-queue@0.1: dependencies: es5-ext "~0.10.2" -luxon@^1.25.0: +luxon@^1.21.3, luxon@^1.25.0: version "1.28.0" resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== +luxon@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-2.3.2.tgz#5f2f3002b8c39b60a7b7ad24b2a85d90dc5db49c" + integrity sha512-MlAQQVMFhGk4WUA6gpfsy0QycnKP0+NlCBJRVRNPxxSIbjrCbQ65nrpJD3FVyJNZLuJ0uoqL57ye6BmDYgHaSw== + lz-string@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" @@ -20129,14 +20135,6 @@ microseconds@0.2.0: resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== -microtime@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/microtime/-/microtime-3.0.0.tgz#d140914bde88aa89b4f9fd2a18620b435af0f39b" - integrity sha512-SirJr7ZL4ow2iWcb54bekS4aWyBQNVcEDBiwAz9D/sTgY59A+uE8UJU15cp5wyZmPBwg/3zf8lyCJ5NUe1nVlQ== - dependencies: - node-addon-api "^1.2.0" - node-gyp-build "^3.8.0" - miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -20867,11 +20865,6 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" -node-addon-api@^1.2.0: - version "1.7.2" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" - integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== - node-addon-api@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" @@ -20913,11 +20906,6 @@ node-forge@^1.2.1, node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== -node-gyp-build@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" - integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== - node-gyp-build@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" @@ -25450,6 +25438,15 @@ rollup@^0.25.8: minimist "^1.2.0" source-map-support "^0.3.2" +rrule@2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.6.4.tgz#7f4f31fda12bc7249bb176c891109a9bc448e035" + integrity sha512-sLdnh4lmjUqq8liFiOUXD5kWp/FcnbDLPwq5YAc/RrN6120XOPb86Ae5zxF7ttBVq8O3LxjjORMEit1baluahA== + dependencies: + tslib "^1.10.0" + optionalDependencies: + luxon "^1.21.3" + rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" From 370fd4534281b46d27c51c3a875fc2b7a8ee8b48 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 11:51:30 -0500 Subject: [PATCH 16/45] Fix task runner test --- .../alerting/common/rule_snooze_type.ts | 2 +- .../server/task_runner/task_runner.test.ts | 249 +++++++++++++++--- .../components/rule_status_dropdown.tsx | 6 +- 3 files changed, 222 insertions(+), 35 deletions(-) diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index e64b9e5b663cd..7ce32be45ce2c 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -8,7 +8,7 @@ export type RuleSnooze = Array<{ startTime: string; duration: number; - timeZone: string; + timeZone?: string; id?: string; rRule?: string; }>; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index d56fe52a5d32e..1fde37cb28daa 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -236,11 +236,15 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenCalledTimes(4); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + ); + expect(logger.debug).nthCalledWith( + 3, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -312,7 +316,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledWith(generateEnqueueFunctionInput()); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(4); + expect(logger.debug).toHaveBeenCalledTimes(5); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -320,7 +324,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 3, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 4, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -377,6 +385,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -416,7 +426,7 @@ describe('Task Runner', () => { expect(actionsClient.ephemeralEnqueuedExecution).toHaveBeenCalledTimes(0); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -428,7 +438,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":1,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -472,6 +486,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -481,7 +497,7 @@ describe('Task Runner', () => { type SnoozeTestParams = [ muteAll: boolean, - snoozeSchedule: string | undefined | null, + snoozeEndTime: string | undefined | null, shouldBeSnoozed: boolean ]; @@ -582,7 +598,7 @@ describe('Task Runner', () => { expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -594,7 +610,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":1,"numberOfGeneratedActions":1,"numberOfActiveAlerts":2,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":2,"triggeredActionsStatus":"complete"}' ); expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); } @@ -699,7 +719,7 @@ describe('Task Runner', () => { await taskRunner.run(); expect(enqueueFunction).toHaveBeenCalledTimes(1); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith( 3, `skipping scheduling of actions for '2' in rule test:1: '${RULE_NAME}': rule is muted` @@ -784,6 +804,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -849,6 +870,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -923,6 +945,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, task: true, consumer: 'bar', }) @@ -1041,6 +1064,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 1, numberOfGeneratedActions: 1, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, task: true, consumer: 'bar', }) @@ -1110,7 +1135,7 @@ describe('Task Runner', () => { ); const logger = customTaskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).toHaveBeenCalledTimes(6); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, @@ -1122,7 +1147,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}' + ); + expect(logger.debug).nthCalledWith( + 5, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1190,6 +1219,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 2, numberOfGeneratedActions: 2, + numberOfActiveAlerts: 1, + numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1263,7 +1294,11 @@ describe('Task Runner', () => { ); expect(logger.debug).nthCalledWith( 4, - `ruleExecutionStatus for test:${alertId}: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` + `ruleRunStatus for test:${alertId}: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"active"}` + ); + expect(logger.debug).nthCalledWith( + 5, + `ruleRunMetrics for test:${alertId}: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":2,"numberOfGeneratedActions":2,"numberOfActiveAlerts":1,"numberOfRecoveredAlerts":1,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}` ); const eventLogger = customTaskRunnerFactoryInitializerParams.eventLogger; @@ -1452,6 +1487,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 2, + numberOfActiveAlerts: 1, + numberOfRecoveredAlerts: 1, task: true, consumer: 'bar', }) @@ -1459,7 +1496,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('validates params before executing the alert type', async () => { + test('validates params before running the rule type', async () => { const taskRunner = new TaskRunner( { ...ruleType, @@ -1547,7 +1584,7 @@ describe('Task Runner', () => { expect(mockUsageCounter.incrementCounter).not.toHaveBeenCalled(); }); - test('rescheduled the Alert if the schedule has update during a task run', async () => { + test('rescheduled the rule if the schedule has update during a task run', async () => { const taskRunner = new TaskRunner( ruleType, mockedTaskInstance, @@ -2058,6 +2095,8 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, + numberOfNewAlerts: 2, task: true, consumer: 'bar', }) @@ -2161,6 +2200,7 @@ describe('Task Runner', () => { status: 'active', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, task: true, consumer: 'bar', }) @@ -2253,6 +2293,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfActiveAlerts: 2, task: true, }) ); @@ -2343,6 +2384,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2429,6 +2471,7 @@ describe('Task Runner', () => { consumer: 'bar', numberOfTriggeredActions: 0, numberOfGeneratedActions: 0, + numberOfRecoveredAlerts: 2, task: true, }) ); @@ -2486,11 +2529,15 @@ describe('Task Runner', () => { expect(call.services).toBeTruthy(); const logger = taskRunnerFactoryInitializerParams.logger; - expect(logger.debug).toHaveBeenCalledTimes(3); + expect(logger.debug).toHaveBeenCalledTimes(4); expect(logger.debug).nthCalledWith(1, 'executing rule test:1 at 1970-01-01T00:00:00.000Z'); expect(logger.debug).nthCalledWith( 2, - 'ruleExecutionStatus for test:1: {"metrics":{"numSearches":3,"esSearchDurationMs":33,"totalSearchDurationMs":23423},"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + 'ruleRunStatus for test:1: {"lastExecutionDate":"1970-01-01T00:00:00.000Z","status":"ok"}' + ); + expect(logger.debug).nthCalledWith( + 3, + 'ruleRunMetrics for test:1: {"numSearches":3,"totalSearchDurationMs":23423,"esSearchDurationMs":33,"numberOfTriggeredActions":0,"numberOfGeneratedActions":0,"numberOfActiveAlerts":0,"numberOfRecoveredAlerts":0,"numberOfNewAlerts":0,"triggeredActionsStatus":"complete"}' ); const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; @@ -2647,13 +2694,11 @@ describe('Task Runner', () => { const runnerResult = await taskRunner.run(); expect(runnerResult.monitoring?.execution.history.length).toBe(200); }); + test('Actions circuit breaker kicked in, should set status as warning and log a message in event log', async () => { - const ruleTypeWithConfig = { - ...ruleType, - config: { - run: { - actions: { max: 3 }, - }, + const actionsConfigMap = { + default: { + max: 3, }, }; @@ -2711,21 +2756,22 @@ describe('Task Runner', () => { ...mockedRuleTypeSavedObject, actions: mockActions, } as jest.ResolvedValue); - ruleTypeRegistry.get.mockReturnValue(ruleTypeWithConfig); + ruleTypeRegistry.get.mockReturnValue(ruleType); encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); const taskRunner = new TaskRunner( - ruleTypeWithConfig, + ruleType, mockedTaskInstance, - taskRunnerFactoryInitializerParams, + { + ...taskRunnerFactoryInitializerParams, + actionsConfigMap, + }, inMemoryMetrics ); const runnerResult = await taskRunner.run(); - expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes( - ruleTypeWithConfig.config.run.actions.max - ); + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(actionsConfigMap.default.max); expect( taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update @@ -2751,6 +2797,15 @@ describe('Task Runner', () => { }, }) ); + + const logger = taskRunnerFactoryInitializerParams.logger; + expect(logger.debug).toHaveBeenCalledTimes(6); + + expect(logger.debug).nthCalledWith( + 3, + 'Rule "1" skipped scheduling action "4" because the maximum number of allowed actions has been reached.' + ); + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; expect(eventLogger.logEvent).toHaveBeenCalledTimes(7); @@ -2824,8 +2879,10 @@ describe('Task Runner', () => { action: EVENT_LOG_ACTIONS.execute, outcome: 'success', status: 'warning', - numberOfTriggeredActions: ruleTypeWithConfig.config.run.actions.max, + numberOfTriggeredActions: actionsConfigMap.default.max, numberOfGeneratedActions: mockActions.length, + numberOfActiveAlerts: 1, + numberOfNewAlerts: 1, reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, task: true, consumer: 'bar', @@ -2833,6 +2890,138 @@ describe('Task Runner', () => { ); }); + test('Actions circuit breaker kicked in with connectorType specific config and multiple alerts', async () => { + const actionsConfigMap = { + default: { + max: 30, + }, + '.server-log': { + max: 1, + }, + }; + + const warning = { + reason: RuleExecutionStatusWarningReasons.MAX_EXECUTABLE_ACTIONS, + message: translations.taskRunner.warning.maxExecutableActions, + }; + + taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); + + ruleType.executor.mockImplementation( + async ({ + services: executorServices, + }: RuleExecutorOptions< + RuleTypeParams, + RuleTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertFactory.create('1').scheduleActions('default'); + executorServices.alertFactory.create('2').scheduleActions('default'); + } + ); + + rulesClient.get.mockResolvedValue({ + ...mockedRuleTypeSavedObject, + actions: [ + { + group: 'default', + id: '1', + actionTypeId: '.server-log', + }, + { + group: 'default', + id: '2', + actionTypeId: '.server-log', + }, + { + group: 'default', + id: '3', + actionTypeId: '.server-log', + }, + { + group: 'default', + id: '4', + actionTypeId: 'any-action', + }, + { + group: 'default', + id: '5', + actionTypeId: 'any-action', + }, + ], + } as jest.ResolvedValue); + + ruleTypeRegistry.get.mockReturnValue(ruleType); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(SAVED_OBJECT); + + const taskRunner = new TaskRunner( + ruleType, + mockedTaskInstance, + { + ...taskRunnerFactoryInitializerParams, + actionsConfigMap, + }, + inMemoryMetrics + ); + + const runnerResult = await taskRunner.run(); + + // 1x(.server-log) and 2x(any-action) per alert + expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(5); + + expect( + taskRunnerFactoryInitializerParams.internalSavedObjectsRepository.update + ).toHaveBeenCalledWith(...generateSavedObjectParams({ status: 'warning', warning })); + + expect(runnerResult).toEqual( + generateRunnerResult({ + state: true, + history: [true], + alertInstances: { + '1': { + meta: { + lastScheduledActions: { + date: new Date(DATE_1970), + group: 'default', + }, + }, + state: { + duration: 0, + start: '1970-01-01T00:00:00.000Z', + }, + }, + '2': { + meta: { + lastScheduledActions: { + date: new Date(DATE_1970), + group: 'default', + }, + }, + state: { + duration: 0, + start: '1970-01-01T00:00:00.000Z', + }, + }, + }, + }) + ); + + const logger = taskRunnerFactoryInitializerParams.logger; + expect(logger.debug).toHaveBeenCalledTimes(6); + + expect(logger.debug).nthCalledWith( + 3, + 'Rule "1" skipped scheduling action "1" because the maximum number of allowed actions for connector type .server-log has been reached.' + ); + + const eventLogger = taskRunnerFactoryInitializerParams.eventLogger; + expect(eventLogger.logEvent).toHaveBeenCalledTimes(11); + }); + test('increments monitoring metrics after execution', async () => { const taskRunner = new TaskRunner( ruleType, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 6319928e932e1..a2bdcd7e41cff 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -159,15 +159,13 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ isEnabled && isSnoozed ? ( - {rule.snoozeSchedule - ? INDEFINITELY - : moment(getRuleSnoozeEndTime(rule) as Date).fromNow(true)} + {rule.muteAll ? INDEFINITELY : moment(getRuleSnoozeEndTime(rule) as Date).fromNow(true)} ) : null; From 6dda9a2b5921f410f634e20e00aedd82613ad3c0 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 11:56:45 -0500 Subject: [PATCH 17/45] Add rrule types --- package.json | 1 + .../alerting/common/is_rule_snoozed.test.ts | 1 - yarn.lock | 16 ++++++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index d52813bd49fe8..eec5a139fc9d4 100644 --- a/package.json +++ b/package.json @@ -213,6 +213,7 @@ "@types/mapbox__vector-tile": "1.3.0", "@types/moment-duration-format": "^2.2.3", "@types/react-is": "^16.7.1", + "@types/rrule": "^2.2.9", "JSONStream": "1.3.5", "abort-controller": "^3.0.0", "antlr4ts": "^0.5.0-alpha.3", diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts index db72df0c6876d..a8a3013dfb118 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts @@ -6,7 +6,6 @@ */ import sinon from 'sinon'; -import { DateTime } from 'luxon'; import { RRule } from 'rrule'; import { isRuleSnoozed } from './is_rule_snoozed'; diff --git a/yarn.lock b/yarn.lock index 6de89e6536199..d893f8a71e992 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6950,6 +6950,13 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== +"@types/rrule@^2.2.9": + version "2.2.9" + resolved "https://registry.yarnpkg.com/@types/rrule/-/rrule-2.2.9.tgz#b25222b5057b9a9e6eea28ce9e94673a957c960f" + integrity sha512-OWTezBoGwsL2nn9SFbLbiTrAic1hpxAIRqeF8QDB84iW6KBEAHM6Oj9T2BEokgeIDgT1q73sfD0gI1S2yElSFA== + dependencies: + rrule "*" + "@types/seedrandom@>=2.0.0 <4.0.0": version "2.4.28" resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f" @@ -25438,6 +25445,15 @@ rollup@^0.25.8: minimist "^1.2.0" source-map-support "^0.3.2" +rrule@*: + version "2.6.9" + resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.6.9.tgz#8ee4ee261451e84852741f92ded769245580744a" + integrity sha512-PE4ErZDMfAcRnc1B35bZgPGS9mbn7Z9bKDgk6+XgrIwvBjeWk7JVEYsqKwHYTrDGzsHPtZTpaon8IyeKzAhj5w== + dependencies: + tslib "^1.10.0" + optionalDependencies: + luxon "^1.21.3" + rrule@2.6.4: version "2.6.4" resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.6.4.tgz#7f4f31fda12bc7249bb176c891109a9bc448e035" From 128950f74c073d280ecb84dd3947c66109085f3f Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 12:37:49 -0500 Subject: [PATCH 18/45] Push snoozeEndTime from server and fix unsnooze --- x-pack/plugins/alerting/common/rule.ts | 4 +++- .../alerting/server/routes/find_rules.ts | 4 ++++ .../plugins/alerting/server/routes/get_rule.ts | 4 ++++ .../server/rules_client/rules_client.ts | 2 +- .../alerting/server/saved_objects/index.ts | 4 ++-- .../lib/rule_api/common_transformations.ts | 2 ++ .../components/rule_status_dropdown.test.tsx | 13 ++++--------- .../components/rule_status_dropdown.tsx | 17 ++++++++--------- 8 files changed, 28 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 6ed8f89bacc8e..57c1c6140ce12 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -112,7 +112,9 @@ export interface Rule { snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API } -export type SanitizedRule = Omit, 'apiKey'>; +export type SanitizedRule = Omit, 'apiKey'> & { + isSnoozedUntil?: string | null; +}; export type ResolvedSanitizedRule = SanitizedRule & Omit; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index 69c6b8206a528..fc47ddf2c3bfc 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -17,6 +17,7 @@ import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, + getRuleSnoozeEndTime, } from '../types'; import { trackLegacyTerminology } from './lib/track_legacy_terminology'; @@ -111,6 +112,9 @@ const rewriteBodyRes: RewriteResponseCase> = ({ params, connector_type_id: actionTypeId, })), + ...(snoozeSchedule != null + ? { is_snoozed_until: getRuleSnoozeEndTime({ snoozeSchedule, muteAll })?.toISOString() } + : {}), }) ), }; diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 12ecd61fd0a20..1ac2cd75cf0d4 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -16,6 +16,7 @@ import { BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, SanitizedRule, + getRuleSnoozeEndTime, } from '../types'; const paramSchema = schema.object({ @@ -62,6 +63,9 @@ const rewriteBodyRes: RewriteResponseCase> = ({ params, connector_type_id: actionTypeId, })), + ...(snoozeSchedule != null + ? { is_snoozed_until: getRuleSnoozeEndTime({ snoozeSchedule, muteAll })?.toISOString() } + : {}), }); interface BuildGetRulesRouteParams { diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index cde69191c48c7..6bbf1016335e0 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -2391,6 +2391,6 @@ function parseDate(dateString: string | undefined, propertyName: string, default function clearUnscheduledSnooze(attributes: { snoozeSchedule?: RuleSnooze }) { return attributes.snoozeSchedule - ? attributes.snoozeSchedule.filter((s) => typeof s.id === 'undefined') + ? attributes.snoozeSchedule.filter((s) => typeof s.id !== 'undefined') : []; } diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index 85e4dc5a8e05b..b87c10f5a8f7a 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -29,7 +29,7 @@ export const AlertAttributesExcludedFromAAD = [ 'updatedAt', 'executionStatus', 'monitoring', - 'snoozeEndTime', + 'snoozeSchedule', ]; // useful for Pick which is a @@ -44,7 +44,7 @@ export type AlertAttributesExcludedFromAADType = | 'updatedAt' | 'executionStatus' | 'monitoring' - | 'snoozeEndTime'; + | 'snoozeSchedule'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts index 19517f98af6be..5648aa30820c2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/common_transformations.ts @@ -44,6 +44,7 @@ export const transformRule: RewriteRequestCase = ({ execution_status: executionStatus, actions: actions, snooze_schedule: snoozeSchedule, + is_snoozed_until: isSnoozedUntil, ...rest }: any) => ({ ruleTypeId, @@ -61,6 +62,7 @@ export const transformRule: RewriteRequestCase = ({ ? actions.map((action: AsApiContract) => transformAction(action)) : [], scheduledTaskId, + isSnoozedUntil, ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx index b900d110e0ba9..99037fb10258b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx @@ -10,12 +10,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RuleStatusDropdown, ComponentOpts } from './rule_status_dropdown'; const NOW_STRING = '2020-03-01T00:00:00.000Z'; -const SNOOZED_SCHEDULE = [ - { - startTime: NOW_STRING, - duration: Date.parse('2020-03-04T00:00:00.000Z') - Date.parse(NOW_STRING), - }, -]; +const SNOOZE_UNTIL = '2020-03-0400:00:00.000Z'; describe('RuleStatusDropdown', () => { const enableRule = jest.fn(); @@ -91,7 +86,7 @@ describe('RuleStatusDropdown', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe('Snoozed'); @@ -113,7 +108,7 @@ describe('RuleStatusDropdown', () => { test('renders status control as disabled when rule is snoozed but also disabled', () => { const wrapper = mountWithIntl( ); expect(wrapper.find('[data-test-subj="statusDropdown"]').first().props().title).toBe( @@ -126,7 +121,7 @@ describe('RuleStatusDropdown', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index a2bdcd7e41cff..22c9b7e95292f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -8,7 +8,6 @@ import React, { useState, useEffect, useCallback } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { isRuleSnoozed, getRuleSnoozeEndTime } from '@kbn/alerting-plugin/common'; import { useGeneratedHtmlId, EuiLoadingSpinner, @@ -37,7 +36,7 @@ import { Rule } from '../../../../types'; type SnoozeUnit = 'm' | 'h' | 'd' | 'w' | 'M'; const SNOOZE_END_TIME_FORMAT = 'LL @ LT'; -type DropdownRuleRecord = Pick; +type DropdownRuleRecord = Pick; export interface ComponentOpts { rule: DropdownRuleRecord; @@ -87,7 +86,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ direction = 'column', }: ComponentOpts) => { const [isEnabled, setIsEnabled] = useState(rule.enabled); - const [isSnoozed, setIsSnoozed] = useState(isRuleSnoozed(rule)); + const [isSnoozed, setIsSnoozed] = useState(Boolean(rule.isSnoozedUntil || rule.muteAll)); const [previousSnoozeInterval, setPreviousSnoozeInterval] = usePreviousSnoozeInterval( propsPreviousSnoozeInterval ); @@ -96,7 +95,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ setIsEnabled(rule.enabled); }, [rule.enabled]); useEffect(() => { - setIsSnoozed(isRuleSnoozed(rule)); + setIsSnoozed(Boolean(rule.isSnoozedUntil || rule.muteAll)); }, [rule]); const [isUpdating, setIsUpdating] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -161,11 +160,11 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ content={ rule.muteAll ? INDEFINITELY - : moment(getRuleSnoozeEndTime(rule) as Date).format(SNOOZE_END_TIME_FORMAT) + : moment(new Date(rule.isSnoozedUntil!)).format(SNOOZE_END_TIME_FORMAT) } > - {rule.muteAll ? INDEFINITELY : moment(getRuleSnoozeEndTime(rule) as Date).fromNow(true)} + {rule.muteAll ? INDEFINITELY : moment(new Date(rule.isSnoozedUntil!)).fromNow(true)} ) : null; @@ -218,7 +217,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ onChangeSnooze={onChangeSnooze} isEnabled={isEnabled} isSnoozed={isSnoozed} - snoozeEndTime={getRuleSnoozeEndTime(rule)} + snoozeEndTime={rule.isSnoozedUntil} previousSnoozeInterval={previousSnoozeInterval} /> @@ -239,7 +238,7 @@ interface RuleStatusMenuProps { onClosePopover: () => void; isEnabled: boolean; isSnoozed: boolean; - snoozeEndTime?: Date | null; + snoozeEndTime?: string | null; previousSnoozeInterval: string | null; } @@ -477,7 +476,7 @@ const SnoozePanel: React.FunctionComponent = ({ ); }; -const futureTimeToInterval = (time?: Date | null) => { +const futureTimeToInterval = (time?: string | null) => { if (!time) return; const relativeTime = moment(time).locale('en').fromNow(true); const [valueStr, unitStr] = relativeTime.split(' '); From 8f3546f921dca27826da23cfbe9ab9834dccee23 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 12:53:09 -0500 Subject: [PATCH 19/45] Fix Jest Tests 5 --- .../alerting/server/rules_client/tests/aggregate.test.ts | 2 +- .../plugins/alerting/server/rules_client/tests/mute_all.test.ts | 2 +- .../alerting/server/rules_client/tests/unmute_all.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index b74059e4be3d6..a33a3488019a6 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -182,7 +182,7 @@ describe('aggregate()', () => { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeEndTime', + field: 'alert.attributes.snoozeSchedule.startTime', format: 'strict_date_time', ranges: [{ from: 'now' }], }, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts index 7f8ae28a20c6e..e2625be88482c 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/mute_all.test.ts @@ -82,7 +82,7 @@ describe('muteAll()', () => { { muteAll: true, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeSchedule: [], updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts index cf063eea07862..f5d4cb372f867 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/unmute_all.test.ts @@ -82,7 +82,7 @@ describe('unmuteAll()', () => { { muteAll: false, mutedInstanceIds: [], - snoozeEndTime: null, + snoozeSchedule: [], updatedAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', }, From 8b69fad05712b8856569d32f1a3418bf65d4bb2a Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 15:51:45 -0500 Subject: [PATCH 20/45] Fix rulestatusdropdown test --- .../rules_list/components/rule_status_dropdown.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx index 99037fb10258b..6080b743a5e35 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx @@ -10,7 +10,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RuleStatusDropdown, ComponentOpts } from './rule_status_dropdown'; const NOW_STRING = '2020-03-01T00:00:00.000Z'; -const SNOOZE_UNTIL = '2020-03-0400:00:00.000Z'; +const SNOOZE_UNTIL = '2020-03-04T00:00:00.000Z'; describe('RuleStatusDropdown', () => { const enableRule = jest.fn(); @@ -121,7 +121,7 @@ describe('RuleStatusDropdown', () => { From 01ae2b2b6a1aad2da1bcb5402a6807e2b5436301 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 15:53:13 -0500 Subject: [PATCH 21/45] Fix jest tests 1 --- .../alerting/server/rules_client/tests/aggregate.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index a33a3488019a6..5c5e4a9bad5aa 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -216,7 +216,7 @@ describe('aggregate()', () => { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeEndTime', + field: 'alert.attributes.snoozeSchedule.startTime', format: 'strict_date_time', ranges: [{ from: 'now' }], }, From 3af6c23e32d5b5d363b510c2d1829386836a76af Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Fri, 29 Apr 2022 15:58:50 -0500 Subject: [PATCH 22/45] Fix snooze_end_time refs in functional tests --- .../rule_status_dropdown_sandbox.tsx | 15 ++++++--------- .../security_and_spaces/tests/alerting/find.ts | 4 ++-- .../security_and_spaces/tests/alerting/get.ts | 2 +- .../tests/alerting/mute_all.ts | 8 ++++---- .../security_and_spaces/tests/alerting/snooze.ts | 10 +++++----- .../tests/alerting/unmute_all.ts | 8 ++++---- .../tests/alerting/unsnooze.ts | 8 ++++---- .../spaces_only/tests/alerting/find.ts | 2 +- .../spaces_only/tests/alerting/get.ts | 2 +- .../spaces_only/tests/alerting/mute_all.ts | 4 ++-- .../spaces_only/tests/alerting/snooze.ts | 4 ++-- .../spaces_only/tests/alerting/unmute_all.ts | 4 ++-- 12 files changed, 34 insertions(+), 37 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx index 1e3f9765a0ffa..5b61d7c0ed643 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx @@ -6,40 +6,37 @@ */ import React, { useState } from 'react'; -import { RuleSnooze } from '@kbn/alerting-plugin/common'; import { getRuleStatusDropdownLazy } from '../../../common/get_rule_status_dropdown'; export const RuleStatusDropdownSandbox: React.FC<{}> = () => { const [enabled, setEnabled] = useState(true); - const [snoozeSchedule, setSnoozeSchedule] = useState([]); + const [isSnoozedUntil, setIsSnoozedUntil] = useState(null); const [muteAll, setMuteAll] = useState(false); return getRuleStatusDropdownLazy({ rule: { enabled, - snoozeSchedule, + isSnoozedUntil, muteAll, }, enableRule: async () => { setEnabled(true); setMuteAll(false); - setSnoozeSchedule([]); + setIsSnoozedUntil(null); }, disableRule: async () => setEnabled(false), snoozeRule: async (time) => { if (time === -1) { - setSnoozeSchedule([]); + setIsSnoozedUntil(null); setMuteAll(true); } else { - setSnoozeSchedule([ - { startTime: new Date().toISOString(), duration: Date.parse(time) - Date.now() }, - ]); + setIsSnoozedUntil(new Date(time).toISOString()); setMuteAll(false); } }, unsnoozeRule: async () => { setMuteAll(false); - setSnoozeSchedule([]); + setIsSnoozedUntil(null); }, onRuleChanged: () => {}, isEditable: true, 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 84f0d7709d01a..a0f4c22e1c72b 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 @@ -83,7 +83,7 @@ const findTestUtils = ( muted_alert_ids: [], execution_status: match.execution_status, ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time } + ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); @@ -284,7 +284,7 @@ const findTestUtils = ( updated_at: match.updated_at, execution_status: match.execution_status, ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time } + ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); 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 180a3cf36e27f..a03ea10e51953 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 @@ -84,7 +84,7 @@ const getTestUtils = ( ...(describeType === 'internal' ? { monitoring: response.body.monitoring, - snooze_end_time: response.body.snooze_end_time, + snooze_schedule: response.body.snooze_schedule, } : {}), }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts index 1cac93cb52b78..74d237f5dd694 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_all.ts @@ -99,7 +99,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -156,7 +156,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -224,7 +224,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -292,7 +292,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts index 929b95535e195..854d75a31b970 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts @@ -102,7 +102,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -161,7 +161,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -231,7 +231,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -301,7 +301,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -383,7 +383,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts index e97e7e73abe44..7e38866d4fac2 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_all.ts @@ -104,7 +104,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -166,7 +166,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -239,7 +239,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -312,7 +312,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unsnooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unsnooze.ts index ed37a19d80707..98137ff0b2d49 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unsnooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unsnooze.ts @@ -98,7 +98,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -155,7 +155,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -223,7 +223,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -291,7 +291,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ 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 a1b0f5c7eeb14..3162540538ff2 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 @@ -73,7 +73,7 @@ const findTestUtils = ( updated_at: match.updated_at, execution_status: match.execution_status, ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_end_time: match.snooze_end_time } + ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); 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 58c68def04372..d97eb3e2054b4 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 @@ -55,7 +55,7 @@ const getTestUtils = ( updated_at: response.body.updated_at, execution_status: response.body.execution_status, ...(describeType === 'internal' - ? { monitoring: response.body.monitoring, snooze_end_time: response.body.snooze_end_time } + ? { monitoring: response.body.monitoring, snooze_schedule: response.body.snooze_schedule } : {}), }); expect(Date.parse(response.body.created_at)).to.be.greaterThan(0); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts index 53517b191bab6..9d6ce57e328c1 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts @@ -41,7 +41,7 @@ export default function createMuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ supertest: supertestWithoutAuth, @@ -70,7 +70,7 @@ export default function createMuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts index 5be5b59a15248..662f75ca5bdf6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts @@ -74,7 +74,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -126,7 +126,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.snooze_end_time).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql(null); expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts index 782df6d86d542..4f2517ae88505 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts @@ -42,7 +42,7 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ @@ -76,7 +76,7 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_end_time).to.eql(undefined); + expect(updatedAlert.snooze_schedule).to.eql(undefined); // Ensure AAD isn't broken await checkAAD({ From c526de9b4b74bfafe726a4e99089dbb38b33fe8b Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 2 May 2022 11:52:21 -0500 Subject: [PATCH 23/45] Fix snooze API integration tests --- .../tests/alerting/snooze.ts | 38 ++++++++++++++++--- .../spaces_only/tests/alerting/snooze.ts | 9 ++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts index 854d75a31b970..2b12aedd328f1 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/snooze.ts @@ -97,12 +97,19 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); + const now = Date.now(); const { body: updatedAlert } = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule.length).to.eql(1); + // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time + const { startTime, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( + true + ); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -156,12 +163,19 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); + const now = Date.now(); const { body: updatedAlert } = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule.length).to.eql(1); + // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time + const { startTime, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( + true + ); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -226,12 +240,19 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); + const now = Date.now(); const { body: updatedAlert } = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule.length).to.eql(1); + // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time + const { startTime, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( + true + ); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -296,12 +317,19 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); + const now = Date.now(); const { body: updatedAlert } = await supertestWithoutAuth .get(`${getUrlPrefix(space.id)}/internal/alerting/rule/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule.length).to.eql(1); + // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time + const { startTime, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( + true + ); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -383,7 +411,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .auth(user.username, user.password) .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql([]); expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts index 662f75ca5bdf6..44c50318bd379 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts @@ -70,11 +70,16 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(204); expect(response.body).to.eql(''); + const now = Date.now(); const { body: updatedAlert } = await supertestWithoutAuth .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(FUTURE_SNOOZE_TIME); + expect(updatedAlert.snooze_schedule.length).to.eql(1); + // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time + const { startTime, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be(true); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken await checkAAD({ @@ -126,7 +131,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .get(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${createdRule.id}`) .set('kbn-xsrf', 'foo') .expect(200); - expect(updatedAlert.snooze_schedule).to.eql(null); + expect(updatedAlert.snooze_schedule).to.eql([]); expect(updatedAlert.mute_all).to.eql(true); // Ensure AAD isn't broken await checkAAD({ From 6527c3668d76b4077bc588ace4c83898693ba61c Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 2 May 2022 12:28:36 -0500 Subject: [PATCH 24/45] Move isRuleSnoozed to server --- x-pack/plugins/alerting/common/index.ts | 1 - x-pack/plugins/alerting/server/lib/index.ts | 1 + .../alerting/{common => server/lib}/is_rule_snoozed.test.ts | 0 .../plugins/alerting/{common => server/lib}/is_rule_snoozed.ts | 2 +- x-pack/plugins/alerting/server/routes/find_rules.ts | 3 +-- x-pack/plugins/alerting/server/routes/get_rule.ts | 3 +-- x-pack/plugins/alerting/server/task_runner/task_runner.ts | 2 +- 7 files changed, 5 insertions(+), 7 deletions(-) rename x-pack/plugins/alerting/{common => server/lib}/is_rule_snoozed.test.ts (100%) rename x-pack/plugins/alerting/{common => server/lib}/is_rule_snoozed.ts (95%) diff --git a/x-pack/plugins/alerting/common/index.ts b/x-pack/plugins/alerting/common/index.ts index b62b34eb058e6..eeb3db0be0066 100644 --- a/x-pack/plugins/alerting/common/index.ts +++ b/x-pack/plugins/alerting/common/index.ts @@ -22,7 +22,6 @@ export * from './rule_notify_when_type'; export * from './parse_duration'; export * from './execution_log_types'; export * from './rule_snooze_type'; -export * from './is_rule_snoozed'; export interface AlertingFrameworkHealth { isSufficientlySecure: boolean; diff --git a/x-pack/plugins/alerting/server/lib/index.ts b/x-pack/plugins/alerting/server/lib/index.ts index 57c9a92a8d915..a06e80aada087 100644 --- a/x-pack/plugins/alerting/server/lib/index.ts +++ b/x-pack/plugins/alerting/server/lib/index.ts @@ -26,3 +26,4 @@ export { } from './rule_execution_status'; export { getRecoveredAlerts } from './get_recovered_alerts'; export { createWrappedScopedClusterClientFactory } from './wrap_scoped_cluster_client'; +export { isRuleSnoozed, getRuleSnoozeEndTime } from './is_rule_snoozed'; diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts similarity index 100% rename from x-pack/plugins/alerting/common/is_rule_snoozed.test.ts rename to x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts diff --git a/x-pack/plugins/alerting/common/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts similarity index 95% rename from x-pack/plugins/alerting/common/is_rule_snoozed.ts rename to x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index 30a95d588a406..4c1e96bf972bf 100644 --- a/x-pack/plugins/alerting/common/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -6,7 +6,7 @@ */ import { rrulestr } from 'rrule'; -import { SanitizedRule, RuleTypeParams } from './rule'; +import { SanitizedRule, RuleTypeParams } from '../../common/rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index fc47ddf2c3bfc..b340991e74e50 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -9,7 +9,7 @@ import { omit } from 'lodash'; import { IRouter } from '@kbn/core/server'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState } from '../lib'; +import { ILicenseState, getRuleSnoozeEndTime } from '../lib'; import { FindOptions, FindResult } from '../rules_client'; import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib'; import { @@ -17,7 +17,6 @@ import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, - getRuleSnoozeEndTime, } from '../types'; import { trackLegacyTerminology } from './lib/track_legacy_terminology'; diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 1ac2cd75cf0d4..822a84d8924e8 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -8,7 +8,7 @@ import { omit } from 'lodash'; import { schema } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; -import { ILicenseState } from '../lib'; +import { ILicenseState, getRuleSnoozeEndTime } from '../lib'; import { verifyAccessAndContext, RewriteResponseCase } from './lib'; import { RuleTypeParams, @@ -16,7 +16,6 @@ import { BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, SanitizedRule, - getRuleSnoozeEndTime, } from '../types'; const paramSchema = schema.object({ diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index aa615aa80c0b6..96fd02bd073ea 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -25,6 +25,7 @@ import { getRecoveredAlerts, ruleExecutionStatusToRaw, validateRuleTypeParams, + isRuleSnoozed, } from '../lib'; import { Rule, @@ -54,7 +55,6 @@ import { MONITORING_HISTORY_LIMIT, parseDuration, WithoutReservedActionGroups, - isRuleSnoozed, } from '../../common'; import { NormalizedRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { getEsErrorMessage } from '../lib/errors'; From 7eec7af4bb4c34a8e49848cdecae07e110d6bfbe Mon Sep 17 00:00:00 2001 From: Zacqary Adam Xeper Date: Wed, 4 May 2022 11:33:40 -0500 Subject: [PATCH 25/45] Update x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts Co-authored-by: Patrick Mueller --- x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index 4c1e96bf972bf..29e382f828c5a 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -30,7 +30,7 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { const lastOccurrence = recurrenceRule.before(new Date(now), true); if (!lastOccurrence) continue; const lastOccurrenceEndTime = lastOccurrence.getTime() + duration; - if (lastOccurrenceEndTime > now) return new Date(lastOccurrenceEndTime); + if (now < lastOccurrenceEndTime) return new Date(lastOccurrenceEndTime); } } From 41414cbafac20c6132e9b8568d66ee95d46e27a7 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 4 May 2022 11:38:32 -0500 Subject: [PATCH 26/45] Require timeZone in rulesnooze --- x-pack/plugins/alerting/common/rule_snooze_type.ts | 2 +- x-pack/plugins/alerting/server/rules_client/rules_client.ts | 1 + x-pack/plugins/alerting/server/saved_objects/mappings.ts | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index 7ce32be45ce2c..e64b9e5b663cd 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -8,7 +8,7 @@ export type RuleSnooze = Array<{ startTime: string; duration: number; - timeZone?: string; + timeZone: string; id?: string; rRule?: string; }>; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 6bbf1016335e0..245eaa2fb70c5 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1709,6 +1709,7 @@ export class RulesClient { snoozeSchedule: (attributes.snoozeSchedule ?? []).concat({ startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime) - Date.now(), + timeZone: 'UTC', }), muteAll: false, }; diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index 8d788cee6a17f..b5040b711f145 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -186,6 +186,7 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { }, }, snoozeSchedule: { + type: 'nested', properties: { id: { type: 'keyword', From 060385594f8672d030484e4d4c6e586ec9fce9fb Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 10 May 2022 15:27:47 -0500 Subject: [PATCH 27/45] Add automatic isSnoozedUntil savedobject flag --- x-pack/plugins/alerting/common/rule.ts | 5 +-- .../alerting/server/routes/find_rules.ts | 5 +-- .../server/rules_client/rules_client.ts | 37 ++++++++++++++++++- .../alerting/server/saved_objects/index.ts | 4 +- .../alerting/server/saved_objects/mappings.ts | 4 ++ .../server/task_runner/task_runner.ts | 20 ++++++++++ x-pack/plugins/alerting/server/types.ts | 1 + .../lib/rule_api/map_filters_to_kql.test.ts | 12 +++--- .../lib/rule_api/map_filters_to_kql.ts | 2 +- .../rules_list/components/rules_list.tsx | 3 +- 10 files changed, 76 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 720e834257997..f690e1b603359 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -111,11 +111,10 @@ export interface Rule { executionStatus: RuleExecutionStatus; monitoring?: RuleMonitoring; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API + isSnoozedUntil?: Date | null; } -export type SanitizedRule = Omit, 'apiKey'> & { - isSnoozedUntil?: string | null; -}; +export type SanitizedRule = Omit, 'apiKey'>; export type ResolvedSanitizedRule = SanitizedRule & Omit; diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index b340991e74e50..f049682df8499 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -85,6 +85,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ actions, scheduledTaskId, snoozeSchedule, + isSnoozedUntil, ...rest }) => ({ ...rest, @@ -100,6 +101,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ mute_all: muteAll, // Remove this object spread boolean check after snoozeSchedule is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), + ...(isSnoozedUntil != null ? { is_snoozed_until: isSnoozedUntil } : {}), execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), last_execution_date: executionStatus.lastExecutionDate, @@ -111,9 +113,6 @@ const rewriteBodyRes: RewriteResponseCase> = ({ params, connector_type_id: actionTypeId, })), - ...(snoozeSchedule != null - ? { is_snoozed_until: getRuleSnoozeEndTime({ snoozeSchedule, muteAll })?.toISOString() } - : {}), }) ), }; diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index 00f061db66657..8cb1a51e74b05 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -54,7 +54,12 @@ import { RuleSnooze, RawAlertInstance as RawAlert, } from '../types'; -import { validateRuleTypeParams, ruleExecutionStatusFromRaw, getRuleNotifyWhenType } from '../lib'; +import { + validateRuleTypeParams, + ruleExecutionStatusFromRaw, + getRuleNotifyWhenType, + getRuleSnoozeEndTime, +} from '../lib'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; import { RegistryRuleType, UntypedNormalizedRuleType } from '../rule_type_registry'; import { @@ -232,6 +237,7 @@ export interface CreateOptions { | 'actions' | 'executionStatus' | 'snoozeSchedule' + | 'isSnoozedUntil' > & { actions: NormalizedAlertAction[] }; options?: { id?: string; @@ -421,6 +427,7 @@ export class RulesClient { updatedBy: username, createdAt: new Date(createTime).toISOString(), updatedAt: new Date(createTime).toISOString(), + isSnoozedUntil: null, snoozeSchedule: [], params: updatedParams as RawRule['params'], muteAll: false, @@ -1739,7 +1746,7 @@ export class RulesClient { id, updateAttributes, updateOptions - ); + ).then(() => this.updateSnoozedUntilTime({ id })); } public async unsnooze({ id }: { id: string }): Promise { @@ -1804,6 +1811,30 @@ export class RulesClient { ); } + public async updateSnoozedUntilTime({ id }: { id: string }): Promise { + const { attributes, version } = await this.unsecuredSavedObjectsClient.get( + 'alert', + id + ); + + const isSnoozedUntil = getRuleSnoozeEndTime(attributes); + if (!isSnoozedUntil) return; + + const updateAttributes = this.updateMeta({ + isSnoozedUntil: isSnoozedUntil.toISOString(), + updatedBy: await this.getUserName(), + updatedAt: new Date().toISOString(), + }); + const updateOptions = { version }; + + await partiallyUpdateAlert( + this.unsecuredSavedObjectsClient, + id, + updateAttributes, + updateOptions + ); + } + public async muteAll({ id }: { id: string }): Promise { return await retryIfConflicts( this.logger, @@ -2165,6 +2196,7 @@ export class RulesClient { schedule, actions, snoozeSchedule, + isSnoozedUntil, ...partialRawRule }: Partial, references: SavedObjectReference[] | undefined, @@ -2188,6 +2220,7 @@ export class RulesClient { ...(includeSnoozeSchedule ? { snoozeSchedule: snoozeScheduleDates } : {}), ...(updatedAt ? { updatedAt: new Date(updatedAt) } : {}), ...(createdAt ? { createdAt: new Date(createdAt) } : {}), + ...(isSnoozedUntil ? { isSnoozedUntil: new Date(isSnoozedUntil) } : {}), ...(scheduledTaskId ? { scheduledTaskId } : {}), ...(executionStatus ? { executionStatus: ruleExecutionStatusFromRaw(this.logger, id, executionStatus) } diff --git a/x-pack/plugins/alerting/server/saved_objects/index.ts b/x-pack/plugins/alerting/server/saved_objects/index.ts index d75e52e364b44..f4f23cced722c 100644 --- a/x-pack/plugins/alerting/server/saved_objects/index.ts +++ b/x-pack/plugins/alerting/server/saved_objects/index.ts @@ -31,6 +31,7 @@ export const AlertAttributesExcludedFromAAD = [ 'executionStatus', 'monitoring', 'snoozeSchedule', + 'isSnoozedUntil', ]; // useful for Pick which is a @@ -45,7 +46,8 @@ export type AlertAttributesExcludedFromAADType = | 'updatedAt' | 'executionStatus' | 'monitoring' - | 'snoozeSchedule'; + | 'snoozeSchedule' + | 'isSnoozedUntil'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index b5040b711f145..df973f50d2e76 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -206,5 +206,9 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { }, }, }, + isSnoozedUntil: { + type: 'date', + format: 'strict_date_time', + }, }, }; diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index a418fc4087bd9..90c2e0eba2b54 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -493,6 +493,9 @@ export class TaskRunner< } const ruleIsSnoozed = isRuleSnoozed(rule); + if (ruleIsSnoozed) { + this.markRuleAsSnoozed(rule.id); + } if (!ruleIsSnoozed && this.shouldLogAndScheduleActionsForAlerts()) { const mutedAlertIdsSet = new Set(mutedInstanceIds); @@ -596,6 +599,23 @@ export class TaskRunner< return this.executeRule(fakeRequest, rule, validatedParams, executionHandler, spaceId, event); } + private async markRuleAsSnoozed(id: string) { + let apiKey: string | null; + + const { + params: { alertId: ruleId, spaceId }, + } = this.taskInstance; + try { + const decryptedAttributes = await this.getDecryptedAttributes(ruleId, spaceId); + apiKey = decryptedAttributes.apiKey; + } catch (err) { + throw new ErrorWithReason(RuleExecutionStatusErrorReasons.Decrypt, err); + } + const fakeRequest = this.getFakeKibanaRequest(spaceId, apiKey); + const rulesClient = this.context.getRulesClientWithRequest(fakeRequest); + await rulesClient.updateSnoozedUntilTime({ id }); + } + private async loadRuleAttributesAndRun(event: Event): Promise> { const { params: { alertId: ruleId, spaceId }, diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 9ff1e33fc84e2..ee568fe5097b4 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -250,6 +250,7 @@ export interface RawRule extends SavedObjectAttributes { executionStatus: RawRuleExecutionStatus; monitoring?: RuleMonitoring; snoozeSchedule?: RuleSnooze; // Remove ? when this parameter is made available in the public API + isSnoozedUntil?: string | null; } export interface AlertingPlugin { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.test.ts index f67a27ef5409c..8d744c84d6f77 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.test.ts @@ -46,7 +46,7 @@ describe('mapFiltersToKql', () => { ruleStatusesFilter: ['enabled'], }) ).toEqual([ - 'alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)', + 'alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)', ]); expect( @@ -54,21 +54,21 @@ describe('mapFiltersToKql', () => { ruleStatusesFilter: ['disabled'], }) ).toEqual([ - 'alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)', + 'alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)', ]); expect( mapFiltersToKql({ ruleStatusesFilter: ['snoozed'], }) - ).toEqual(['(alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)']); + ).toEqual(['(alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)']); expect( mapFiltersToKql({ ruleStatusesFilter: ['enabled', 'snoozed'], }) ).toEqual([ - 'alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)', + 'alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)', ]); expect( @@ -76,7 +76,7 @@ describe('mapFiltersToKql', () => { ruleStatusesFilter: ['disabled', 'snoozed'], }) ).toEqual([ - 'alert.attributes.enabled:(false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)', + 'alert.attributes.enabled:(false) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)', ]); expect( @@ -84,7 +84,7 @@ describe('mapFiltersToKql', () => { ruleStatusesFilter: ['enabled', 'disabled', 'snoozed'], }) ).toEqual([ - 'alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)', + 'alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)', ]); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts index ff2a49e3a5e45..6629024e3eb11 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/map_filters_to_kql.ts @@ -57,7 +57,7 @@ export const mapFiltersToKql = ({ if (ruleStatusesFilter && ruleStatusesFilter.length) { const enablementFilter = getEnablementFilter(ruleStatusesFilter); - const snoozedFilter = `(alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)`; + const snoozedFilter = `(alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)`; const hasEnablement = ruleStatusesFilter.includes('enabled') || ruleStatusesFilter.includes('disabled'); const hasSnoozed = ruleStatusesFilter.includes('snoozed'); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index a5b9661835131..3d1ada90200bb 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -172,7 +172,8 @@ export const RulesList: React.FunctionComponent = () => { const [showErrors, setShowErrors] = useState(false); const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter'); - const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter'); + const isRuleStatusFilterEnabled = true; + getIsExperimentalFeatureEnabled('ruleStatusFilter'); useEffect(() => { (async () => { From fc99605cae854f318846bd6fcbdcb824fa0a5966 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 10 May 2022 15:34:12 -0500 Subject: [PATCH 28/45] Check isSnoozedUntil against now --- .../rules_list/components/rule_status_dropdown.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 22c9b7e95292f..0904151292e02 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -74,6 +74,9 @@ const usePreviousSnoozeInterval: (p?: string | null) => [string | null, (n: stri return [previousSnoozeInterval, storeAndSetPreviousSnoozeInterval]; }; +const isRuleSnoozed = (rule: { isSnoozedUntil?: Date | null; muteAll: boolean }) => + Boolean((rule.isSnoozedUntil && rule.isSnoozedUntil.getTime() > Date.now()) || rule.muteAll); + export const RuleStatusDropdown: React.FunctionComponent = ({ rule, onRuleChanged, @@ -86,7 +89,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ direction = 'column', }: ComponentOpts) => { const [isEnabled, setIsEnabled] = useState(rule.enabled); - const [isSnoozed, setIsSnoozed] = useState(Boolean(rule.isSnoozedUntil || rule.muteAll)); + const [isSnoozed, setIsSnoozed] = useState(isRuleSnoozed(rule)); const [previousSnoozeInterval, setPreviousSnoozeInterval] = usePreviousSnoozeInterval( propsPreviousSnoozeInterval ); @@ -95,7 +98,7 @@ export const RuleStatusDropdown: React.FunctionComponent = ({ setIsEnabled(rule.enabled); }, [rule.enabled]); useEffect(() => { - setIsSnoozed(Boolean(rule.isSnoozedUntil || rule.muteAll)); + setIsSnoozed(isRuleSnoozed(rule)); }, [rule]); const [isUpdating, setIsUpdating] = useState(false); const [isPopoverOpen, setIsPopoverOpen] = useState(false); @@ -238,7 +241,7 @@ interface RuleStatusMenuProps { onClosePopover: () => void; isEnabled: boolean; isSnoozed: boolean; - snoozeEndTime?: string | null; + snoozeEndTime?: Date | null; previousSnoozeInterval: string | null; } @@ -476,7 +479,7 @@ const SnoozePanel: React.FunctionComponent = ({ ); }; -const futureTimeToInterval = (time?: string | null) => { +const futureTimeToInterval = (time?: Date | null) => { if (!time) return; const relativeTime = moment(time).locale('en').fromNow(true); const [valueStr, unitStr] = relativeTime.split(' '); From 857c36fbe7d1b17427ebdb53281cad4d871b65d0 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 11 May 2022 11:06:20 -0500 Subject: [PATCH 29/45] Fix jest --- x-pack/plugins/alerting/server/routes/find_rules.ts | 2 +- x-pack/plugins/alerting/server/routes/get_rule.ts | 7 +++---- x-pack/plugins/alerting/server/rules_client.mock.ts | 1 + .../alerting/server/rules_client/tests/create.test.ts | 11 +++++++++++ .../public/application/lib/rule_api/rules.test.ts | 6 +++--- .../components/rule_status_dropdown.test.tsx | 2 +- .../sections/rules_list/components/rules_list.tsx | 3 +-- 7 files changed, 21 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index f049682df8499..f585720536894 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -9,7 +9,7 @@ import { omit } from 'lodash'; import { IRouter } from '@kbn/core/server'; import { UsageCounter } from '@kbn/usage-collection-plugin/server'; import { schema } from '@kbn/config-schema'; -import { ILicenseState, getRuleSnoozeEndTime } from '../lib'; +import { ILicenseState } from '../lib'; import { FindOptions, FindResult } from '../rules_client'; import { RewriteRequestCase, RewriteResponseCase, verifyAccessAndContext } from './lib'; import { diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 822a84d8924e8..8e5b5ad5b4305 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -8,7 +8,7 @@ import { omit } from 'lodash'; import { schema } from '@kbn/config-schema'; import { IRouter } from '@kbn/core/server'; -import { ILicenseState, getRuleSnoozeEndTime } from '../lib'; +import { ILicenseState } from '../lib'; import { verifyAccessAndContext, RewriteResponseCase } from './lib'; import { RuleTypeParams, @@ -36,6 +36,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ actions, scheduledTaskId, snoozeSchedule, + isSnoozedUntil, ...rest }) => ({ ...rest, @@ -48,6 +49,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ notify_when: notifyWhen, muted_alert_ids: mutedInstanceIds, mute_all: muteAll, + ...(isSnoozedUntil !== undefined ? { is_snoozed_until: isSnoozedUntil } : {}), // Remove this object spread boolean check after snooze props is added to the public API ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), scheduled_task_id: scheduledTaskId, @@ -62,9 +64,6 @@ const rewriteBodyRes: RewriteResponseCase> = ({ params, connector_type_id: actionTypeId, })), - ...(snoozeSchedule != null - ? { is_snoozed_until: getRuleSnoozeEndTime({ snoozeSchedule, muteAll })?.toISOString() } - : {}), }); interface BuildGetRulesRouteParams { diff --git a/x-pack/plugins/alerting/server/rules_client.mock.ts b/x-pack/plugins/alerting/server/rules_client.mock.ts index bc5c9c0a5e0cd..5abe4023c0792 100644 --- a/x-pack/plugins/alerting/server/rules_client.mock.ts +++ b/x-pack/plugins/alerting/server/rules_client.mock.ts @@ -34,6 +34,7 @@ const createRulesClientMock = () => { getSpaceId: jest.fn(), snooze: jest.fn(), unsnooze: jest.fn(), + updateSnoozedUntilTime: jest.fn(), }; return mocked; }; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index faed4d1edff3d..699e3b965e763 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -407,6 +407,7 @@ describe('create()', () => { "status": "pending", "warning": null, }, + "isSnoozedUntil": null, "legacyId": null, "meta": Object { "versionApiKeyLastmodified": "v8.0.0", @@ -613,6 +614,7 @@ describe('create()', () => { "status": "pending", "warning": null, }, + "isSnoozedUntil": null, "legacyId": "123", "meta": Object { "versionApiKeyLastmodified": "v7.10.0", @@ -1039,6 +1041,7 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', enabled: true, + isSnoozedUntil: null, legacyId: null, executionStatus: { error: null, @@ -1238,6 +1241,7 @@ describe('create()', () => { createdAt: '2019-02-12T21:01:22.479Z', createdBy: 'elastic', enabled: true, + isSnoozedUntil: null, legacyId: null, executionStatus: { error: null, @@ -1402,6 +1406,7 @@ describe('create()', () => { alertTypeId: '123', apiKey: null, apiKeyOwner: null, + isSnoozedUntil: null, legacyId: null, consumer: 'bar', createdAt: '2019-02-12T21:01:22.479Z', @@ -1566,6 +1571,7 @@ describe('create()', () => { alertTypeId: '123', consumer: 'bar', name: 'abc', + isSnoozedUntil: null, legacyId: null, params: { bar: true }, apiKey: null, @@ -1695,6 +1701,7 @@ describe('create()', () => { params: { foo: true }, }, ], + isSnoozedUntil: null, legacyId: null, alertTypeId: '123', consumer: 'bar', @@ -1827,6 +1834,7 @@ describe('create()', () => { params: { foo: true }, }, ], + isSnoozedUntil: null, legacyId: null, alertTypeId: '123', consumer: 'bar', @@ -1988,6 +1996,7 @@ describe('create()', () => { ], apiKeyOwner: null, apiKey: null, + isSnoozedUntil: null, legacyId: null, createdBy: 'elastic', updatedBy: 'elastic', @@ -2348,6 +2357,7 @@ describe('create()', () => { alertTypeId: '123', consumer: 'bar', name: 'abc', + isSnoozedUntil: null, legacyId: null, params: { bar: true }, apiKey: Buffer.from('123:abc').toString('base64'), @@ -2447,6 +2457,7 @@ describe('create()', () => { params: { foo: true }, }, ], + isSnoozedUntil: null, legacyId: null, alertTypeId: '123', consumer: 'bar', diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.test.ts index 2a20c9d9469f5..e06ee24464d78 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules.test.ts @@ -266,7 +266,7 @@ describe('loadRules', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "page": 1, "per_page": 10, "search": undefined, @@ -295,7 +295,7 @@ describe('loadRules', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(false) and not (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "page": 1, "per_page": 10, "search": undefined, @@ -324,7 +324,7 @@ describe('loadRules', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(true or false) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "page": 1, "per_page": 10, "search": undefined, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx index 6080b743a5e35..b2ea5e9a78aae 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.test.tsx @@ -10,7 +10,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { RuleStatusDropdown, ComponentOpts } from './rule_status_dropdown'; const NOW_STRING = '2020-03-01T00:00:00.000Z'; -const SNOOZE_UNTIL = '2020-03-04T00:00:00.000Z'; +const SNOOZE_UNTIL = new Date('2020-03-04T00:00:00.000Z'); describe('RuleStatusDropdown', () => { const enableRule = jest.fn(); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 3d1ada90200bb..a5b9661835131 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -172,8 +172,7 @@ export const RulesList: React.FunctionComponent = () => { const [showErrors, setShowErrors] = useState(false); const isRuleTagFilterEnabled = getIsExperimentalFeatureEnabled('ruleTagFilter'); - const isRuleStatusFilterEnabled = true; - getIsExperimentalFeatureEnabled('ruleStatusFilter'); + const isRuleStatusFilterEnabled = getIsExperimentalFeatureEnabled('ruleStatusFilter'); useEffect(() => { (async () => { From 52f281fd3f872b6dd2eb18627c30d31a59cc32c3 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 11 May 2022 12:51:22 -0500 Subject: [PATCH 30/45] Fix typecheck --- .../rule_status_dropdown_sandbox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx index 5b61d7c0ed643..e17721930858d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/internal/shareable_components_sandbox/rule_status_dropdown_sandbox.tsx @@ -10,7 +10,7 @@ import { getRuleStatusDropdownLazy } from '../../../common/get_rule_status_dropd export const RuleStatusDropdownSandbox: React.FC<{}> = () => { const [enabled, setEnabled] = useState(true); - const [isSnoozedUntil, setIsSnoozedUntil] = useState(null); + const [isSnoozedUntil, setIsSnoozedUntil] = useState(null); const [muteAll, setMuteAll] = useState(false); return getRuleStatusDropdownLazy({ @@ -30,7 +30,7 @@ export const RuleStatusDropdownSandbox: React.FC<{}> = () => { setIsSnoozedUntil(null); setMuteAll(true); } else { - setIsSnoozedUntil(new Date(time).toISOString()); + setIsSnoozedUntil(new Date(time)); setMuteAll(false); } }, From 24340801c6f7d307df18e45fa848750095d5d9de Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 11 May 2022 15:13:17 -0500 Subject: [PATCH 31/45] Fix jest --- .../public/application/lib/rule_api/aggregate.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.test.ts index 5377e4269f46e..104f0507aef8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.test.ts @@ -243,7 +243,7 @@ describe('loadRuleAggregations', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(true) and not (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "search": undefined, "search_fields": undefined, }, @@ -262,7 +262,7 @@ describe('loadRuleAggregations', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "search": undefined, "search_fields": undefined, }, @@ -281,7 +281,7 @@ describe('loadRuleAggregations', () => { Object { "query": Object { "default_search_operator": "AND", - "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.snoozeEndTime > now)", + "filter": "alert.attributes.enabled:(true) or (alert.attributes.muteAll:true OR alert.attributes.isSnoozedUntil > now)", "search": undefined, "search_fields": undefined, }, From 70a2aa704919160a9c14c7dd4b545afd20c22e0f Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 11 May 2022 15:23:15 -0500 Subject: [PATCH 32/45] Fix snoozedUntil date parsing --- .../sections/rules_list/components/rule_status_dropdown.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx index 0904151292e02..8a68abecf2873 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rule_status_dropdown.tsx @@ -75,7 +75,9 @@ const usePreviousSnoozeInterval: (p?: string | null) => [string | null, (n: stri }; const isRuleSnoozed = (rule: { isSnoozedUntil?: Date | null; muteAll: boolean }) => - Boolean((rule.isSnoozedUntil && rule.isSnoozedUntil.getTime() > Date.now()) || rule.muteAll); + Boolean( + (rule.isSnoozedUntil && new Date(rule.isSnoozedUntil).getTime() > Date.now()) || rule.muteAll + ); export const RuleStatusDropdown: React.FunctionComponent = ({ rule, From 36464583a5eaec43c4af5866c2c87e252a4dfc3f Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 12 May 2022 10:31:42 -0500 Subject: [PATCH 33/45] Fix rewriterule --- x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts index 626d4baf803c1..3584ca6ef1dbd 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -21,6 +21,7 @@ export const rewriteRule = ({ executionStatus, actions, scheduledTaskId, + snoozeSchedule, isSnoozedUntil, ...rest }: SanitizedRule) => ({ @@ -35,6 +36,8 @@ export const rewriteRule = ({ mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, + // Remove this object spread boolean check after snooze props is added to the public API + ...(snoozeSchedule != null ? { snooze_schedule: snoozeSchedule } : {}), ...(isSnoozedUntil != null ? { is_snoozed_until: isSnoozedUntil } : {}), execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), From 2c53cb7c4ad6ade963f03853778ddd4aae9c2a81 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Thu, 12 May 2022 10:44:33 -0500 Subject: [PATCH 34/45] Add error handling for RRule --- .../plugins/alerting/server/lib/is_rule_snoozed.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index 29e382f828c5a..5bf66342616d7 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -26,11 +26,15 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { // Check to see if now is during a recurrence of the snooze if (rRule) { - const recurrenceRule = rrulestr(rRule); - const lastOccurrence = recurrenceRule.before(new Date(now), true); - if (!lastOccurrence) continue; - const lastOccurrenceEndTime = lastOccurrence.getTime() + duration; - if (now < lastOccurrenceEndTime) return new Date(lastOccurrenceEndTime); + try { + const recurrenceRule = rrulestr(rRule); + const lastOccurrence = recurrenceRule.before(new Date(now), true); + if (!lastOccurrence) continue; + const lastOccurrenceEndTime = lastOccurrence.getTime() + duration; + if (now < lastOccurrenceEndTime) return new Date(lastOccurrenceEndTime); + } catch (e) { + throw new Error(`Failed to process RRule ${rRule}: ${e}`); + } } } From ed75133db28d7b01a4b4d2186da88b9bcb36233f Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 16 May 2022 11:27:34 -0500 Subject: [PATCH 35/45] Fix re-snoozing --- x-pack/plugins/alerting/server/rules_client/rules_client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d5d6dd3e30f0c..d98d54ec041d3 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -2151,7 +2151,7 @@ export class RulesClient { snoozeSchedule: clearUnscheduledSnooze(attributes), } : { - snoozeSchedule: (attributes.snoozeSchedule ?? []).concat({ + snoozeSchedule: clearUnscheduledSnooze(attributes).concat({ startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime) - Date.now(), timeZone: 'UTC', From 083782c07b2a284f2f4ceaef6d90a408b30f2fc3 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 16 May 2022 11:42:18 -0500 Subject: [PATCH 36/45] Add comments to rulesnoozetype --- x-pack/plugins/alerting/common/rule_snooze_type.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index e64b9e5b663cd..ee13819772b9f 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -9,6 +9,8 @@ export type RuleSnooze = Array<{ startTime: string; duration: number; timeZone: string; + // For scheduled/recurring snoozes, `id` uniquely identifies them so that they can be displayed, modified, and deleted individually id?: string; + // An iCal RRULE string to define a recurrence schedule, see https://github.com/jakubroztocil/rrule for the spec rRule?: string; }>; From f11125dac8cf366e7a86c3098d16bebf4a07012c Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 16 May 2022 16:29:44 -0500 Subject: [PATCH 37/45] Restructure data model to use rRule for everything --- .../alerting/common/rule_snooze_type.ts | 27 ++- .../server/lib/is_rule_snoozed.test.ts | 187 ++++++++---------- .../alerting/server/lib/is_rule_snoozed.ts | 26 ++- .../server/rules_client/rules_client.ts | 17 +- .../alerting/server/saved_objects/mappings.ts | 61 +++++- .../server/saved_objects/migrations.test.ts | 2 +- .../server/saved_objects/migrations.ts | 7 +- 7 files changed, 202 insertions(+), 125 deletions(-) diff --git a/x-pack/plugins/alerting/common/rule_snooze_type.ts b/x-pack/plugins/alerting/common/rule_snooze_type.ts index ee13819772b9f..405cbef357242 100644 --- a/x-pack/plugins/alerting/common/rule_snooze_type.ts +++ b/x-pack/plugins/alerting/common/rule_snooze_type.ts @@ -5,12 +5,31 @@ * 2.0. */ +import type { WeekdayStr } from 'rrule'; + export type RuleSnooze = Array<{ - startTime: string; duration: number; - timeZone: string; + rRule: Partial & Pick; // For scheduled/recurring snoozes, `id` uniquely identifies them so that they can be displayed, modified, and deleted individually id?: string; - // An iCal RRULE string to define a recurrence schedule, see https://github.com/jakubroztocil/rrule for the spec - rRule?: string; }>; + +// An iCal RRULE to define a recurrence schedule, see https://github.com/jakubroztocil/rrule for the spec +export interface RRuleRecord { + dtstart: string; + tzid: string; + freq?: 0 | 1 | 2 | 3 | 4 | 5 | 6; + until?: string; + count?: number; + interval?: number; + wkst?: WeekdayStr; + byweekday?: Array; + bymonth?: number[]; + bysetpos?: number[]; + bymonthday: number; + byyearday: number[]; + byweekno: number[]; + byhour: number[]; + byminute: number[]; + bysecond: number[]; +} diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts index a8a3013dfb118..14ad981a5e903 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.test.ts @@ -8,6 +8,7 @@ import sinon from 'sinon'; import { RRule } from 'rrule'; import { isRuleSnoozed } from './is_rule_snoozed'; +import { RRuleRecord } from '../types'; const DATE_9999 = '9999-12-31T12:34:56.789Z'; const DATE_1970 = '1970-01-01T00:00:00.000Z'; @@ -31,9 +32,12 @@ describe('isRuleSnoozed', () => { test('returns false when snooze has not yet started', () => { const snoozeSchedule = [ { - startTime: DATE_9999, duration: 100000000, - timeZone: 'UTC', + rRule: { + dtstart: DATE_9999, + tzid: 'UTC', + count: 1, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); @@ -42,9 +46,12 @@ describe('isRuleSnoozed', () => { test('returns true when snooze has started', () => { const snoozeSchedule = [ { - startTime: NOW, duration: 100000000, - timeZone: 'UTC', + rRule: { + dtstart: NOW, + tzid: 'UTC', + count: 1, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(true); @@ -53,9 +60,13 @@ describe('isRuleSnoozed', () => { test('returns false when snooze has ended', () => { const snoozeSchedule = [ { - startTime: DATE_2019, duration: 100000000, - timeZone: 'UTC', + + rRule: { + dtstart: DATE_2019, + tzid: 'UTC', + count: 1, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: false })).toBe(false); @@ -64,9 +75,13 @@ describe('isRuleSnoozed', () => { test('returns true when snooze is indefinite', () => { const snoozeSchedule = [ { - startTime: DATE_9999, duration: 100000000, - timeZone: 'UTC', + + rRule: { + dtstart: DATE_9999, + tzid: 'UTC', + count: 1, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule, muteAll: true })).toBe(true); @@ -75,43 +90,37 @@ describe('isRuleSnoozed', () => { test('returns as expected for an indefinitely recurring snooze', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { + dtstart: DATE_2019, + tzid: 'UTC', freq: RRule.DAILY, interval: 1, - tzid: 'UTC', - dtstart: new Date(DATE_2019), - }).toString(), + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_2019_PLUS_6_HOURS, duration: 60 * 1000, - rRule: new RRule({ + rRule: { + dtstart: DATE_2019_PLUS_6_HOURS, + tzid: 'UTC', freq: RRule.DAILY, interval: 1, - tzid: 'UTC', - dtstart: new Date(DATE_2019_PLUS_6_HOURS), - }).toString(), - timeZone: 'UTC', + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); const snoozeScheduleC = [ { - startTime: DATE_2020_MINUS_1_HOUR, duration: 60 * 1000, - rRule: new RRule({ + rRule: { + dtstart: DATE_2020_MINUS_1_HOUR, + tzid: 'UTC', freq: RRule.HOURLY, interval: 1, - tzid: 'UTC', - dtstart: new Date(DATE_2020_MINUS_1_HOUR), - }).toString(), - timeZone: 'UTC', + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(true); @@ -120,46 +129,42 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze with limited occurrences', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.HOURLY, interval: 1, tzid: 'UTC', count: 8761, - dtstart: new Date(DATE_2019), - }).toString(), + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + + rRule: { freq: RRule.HOURLY, interval: 1, tzid: 'UTC', count: 25, - dtstart: new Date(DATE_2019), - }).toString(), + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); const snoozeScheduleC = [ { - startTime: DATE_1970, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + + rRule: { freq: RRule.YEARLY, interval: 1, tzid: 'UTC', count: 60, - dtstart: new Date(DATE_1970), - }).toString(), + dtstart: DATE_1970, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(true); @@ -168,31 +173,27 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze with an end date', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.HOURLY, interval: 1, tzid: 'UTC', - until: new Date(DATE_9999), - dtstart: new Date(DATE_2019), - }).toString(), + until: DATE_9999, + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.HOURLY, interval: 1, tzid: 'UTC', - until: new Date(DATE_2020_MINUS_1_HOUR), - dtstart: new Date(DATE_2019), - }).toString(), + until: DATE_2020_MINUS_1_HOUR, + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); @@ -201,63 +202,55 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze on a day of the week', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.MO, RRule.WE, RRule.FR], - dtstart: new Date(DATE_2019), - }).toString(), // Monday Wednesday Friday; Jan 1 2020 was a Wednesday + byweekday: ['MO', 'WE', 'FR'], // Jan 1 2020 was a Wednesday + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.TU, RRule.TH, RRule.SA, RRule.SU], - dtstart: new Date(DATE_2019), - }).toString(), + byweekday: ['TU', 'TH', 'SA', 'SU'], + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); const snoozeScheduleC = [ { - startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.MO, RRule.WE, RRule.FR], + byweekday: ['MO', 'WE', 'FR'], count: 12, - dtstart: new Date(DATE_2020_MINUS_1_MONTH), - }).toString(), + dtstart: DATE_2020_MINUS_1_MONTH, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleC, muteAll: false })).toBe(false); const snoozeScheduleD = [ { - startTime: DATE_2020_MINUS_1_MONTH, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.MO, RRule.WE, RRule.FR], + byweekday: ['MO', 'WE', 'FR'], count: 15, - dtstart: new Date(DATE_2020_MINUS_1_MONTH), - }).toString(), + dtstart: DATE_2020_MINUS_1_MONTH, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleD, muteAll: false })).toBe(true); @@ -266,31 +259,27 @@ describe('isRuleSnoozed', () => { test('returns as expected for a recurring snooze on an nth day of the week of a month', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.MONTHLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.WE.nth(1)], // Jan 1 2020 was the first Wednesday of the month - dtstart: new Date(DATE_2019), - }).toString(), + byweekday: ['+1WE'], // Jan 1 2020 was the first Wednesday of the month + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(true); const snoozeScheduleB = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.MONTHLY, interval: 1, tzid: 'UTC', - byweekday: [RRule.WE.nth(2)], - dtstart: new Date(DATE_2019), - }).toString(), + byweekday: ['+2WE'], + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(false); @@ -299,34 +288,30 @@ describe('isRuleSnoozed', () => { test('using a timezone, returns as expected for a recurring snooze on a day of the week', () => { const snoozeScheduleA = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'Asia/Taipei', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, - byweekday: [RRule.WE], + byweekday: ['WE'], tzid: 'Asia/Taipei', - dtstart: new Date(DATE_2019), - }).toString(), + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleA, muteAll: false })).toBe(false); const snoozeScheduleB = [ { - startTime: DATE_2019, duration: 60 * 1000, - timeZone: 'UTC', - rRule: new RRule({ + rRule: { freq: RRule.WEEKLY, interval: 1, - byweekday: [RRule.WE], + byweekday: ['WE'], byhour: [0], byminute: [0], tzid: 'UTC', - dtstart: new Date(DATE_2019), - }).toString(), + dtstart: DATE_2019, + } as RRuleRecord, }, ]; expect(isRuleSnoozed({ snoozeSchedule: snoozeScheduleB, muteAll: false })).toBe(true); diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index 5bf66342616d7..927a21cd8cb14 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { rrulestr } from 'rrule'; +import { RRule, ByWeekday, Weekday, WeekdayStr } from 'rrule'; import { SanitizedRule, RuleTypeParams } from '../../common/rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; @@ -17,8 +17,8 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { const now = Date.now(); for (const snooze of rule.snoozeSchedule) { - const { startTime, duration, rRule } = snooze; - const startTimeMS = Date.parse(startTime); + const { duration, rRule } = snooze; + const startTimeMS = Date.parse(rRule.dtstart); const initialEndTime = startTimeMS + duration; // If now is during the first occurrence of the snooze @@ -27,7 +27,15 @@ export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { // Check to see if now is during a recurrence of the snooze if (rRule) { try { - const recurrenceRule = rrulestr(rRule); + const rRuleOptions = { + ...rRule, + dtstart: new Date(rRule.dtstart), + until: rRule.until ? new Date(rRule.until) : null, + wkst: rRule.wkst ? Weekday.fromStr(rRule.wkst) : null, + byweekday: rRule.byweekday ? parseByWeekday(rRule.byweekday) : null, + }; + + const recurrenceRule = new RRule(rRuleOptions); const lastOccurrence = recurrenceRule.before(new Date(now), true); if (!lastOccurrence) continue; const lastOccurrenceEndTime = lastOccurrence.getTime() + duration; @@ -47,3 +55,13 @@ export function isRuleSnoozed(rule: RuleSnoozeProps) { } return Boolean(getRuleSnoozeEndTime(rule)); } + +function parseByWeekday(byweekday: Array): ByWeekday[] { + return byweekday.map((w) => { + if (typeof w === 'number') return w; + if (['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'].includes(w)) return w as WeekdayStr; + const dayOfWeek = w.slice(-2) as WeekdayStr; + const nthDay = w.slice(0, 2); + return RRule[dayOfWeek].nth(Number(nthDay)); + }); +} diff --git a/x-pack/plugins/alerting/server/rules_client/rules_client.ts b/x-pack/plugins/alerting/server/rules_client/rules_client.ts index d98d54ec041d3..936ed7c819114 100644 --- a/x-pack/plugins/alerting/server/rules_client/rules_client.ts +++ b/x-pack/plugins/alerting/server/rules_client/rules_client.ts @@ -1022,7 +1022,7 @@ export class RulesClient { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeSchedule.startTime', + field: 'alert.attributes.snoozeSchedule.rRule.dtstart', format: 'strict_date_time', ranges: [{ from: 'now' }], }, @@ -2152,9 +2152,12 @@ export class RulesClient { } : { snoozeSchedule: clearUnscheduledSnooze(attributes).concat({ - startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime) - Date.now(), - timeZone: 'UTC', + rRule: { + dtstart: new Date().toISOString(), + tzid: 'UTC', + count: 1, + }, }), muteAll: false, }; @@ -2630,9 +2633,13 @@ export class RulesClient { ): PartialRule | PartialRuleWithLegacyId { const snoozeScheduleDates = snoozeSchedule?.map((s) => ({ ...s, - startTime: new Date(s.startTime), + rRule: { + ...s.rRule, + dtstart: new Date(s.rRule.dtstart), + ...(s.rRule.until ? { until: new Date(s.rRule.until) } : {}), + }, })); - const includeSnoozeSchedule = snoozeSchedule !== undefined && !excludeFromPublicApi; + const includeSnoozeSchedule = snoozeSchedule !== undefined; const rule = { id, notifyWhen, diff --git a/x-pack/plugins/alerting/server/saved_objects/mappings.ts b/x-pack/plugins/alerting/server/saved_objects/mappings.ts index df973f50d2e76..31ad40117a7ec 100644 --- a/x-pack/plugins/alerting/server/saved_objects/mappings.ts +++ b/x-pack/plugins/alerting/server/saved_objects/mappings.ts @@ -191,18 +191,63 @@ export const alertMappings: SavedObjectsTypeMappingDefinition = { id: { type: 'keyword', }, - timeZone: { - type: 'keyword', - }, - startTime: { - type: 'date', - format: 'strict_date_time', - }, duration: { type: 'long', }, rRule: { - type: 'keyword', + type: 'nested', + properties: { + freq: { + type: 'keyword', + }, + dtstart: { + type: 'date', + format: 'strict_date_time', + }, + tzid: { + type: 'keyword', + }, + until: { + type: 'date', + format: 'strict_date_time', + }, + count: { + type: 'long', + }, + interval: { + type: 'long', + }, + wkst: { + type: 'keyword', + }, + byweekday: { + type: 'keyword', + }, + bymonth: { + type: 'short', + }, + bysetpos: { + type: 'long', + }, + bymonthday: { + type: 'short', + }, + byyearday: { + type: 'short', + }, + byweekno: { + type: 'short', + }, + byhour: { + type: 'long', + }, + byminute: { + type: 'long', + }, + bysecond: { + type: 'long', + }, + }, }, }, }, diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts index b39bf84c8d041..bbf93f85450cb 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.test.ts @@ -2333,7 +2333,7 @@ describe('successful migrations', () => { const migratedMutedAlert830 = migration830(mutedAlert, migrationContext); expect(migratedMutedAlert830.attributes.snoozeSchedule.length).toEqual(1); - expect(migratedMutedAlert830.attributes.snoozeSchedule[0].startTime).toEqual( + expect(migratedMutedAlert830.attributes.snoozeSchedule[0].rRule.dtstart).toEqual( '1970-01-01T00:00:00.000Z' ); expect(migratedMutedAlert830.attributes.snoozeSchedule[0].duration).toEqual(86400000); diff --git a/x-pack/plugins/alerting/server/saved_objects/migrations.ts b/x-pack/plugins/alerting/server/saved_objects/migrations.ts index 3c115da9d7d04..ddae200ae8fa6 100644 --- a/x-pack/plugins/alerting/server/saved_objects/migrations.ts +++ b/x-pack/plugins/alerting/server/saved_objects/migrations.ts @@ -904,9 +904,12 @@ function convertSnoozes( snoozeSchedule: snoozeEndTime ? [ { - startTime: new Date().toISOString(), duration: Date.parse(snoozeEndTime as string) - Date.now(), - timeZone: moment.tz.guess(), + rRule: { + dtstart: new Date().toISOString(), + tzid: moment.tz.guess(), + count: 1, + }, }, ] : [], From 409c606d8d08e053b453cdbe11efcb2b56e2a61e Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Mon, 16 May 2022 17:06:12 -0500 Subject: [PATCH 38/45] Fix functional tests --- .../security_and_spaces/group1/tests/alerting/create.ts | 1 + .../spaces_only/tests/alerting/create.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts index e601c6ee15ec7..2d3829f42a678 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts @@ -115,6 +115,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { created_by: user.username, schedule: { interval: '1m' }, scheduled_task_id: response.body.scheduled_task_id, + snooze_schedule: response.body.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, throttle: '1m', 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 143d845d074c4..0d5ade3326520 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 @@ -85,6 +85,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { created_by: null, schedule: { interval: '1m' }, scheduled_task_id: response.body.scheduled_task_id, + snooze_schedule: response.body.snooze_schedule, updated_by: null, api_key_owner: null, throttle: '1m', From eb1f391c198b82d0aa3dc828d0d854a3aee650b3 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 17 May 2022 11:02:58 -0500 Subject: [PATCH 39/45] Fix jest --- .../rules_client/tests/aggregate.test.ts | 4 +-- .../server/rules_client/tests/create.test.ts | 5 +++ .../server/task_runner/task_runner.test.ts | 32 ++++++++++++++++--- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts index 967f7cd7a8bca..bc1c8d276aedd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/aggregate.test.ts @@ -203,7 +203,7 @@ describe('aggregate()', () => { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeSchedule.startTime', + field: 'alert.attributes.snoozeSchedule.rRule.dtstart', format: 'strict_date_time', ranges: [{ from: 'now' }], }, @@ -240,7 +240,7 @@ describe('aggregate()', () => { }, snoozed: { date_range: { - field: 'alert.attributes.snoozeSchedule.startTime', + field: 'alert.attributes.snoozeSchedule.rRule.dtstart', format: 'strict_date_time', ranges: [{ from: 'now' }], }, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts index 37059924533b2..f5c839c5006fd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/create.test.ts @@ -376,6 +376,7 @@ describe('create()', () => { "interval": "1m", }, "scheduledTaskId": "task-123", + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -1644,6 +1645,7 @@ describe('create()', () => { "interval": "1m", }, "scheduledTaskId": "task-123", + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -1777,6 +1779,7 @@ describe('create()', () => { "interval": "1m", }, "scheduledTaskId": "task-123", + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -1910,6 +1913,7 @@ describe('create()', () => { "interval": "1m", }, "scheduledTaskId": "task-123", + "snoozeSchedule": Array [], "tags": Array [ "foo", ], @@ -2075,6 +2079,7 @@ describe('create()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "snoozeSchedule": Array [], "tags": Array [ "foo", ], diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 09196b262abd4..f3d2c7039585b 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -471,12 +471,36 @@ describe('Task Runner', () => { [false, null, false], [false, undefined, false], // Stringify the snooze schedules for better failure reporting - [false, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), false], - [false, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], + [ + false, + JSON.stringify([ + { rRule: { dtstart: DATE_9999, tzid: 'UTC', count: 1 }, duration: 100000000 }, + ]), + false, + ], + [ + false, + JSON.stringify([ + { rRule: { dtstart: DATE_1970, tzid: 'UTC', count: 1 }, duration: 100000000 }, + ]), + true, + ], [true, null, true], [true, undefined, true], - [true, JSON.stringify([{ startTime: DATE_9999, duration: 100000000 }]), true], - [true, JSON.stringify([{ startTime: DATE_1970, duration: 100000000 }]), true], + [ + true, + JSON.stringify([ + { rRule: { dtstart: DATE_9999, tzid: 'UTC', count: 1 }, duration: 100000000 }, + ]), + true, + ], + [ + true, + JSON.stringify([ + { rRule: { dtstart: DATE_1970, tzid: 'UTC', count: 1 }, duration: 100000000 }, + ]), + true, + ], ]; test.each(snoozeTestParams)( From ffeb94db32d449dc16f8f9b1b2962353245fdb96 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 17 May 2022 11:07:37 -0500 Subject: [PATCH 40/45] Fix functional tests --- x-pack/plugins/alerting/server/routes/create_rule.ts | 2 ++ x-pack/plugins/alerting/server/routes/get_rule.ts | 3 +-- x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts | 3 +-- .../security_and_spaces/group1/tests/alerting/find.ts | 1 + .../spaces_only/tests/alerting/find.ts | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/create_rule.ts b/x-pack/plugins/alerting/server/routes/create_rule.ts index cf044c94f2529..442162ae21cbb 100644 --- a/x-pack/plugins/alerting/server/routes/create_rule.ts +++ b/x-pack/plugins/alerting/server/routes/create_rule.ts @@ -67,12 +67,14 @@ const rewriteBodyRes: RewriteResponseCase> = ({ notifyWhen, muteAll, mutedInstanceIds, + snoozeSchedule, executionStatus: { lastExecutionDate, lastDuration, ...executionStatus }, ...rest }) => ({ ...rest, rule_type_id: alertTypeId, scheduled_task_id: scheduledTaskId, + snooze_schedule: snoozeSchedule, created_by: createdBy, updated_by: updatedBy, created_at: createdAt, diff --git a/x-pack/plugins/alerting/server/routes/get_rule.ts b/x-pack/plugins/alerting/server/routes/get_rule.ts index 8e5b5ad5b4305..c735d68f83bbe 100644 --- a/x-pack/plugins/alerting/server/routes/get_rule.ts +++ b/x-pack/plugins/alerting/server/routes/get_rule.ts @@ -50,8 +50,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ muted_alert_ids: mutedInstanceIds, mute_all: muteAll, ...(isSnoozedUntil !== undefined ? { is_snoozed_until: isSnoozedUntil } : {}), - // Remove this object spread boolean check after snooze props is added to the public API - ...(snoozeSchedule !== undefined ? { snooze_schedule: snoozeSchedule } : {}), + snooze_schedule: snoozeSchedule, scheduled_task_id: scheduledTaskId, execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts index 3584ca6ef1dbd..162177d695e0a 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -36,8 +36,7 @@ export const rewriteRule = ({ mute_all: muteAll, muted_alert_ids: mutedInstanceIds, scheduled_task_id: scheduledTaskId, - // Remove this object spread boolean check after snooze props is added to the public API - ...(snoozeSchedule != null ? { snooze_schedule: snoozeSchedule } : {}), + snooze_schedule: snoozeSchedule, ...(isSnoozedUntil != null ? { is_snoozed_until: isSnoozedUntil } : {}), execution_status: executionStatus && { ...omit(executionStatus, 'lastExecutionDate', 'lastDuration'), diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts index de51e6ad86118..aaa972fd888af 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts @@ -73,6 +73,7 @@ const findTestUtils = ( params: {}, created_by: 'elastic', scheduled_task_id: match.scheduled_task_id, + snooze_schedule: match.snooze_schedule, created_at: match.created_at, updated_at: match.updated_at, 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 3162540538ff2..1978f0d30fe46 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 @@ -64,6 +64,7 @@ const findTestUtils = ( created_by: null, api_key_owner: null, scheduled_task_id: match.scheduled_task_id, + snooze_schedule: match.snooze_schedule, updated_by: null, throttle: '1m', notify_when: 'onThrottleInterval', From f8fe600e2bbad9c49b75d794249f50593ea4e50a Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 17 May 2022 12:21:39 -0500 Subject: [PATCH 41/45] Fix functional tests --- x-pack/plugins/alerting/server/routes/update_rule.ts | 4 ++++ .../security_and_spaces/group1/tests/alerting/get.ts | 2 +- .../security_and_spaces/group2/tests/alerting/mute_all.ts | 8 ++++---- .../group2/tests/alerting/unmute_all.ts | 8 ++++---- .../security_and_spaces/group2/tests/alerting/update.ts | 5 +++++ .../spaces_only/tests/alerting/create.ts | 1 + .../spaces_only/tests/alerting/find.ts | 1 + .../spaces_only/tests/alerting/get.ts | 2 ++ .../spaces_only/tests/alerting/mute_all.ts | 4 ++-- .../spaces_only/tests/alerting/unmute_all.ts | 4 ++-- .../spaces_only/tests/alerting/update.ts | 2 ++ 11 files changed, 28 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index d2130e1f33541..1faddd66c8f0e 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -70,12 +70,16 @@ const rewriteBodyRes: RewriteResponseCase> = ({ muteAll, mutedInstanceIds, executionStatus, + snoozeSchedule, + isSnoozedUntil, ...rest }) => ({ ...rest, api_key_owner: apiKeyOwner, created_by: createdBy, updated_by: updatedBy, + snooze_schedule: snoozeSchedule, + ...(isSnoozedUntil ? { is_snoozed_until: isSnoozedUntil } : {}), ...(alertTypeId ? { rule_type_id: alertTypeId } : {}), ...(scheduledTaskId ? { scheduled_task_id: scheduledTaskId } : {}), ...(createdAt ? { created_at: createdAt } : {}), diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts index 2752707843c0d..c2c94af19b209 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts @@ -72,6 +72,7 @@ const getTestUtils = ( params: {}, created_by: 'elastic', scheduled_task_id: response.body.scheduled_task_id, + snooze_schedule: response.body.snooze_schedule, updated_at: response.body.updated_at, created_at: response.body.created_at, throttle: '1m', @@ -84,7 +85,6 @@ const getTestUtils = ( ...(describeType === 'internal' ? { monitoring: response.body.monitoring, - snooze_schedule: response.body.snooze_schedule, } : {}), }); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts index 429d5700497ad..f0ce5962de368 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts @@ -99,7 +99,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -156,7 +156,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -224,7 +224,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -292,7 +292,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts index 5b00a48f9ecc8..9c918b3225f9e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts @@ -104,7 +104,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -166,7 +166,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -239,7 +239,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -312,7 +312,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex .auth(user.username, user.password) .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts index c49fa62c606b6..d28b81f479b11 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts @@ -129,6 +129,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { }, ], scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, @@ -213,6 +214,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, @@ -308,6 +310,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, @@ -403,6 +406,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, @@ -496,6 +500,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { mute_all: false, muted_alert_ids: [], scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, 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 0d5ade3326520..c57ce50b25706 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 @@ -476,6 +476,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdBy: null, schedule: { interval: '1m' }, scheduledTaskId: response.body.scheduledTaskId, + snoozeSchedule: response.body.snoozeSchedule, updatedBy: null, apiKeyOwner: 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 1978f0d30fe46..48b2fbf0ec60d 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 @@ -297,6 +297,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { createdBy: null, apiKeyOwner: null, scheduledTaskId: match.scheduledTaskId, + snoozeSchedule: match.snoozeSchedule, updatedBy: null, throttle: '1m', notifyWhen: 'onThrottleInterval', 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 d97eb3e2054b4..ee993c425fa38 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 @@ -45,6 +45,7 @@ const getTestUtils = ( params: {}, created_by: null, scheduled_task_id: response.body.scheduled_task_id, + snooze_schedule: response.body.snooze_schedule, updated_by: null, api_key_owner: null, throttle: '1m', @@ -136,6 +137,7 @@ export default function createGetTests({ getService }: FtrProviderContext) { params: {}, createdBy: null, scheduledTaskId: response.body.scheduledTaskId, + snoozeSchedule: response.body.snoozeSchedule, updatedBy: null, apiKeyOwner: null, throttle: '1m', diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts index 9d6ce57e328c1..a56b95ed09219 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/mute_all.ts @@ -41,7 +41,7 @@ export default function createMuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ supertest: supertestWithoutAuth, @@ -70,7 +70,7 @@ export default function createMuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(true); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts index 4f2517ae88505..62ff63052f841 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/unmute_all.ts @@ -42,7 +42,7 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ @@ -76,7 +76,7 @@ export default function createUnmuteTests({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.mute_all).to.eql(false); - expect(updatedAlert.snooze_schedule).to.eql(undefined); + expect(updatedAlert.snooze_schedule.length).to.eql(0); // Ensure AAD isn't broken await checkAAD({ 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 c5a9c93d45e81..c431654f0fd20 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 @@ -60,6 +60,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { muted_alert_ids: [], notify_when: 'onThrottleInterval', scheduled_task_id: createdAlert.scheduled_task_id, + snooze_schedule: createdAlert.snooze_schedule, created_at: response.body.created_at, updated_at: response.body.updated_at, execution_status: response.body.execution_status, @@ -160,6 +161,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { mutedInstanceIds: [], notifyWhen: 'onThrottleInterval', scheduledTaskId: createdAlert.scheduled_task_id, + snoozeSchedule: createdAlert.snooze_schedule, createdAt: response.body.createdAt, updatedAt: response.body.updatedAt, executionStatus: response.body.executionStatus, From 22e839ea79fc5ff0d4ce348916bcd9a90dafc2bb Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 17 May 2022 19:34:53 -0500 Subject: [PATCH 42/45] Fix functional tests --- .../group1/tests/alerting/find.ts | 9 +++---- .../group2/tests/alerting/snooze.ts | 24 +++++++++---------- .../spaces_only/tests/alerting/create.ts | 1 + .../spaces_only/tests/alerting/find.ts | 4 +--- .../spaces_only/tests/alerting/snooze.ts | 6 ++--- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts index aaa972fd888af..177e51ab78eea 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/find.ts @@ -83,9 +83,7 @@ const findTestUtils = ( mute_all: false, muted_alert_ids: [], execution_status: match.execution_status, - ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } - : {}), + ...(describeType === 'internal' ? { monitoring: match.monitoring } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); expect(Date.parse(match.updated_at)).to.be.greaterThan(0); @@ -284,9 +282,8 @@ const findTestUtils = ( created_at: match.created_at, updated_at: match.updated_at, execution_status: match.execution_status, - ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } - : {}), + snooze_schedule: match.snooze_schedule, + ...(describeType === 'internal' ? { monitoring: match.monitoring } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); expect(Date.parse(match.updated_at)).to.be.greaterThan(0); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts index e2785a6ab26cc..0ca1ce4bf1eb7 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts @@ -104,9 +104,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .auth(user.username, user.password) .expect(200); expect(updatedAlert.snooze_schedule.length).to.eql(1); - // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time - const { startTime, duration } = updatedAlert.snooze_schedule[0]; - expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + // Due to latency, test to make sure the returned rRule.dtstart is within 10 seconds of the current time + const { rRule, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(rRule.dtstart) - now) < 10000).to.be(true); expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( true ); @@ -170,9 +170,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .auth(user.username, user.password) .expect(200); expect(updatedAlert.snooze_schedule.length).to.eql(1); - // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time - const { startTime, duration } = updatedAlert.snooze_schedule[0]; - expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + // Due to latency, test to make sure the returned rRule.dtstart is within 10 seconds of the current time + const { rRule, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(rRule.dtstart) - now) < 10000).to.be(true); expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( true ); @@ -247,9 +247,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .auth(user.username, user.password) .expect(200); expect(updatedAlert.snooze_schedule.length).to.eql(1); - // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time - const { startTime, duration } = updatedAlert.snooze_schedule[0]; - expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + // Due to latency, test to make sure the returned rRule.dtstart is within 10 seconds of the current time + const { rRule, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(rRule.dtstart) - now) < 10000).to.be(true); expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( true ); @@ -324,9 +324,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .auth(user.username, user.password) .expect(200); expect(updatedAlert.snooze_schedule.length).to.eql(1); - // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time - const { startTime, duration } = updatedAlert.snooze_schedule[0]; - expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + // Due to latency, test to make sure the returned rRule.dtstart is within 10 seconds of the current time + const { rRule, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(rRule.dtstart) - now) < 10000).to.be(true); expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be( true ); 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 c57ce50b25706..a33f7fc5a1a2c 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 @@ -181,6 +181,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { created_by: null, schedule: { interval: '1m' }, scheduled_task_id: response.body.scheduled_task_id, + snooze_schedule: response.body.snooze_schedule, updated_by: null, api_key_owner: 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 48b2fbf0ec60d..021a2be1ebb5d 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 @@ -73,9 +73,7 @@ const findTestUtils = ( created_at: match.created_at, updated_at: match.updated_at, execution_status: match.execution_status, - ...(describeType === 'internal' - ? { monitoring: match.monitoring, snooze_schedule: match.snooze_schedule } - : {}), + ...(describeType === 'internal' ? { monitoring: match.monitoring } : {}), }); expect(Date.parse(match.created_at)).to.be.greaterThan(0); expect(Date.parse(match.updated_at)).to.be.greaterThan(0); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts index 44c50318bd379..80cfa5a105467 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/snooze.ts @@ -76,9 +76,9 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext .set('kbn-xsrf', 'foo') .expect(200); expect(updatedAlert.snooze_schedule.length).to.eql(1); - // Due to latency, test to make sure the returned startTime is within 10 seconds of the current time - const { startTime, duration } = updatedAlert.snooze_schedule[0]; - expect(Math.abs(Date.parse(startTime) - now) < 10000).to.be(true); + // Due to latency, test to make sure the returned rRule.dtstart is within 10 seconds of the current time + const { rRule, duration } = updatedAlert.snooze_schedule[0]; + expect(Math.abs(Date.parse(rRule.dtstart) - now) < 10000).to.be(true); expect(Math.abs(duration - (Date.parse(FUTURE_SNOOZE_TIME) - now)) < 10000).to.be(true); expect(updatedAlert.mute_all).to.eql(false); // Ensure AAD isn't broken From 25f8f7dce5ea4a106c9b75beafa81ee861fb83cd Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Tue, 17 May 2022 19:37:20 -0500 Subject: [PATCH 43/45] Clarify isRuleSnoozed --- x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index 927a21cd8cb14..a950171beb291 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -10,6 +10,8 @@ import { SanitizedRule, RuleTypeParams } from '../../common/rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; +const WEEKDAY_CODES = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; + export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { if (rule.snoozeSchedule == null) { return null; @@ -59,7 +61,8 @@ export function isRuleSnoozed(rule: RuleSnoozeProps) { function parseByWeekday(byweekday: Array): ByWeekday[] { return byweekday.map((w) => { if (typeof w === 'number') return w; - if (['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA'].includes(w)) return w as WeekdayStr; + if (WEEKDAY_CODES.includes(w)) return w as WeekdayStr; + // Handle nth day codes, for example, +1MO means the first Monday of the month, -1FR means the last Friday, -2TH means second to last Thursday const dayOfWeek = w.slice(-2) as WeekdayStr; const nthDay = w.slice(0, 2); return RRule[dayOfWeek].nth(Number(nthDay)); From 02baeeabefd3b1c8dbc3c43d3f9882e0915de751 Mon Sep 17 00:00:00 2001 From: Zacqary Xeper Date: Wed, 18 May 2022 10:59:10 -0500 Subject: [PATCH 44/45] Use rrule to parse byweekday --- .../alerting/server/lib/is_rule_snoozed.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index a950171beb291..dce60a6e0ceb2 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -5,13 +5,11 @@ * 2.0. */ -import { RRule, ByWeekday, Weekday, WeekdayStr } from 'rrule'; +import { RRule, ByWeekday, Weekday, WeekdayStr, rrulestr } from 'rrule'; import { SanitizedRule, RuleTypeParams } from '../../common/rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>; -const WEEKDAY_CODES = ['SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA']; - export function getRuleSnoozeEndTime(rule: RuleSnoozeProps): Date | null { if (rule.snoozeSchedule == null) { return null; @@ -59,12 +57,7 @@ export function isRuleSnoozed(rule: RuleSnoozeProps) { } function parseByWeekday(byweekday: Array): ByWeekday[] { - return byweekday.map((w) => { - if (typeof w === 'number') return w; - if (WEEKDAY_CODES.includes(w)) return w as WeekdayStr; - // Handle nth day codes, for example, +1MO means the first Monday of the month, -1FR means the last Friday, -2TH means second to last Thursday - const dayOfWeek = w.slice(-2) as WeekdayStr; - const nthDay = w.slice(0, 2); - return RRule[dayOfWeek].nth(Number(nthDay)); - }); + const rRuleString = `RRULE:BYDAY=${byweekday.join(',')}`; + const parsedRRule = rrulestr(rRuleString); + return parsedRRule.origOptions.byweekday as ByWeekday[]; } From 07bb7e9769ac877071141829873872757b8e2644 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 18 May 2022 16:05:11 +0000 Subject: [PATCH 45/45] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts index dce60a6e0ceb2..7ae4b99e4df75 100644 --- a/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts +++ b/x-pack/plugins/alerting/server/lib/is_rule_snoozed.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { RRule, ByWeekday, Weekday, WeekdayStr, rrulestr } from 'rrule'; +import { RRule, ByWeekday, Weekday, rrulestr } from 'rrule'; import { SanitizedRule, RuleTypeParams } from '../../common/rule'; type RuleSnoozeProps = Pick, 'muteAll' | 'snoozeSchedule'>;