From 1b693324b65638265a78aa342a3158cfa9bac72a Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Wed, 10 Jan 2024 16:08:52 -0800 Subject: [PATCH 01/15] Adding the notification delay --- .../src/lifecycle_state.ts | 5 + .../src/task_state/v1/schema.ts | 5 + .../routes/rule/apis/create/schemas/v1.ts | 3 +- .../routes/rule/apis/create/types/v1.ts | 1 + .../common/routes/rule/response/index.ts | 1 + .../common/routes/rule/response/schemas/v1.ts | 5 + .../common/routes/rule/response/types/v1.ts | 1 + x-pack/plugins/alerting/common/rule.ts | 5 + x-pack/plugins/alerting/server/alert/alert.ts | 16 ++ .../create/schemas/create_rule_data_schema.ts | 7 +- .../methods/create/types/create_rule_data.ts | 1 + .../server/application/rule/schemas/index.ts | 1 + .../application/rule/schemas/rule_schemas.ts | 6 + ...ransform_rule_attributes_to_rule_domain.ts | 1 + .../transform_rule_domain_to_rule.ts | 1 + ...ransform_rule_domain_to_rule_attributes.ts | 1 + .../server/application/rule/types/rule.ts | 2 + .../server/data/rule/types/rule_attributes.ts | 5 + .../server/lib/get_alerts_for_notification.ts | 2 + .../transforms/transform_create_body/v1.ts | 3 + .../transform_rule_to_rule_response/v1.ts | 3 + .../saved_objects/schemas/raw_rule/v1.ts | 5 + .../server/task_runner/execution_handler.ts | 9 + x-pack/plugins/alerting/server/types.ts | 3 + .../server/utils/create_lifecycle_executor.ts | 76 +++++-- .../utils/get_alerts_for_notification.ts | 12 +- .../tests/alerting/group1/event_log.ts | 76 +++++++ .../alerts_as_data/alerts_as_data_flapping.ts | 2 +- .../tests/alerting/group4/index.ts | 1 + .../alerting/group4/notification_delay.ts | 210 ++++++++++++++++++ 30 files changed, 440 insertions(+), 29 deletions(-) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts diff --git a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts index 0d8fb3e5aaadae..239ef668ecd5cc 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts @@ -17,7 +17,12 @@ const trackedAlertStateRt = t.type({ flappingHistory: t.array(t.boolean), // flapping flag that indicates whether the alert is flapping flapping: t.boolean, + // count of consecutive recovered alerts for flapping + // will reset if the alert is active or if equal to the alert status change threshold pendingRecoveredCount: t.number, + // count of consecutive active alerts + // will reset if the alert is recovered + activeCount: t.number, }); export type TrackedLifecycleAlertState = t.TypeOf; diff --git a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts index 62e802483dcf7e..3781bc8cd74db4 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts @@ -32,8 +32,13 @@ export const metaSchema = schema.object({ // flapping flag that indicates whether the alert is flapping flapping: schema.maybe(schema.boolean()), maintenanceWindowIds: schema.maybe(schema.arrayOf(schema.string())), + // count of consecutive recovered alerts for flapping + // will reset if the alert is active or if equal to the alert status change threshold pendingRecoveredCount: schema.maybe(schema.number()), uuid: schema.maybe(schema.string()), + // count of consecutive active alerts + // will reset if the alert is recovered + activeCount: schema.maybe(schema.number()), }); export const rawAlertInstanceSchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts index 30a294d5c0527f..bdc60a6562fe20 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; -import { notifyWhenSchemaV1 } from '../../../response'; +import { notifyWhenSchemaV1, notificationDelaySchemaV1 } from '../../../response'; import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; export const actionFrequencySchema = schema.object({ @@ -68,6 +68,7 @@ export const createBodySchema = schema.object({ }), actions: schema.arrayOf(actionSchema, { defaultValue: [] }), notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)), + notification_delay: schema.maybe(notificationDelaySchemaV1), }); export const createParamsSchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts index 466f5d61eac466..328f44fe185bf4 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts @@ -30,6 +30,7 @@ export interface CreateRuleRequestBody { schedule: CreateBodySchema['schedule']; actions: CreateBodySchema['actions']; notify_when?: CreateBodySchema['notify_when']; + notification_delay?: CreateBodySchema['notification_delay']; } export interface CreateRuleResponse { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/index.ts b/x-pack/plugins/alerting/common/routes/rule/response/index.ts index ec8734240cc2bd..8e405c599e4835 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/index.ts @@ -37,6 +37,7 @@ export { ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1, notifyWhenSchema as notifyWhenSchemaV1, scheduleIdsSchema as scheduleIdsSchemaV1, + notificationDelaySchema as notificationDelaySchemaV1, } from './schemas/v1'; export type { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 1c7b202f59060b..5b1e31e19d6d52 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -182,6 +182,10 @@ export const ruleSnoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); +export const notificationDelaySchema = schema.object({ + active_count: schema.number(), +}); + export const ruleResponseSchema = schema.object({ id: schema.string(), enabled: schema.boolean(), @@ -214,6 +218,7 @@ export const ruleResponseSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), view_in_app_relative_url: schema.maybe(schema.nullable(schema.string())), + notification_delay: schema.maybe(notificationDelaySchema), }); export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); diff --git a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts index c6c2c7218ed886..453d1a96d24dcf 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts @@ -53,4 +53,5 @@ export interface RuleResponse { revision: RuleResponseSchemaType['revision']; running?: RuleResponseSchemaType['running']; view_in_app_relative_url?: RuleResponseSchemaType['view_in_app_relative_url']; + notification_delay?: RuleResponseSchemaType['notification_delay']; } diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index fdd0705629486f..09142166356353 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -141,6 +141,10 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; +export interface NotificationDelay { + activeCount: number; +} + export interface Rule { id: string; enabled: boolean; @@ -174,6 +178,7 @@ export interface Rule { revision: number; running?: boolean | null; viewInAppRelativeUrl?: string; + notificationDelay?: NotificationDelay; } export interface SanitizedAlertsFilter extends AlertsFilter { diff --git a/x-pack/plugins/alerting/server/alert/alert.ts b/x-pack/plugins/alerting/server/alert/alert.ts index 835b8db17a8930..51a9f29891bcf6 100644 --- a/x-pack/plugins/alerting/server/alert/alert.ts +++ b/x-pack/plugins/alerting/server/alert/alert.ts @@ -255,6 +255,7 @@ export class Alert< flappingHistory: this.meta.flappingHistory, flapping: this.meta.flapping, uuid: this.meta.uuid, + activeCount: this.meta.activeCount, }, } : { @@ -327,4 +328,19 @@ export class Alert< getMaintenanceWindowIds() { return this.meta.maintenanceWindowIds ?? []; } + + incrementActiveCount() { + if (!this.meta.activeCount) { + this.meta.activeCount = 0; + } + this.meta.activeCount++; + } + + getActiveCount() { + return this.meta.activeCount || 0; + } + + resetActiveCount() { + this.meta.activeCount = 0; + } } diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts index b7e55919969572..44a89e05992fe3 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts @@ -7,7 +7,11 @@ import { schema } from '@kbn/config-schema'; import { validateDuration } from '../../../validation'; -import { notifyWhenSchema, actionAlertsFilterSchema } from '../../../schemas'; +import { + notifyWhenSchema, + actionAlertsFilterSchema, + notificationDelaySchema, +} from '../../../schemas'; export const createRuleDataSchema = schema.object({ name: schema.string(), @@ -40,4 +44,5 @@ export const createRuleDataSchema = schema.object({ { defaultValue: [] } ), notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)), + notificationDelay: schema.maybe(notificationDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts index e75cbb5456c228..f99beda90e80ae 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts @@ -22,4 +22,5 @@ export interface CreateRuleData { schedule: CreateRuleDataType['schedule']; actions: CreateRuleDataType['actions']; notifyWhen?: CreateRuleDataType['notifyWhen']; + notificationDelay?: CreateRuleDataType['notificationDelay']; } diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts index 50cecadfe4a712..d039d190f1e96b 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts @@ -13,6 +13,7 @@ export { monitoringSchema, ruleSchema, ruleDomainSchema, + notificationDelaySchema, } from './rule_schemas'; export { diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts index ef8f1dc652bff5..680922c245b0c8 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts @@ -132,6 +132,10 @@ export const snoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); +export const notificationDelaySchema = schema.object({ + activeCount: schema.number(), +}); + /** * Unsanitized (domain) rule schema, used by internal rules clients */ @@ -168,6 +172,7 @@ export const ruleDomainSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), + notificationDelay: schema.maybe(notificationDelaySchema), }); /** @@ -205,4 +210,5 @@ export const ruleSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), + notificationDelay: schema.maybe(notificationDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts index 26831b9dff81c5..ea331cec4fcc31 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts @@ -216,6 +216,7 @@ export const transformRuleAttributesToRuleDomain = ( revision: ruleDomain.revision, running: ruleDomain.running, viewInAppRelativeUrl: ruleDomain.viewInAppRelativeUrl, + notificationDelay: ruleDomain.notificationDelay, }; if (isPublic) { diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts index 20eef8f851e082..2eb21c42587d3f 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts @@ -68,5 +68,6 @@ export const transformRuleDomainToRuleAttributes = ( ...(rule.nextRun !== undefined ? { nextRun: rule.nextRun?.toISOString() || null } : {}), revision: rule.revision, ...(rule.running !== undefined ? { running: rule.running } : {}), + ...(rule.notificationDelay !== undefined ? { notificationDelay: rule.notificationDelay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/application/rule/types/rule.ts b/x-pack/plugins/alerting/server/application/rule/types/rule.ts index d8dbabb72b23b3..1a7e7e1d371182 100644 --- a/x-pack/plugins/alerting/server/application/rule/types/rule.ts +++ b/x-pack/plugins/alerting/server/application/rule/types/rule.ts @@ -85,6 +85,7 @@ export interface Rule { revision: RuleSchemaType['revision']; running?: RuleSchemaType['running']; viewInAppRelativeUrl?: RuleSchemaType['viewInAppRelativeUrl']; + notificationDelay?: RuleSchemaType['notificationDelay']; } export interface RuleDomain { @@ -120,4 +121,5 @@ export interface RuleDomain { revision: RuleDomainSchemaType['revision']; running?: RuleDomainSchemaType['running']; viewInAppRelativeUrl?: RuleDomainSchemaType['viewInAppRelativeUrl']; + notificationDelay?: RuleSchemaType['notificationDelay']; } diff --git a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts index 19a669e6bd33ee..c7608ba02c5c18 100644 --- a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts @@ -142,6 +142,10 @@ interface RuleMetaAttributes { versionApiKeyLastmodified?: string; } +interface NotificationDelayAttributes { + activeCount: number; +} + export interface RuleAttributes { name: string; tags: string[]; @@ -174,4 +178,5 @@ export interface RuleAttributes { nextRun?: string | null; revision: number; running?: boolean | null; + notificationDelay?: NotificationDelayAttributes; } diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts index 4ff8408d67e119..63e95402549a4a 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts @@ -28,12 +28,14 @@ export function getAlertsForNotification< for (const id of keys(activeAlerts)) { const alert = activeAlerts[id]; + alert.incrementActiveCount(); alert.resetPendingRecoveredCount(); currentActiveAlerts[id] = alert; } for (const id of keys(currentRecoveredAlerts)) { const alert = recoveredAlerts[id]; + alert.resetActiveCount(); if (flappingSettings.enabled) { const flapping = alert.getFlapping(); if (flapping) { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index 97ff8b383e4c53..f06ef7b418d180 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -58,5 +58,8 @@ export const transformCreateBody = ( schedule: createBody.schedule, actions: transformCreateBodyActions(createBody.actions), ...(createBody.notify_when ? { notifyWhen: createBody.notify_when } : {}), + ...(createBody.notification_delay + ? { notificationDelay: { activeCount: createBody.notification_delay.active_count } } + : {}), }; }; diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index fb6799f2b5e480..907d114a15c113 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -119,4 +119,7 @@ export const transformRuleToRuleResponse = ( ...(rule.viewInAppRelativeUrl !== undefined ? { view_in_app_relative_url: rule.viewInAppRelativeUrl } : {}), + ...(rule.notificationDelay !== undefined + ? { notification_delay: { active_count: rule.notificationDelay.activeCount } } + : {}), }); diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts index 76c6241396dc30..ef124f38eacfa3 100644 --- a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts @@ -213,6 +213,10 @@ const rawRuleActionSchema = schema.object({ useAlertDataForTemplate: schema.maybe(schema.boolean()), }); +export const notificationDelaySchema = schema.object({ + activeCount: schema.number(), +}); + export const rawRuleSchema = schema.object({ name: schema.string(), enabled: schema.boolean(), @@ -270,4 +274,5 @@ export const rawRuleSchema = schema.object({ ), params: schema.recordOf(schema.string(), schema.maybe(schema.any())), typeVersion: schema.maybe(schema.number()), + notificationDelay: schema.maybe(notificationDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 3a464c8f305216..45c4d52907d4a2 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -625,6 +625,15 @@ export class ExecutionHandler< continue; } + if ( + this.rule.notificationDelay && + alert.getActiveCount() < this.rule.notificationDelay.activeCount + ) { + continue; + } else { + alert.resetActiveCount(); + } + const actionGroup = this.getActionGroup(alert); if (!this.ruleTypeActionGroups!.has(actionGroup)) { diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index f0b254c7673356..b03c27d1dbe488 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -60,6 +60,7 @@ import { AlertsFilter, AlertsFilterTimeframe, RuleAlertData, + NotificationDelay, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; import { RulesSettingsFlappingProperties } from '../common/rules_settings'; @@ -409,6 +410,7 @@ export type PublicRuleResultService = PublicLastRunSetters; export interface RawRuleLastRun extends SavedObjectAttributes, RuleLastRun {} export interface RawRuleMonitoring extends SavedObjectAttributes, RuleMonitoring {} +export interface RawNotificationDelay extends SavedObjectAttributes, NotificationDelay {} export interface RawRuleAlertsFilter extends AlertsFilter { query?: { @@ -485,6 +487,7 @@ export interface RawRule extends SavedObjectAttributes { nextRun?: string | null; revision: number; running?: boolean | null; + notificationDelay?: RawNotificationDelay; } export type { DataStreamAdapter } from './alerts_service/lib/data_stream_adapter'; diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 91d30fae7b3dc9..66df1cab108e36 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -290,7 +290,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount, activeCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -299,6 +299,7 @@ export const createLifecycleExecutor = ? state.trackedAlertsRecovered[alertId].flapping : false, pendingRecoveredCount: 0, + activeCount: 0, }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { @@ -342,6 +343,7 @@ export const createLifecycleExecutor = flappingHistory, flapping, pendingRecoveredCount, + activeCount, }; }); @@ -394,16 +396,32 @@ export const createLifecycleExecutor = const nextTrackedAlerts = Object.fromEntries( allEventsToIndex .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); const nextTrackedAlertsRecovered = Object.fromEntries( @@ -416,16 +434,32 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); return { diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index 878db2a9180227..cbea23154d6c6d 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -20,12 +20,14 @@ export function getAlertsForNotification( ) { return trackedEventsToIndex.map((trackedEvent) => { if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { + const count = trackedEvent.activeCount || 0; + trackedEvent.activeCount = count + 1; + trackedEvent.pendingRecoveredCount = 0; - } else if ( - flappingSettings.enabled && - trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED - ) { - if (trackedEvent.flapping) { + } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) { + trackedEvent.activeCount = 0; + + if (flappingSettings.enabled && trackedEvent.flapping) { const count = trackedEvent.pendingRecoveredCount || 0; trackedEvent.pendingRecoveredCount = count + 1; if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 4b0bcba103e109..b0b7162888b464 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -1848,6 +1848,82 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(hasActions).eql(false); }); + + it('should generate expected events with a notificationDelay', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const pattern = { + instance: [true, true, true, false, true], + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + notification_delay: { + active_count: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 5 }], + ['execute', { gte: 5 }], + ['new-instance', { equal: 2 }], + ['active-instance', { gte: 1 }], + ['recovered-instance', { equal: 1 }], + ]), + }); + }); + + const actualTriggeredActions = events + .filter((event) => event?.event?.action === 'execute') + .reduce( + (acc, event) => + acc + + (event?.kibana?.alert?.rule?.execution?.metrics + ?.number_of_triggered_actions as number), + 0 + ); + expect(actualTriggeredActions).to.eql(1); + }); }); } }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts index 87e2d8d91b59f6..59cc5c14730a1e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_flapping.ts @@ -21,7 +21,7 @@ import { } from '../../../../../common/lib'; // eslint-disable-next-line import/no-default-export -export default function createAlertsAsDataInstallResourcesTest({ getService }: FtrProviderContext) { +export default function createAlertsAsDataFlappingTest({ getService }: FtrProviderContext) { const es = getService('es'); const retry = getService('retry'); const supertest = getService('supertest'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts index fda6c1b9c80b26..b73477cf3df302 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts @@ -28,6 +28,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./run_soon')); loadTestFile(require.resolve('./flapping_history')); loadTestFile(require.resolve('./check_registered_rule_types')); + loadTestFile(require.resolve('./notification_delay')); loadTestFile(require.resolve('./generate_alert_schemas')); // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts new file mode 100644 index 00000000000000..598cbce2e0d93a --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts @@ -0,0 +1,210 @@ +/* + * 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 expect from '@kbn/expect'; +import { get } from 'lodash'; +import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { Spaces } from '../../../scenarios'; + +// eslint-disable-next-line import/no-default-export +export default function createNotificationDelayTests({ getService }: FtrProviderContext) { + const es = getService('es'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const retry = getService('retry'); + const supertest = getService('supertest'); + const space = Spaces.default; + + const ACTIVE_PATH = 'alertInstances.instance.meta.activeCount'; + const RECOVERED_PATH = 'alertRecoveredInstances.instance.meta.activeCount'; + + describe('Notification Delay', () => { + let actionId: string; + const objectRemover = new ObjectRemover(supertestWithoutAuth); + + before(async () => { + actionId = await createAction(); + }); + + after(async () => { + objectRemover.add(space.id, actionId, 'connector', 'actions'); + await objectRemover.removeAll(); + }); + + afterEach(() => objectRemover.removeAll()); + + it('should clear the activeCount if the notificationDelay is not configured for the rule', async () => { + const start = new Date().toISOString(); + const pattern = { + instance: [true], + }; + + const ruleId = await createRule(actionId, pattern); + objectRemover.add(space.id, ruleId, 'rule', 'alerting'); + + const state = await getAlertState(start, ruleId, 0); + expect(get(state, ACTIVE_PATH)).to.eql(0); + }); + + it('should update the activeCount when alert is active and clear when recovered if the notificationDelay is configured for the rule', async () => { + let start = new Date().toISOString(); + const pattern = { + instance: [true, true, true, false, true], + }; + + const ruleId = await createRule(actionId, pattern, 20); + objectRemover.add(space.id, ruleId, 'rule', 'alerting'); + + let state = await getAlertState(start, ruleId); + expect(get(state, ACTIVE_PATH)).to.eql(1); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 2, true); + expect(get(state, ACTIVE_PATH)).to.eql(2); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 3, true); + expect(get(state, ACTIVE_PATH)).to.eql(3); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 0, true, true); + expect(get(state, RECOVERED_PATH)).to.eql(0); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 1, true); + expect(get(state, ACTIVE_PATH)).to.eql(1); + }); + + it('should reset the activeCount when count of consecutive active alerts exceeds the notificationDelay count', async () => { + let start = new Date().toISOString(); + const pattern = { + instance: [true, true, true, true, true], + }; + + const ruleId = await createRule(actionId, pattern, 3); + objectRemover.add(space.id, ruleId, 'rule', 'alerting'); + + let state = await getAlertState(start, ruleId); + expect(get(state, ACTIVE_PATH)).to.eql(1); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 2, true); + expect(get(state, ACTIVE_PATH)).to.eql(2); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 0, true); + expect(get(state, ACTIVE_PATH)).to.eql(0); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 1, true); + expect(get(state, ACTIVE_PATH)).to.eql(1); + + start = new Date().toISOString(); + state = await getAlertState(start, ruleId, 2, true); + expect(get(state, ACTIVE_PATH)).to.eql(2); + }); + }); + + async function getState(start: string, count: number, recovered: boolean) { + const result: any = await retry.try(async () => { + const searchResult = await es.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.taskType': 'alerting:test.patternFiring', + }, + }, + { + range: { + 'task.scheduledAt': { + gte: start, + }, + }, + }, + ], + }, + }, + }, + }); + + const taskDoc: any = searchResult.hits.hits[0]; + const state = JSON.parse(taskDoc._source.task.state); + const activeCount = recovered ? get(state, RECOVERED_PATH) : get(state, ACTIVE_PATH); + if (activeCount !== count) { + throw new Error(`Expected ${count} rule executions but received ${activeCount}.`); + } + + return state; + }); + + return result; + } + + async function getAlertState( + start: string, + ruleId: string, + count: number = 1, + runRule: boolean = false, + recovered: boolean = false + ) { + if (runRule) { + const response = await supertest + .post(`${getUrlPrefix(space.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(response.status).to.eql(204); + } + return await getState(start, count, recovered); + } + + async function createRule( + actionId: string, + pattern: { instance: boolean[] }, + activeCount?: number + ) { + const { body: createdRule } = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiring', + schedule: { interval: '24h' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: actionId, + group: 'default', + params: {}, + }, + ], + ...(activeCount ? { notification_delay: { active_count: activeCount } } : {}), + }) + ) + .expect(200); + return createdRule.id; + } + + async function createAction() { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + return createdAction.id; + } +} From 89cc726cfe22b3da388d817dec336677ce972451 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Fri, 12 Jan 2024 10:22:45 -0800 Subject: [PATCH 02/15] Adding AAD test --- .../server/utils/create_lifecycle_executor.ts | 86 +++++++------------ .../utils/get_alerts_for_notification.ts | 5 -- .../tests/alerting/group1/event_log.ts | 76 ++++++++++++++++ 3 files changed, 109 insertions(+), 58 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 66df1cab108e36..87148f9e1a4145 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -290,7 +290,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { alertUuid, started, flapping, pendingRecoveredCount, activeCount } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -299,7 +299,6 @@ export const createLifecycleExecutor = ? state.trackedAlertsRecovered[alertId].flapping : false, pendingRecoveredCount: 0, - activeCount: 0, }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { @@ -343,7 +342,6 @@ export const createLifecycleExecutor = flappingHistory, flapping, pendingRecoveredCount, - activeCount, }; }); @@ -396,32 +394,23 @@ export const createLifecycleExecutor = const nextTrackedAlerts = Object.fromEntries( allEventsToIndex .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map( - ({ - event, - flappingHistory, - flapping: isCurrentlyFlapping, - pendingRecoveredCount, - activeCount, - }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { alertId, - { - alertId, - alertUuid, - started, - flappingHistory, - flapping, - pendingRecoveredCount, - activeCount, - }, - ]; - } - ) + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + }, + ]; + }) ); const nextTrackedAlertsRecovered = Object.fromEntries( @@ -434,32 +423,23 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map( - ({ - event, - flappingHistory, - flapping: isCurrentlyFlapping, - pendingRecoveredCount, - activeCount, - }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ + .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { alertId, - { - alertId, - alertUuid, - started, - flappingHistory, - flapping, - pendingRecoveredCount, - activeCount, - }, - ]; - } - ) + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + }, + ]; + }) ); return { diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index cbea23154d6c6d..8e0df34587762a 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -20,13 +20,8 @@ export function getAlertsForNotification( ) { return trackedEventsToIndex.map((trackedEvent) => { if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { - const count = trackedEvent.activeCount || 0; - trackedEvent.activeCount = count + 1; - trackedEvent.pendingRecoveredCount = 0; } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) { - trackedEvent.activeCount = 0; - if (flappingSettings.enabled && trackedEvent.flapping) { const count = trackedEvent.pendingRecoveredCount || 0; trackedEvent.pendingRecoveredCount = count + 1; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index b0b7162888b464..b8116ad68b7c7e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -1924,6 +1924,82 @@ export default function eventLogTests({ getService }: FtrProviderContext) { ); expect(actualTriggeredActions).to.eql(1); }); + + it('should generate expected events with a notificationDelay with AAD', async () => { + const { body: createdAction } = await supertest + .post(`${getUrlPrefix(space.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const pattern = { + instance: [true, true, true, false, true], + }; + + const response = await supertest + .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiringAad', + schedule: { interval: '1s' }, + throttle: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + }, + ], + notification_delay: { + active_count: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const alertId = response.body.id; + objectRemover.add(space.id, alertId, 'rule', 'alerting'); + + // get the events we're expecting + const events = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: space.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([ + // make sure the counts of the # of events per type are as expected + ['execute-start', { gte: 5 }], + ['execute', { gte: 5 }], + ['new-instance', { equal: 2 }], + ['active-instance', { gte: 1 }], + ['recovered-instance', { equal: 1 }], + ]), + }); + }); + + const actualTriggeredActions = events + .filter((event) => event?.event?.action === 'execute') + .reduce( + (acc, event) => + acc + + (event?.kibana?.alert?.rule?.execution?.metrics + ?.number_of_triggered_actions as number), + 0 + ); + expect(actualTriggeredActions).to.eql(1); + }); }); } }); From 275520b3839b705850a9b1f98e24cc43ba561d67 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Fri, 12 Jan 2024 10:26:02 -0800 Subject: [PATCH 03/15] Removing changes to rule registry --- .../server/utils/create_lifecycle_executor.ts | 18 ++---------------- .../utils/get_alerts_for_notification.ts | 7 +++++-- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 87148f9e1a4145..91d30fae7b3dc9 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -401,14 +401,7 @@ export const createLifecycleExecutor = const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, - { - alertId, - alertUuid, - started, - flappingHistory, - flapping, - pendingRecoveredCount, - }, + { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, ]; }) ); @@ -430,14 +423,7 @@ export const createLifecycleExecutor = const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); return [ alertId, - { - alertId, - alertUuid, - started, - flappingHistory, - flapping, - pendingRecoveredCount, - }, + { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, ]; }) ); diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index 8e0df34587762a..878db2a9180227 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -21,8 +21,11 @@ export function getAlertsForNotification( return trackedEventsToIndex.map((trackedEvent) => { if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { trackedEvent.pendingRecoveredCount = 0; - } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) { - if (flappingSettings.enabled && trackedEvent.flapping) { + } else if ( + flappingSettings.enabled && + trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED + ) { + if (trackedEvent.flapping) { const count = trackedEvent.pendingRecoveredCount || 0; trackedEvent.pendingRecoveredCount = count + 1; if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { From 293166644d07762f250523eb6966409da145f621 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Fri, 12 Jan 2024 11:13:15 -0800 Subject: [PATCH 04/15] Change activeCount to active --- .../alerting/common/routes/rule/response/schemas/v1.ts | 2 +- x-pack/plugins/alerting/common/rule.ts | 2 +- .../alerting/server/application/rule/schemas/rule_schemas.ts | 2 +- .../alerting/server/data/rule/types/rule_attributes.ts | 2 +- .../rule/apis/create/transforms/transform_create_body/v1.ts | 4 +--- .../rule/transforms/transform_rule_to_rule_response/v1.ts | 4 +--- .../alerting/server/saved_objects/schemas/raw_rule/v1.ts | 2 +- .../plugins/alerting/server/task_runner/execution_handler.ts | 2 +- .../spaces_only/tests/alerting/group1/event_log.ts | 4 ++-- .../spaces_only/tests/alerting/group4/notification_delay.ts | 2 +- 10 files changed, 11 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 5b1e31e19d6d52..67f57926d54603 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -183,7 +183,7 @@ export const ruleSnoozeScheduleSchema = schema.object({ }); export const notificationDelaySchema = schema.object({ - active_count: schema.number(), + active: schema.number(), }); export const ruleResponseSchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 09142166356353..590c1d4312d57b 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -142,7 +142,7 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; export interface NotificationDelay { - activeCount: number; + active: number; } export interface Rule { diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts index 680922c245b0c8..b75a6e4f76aade 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts @@ -133,7 +133,7 @@ export const snoozeScheduleSchema = schema.object({ }); export const notificationDelaySchema = schema.object({ - activeCount: schema.number(), + active: schema.number(), }); /** diff --git a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts index c7608ba02c5c18..316578149c5ca8 100644 --- a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts @@ -143,7 +143,7 @@ interface RuleMetaAttributes { } interface NotificationDelayAttributes { - activeCount: number; + active: number; } export interface RuleAttributes { diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index f06ef7b418d180..fcf92b386aaa2b 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -58,8 +58,6 @@ export const transformCreateBody = ( schedule: createBody.schedule, actions: transformCreateBodyActions(createBody.actions), ...(createBody.notify_when ? { notifyWhen: createBody.notify_when } : {}), - ...(createBody.notification_delay - ? { notificationDelay: { activeCount: createBody.notification_delay.active_count } } - : {}), + ...(createBody.notification_delay ? { notificationDelay: createBody.notification_delay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index 907d114a15c113..4f55401160ce9f 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -119,7 +119,5 @@ export const transformRuleToRuleResponse = ( ...(rule.viewInAppRelativeUrl !== undefined ? { view_in_app_relative_url: rule.viewInAppRelativeUrl } : {}), - ...(rule.notificationDelay !== undefined - ? { notification_delay: { active_count: rule.notificationDelay.activeCount } } - : {}), + ...(rule.notificationDelay !== undefined ? { notification_delay: rule.notificationDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts index ef124f38eacfa3..495c2493f2e434 100644 --- a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts @@ -214,7 +214,7 @@ const rawRuleActionSchema = schema.object({ }); export const notificationDelaySchema = schema.object({ - activeCount: schema.number(), + active: schema.number(), }); export const rawRuleSchema = schema.object({ diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 45c4d52907d4a2..b10fd885bbc4cf 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -627,7 +627,7 @@ export class ExecutionHandler< if ( this.rule.notificationDelay && - alert.getActiveCount() < this.rule.notificationDelay.activeCount + alert.getActiveCount() < this.rule.notificationDelay.active ) { continue; } else { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index b8116ad68b7c7e..5228b1c76d3d92 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -1885,7 +1885,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }, ], notification_delay: { - active_count: 3, + active: 3, }, }) ); @@ -1961,7 +1961,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }, ], notification_delay: { - active_count: 3, + active: 3, }, }) ); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts index 598cbce2e0d93a..2b632686d57936 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts @@ -187,7 +187,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider params: {}, }, ], - ...(activeCount ? { notification_delay: { active_count: activeCount } } : {}), + ...(activeCount ? { notification_delay: { active: activeCount } } : {}), }) ) .expect(200); From 4f160befa056f924cff2eb77ef1ab250decdaf67 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Fri, 12 Jan 2024 13:28:04 -0800 Subject: [PATCH 05/15] Fixing tests --- .../src/lifecycle_state.ts | 5 +-- .../src/task_state/v1/schema.ts | 4 +-- .../lib/get_alerts_for_notification.test.ts | 36 ++++++++++++++----- .../alerting/server/task_runner/fixtures.ts | 1 + .../server/task_runner/task_runner.test.ts | 3 ++ 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts index 239ef668ecd5cc..8fa5cb111f6d91 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts @@ -18,11 +18,8 @@ const trackedAlertStateRt = t.type({ // flapping flag that indicates whether the alert is flapping flapping: t.boolean, // count of consecutive recovered alerts for flapping - // will reset if the alert is active or if equal to the alert status change threshold + // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: t.number, - // count of consecutive active alerts - // will reset if the alert is recovered - activeCount: t.number, }); export type TrackedLifecycleAlertState = t.TypeOf; diff --git a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts index 3781bc8cd74db4..247c0d7fc8d87f 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts @@ -33,11 +33,11 @@ export const metaSchema = schema.object({ flapping: schema.maybe(schema.boolean()), maintenanceWindowIds: schema.maybe(schema.arrayOf(schema.string())), // count of consecutive recovered alerts for flapping - // will reset if the alert is active or if equal to the alert status change threshold + // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: schema.maybe(schema.number()), uuid: schema.maybe(schema.string()), // count of consecutive active alerts - // will reset if the alert is recovered + // will reset if the alert is recovered or if equal to notificationDelay.active stored in the rule activeCount: schema.maybe(schema.number()), }); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index 04f732426cff5a..883c2775dcbed9 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -35,6 +35,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 1, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -49,6 +50,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 1, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -59,6 +61,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 1, "flapping": false, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -105,6 +108,7 @@ describe('getAlertsForNotification', () => { Object { "3": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -129,6 +133,7 @@ describe('getAlertsForNotification', () => { Object { "3": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -141,18 +146,19 @@ describe('getAlertsForNotification', () => { `); expect(Object.values(currentActiveAlerts).map((a) => a.getScheduledActionOptions())) .toMatchInlineSnapshot(` - Array [ - Object { - "actionGroup": "default", - "context": Object {}, - "state": Object {}, - }, - ] - `); + Array [ + Object { + "actionGroup": "default", + "context": Object {}, + "state": Object {}, + }, + ] + `); expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(` Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -163,6 +169,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -176,6 +183,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -186,6 +194,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -233,6 +242,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [ true, @@ -247,6 +257,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [ true, @@ -261,6 +272,7 @@ describe('getAlertsForNotification', () => { }, "3": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [ true, @@ -279,6 +291,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [ true, @@ -293,6 +306,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [ true, @@ -307,6 +321,7 @@ describe('getAlertsForNotification', () => { }, "3": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [ true, @@ -357,6 +372,7 @@ describe('getAlertsForNotification', () => { Object { "3": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -385,6 +401,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -395,6 +412,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -408,6 +426,7 @@ describe('getAlertsForNotification', () => { Object { "1": Object { "meta": Object { + "activeCount": 0, "flapping": true, "flappingHistory": Array [], "maintenanceWindowIds": Array [], @@ -418,6 +437,7 @@ describe('getAlertsForNotification', () => { }, "2": Object { "meta": Object { + "activeCount": 0, "flapping": false, "flappingHistory": Array [], "maintenanceWindowIds": Array [], diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index b975f213040133..3d647966414f5c 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -454,6 +454,7 @@ export const generateAlertInstance = ( flapping: false, maintenanceWindowIds: maintenanceWindowIds || [], pendingRecoveredCount: 0, + activeCount: 0, }, state: { bar: false, 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 d395ff3364cd73..d14d44010252d8 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 @@ -2954,6 +2954,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, state: { duration: '0', @@ -3124,6 +3125,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, state: { duration: '0', @@ -3141,6 +3143,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, state: { duration: '0', From f655c35141ad7c0aff17d7cbedc89e41e6ae561f Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Tue, 16 Jan 2024 10:24:54 -0800 Subject: [PATCH 06/15] Adding jest tests --- .../alerting/server/alert/alert.test.ts | 43 +++++ .../lib/get_alerts_for_notification.test.ts | 126 ++++++++++++++ .../task_runner/execution_handler.test.ts | 162 ++++++++++++++++++ .../server/task_runner/execution_handler.ts | 7 + 4 files changed, 338 insertions(+) diff --git a/x-pack/plugins/alerting/server/alert/alert.test.ts b/x-pack/plugins/alerting/server/alert/alert.test.ts index a0d0ef25f9a869..6008f862891412 100644 --- a/x-pack/plugins/alerting/server/alert/alert.test.ts +++ b/x-pack/plugins/alerting/server/alert/alert.test.ts @@ -543,6 +543,7 @@ describe('toRaw', () => { }, flappingHistory: [false, true, true], pendingRecoveredCount: 2, + activeCount: 1, }, }; const alertInstance = new Alert( @@ -562,6 +563,7 @@ describe('toRaw', () => { }, flappingHistory: [false, true, true], flapping: false, + activeCount: 1, }, }; const alertInstance = new Alert( @@ -574,6 +576,7 @@ describe('toRaw', () => { flapping: false, maintenanceWindowIds: [], uuid: expect.any(String), + activeCount: 1, }, }); }); @@ -746,3 +749,43 @@ describe('isFilteredOut', () => { expect(alert.isFilteredOut(summarizedAlerts)).toBe(true); }); }); + +describe('incrementActiveCount', () => { + test('correctly increments activeCount', () => { + const alert = new Alert('1', { + meta: { activeCount: 3 }, + }); + alert.incrementActiveCount(); + expect(alert.getActiveCount()).toEqual(4); + }); + + test('correctly increments activeCount when it is not already defined', () => { + const alert = new Alert('1'); + alert.incrementActiveCount(); + expect(alert.getActiveCount()).toEqual(1); + }); +}); + +describe('getActiveCount', () => { + test('returns ActiveCount', () => { + const alert = new Alert('1', { + meta: { activeCount: 3 }, + }); + expect(alert.getActiveCount()).toEqual(3); + }); + + test('defines and returns activeCount when it is not already defined', () => { + const alert = new Alert('1'); + expect(alert.getActiveCount()).toEqual(0); + }); +}); + +describe('resetActiveCount', () => { + test('resets activeCount to 0', () => { + const alert = new Alert('1', { + meta: { activeCount: 3 }, + }); + alert.resetActiveCount(); + expect(alert.getActiveCount()).toEqual(0); + }); +}); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index 883c2775dcbed9..c1465d5b7a2388 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -448,4 +448,130 @@ describe('getAlertsForNotification', () => { } `); }); + + test('should increment activeCount for all active alerts', () => { + const alert1 = new Alert('1', { + meta: { activeCount: 1, uuid: 'uuid-1' }, + }); + const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); + + const { newAlerts, activeAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + { + '1': alert1, + }, + { + '1': alert1, + '2': alert2, + }, + {}, + {} + ); + expect(newAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + } + `); + expect(activeAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + "2": Object { + "meta": Object { + "activeCount": 1, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-2", + }, + "state": Object {}, + }, + } + `); + }); + + test('should reset activeCount for all recovered alerts', () => { + const alert1 = new Alert('1', { meta: { activeCount: 3 } }); + const alert3 = new Alert('3'); + + const { recoveredAlerts, currentRecoveredAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + {}, + {}, + { + '1': alert1, + '3': alert3, + }, + { + '1': alert1, + '3': alert3, + } + ); + + expect(alertsWithAnyUUID(recoveredAlerts)).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 0, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "uuid": Any, + }, + "state": Object {}, + }, + "3": Object { + "meta": Object { + "activeCount": 0, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "uuid": Any, + }, + "state": Object {}, + }, + } + `); + expect(alertsWithAnyUUID(currentRecoveredAlerts)).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 0, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "uuid": Any, + }, + "state": Object {}, + }, + "3": Object { + "meta": Object { + "activeCount": 0, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "uuid": Any, + }, + "state": Object {}, + }, + } + `); + }); }); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index c08fb5490fbb93..a10b38c0ec5001 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -160,6 +160,7 @@ const generateAlert = ({ lastScheduledActionsGroup = 'default', maintenanceWindowIds, pendingRecoveredCount, + activeCount, }: { id: number; group?: ActiveActionGroup | 'recovered'; @@ -170,6 +171,7 @@ const generateAlert = ({ lastScheduledActionsGroup?: string; maintenanceWindowIds?: string[]; pendingRecoveredCount?: number; + activeCount?: number; }) => { const alert = new Alert( String(id), @@ -183,6 +185,7 @@ const generateAlert = ({ actions: throttledActions, }, pendingRecoveredCount, + activeCount, }, } ); @@ -2006,6 +2009,165 @@ describe('Execution Handler', () => { `); }); + test('does not schedule actions for alerts with activeCount less than the notificationDelay.active threshold', async () => { + const executionHandler = new ExecutionHandler( + generateExecutionParams({ + ...defaultExecutionParams, + rule: { + ...defaultExecutionParams.rule, + notificationDelay: { + active: 3, + }, + }, + }) + ); + + await executionHandler.run({ + ...generateAlert({ id: 1 }), + ...generateAlert({ id: 2, activeCount: 2 }), + }); + + expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); + expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(2); + + expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( + 'no scheduling of action "1" for rule "1": the alert activeCount: 0 is less than the rule notificationDelay.active: 3 threshold.' + ); + expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( + 'no scheduling of action "1" for rule "1": the alert activeCount: 2 is less than the rule notificationDelay.active: 3 threshold.' + ); + }); + + test('schedules actions for alerts with activeCount greater than or equal the notificationDelay.active threshold', async () => { + const executionHandler = new ExecutionHandler( + generateExecutionParams({ + ...defaultExecutionParams, + rule: { + ...defaultExecutionParams.rule, + notificationDelay: { + active: 3, + }, + }, + }) + ); + + await executionHandler.run({ + ...generateAlert({ id: 1, activeCount: 3 }), + ...generateAlert({ id: 2, activeCount: 4 }), + }); + + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); + }); + + test('schedules actions if notificationDelay.active threshold is not defined', async () => { + const executionHandler = new ExecutionHandler(generateExecutionParams()); + + await executionHandler.run({ + ...generateAlert({ id: 1, activeCount: 1 }), + }); + + expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); + expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "actionTypeId": "test", + "apiKey": "MTIzOmFiYw==", + "consumer": "rule-consumer", + "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", + "id": "1", + "params": Object { + "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", + "contextVal": "My goes here", + "foo": true, + "stateVal": "My goes here", + }, + "relatedSavedObjects": Array [ + Object { + "id": "1", + "namespace": "test1", + "type": "alert", + "typeId": "test", + }, + ], + "source": Object { + "source": Object { + "id": "1", + "type": "alert", + }, + "type": "SAVED_OBJECT", + }, + "spaceId": "test1", + }, + ], + ] + `); + }); + describe('rule url', () => { const ruleWithUrl = { ...rule, diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index b10fd885bbc4cf..c0fcfb0e682293 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -629,6 +629,13 @@ export class ExecutionHandler< this.rule.notificationDelay && alert.getActiveCount() < this.rule.notificationDelay.active ) { + this.logger.debug( + `no scheduling of action "${action.id}" for rule "${ + this.taskInstance.params.alertId + }": the alert activeCount: ${alert.getActiveCount()} is less than the rule notificationDelay.active: ${ + this.rule.notificationDelay.active + } threshold.` + ); continue; } else { alert.resetActiveCount(); From 95ddef591b06ee4ea9f866a5117d412e6e3780c7 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Thu, 18 Jan 2024 11:45:46 -0800 Subject: [PATCH 07/15] Adding notification delay to the front end --- x-pack/plugins/alerting/common/rule.ts | 2 +- .../server/routes/lib/rewrite_rule.ts | 2 + .../alerting/server/routes/update_rule.ts | 15 +- .../server/rules_client/methods/update.ts | 7 +- x-pack/plugins/alerting/server/types.ts | 3 +- .../lib/rule_api/common_transformations.ts | 2 + .../public/application/lib/rule_api/create.ts | 2 + .../public/application/lib/rule_api/update.ts | 25 ++- .../sections/rule_form/rule_form.tsx | 144 ++++++++++++------ .../sections/rule_form/rule_reducer.ts | 26 ++++ 10 files changed, 175 insertions(+), 53 deletions(-) diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 590c1d4312d57b..4044761db62710 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -141,7 +141,7 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; -export interface NotificationDelay { +export interface NotificationDelay extends SavedObjectAttributes { active: number; } 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 953211a5ef4f77..24c887f62fc8b4 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -37,6 +37,7 @@ export const rewriteRule = ({ activeSnoozes, lastRun, nextRun, + notificationDelay, ...rest }: SanitizedRule & { activeSnoozes?: string[] }) => ({ ...rest, @@ -78,4 +79,5 @@ export const rewriteRule = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), + ...(notificationDelay !== undefined ? { notification_delay: notificationDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index d24af256de6139..aa0615b6d9d1b1 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -52,16 +52,27 @@ const bodySchema = schema.object({ ) ) ), + notification_delay: schema.maybe( + schema.object({ + active: schema.number(), + }) + ), }); const rewriteBodyReq: RewriteRequestCase> = (result) => { - const { notify_when: notifyWhen, actions, ...rest } = result.data; + const { + notify_when: notifyWhen, + notification_delay: notificationDelay, + actions, + ...rest + } = result.data; return { ...result, data: { ...rest, notifyWhen, actions: rewriteActionsReq(actions), + notificationDelay, }, }; }; @@ -83,6 +94,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ isSnoozedUntil, lastRun, nextRun, + notificationDelay, ...rest }) => ({ ...rest, @@ -115,6 +127,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), + ...(notificationDelay ? { notification_delay: notificationDelay } : {}), }); export const updateRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index 8fb0de5f3519ca..b00fb17d6d4d37 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -17,7 +17,11 @@ import { } from '../../types'; import { validateRuleTypeParams, getRuleNotifyWhenType } from '../../lib'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { parseDuration, getRuleCircuitBreakerErrorMessage } from '../../../common'; +import { + parseDuration, + getRuleCircuitBreakerErrorMessage, + NotificationDelay, +} from '../../../common'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; @@ -51,6 +55,7 @@ export interface UpdateOptions { params: Params; throttle?: string | null; notifyWhen?: RuleNotifyWhenType | null; + notificationDelay?: NotificationDelay; }; allowMissingConnectorSecrets?: boolean; shouldIncrementRevision?: ShouldIncrementRevision; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index b03c27d1dbe488..1627a74e867bf8 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -410,7 +410,6 @@ export type PublicRuleResultService = PublicLastRunSetters; export interface RawRuleLastRun extends SavedObjectAttributes, RuleLastRun {} export interface RawRuleMonitoring extends SavedObjectAttributes, RuleMonitoring {} -export interface RawNotificationDelay extends SavedObjectAttributes, NotificationDelay {} export interface RawRuleAlertsFilter extends AlertsFilter { query?: { @@ -487,7 +486,7 @@ export interface RawRule extends SavedObjectAttributes { nextRun?: string | null; revision: number; running?: boolean | null; - notificationDelay?: RawNotificationDelay; + notificationDelay?: NotificationDelay; } export type { DataStreamAdapter } from './alerts_service/lib/data_stream_adapter'; 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 64949d9014c507..42c9b210f4452d 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 @@ -77,6 +77,7 @@ export const transformRule: RewriteRequestCase = ({ active_snoozes: activeSnoozes, last_run: lastRun, next_run: nextRun, + notification_delay: notificationDelay, ...rest }: any) => ({ ruleTypeId, @@ -99,6 +100,7 @@ export const transformRule: RewriteRequestCase = ({ ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), ...(nextRun ? { nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), + ...(notificationDelay ? { notificationDelay } : {}), ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts index 2304ee8c489301..58638670e820ec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts @@ -23,6 +23,7 @@ type RuleCreateBody = Omit< const rewriteBodyRequest: RewriteResponseCase = ({ ruleTypeId, actions, + notificationDelay, ...res }): any => ({ ...res, @@ -43,6 +44,7 @@ const rewriteBodyRequest: RewriteResponseCase = ({ : {}), }) ), + ...(notificationDelay ? { notification_delay: notificationDelay } : {}), }); export async function createRule({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts index 52158bfa2f0343..68515df7b22f03 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts @@ -13,9 +13,20 @@ import { transformRule } from './common_transformations'; type RuleUpdatesBody = Pick< RuleUpdates, - 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'throttle' | 'notifyWhen' + | 'name' + | 'tags' + | 'schedule' + | 'actions' + | 'params' + | 'throttle' + | 'notifyWhen' + | 'notificationDelay' >; -const rewriteBodyRequest: RewriteResponseCase = ({ actions, ...res }): any => ({ +const rewriteBodyRequest: RewriteResponseCase = ({ + actions, + notificationDelay, + ...res +}): any => ({ ...res, actions: actions.map( ({ group, id, params, frequency, uuid, alertsFilter, useAlertDataForTemplate }) => ({ @@ -34,6 +45,7 @@ const rewriteBodyRequest: RewriteResponseCase = ({ actions, ... ...(uuid && { uuid }), }) ), + ...(notificationDelay ? { notification_delay: notificationDelay } : {}), }); export async function updateRule({ @@ -42,14 +54,19 @@ export async function updateRule({ id, }: { http: HttpSetup; - rule: Pick; + rule: Pick< + RuleUpdates, + 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notificationDelay' + >; id: string; }): Promise { const res = await http.put>( `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, { body: JSON.stringify( - rewriteBodyRequest(pick(rule, ['name', 'tags', 'schedule', 'params', 'actions'])) + rewriteBodyRequest( + pick(rule, ['name', 'tags', 'schedule', 'params', 'actions', 'notificationDelay']) + ) ), } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 98e2547dabdd3a..f71fcefe68b3ae 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -215,6 +215,9 @@ export const RuleForm = ({ ? getDurationUnitValue(rule.schedule.interval) : defaultScheduleIntervalUnit ); + const [notificationDelay, setNotificationDelay] = useState( + rule.notificationDelay?.active + ); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); const [availableRuleTypes, setAvailableRuleTypes] = useState([]); @@ -328,6 +331,12 @@ export const RuleForm = ({ } }, [rule.schedule.interval, defaultScheduleInterval, defaultScheduleIntervalUnit]); + useEffect(() => { + if (rule.notificationDelay) { + setNotificationDelay(rule.notificationDelay.active); + } + }, [rule.notificationDelay]); + useEffect(() => { if (!flyoutBodyOverflowRef.current) { // We're using this as a reliable way to reset the scroll position @@ -393,6 +402,10 @@ export const RuleForm = ({ [dispatch] ); + const setNotificationDelayProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setNotificationDelayProperty' }, payload: { key, value } }); + }; + useEffect(() => { const searchValue = searchText ? searchText.trim().toLocaleLowerCase() : null; setFilteredRuleTypes( @@ -766,51 +779,94 @@ export const RuleForm = ({ ) : null} {hideInterval !== true && ( - - 0} - error={errors['schedule.interval']} - > - - - 0} - value={ruleInterval || ''} - name="interval" - data-test-subj="intervalInput" - onChange={(e) => { - const value = e.target.value; - if (value === '' || INTEGER_REGEX.test(value)) { - const parsedValue = value === '' ? '' : parseInt(value, 10); - setRuleInterval(parsedValue || undefined); - setScheduleProperty('interval', `${parsedValue}${ruleIntervalUnit}`); - } - }} - /> - - - { - setRuleIntervalUnit(e.target.value); - setScheduleProperty('interval', `${ruleInterval}${e.target.value}`); - }} - data-test-subj="intervalInputUnit" - /> - - - - + <> + + 0} + error={errors['schedule.interval']} + > + + + 0} + value={ruleInterval || ''} + name="interval" + data-test-subj="intervalInput" + onChange={(e) => { + const value = e.target.value; + if (value === '' || INTEGER_REGEX.test(value)) { + const parsedValue = value === '' ? '' : parseInt(value, 10); + setRuleInterval(parsedValue || undefined); + setScheduleProperty('interval', `${parsedValue}${ruleIntervalUnit}`); + } + }} + /> + + + { + setRuleIntervalUnit(e.target.value); + setScheduleProperty('interval', `${ruleInterval}${e.target.value}`); + }} + data-test-subj="intervalInputUnit" + /> + + + + + + )} + + + , + + } + />, + ]} + > + { + const value = e.target.value; + if (value === '' || INTEGER_REGEX.test(value)) { + const parsedValue = value === '' ? '' : parseInt(value, 10); + setNotificationDelayProperty('active', parsedValue || 0); + setNotificationDelay(parsedValue || undefined); + } + }} + /> + + {shouldShowConsumerSelect && ( <> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts index 54f3871928fb35..c5848177a52e78 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts @@ -12,6 +12,7 @@ import { RuleActionParam, IntervalSchedule, RuleActionAlertsFilterProperty, + NotificationDelay, } from '@kbn/alerting-plugin/common'; import { isEmpty } from 'lodash/fp'; import { Rule, RuleAction } from '../../../types'; @@ -30,6 +31,7 @@ interface CommandType< | 'setRuleActionProperty' | 'setRuleActionFrequency' | 'setRuleActionAlertsFilter' + | 'setNotificationDelayProperty' > { type: T; } @@ -94,6 +96,10 @@ export type RuleReducerAction = | { command: CommandType<'setRuleActionAlertsFilter'>; payload: Payload; + } + | { + command: CommandType<'setNotificationDelayProperty'>; + payload: Payload; }; export type InitialRuleReducer = Reducer<{ rule: InitialRule }, RuleReducerAction>; @@ -281,5 +287,25 @@ export const ruleReducer = ( }; } } + case 'setNotificationDelayProperty': { + const { key, value } = action.payload as Payload< + keyof NotificationDelay, + SavedObjectAttribute + >; + if (rule.notificationDelay && isEqual(rule.notificationDelay[key], value)) { + return state; + } else { + return { + ...state, + rule: { + ...rule, + notificationDelay: { + ...rule.notificationDelay, + [key]: value, + }, + }, + }; + } + } } }; From 324b1516dc24836811dd7aa2b22aecec701415b6 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Thu, 18 Jan 2024 14:23:20 -0800 Subject: [PATCH 08/15] Updating tests --- .../server/routes/lib/rewrite_rule.test.ts | 3 +++ .../server/routes/update_rule.test.ts | 10 +++++++++ .../server/rules_client/tests/update.test.ts | 12 +++++++++++ .../application/lib/rule_api/create.test.ts | 9 ++++++++ .../application/lib/rule_api/update.test.ts | 5 ++++- .../sections/rule_form/rule_form.test.tsx | 21 +++++++++++++++++++ .../sections/rule_form/rule_reducer.test.ts | 17 +++++++++++++++ .../sections/rule_form/rule_reducer.ts | 8 ++++++- 8 files changed, 83 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts index 7a348e583ac6c4..aeeadd1b2e6692 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts @@ -62,6 +62,9 @@ const sampleRule: SanitizedRule & { activeSnoozes?: string[] } = }, nextRun: DATE_2020, revision: 0, + notificationDelay: { + active: 10, + }, }; describe('rewriteRule', () => { diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index 5a4b3a19c0d7ce..c24823de25f79f 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -59,6 +59,9 @@ describe('updateRuleRoute', () => { }, ], notifyWhen: RuleNotifyWhen.CHANGE, + notificationDelay: { + active: 10, + }, }; const updateRequest: AsApiContract['data']> = { @@ -73,6 +76,9 @@ describe('updateRuleRoute', () => { alerts_filter: mockedAlert.actions[0].alertsFilter, }, ], + notification_delay: { + active: 10, + }, }; const updateResult: AsApiContract> = { @@ -86,6 +92,7 @@ describe('updateRuleRoute', () => { connector_type_id: actionTypeId, alerts_filter: alertsFilter, })), + notification_delay: mockedAlert.notificationDelay, }; it('updates a rule with proper parameters', async () => { @@ -136,6 +143,9 @@ describe('updateRuleRoute', () => { }, ], "name": "abc", + "notificationDelay": Object { + "active": 10, + }, "notifyWhen": "onActionGroupChange", "params": Object { "otherField": false, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index aa03a71d93e141..a20e949e768aff 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -277,6 +277,9 @@ describe('update()', () => { scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), + notificationDelay: { + active: 5, + }, }, references: [ { @@ -332,6 +335,9 @@ describe('update()', () => { }, }, ], + notificationDelay: { + active: 10, + }, }, }); expect(result).toMatchInlineSnapshot(` @@ -365,6 +371,9 @@ describe('update()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", + "notificationDelay": Object { + "active": 5, + }, "notifyWhen": "onActiveAlert", "params": Object { "bar": true, @@ -434,6 +443,9 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", + "notificationDelay": Object { + "active": 10, + }, "notifyWhen": "onActiveAlert", "params": Object { "bar": true, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts index 76e5fd7f09207f..66a27cd185158d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts @@ -52,6 +52,9 @@ describe('createRule', () => { execution_status: { status: 'pending', last_execution_date: '2021-04-01T21:33:13.250Z' }, create_at: '2021-04-01T21:33:13.247Z', updated_at: '2021-04-01T21:33:13.247Z', + notification_delay: { + active: 10, + }, }; const ruleToCreate: Omit< RuleUpdates, @@ -96,6 +99,9 @@ describe('createRule', () => { updatedAt: new Date('2021-04-01T21:33:13.247Z'), apiKeyOwner: '', revision: 0, + notificationDelay: { + active: 10, + }, }; http.post.mockResolvedValueOnce(resolvedValue); @@ -148,6 +154,9 @@ describe('createRule', () => { tags: [], updatedAt: '2021-04-01T21:33:13.247Z', updatedBy: undefined, + notificationDelay: { + active: 10, + }, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts index 8b3ebc3f96e523..465c82334e48fc 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts @@ -27,6 +27,9 @@ describe('updateRule', () => { apiKey: null, apiKeyOwner: null, revision: 0, + notificationDelay: { + active: 10, + }, }; const resolvedValue: Rule = { ...ruleToUpdate, @@ -51,7 +54,7 @@ describe('updateRule', () => { Array [ "/api/alerting/rule/12%2F3", Object { - "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", + "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notification_delay\\":{\\"active\\":10}}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx index 24592566d54655..f4eb9148803854 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx @@ -375,6 +375,9 @@ describe('rule_form', () => { enabled: false, mutedInstanceIds: [], ...(!showRulesList ? { ruleTypeId: ruleType.id } : {}), + notificationDelay: { + active: 1, + }, } as unknown as Rule; wrapper = mountWithIntl( @@ -1034,6 +1037,24 @@ describe('rule_form', () => { expect(wrapper.find(ActionForm).props().hasFieldsForAAD).toEqual(true); }); + + it('renders rule notification delay', async () => { + const getNotificationDelayInput = () => { + return wrapper.find('[data-test-subj="notificationDelayInput"] input').first(); + }; + + await setup(); + expect(getNotificationDelayInput().props().value).toEqual(1); + + getNotificationDelayInput().simulate('change', { target: { value: '2' } }); + expect(getNotificationDelayInput().props().value).toEqual(2); + + getNotificationDelayInput().simulate('change', { target: { value: '20' } }); + expect(getNotificationDelayInput().props().value).toEqual(20); + + getNotificationDelayInput().simulate('change', { target: { value: '999' } }); + expect(getNotificationDelayInput().props().value).toEqual(999); + }); }); describe('rule_form create rule non ruleing consumer and producer', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts index 996676b73d59e8..4bf41e46246fb5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts @@ -21,6 +21,9 @@ describe('rule reducer', () => { actions: [], tags: [], notifyWhen: 'onActionGroupChange', + notificationDelay: { + active: 5, + }, } as unknown as Rule; }); @@ -211,4 +214,18 @@ describe('rule reducer', () => { ); expect(updatedRule.rule.actions[0].frequency?.notifyWhen).toBe('onThrottleInterval'); }); + + test('if initial notification delay property was updated', () => { + const updatedRule = ruleReducer( + { rule: initialRule }, + { + command: { type: 'setNotificationDelayProperty' }, + payload: { + key: 'active', + value: 10, + }, + } + ); + expect(updatedRule.rule.notificationDelay?.active).toBe(10); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts index c5848177a52e78..8b563d860ee5a4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts @@ -64,6 +64,12 @@ interface RuleSchedulePayload { index?: number; } +interface NotificationDelayPayload { + key: Key; + value: NotificationDelay[Key] | null; + index?: number; +} + export type RuleReducerAction = | { command: CommandType<'setRule'>; @@ -99,7 +105,7 @@ export type RuleReducerAction = } | { command: CommandType<'setNotificationDelayProperty'>; - payload: Payload; + payload: NotificationDelayPayload; }; export type InitialRuleReducer = Reducer<{ rule: InitialRule }, RuleReducerAction>; From 8aec35a76af7286fd3f3e70409a096fa105668c9 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Mon, 29 Jan 2024 06:47:56 -0800 Subject: [PATCH 09/15] Renaming to alert delay --- .../src/task_state/v1/schema.ts | 2 +- .../routes/rule/apis/create/schemas/v1.ts | 4 +-- .../routes/rule/apis/create/types/v1.ts | 2 +- .../common/routes/rule/response/index.ts | 2 +- .../common/routes/rule/response/schemas/v1.ts | 4 +-- .../common/routes/rule/response/types/v1.ts | 2 +- x-pack/plugins/alerting/common/rule.ts | 4 +-- .../create/schemas/create_rule_data_schema.ts | 8 ++--- .../methods/create/types/create_rule_data.ts | 2 +- .../server/application/rule/schemas/index.ts | 2 +- .../application/rule/schemas/rule_schemas.ts | 6 ++-- ...ransform_rule_attributes_to_rule_domain.ts | 2 +- .../transform_rule_domain_to_rule.ts | 2 +- ...ransform_rule_domain_to_rule_attributes.ts | 2 +- .../server/application/rule/types/rule.ts | 4 +-- .../server/data/rule/types/rule_attributes.ts | 4 +-- .../server/routes/lib/rewrite_rule.test.ts | 2 +- .../server/routes/lib/rewrite_rule.ts | 4 +-- .../transforms/transform_create_body/v1.ts | 2 +- .../transform_rule_to_rule_response/v1.ts | 2 +- .../server/routes/update_rule.test.ts | 8 ++--- .../alerting/server/routes/update_rule.ts | 15 +++------ .../server/rules_client/methods/update.ts | 8 ++--- .../server/rules_client/tests/update.test.ts | 8 ++--- .../saved_objects/schemas/raw_rule/v1.ts | 4 +-- .../task_runner/execution_handler.test.ts | 14 ++++---- .../server/task_runner/execution_handler.ts | 9 ++---- x-pack/plugins/alerting/server/types.ts | 4 +-- .../lib/rule_api/common_transformations.ts | 4 +-- .../application/lib/rule_api/create.test.ts | 6 ++-- .../public/application/lib/rule_api/create.ts | 4 +-- .../application/lib/rule_api/update.test.ts | 4 +-- .../public/application/lib/rule_api/update.ts | 20 +++--------- .../sections/rule_form/rule_form.test.tsx | 22 ++++++------- .../sections/rule_form/rule_form.tsx | 32 +++++++++---------- .../sections/rule_form/rule_reducer.test.ts | 8 ++--- .../sections/rule_form/rule_reducer.ts | 25 +++++++-------- .../tests/alerting/group1/event_log.ts | 8 ++--- .../{notification_delay.ts => alert_delay.ts} | 12 +++---- .../tests/alerting/group4/index.ts | 2 +- 40 files changed, 124 insertions(+), 155 deletions(-) rename x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/{notification_delay.ts => alert_delay.ts} (92%) diff --git a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts index 247c0d7fc8d87f..91353fc3cd1202 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts @@ -37,7 +37,7 @@ export const metaSchema = schema.object({ pendingRecoveredCount: schema.maybe(schema.number()), uuid: schema.maybe(schema.string()), // count of consecutive active alerts - // will reset if the alert is recovered or if equal to notificationDelay.active stored in the rule + // will reset if the alert is recovered or if equal to alertDelay.active stored in the rule activeCount: schema.maybe(schema.number()), }); diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts index bdc60a6562fe20..7d1a37b7c75f73 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/schemas/v1.ts @@ -7,7 +7,7 @@ import { schema } from '@kbn/config-schema'; import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation'; -import { notifyWhenSchemaV1, notificationDelaySchemaV1 } from '../../../response'; +import { notifyWhenSchemaV1, alertDelaySchemaV1 } from '../../../response'; import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query'; export const actionFrequencySchema = schema.object({ @@ -68,7 +68,7 @@ export const createBodySchema = schema.object({ }), actions: schema.arrayOf(actionSchema, { defaultValue: [] }), notify_when: schema.maybe(schema.nullable(notifyWhenSchemaV1)), - notification_delay: schema.maybe(notificationDelaySchemaV1), + alert_delay: schema.maybe(alertDelaySchemaV1), }); export const createParamsSchema = schema.object({ diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts index 328f44fe185bf4..9d5d1e39525ab7 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/create/types/v1.ts @@ -30,7 +30,7 @@ export interface CreateRuleRequestBody { schedule: CreateBodySchema['schedule']; actions: CreateBodySchema['actions']; notify_when?: CreateBodySchema['notify_when']; - notification_delay?: CreateBodySchema['notification_delay']; + alert_delay?: CreateBodySchema['alert_delay']; } export interface CreateRuleResponse { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/index.ts b/x-pack/plugins/alerting/common/routes/rule/response/index.ts index 8e405c599e4835..8c784e744d4730 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/index.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/index.ts @@ -37,7 +37,7 @@ export { ruleSnoozeScheduleSchema as ruleSnoozeScheduleSchemaV1, notifyWhenSchema as notifyWhenSchemaV1, scheduleIdsSchema as scheduleIdsSchemaV1, - notificationDelaySchema as notificationDelaySchemaV1, + alertDelaySchema as alertDelaySchemaV1, } from './schemas/v1'; export type { diff --git a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts index 67f57926d54603..f485aa7374d6cd 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/schemas/v1.ts @@ -182,7 +182,7 @@ export const ruleSnoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -218,7 +218,7 @@ export const ruleResponseSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), view_in_app_relative_url: schema.maybe(schema.nullable(schema.string())), - notification_delay: schema.maybe(notificationDelaySchema), + alert_delay: schema.maybe(alertDelaySchema), }); export const scheduleIdsSchema = schema.maybe(schema.arrayOf(schema.string())); diff --git a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts index 453d1a96d24dcf..25e08ab407ebb6 100644 --- a/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/response/types/v1.ts @@ -53,5 +53,5 @@ export interface RuleResponse { revision: RuleResponseSchemaType['revision']; running?: RuleResponseSchemaType['running']; view_in_app_relative_url?: RuleResponseSchemaType['view_in_app_relative_url']; - notification_delay?: RuleResponseSchemaType['notification_delay']; + alert_delay?: RuleResponseSchemaType['alert_delay']; } diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index 4044761db62710..db888302b5d00b 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -141,7 +141,7 @@ export interface MappedParamsProperties { export type MappedParams = SavedObjectAttributes & MappedParamsProperties; -export interface NotificationDelay extends SavedObjectAttributes { +export interface AlertDelay extends SavedObjectAttributes { active: number; } @@ -178,7 +178,7 @@ export interface Rule { revision: number; running?: boolean | null; viewInAppRelativeUrl?: string; - notificationDelay?: NotificationDelay; + alertDelay?: AlertDelay; } export interface SanitizedAlertsFilter extends AlertsFilter { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts index 44a89e05992fe3..9d1823381d17d6 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/schemas/create_rule_data_schema.ts @@ -7,11 +7,7 @@ import { schema } from '@kbn/config-schema'; import { validateDuration } from '../../../validation'; -import { - notifyWhenSchema, - actionAlertsFilterSchema, - notificationDelaySchema, -} from '../../../schemas'; +import { notifyWhenSchema, actionAlertsFilterSchema, alertDelaySchema } from '../../../schemas'; export const createRuleDataSchema = schema.object({ name: schema.string(), @@ -44,5 +40,5 @@ export const createRuleDataSchema = schema.object({ { defaultValue: [] } ), notifyWhen: schema.maybe(schema.nullable(notifyWhenSchema)), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts index f99beda90e80ae..abee30ec9a5248 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/types/create_rule_data.ts @@ -22,5 +22,5 @@ export interface CreateRuleData { schedule: CreateRuleDataType['schedule']; actions: CreateRuleDataType['actions']; notifyWhen?: CreateRuleDataType['notifyWhen']; - notificationDelay?: CreateRuleDataType['notificationDelay']; + alertDelay?: CreateRuleDataType['alertDelay']; } diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts index d039d190f1e96b..06645e90d7baff 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/index.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/index.ts @@ -13,7 +13,7 @@ export { monitoringSchema, ruleSchema, ruleDomainSchema, - notificationDelaySchema, + alertDelaySchema, } from './rule_schemas'; export { diff --git a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts index b75a6e4f76aade..6041e475daf527 100644 --- a/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts +++ b/x-pack/plugins/alerting/server/application/rule/schemas/rule_schemas.ts @@ -132,7 +132,7 @@ export const snoozeScheduleSchema = schema.object({ skipRecurrences: schema.maybe(schema.arrayOf(schema.string())), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -172,7 +172,7 @@ export const ruleDomainSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); /** @@ -210,5 +210,5 @@ export const ruleSchema = schema.object({ revision: schema.number(), running: schema.maybe(schema.nullable(schema.boolean())), viewInAppRelativeUrl: schema.maybe(schema.nullable(schema.string())), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts index ea331cec4fcc31..9cc8131b1fa2d6 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_attributes_to_rule_domain.ts @@ -216,7 +216,7 @@ export const transformRuleAttributesToRuleDomain = ( revision: ruleDomain.revision, running: ruleDomain.running, viewInAppRelativeUrl: ruleDomain.viewInAppRelativeUrl, - notificationDelay: ruleDomain.notificationDelay, + alertDelay: ruleDomain.alertDelay, }; if (isPublic) { diff --git a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts index 2eb21c42587d3f..e527bd6f31df0e 100644 --- a/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts +++ b/x-pack/plugins/alerting/server/application/rule/transforms/transform_rule_domain_to_rule_attributes.ts @@ -68,6 +68,6 @@ export const transformRuleDomainToRuleAttributes = ( ...(rule.nextRun !== undefined ? { nextRun: rule.nextRun?.toISOString() || null } : {}), revision: rule.revision, ...(rule.running !== undefined ? { running: rule.running } : {}), - ...(rule.notificationDelay !== undefined ? { notificationDelay: rule.notificationDelay } : {}), + ...(rule.alertDelay !== undefined ? { alertDelay: rule.alertDelay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/application/rule/types/rule.ts b/x-pack/plugins/alerting/server/application/rule/types/rule.ts index 1a7e7e1d371182..f59056b382440f 100644 --- a/x-pack/plugins/alerting/server/application/rule/types/rule.ts +++ b/x-pack/plugins/alerting/server/application/rule/types/rule.ts @@ -85,7 +85,7 @@ export interface Rule { revision: RuleSchemaType['revision']; running?: RuleSchemaType['running']; viewInAppRelativeUrl?: RuleSchemaType['viewInAppRelativeUrl']; - notificationDelay?: RuleSchemaType['notificationDelay']; + alertDelay?: RuleSchemaType['alertDelay']; } export interface RuleDomain { @@ -121,5 +121,5 @@ export interface RuleDomain { revision: RuleDomainSchemaType['revision']; running?: RuleDomainSchemaType['running']; viewInAppRelativeUrl?: RuleDomainSchemaType['viewInAppRelativeUrl']; - notificationDelay?: RuleSchemaType['notificationDelay']; + alertDelay?: RuleSchemaType['alertDelay']; } diff --git a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts index 316578149c5ca8..e047be1b9cddf5 100644 --- a/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts +++ b/x-pack/plugins/alerting/server/data/rule/types/rule_attributes.ts @@ -142,7 +142,7 @@ interface RuleMetaAttributes { versionApiKeyLastmodified?: string; } -interface NotificationDelayAttributes { +interface AlertDelayAttributes { active: number; } @@ -178,5 +178,5 @@ export interface RuleAttributes { nextRun?: string | null; revision: number; running?: boolean | null; - notificationDelay?: NotificationDelayAttributes; + alertDelay?: AlertDelayAttributes; } diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts index aeeadd1b2e6692..826ee952a6bb65 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts @@ -62,7 +62,7 @@ const sampleRule: SanitizedRule & { activeSnoozes?: string[] } = }, nextRun: DATE_2020, revision: 0, - notificationDelay: { + alertDelay: { active: 10, }, }; 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 24c887f62fc8b4..d0e59278b13c5b 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -37,7 +37,7 @@ export const rewriteRule = ({ activeSnoozes, lastRun, nextRun, - notificationDelay, + alertDelay, ...rest }: SanitizedRule & { activeSnoozes?: string[] }) => ({ ...rest, @@ -79,5 +79,5 @@ export const rewriteRule = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), - ...(notificationDelay !== undefined ? { notification_delay: notificationDelay } : {}), + ...(alertDelay !== undefined ? { alert_delay: alertDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts index fcf92b386aaa2b..5dea295c40ed7b 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/create/transforms/transform_create_body/v1.ts @@ -58,6 +58,6 @@ export const transformCreateBody = ( schedule: createBody.schedule, actions: transformCreateBodyActions(createBody.actions), ...(createBody.notify_when ? { notifyWhen: createBody.notify_when } : {}), - ...(createBody.notification_delay ? { notificationDelay: createBody.notification_delay } : {}), + ...(createBody.alert_delay ? { alertDelay: createBody.alert_delay } : {}), }; }; diff --git a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts index 4f55401160ce9f..f4fdedfc6f436e 100644 --- a/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/transforms/transform_rule_to_rule_response/v1.ts @@ -119,5 +119,5 @@ export const transformRuleToRuleResponse = ( ...(rule.viewInAppRelativeUrl !== undefined ? { view_in_app_relative_url: rule.viewInAppRelativeUrl } : {}), - ...(rule.notificationDelay !== undefined ? { notification_delay: rule.notificationDelay } : {}), + ...(rule.alertDelay !== undefined ? { alert_delay: rule.alertDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index c24823de25f79f..c942546ba8faef 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -59,7 +59,7 @@ describe('updateRuleRoute', () => { }, ], notifyWhen: RuleNotifyWhen.CHANGE, - notificationDelay: { + alertDelay: { active: 10, }, }; @@ -76,7 +76,7 @@ describe('updateRuleRoute', () => { alerts_filter: mockedAlert.actions[0].alertsFilter, }, ], - notification_delay: { + alert_delay: { active: 10, }, }; @@ -92,7 +92,7 @@ describe('updateRuleRoute', () => { connector_type_id: actionTypeId, alerts_filter: alertsFilter, })), - notification_delay: mockedAlert.notificationDelay, + alert_delay: mockedAlert.alertDelay, }; it('updates a rule with proper parameters', async () => { @@ -143,7 +143,7 @@ describe('updateRuleRoute', () => { }, ], "name": "abc", - "notificationDelay": Object { + "alertDelay": Object { "active": 10, }, "notifyWhen": "onActionGroupChange", diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index aa0615b6d9d1b1..9419d84d063413 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -52,7 +52,7 @@ const bodySchema = schema.object({ ) ) ), - notification_delay: schema.maybe( + alert_delay: schema.maybe( schema.object({ active: schema.number(), }) @@ -60,19 +60,14 @@ const bodySchema = schema.object({ }); const rewriteBodyReq: RewriteRequestCase> = (result) => { - const { - notify_when: notifyWhen, - notification_delay: notificationDelay, - actions, - ...rest - } = result.data; + const { notify_when: notifyWhen, alert_delay: alertDelay, actions, ...rest } = result.data; return { ...result, data: { ...rest, notifyWhen, actions: rewriteActionsReq(actions), - notificationDelay, + alertDelay, }, }; }; @@ -94,7 +89,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ isSnoozedUntil, lastRun, nextRun, - notificationDelay, + alertDelay, ...rest }) => ({ ...rest, @@ -127,7 +122,7 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), - ...(notificationDelay ? { notification_delay: notificationDelay } : {}), + ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export const updateRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index 07c0b0cd47520f..1255173beefe49 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -17,11 +17,7 @@ import { } from '../../types'; import { validateRuleTypeParams, getRuleNotifyWhenType } from '../../lib'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { - parseDuration, - getRuleCircuitBreakerErrorMessage, - NotificationDelay, -} from '../../../common'; +import { parseDuration, getRuleCircuitBreakerErrorMessage, AlertDelay } from '../../../common'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; @@ -55,7 +51,7 @@ export interface UpdateOptions { params: Params; throttle?: string | null; notifyWhen?: RuleNotifyWhenType | null; - notificationDelay?: NotificationDelay; + alertDelay?: AlertDelay; }; allowMissingConnectorSecrets?: boolean; shouldIncrementRevision?: ShouldIncrementRevision; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index d58630b084eab3..ceab7ad16f01c1 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -279,7 +279,7 @@ describe('update()', () => { scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), - notificationDelay: { + alertDelay: { active: 5, }, }, @@ -337,7 +337,7 @@ describe('update()', () => { }, }, ], - notificationDelay: { + alertDelay: { active: 10, }, }, @@ -373,7 +373,7 @@ describe('update()', () => { "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", - "notificationDelay": Object { + "alertDelay": Object { "active": 5, }, "notifyWhen": "onActiveAlert", @@ -445,7 +445,7 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "notificationDelay": Object { + "alertDelay": Object { "active": 10, }, "notifyWhen": "onActiveAlert", diff --git a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts index 495c2493f2e434..e0641e9b275ea6 100644 --- a/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts +++ b/x-pack/plugins/alerting/server/saved_objects/schemas/raw_rule/v1.ts @@ -213,7 +213,7 @@ const rawRuleActionSchema = schema.object({ useAlertDataForTemplate: schema.maybe(schema.boolean()), }); -export const notificationDelaySchema = schema.object({ +export const alertDelaySchema = schema.object({ active: schema.number(), }); @@ -274,5 +274,5 @@ export const rawRuleSchema = schema.object({ ), params: schema.recordOf(schema.string(), schema.maybe(schema.any())), typeVersion: schema.maybe(schema.number()), - notificationDelay: schema.maybe(notificationDelaySchema), + alertDelay: schema.maybe(alertDelaySchema), }); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index 8f5147ea4de309..9fe8f1a8202771 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -2052,13 +2052,13 @@ describe('Execution Handler', () => { `); }); - test('does not schedule actions for alerts with activeCount less than the notificationDelay.active threshold', async () => { + test('does not schedule actions for alerts with activeCount less than the alertDelay.active threshold', async () => { const executionHandler = new ExecutionHandler( generateExecutionParams({ ...defaultExecutionParams, rule: { ...defaultExecutionParams.rule, - notificationDelay: { + alertDelay: { active: 3, }, }, @@ -2074,20 +2074,20 @@ describe('Execution Handler', () => { expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(2); expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 0 is less than the rule notificationDelay.active: 3 threshold.' + 'no scheduling of action "1" for rule "1": the alert activeCount: 0 is less than the rule alertDelay.active: 3 threshold.' ); expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 2 is less than the rule notificationDelay.active: 3 threshold.' + 'no scheduling of action "1" for rule "1": the alert activeCount: 2 is less than the rule alertDelay.active: 3 threshold.' ); }); - test('schedules actions for alerts with activeCount greater than or equal the notificationDelay.active threshold', async () => { + test('schedules actions for alerts with activeCount greater than or equal the alertDelay.active threshold', async () => { const executionHandler = new ExecutionHandler( generateExecutionParams({ ...defaultExecutionParams, rule: { ...defaultExecutionParams.rule, - notificationDelay: { + alertDelay: { active: 3, }, }, @@ -2166,7 +2166,7 @@ describe('Execution Handler', () => { `); }); - test('schedules actions if notificationDelay.active threshold is not defined', async () => { + test('schedules actions if alertDelay.active threshold is not defined', async () => { const executionHandler = new ExecutionHandler(generateExecutionParams()); await executionHandler.run({ diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index e118b4d327ce18..10a4d7484b0c6a 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -628,15 +628,12 @@ export class ExecutionHandler< continue; } - if ( - this.rule.notificationDelay && - alert.getActiveCount() < this.rule.notificationDelay.active - ) { + if (this.rule.alertDelay && alert.getActiveCount() < this.rule.alertDelay.active) { this.logger.debug( `no scheduling of action "${action.id}" for rule "${ this.taskInstance.params.alertId - }": the alert activeCount: ${alert.getActiveCount()} is less than the rule notificationDelay.active: ${ - this.rule.notificationDelay.active + }": the alert activeCount: ${alert.getActiveCount()} is less than the rule alertDelay.active: ${ + this.rule.alertDelay.active } threshold.` ); continue; diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 1627a74e867bf8..d15fc0256fe8b1 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -60,7 +60,7 @@ import { AlertsFilter, AlertsFilterTimeframe, RuleAlertData, - NotificationDelay, + AlertDelay, } from '../common'; import { PublicAlertFactory } from './alert/create_alert_factory'; import { RulesSettingsFlappingProperties } from '../common/rules_settings'; @@ -486,7 +486,7 @@ export interface RawRule extends SavedObjectAttributes { nextRun?: string | null; revision: number; running?: boolean | null; - notificationDelay?: NotificationDelay; + alertDelay?: AlertDelay; } export type { DataStreamAdapter } from './alerts_service/lib/data_stream_adapter'; 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 42c9b210f4452d..b00b874b079ef5 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 @@ -77,7 +77,7 @@ export const transformRule: RewriteRequestCase = ({ active_snoozes: activeSnoozes, last_run: lastRun, next_run: nextRun, - notification_delay: notificationDelay, + alert_delay: alertDelay, ...rest }: any) => ({ ruleTypeId, @@ -100,7 +100,7 @@ export const transformRule: RewriteRequestCase = ({ ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), ...(nextRun ? { nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), - ...(notificationDelay ? { notificationDelay } : {}), + ...(alertDelay ? { alertDelay } : {}), ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts index 66a27cd185158d..b27d9cad0c0560 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts @@ -52,7 +52,7 @@ describe('createRule', () => { execution_status: { status: 'pending', last_execution_date: '2021-04-01T21:33:13.250Z' }, create_at: '2021-04-01T21:33:13.247Z', updated_at: '2021-04-01T21:33:13.247Z', - notification_delay: { + alert_delay: { active: 10, }, }; @@ -99,7 +99,7 @@ describe('createRule', () => { updatedAt: new Date('2021-04-01T21:33:13.247Z'), apiKeyOwner: '', revision: 0, - notificationDelay: { + alertDelay: { active: 10, }, }; @@ -154,7 +154,7 @@ describe('createRule', () => { tags: [], updatedAt: '2021-04-01T21:33:13.247Z', updatedBy: undefined, - notificationDelay: { + alertDelay: { active: 10, }, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts index 58638670e820ec..48fa1783f3c1ff 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts @@ -23,7 +23,7 @@ type RuleCreateBody = Omit< const rewriteBodyRequest: RewriteResponseCase = ({ ruleTypeId, actions, - notificationDelay, + alertDelay, ...res }): any => ({ ...res, @@ -44,7 +44,7 @@ const rewriteBodyRequest: RewriteResponseCase = ({ : {}), }) ), - ...(notificationDelay ? { notification_delay: notificationDelay } : {}), + ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export async function createRule({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts index 465c82334e48fc..591cdc83e86cf3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts @@ -27,7 +27,7 @@ describe('updateRule', () => { apiKey: null, apiKeyOwner: null, revision: 0, - notificationDelay: { + alertDelay: { active: 10, }, }; @@ -54,7 +54,7 @@ describe('updateRule', () => { Array [ "/api/alerting/rule/12%2F3", Object { - "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"notification_delay\\":{\\"active\\":10}}", + "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"alert_delay\\":{\\"active\\":10}}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts index 68515df7b22f03..80346ff2f65daf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts @@ -13,18 +13,11 @@ import { transformRule } from './common_transformations'; type RuleUpdatesBody = Pick< RuleUpdates, - | 'name' - | 'tags' - | 'schedule' - | 'actions' - | 'params' - | 'throttle' - | 'notifyWhen' - | 'notificationDelay' + 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'throttle' | 'notifyWhen' | 'alertDelay' >; const rewriteBodyRequest: RewriteResponseCase = ({ actions, - notificationDelay, + alertDelay, ...res }): any => ({ ...res, @@ -45,7 +38,7 @@ const rewriteBodyRequest: RewriteResponseCase = ({ ...(uuid && { uuid }), }) ), - ...(notificationDelay ? { notification_delay: notificationDelay } : {}), + ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export async function updateRule({ @@ -54,10 +47,7 @@ export async function updateRule({ id, }: { http: HttpSetup; - rule: Pick< - RuleUpdates, - 'name' | 'tags' | 'schedule' | 'params' | 'actions' | 'notificationDelay' - >; + rule: Pick; id: string; }): Promise { const res = await http.put>( @@ -65,7 +55,7 @@ export async function updateRule({ { body: JSON.stringify( rewriteBodyRequest( - pick(rule, ['name', 'tags', 'schedule', 'params', 'actions', 'notificationDelay']) + pick(rule, ['name', 'tags', 'schedule', 'params', 'actions', 'alertDelay']) ) ), } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx index f4eb9148803854..8902b4d472ad2d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx @@ -375,7 +375,7 @@ describe('rule_form', () => { enabled: false, mutedInstanceIds: [], ...(!showRulesList ? { ruleTypeId: ruleType.id } : {}), - notificationDelay: { + alertDelay: { active: 1, }, } as unknown as Rule; @@ -1038,22 +1038,22 @@ describe('rule_form', () => { expect(wrapper.find(ActionForm).props().hasFieldsForAAD).toEqual(true); }); - it('renders rule notification delay', async () => { - const getNotificationDelayInput = () => { - return wrapper.find('[data-test-subj="notificationDelayInput"] input').first(); + it('renders rule alert delay', async () => { + const getAlertDelayInput = () => { + return wrapper.find('[data-test-subj="alertDelayInput"] input').first(); }; await setup(); - expect(getNotificationDelayInput().props().value).toEqual(1); + expect(getAlertDelayInput().props().value).toEqual(1); - getNotificationDelayInput().simulate('change', { target: { value: '2' } }); - expect(getNotificationDelayInput().props().value).toEqual(2); + getAlertDelayInput().simulate('change', { target: { value: '2' } }); + expect(getAlertDelayInput().props().value).toEqual(2); - getNotificationDelayInput().simulate('change', { target: { value: '20' } }); - expect(getNotificationDelayInput().props().value).toEqual(20); + getAlertDelayInput().simulate('change', { target: { value: '20' } }); + expect(getAlertDelayInput().props().value).toEqual(20); - getNotificationDelayInput().simulate('change', { target: { value: '999' } }); - expect(getNotificationDelayInput().props().value).toEqual(999); + getAlertDelayInput().simulate('change', { target: { value: '999' } }); + expect(getAlertDelayInput().props().value).toEqual(999); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index f71fcefe68b3ae..e65f8c90bd03d6 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -215,9 +215,7 @@ export const RuleForm = ({ ? getDurationUnitValue(rule.schedule.interval) : defaultScheduleIntervalUnit ); - const [notificationDelay, setNotificationDelay] = useState( - rule.notificationDelay?.active - ); + const [alertDelay, setAlertDelay] = useState(rule.alertDelay?.active); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); const [availableRuleTypes, setAvailableRuleTypes] = useState([]); @@ -332,10 +330,10 @@ export const RuleForm = ({ }, [rule.schedule.interval, defaultScheduleInterval, defaultScheduleIntervalUnit]); useEffect(() => { - if (rule.notificationDelay) { - setNotificationDelay(rule.notificationDelay.active); + if (rule.alertDelay) { + setAlertDelay(rule.alertDelay.active); } - }, [rule.notificationDelay]); + }, [rule.alertDelay]); useEffect(() => { if (!flyoutBodyOverflowRef.current) { @@ -402,8 +400,8 @@ export const RuleForm = ({ [dispatch] ); - const setNotificationDelayProperty = (key: string, value: any) => { - dispatch({ command: { type: 'setNotificationDelayProperty' }, payload: { key, value } }); + const setAlertDelayProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setAlertDelayProperty' }, payload: { key, value } }); }; useEffect(() => { @@ -831,19 +829,19 @@ export const RuleForm = ({ , } @@ -853,15 +851,15 @@ export const RuleForm = ({ { const value = e.target.value; if (value === '' || INTEGER_REGEX.test(value)) { const parsedValue = value === '' ? '' : parseInt(value, 10); - setNotificationDelayProperty('active', parsedValue || 0); - setNotificationDelay(parsedValue || undefined); + setAlertDelayProperty('active', parsedValue || 0); + setAlertDelay(parsedValue || undefined); } }} /> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts index 4bf41e46246fb5..6eadf1fce5ff43 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts @@ -21,7 +21,7 @@ describe('rule reducer', () => { actions: [], tags: [], notifyWhen: 'onActionGroupChange', - notificationDelay: { + alertDelay: { active: 5, }, } as unknown as Rule; @@ -215,17 +215,17 @@ describe('rule reducer', () => { expect(updatedRule.rule.actions[0].frequency?.notifyWhen).toBe('onThrottleInterval'); }); - test('if initial notification delay property was updated', () => { + test('if initial alert delay property was updated', () => { const updatedRule = ruleReducer( { rule: initialRule }, { - command: { type: 'setNotificationDelayProperty' }, + command: { type: 'setAlertDelayProperty' }, payload: { key: 'active', value: 10, }, } ); - expect(updatedRule.rule.notificationDelay?.active).toBe(10); + expect(updatedRule.rule.alertDelay?.active).toBe(10); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts index 8b563d860ee5a4..257df764ebc1ef 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts @@ -12,7 +12,7 @@ import { RuleActionParam, IntervalSchedule, RuleActionAlertsFilterProperty, - NotificationDelay, + AlertDelay, } from '@kbn/alerting-plugin/common'; import { isEmpty } from 'lodash/fp'; import { Rule, RuleAction } from '../../../types'; @@ -31,7 +31,7 @@ interface CommandType< | 'setRuleActionProperty' | 'setRuleActionFrequency' | 'setRuleActionAlertsFilter' - | 'setNotificationDelayProperty' + | 'setAlertDelayProperty' > { type: T; } @@ -64,9 +64,9 @@ interface RuleSchedulePayload { index?: number; } -interface NotificationDelayPayload { +interface AlertDelayPayload { key: Key; - value: NotificationDelay[Key] | null; + value: AlertDelay[Key] | null; index?: number; } @@ -104,8 +104,8 @@ export type RuleReducerAction = payload: Payload; } | { - command: CommandType<'setNotificationDelayProperty'>; - payload: NotificationDelayPayload; + command: CommandType<'setAlertDelayProperty'>; + payload: AlertDelayPayload; }; export type InitialRuleReducer = Reducer<{ rule: InitialRule }, RuleReducerAction>; @@ -293,20 +293,17 @@ export const ruleReducer = ( }; } } - case 'setNotificationDelayProperty': { - const { key, value } = action.payload as Payload< - keyof NotificationDelay, - SavedObjectAttribute - >; - if (rule.notificationDelay && isEqual(rule.notificationDelay[key], value)) { + case 'setAlertDelayProperty': { + const { key, value } = action.payload as Payload; + if (rule.alertDelay && isEqual(rule.alertDelay[key], value)) { return state; } else { return { ...state, rule: { ...rule, - notificationDelay: { - ...rule.notificationDelay, + alertDelay: { + ...rule.alertDelay, [key]: value, }, }, diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 5228b1c76d3d92..4231439c892392 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -1849,7 +1849,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(hasActions).eql(false); }); - it('should generate expected events with a notificationDelay', async () => { + it('should generate expected events with a alertDelay', async () => { const { body: createdAction } = await supertest .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -1884,7 +1884,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { params: {}, }, ], - notification_delay: { + alert_delay: { active: 3, }, }) @@ -1925,7 +1925,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { expect(actualTriggeredActions).to.eql(1); }); - it('should generate expected events with a notificationDelay with AAD', async () => { + it('should generate expected events with a alertDelay with AAD', async () => { const { body: createdAction } = await supertest .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -1960,7 +1960,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { params: {}, }, ], - notification_delay: { + alert_delay: { active: 3, }, }) diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts similarity index 92% rename from x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts rename to x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts index 2b632686d57936..30429e9f433a3c 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/notification_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts @@ -12,7 +12,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common import { Spaces } from '../../../scenarios'; // eslint-disable-next-line import/no-default-export -export default function createNotificationDelayTests({ getService }: FtrProviderContext) { +export default function createAlertDelayTests({ getService }: FtrProviderContext) { const es = getService('es'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const retry = getService('retry'); @@ -22,7 +22,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider const ACTIVE_PATH = 'alertInstances.instance.meta.activeCount'; const RECOVERED_PATH = 'alertRecoveredInstances.instance.meta.activeCount'; - describe('Notification Delay', () => { + describe('Alert Delay', () => { let actionId: string; const objectRemover = new ObjectRemover(supertestWithoutAuth); @@ -37,7 +37,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider afterEach(() => objectRemover.removeAll()); - it('should clear the activeCount if the notificationDelay is not configured for the rule', async () => { + it('should clear the activeCount if the alertDelay is not configured for the rule', async () => { const start = new Date().toISOString(); const pattern = { instance: [true], @@ -50,7 +50,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider expect(get(state, ACTIVE_PATH)).to.eql(0); }); - it('should update the activeCount when alert is active and clear when recovered if the notificationDelay is configured for the rule', async () => { + it('should update the activeCount when alert is active and clear when recovered if the alertDelay is configured for the rule', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, false, true], @@ -79,7 +79,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider expect(get(state, ACTIVE_PATH)).to.eql(1); }); - it('should reset the activeCount when count of consecutive active alerts exceeds the notificationDelay count', async () => { + it('should reset the activeCount when count of consecutive active alerts exceeds the alertDelay count', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, true, true], @@ -187,7 +187,7 @@ export default function createNotificationDelayTests({ getService }: FtrProvider params: {}, }, ], - ...(activeCount ? { notification_delay: { active: activeCount } } : {}), + ...(activeCount ? { alert_delay: { active: activeCount } } : {}), }) ) .expect(200); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts index b73477cf3df302..15084a47f4d863 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/index.ts @@ -28,7 +28,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC loadTestFile(require.resolve('./run_soon')); loadTestFile(require.resolve('./flapping_history')); loadTestFile(require.resolve('./check_registered_rule_types')); - loadTestFile(require.resolve('./notification_delay')); + loadTestFile(require.resolve('./alert_delay')); loadTestFile(require.resolve('./generate_alert_schemas')); // Do not place test files here, due to https://github.com/elastic/kibana/issues/123059 From 9f3575ae74bed49e60fe38d11313132ca8adf1ac Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Mon, 29 Jan 2024 15:08:32 -0800 Subject: [PATCH 10/15] Updating the alert creation delay --- .../src/lifecycle_state.ts | 2 + .../src/task_state/v1/schema.ts | 3 +- x-pack/plugins/alerting/common/rule.ts | 1 + .../alerts_client/alerts_client.test.ts | 20 +++ .../server/alerts_client/alerts_client.ts | 5 + .../alerts_client/alerts_client_fixtures.ts | 1 + .../legacy_alerts_client.test.ts | 5 +- .../alerts_client/legacy_alerts_client.ts | 7 +- .../alerting/server/alerts_client/types.ts | 3 + .../lib/get_alerts_for_notification.test.ts | 106 +++++++++++- .../server/lib/get_alerts_for_notification.ts | 21 ++- .../task_runner/execution_handler.test.ts | 159 ------------------ .../server/task_runner/execution_handler.ts | 13 -- .../alerting/server/task_runner/fixtures.ts | 2 +- .../server/task_runner/task_runner.test.ts | 6 +- .../server/task_runner/task_runner.ts | 2 + .../utils/create_lifecycle_executor.test.ts | 34 ++++ .../server/utils/create_lifecycle_executor.ts | 90 +++++++--- .../utils/get_alerts_for_notification.test.ts | 130 +++++++++++++- .../utils/get_alerts_for_notification.ts | 59 +++++-- .../sections/rule_form/rule_form.tsx | 4 +- 21 files changed, 439 insertions(+), 234 deletions(-) diff --git a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts index 8fa5cb111f6d91..f82791dd4e4ea1 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/lifecycle_state.ts @@ -20,6 +20,8 @@ const trackedAlertStateRt = t.type({ // count of consecutive recovered alerts for flapping // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: t.number, + // count of consecutive active alerts will reset if the alert is recovered + activeCount: t.number, }); export type TrackedLifecycleAlertState = t.TypeOf; diff --git a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts index 91353fc3cd1202..247cc7124c000b 100644 --- a/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts +++ b/x-pack/packages/kbn-alerting-state-types/src/task_state/v1/schema.ts @@ -36,8 +36,7 @@ export const metaSchema = schema.object({ // will reset if the alert is active or if equal to the statusChangeThreshold stored in the rule settings pendingRecoveredCount: schema.maybe(schema.number()), uuid: schema.maybe(schema.string()), - // count of consecutive active alerts - // will reset if the alert is recovered or if equal to alertDelay.active stored in the rule + // count of consecutive active alerts will reset if the alert is recovered activeCount: schema.maybe(schema.number()), }); diff --git a/x-pack/plugins/alerting/common/rule.ts b/x-pack/plugins/alerting/common/rule.ts index db888302b5d00b..7cec5bdbdd7a62 100644 --- a/x-pack/plugins/alerting/common/rule.ts +++ b/x-pack/plugins/alerting/common/rule.ts @@ -222,6 +222,7 @@ export type SanitizedRuleConfig = Pick< | 'muteAll' | 'revision' | 'snoozeSchedule' + | 'alertDelay' > & { producer: string; ruleTypeId: string; diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts index d00e1dfafb0830..e587bb311137d0 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts @@ -308,6 +308,7 @@ describe('Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, notifyOnActionGroupChange: true, maintenanceWindowIds: [], + alertDelay: 0, }; }); @@ -516,6 +517,25 @@ describe('Alerts Client', () => { }); }); + test('should not index new alerts if the activeCount is less than the rule alertDelay', async () => { + const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>({ + ...alertsClientParams, + rule: { ...alertsClientParams.rule, alertDelay: 3 }, + }); + + await alertsClient.initializeExecution(defaultExecutionOpts); + + // Report 1 new alerts + const alertExecutorService = alertsClient.factory(); + alertExecutorService.create('1').scheduleActions('default'); + + alertsClient.processAndLogAlerts(processAndLogAlertsOpts); + + await alertsClient.persistAlerts(); + + expect(clusterClient.bulk).not.toHaveBeenCalled(); + }); + test('should update ongoing alerts in existing index', async () => { clusterClient.search.mockResolvedValue({ took: 10, diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 144a0203e4909f..30c9ee669f0c7f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -343,6 +343,11 @@ export class AlertsClient< }) ); } else { + // skip writing the alert document if the number of consecutive + // active alerts is less than the rule alertDelay threshold + if (activeAlerts[id].getActiveCount() < this.options.rule.alertDelay) { + continue; + } activeAlertsToIndex.push( buildNewAlert< AlertData, diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts index 90c2f4c40b65bd..0da20a5e49b70f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client_fixtures.ts @@ -25,6 +25,7 @@ export const alertRuleData: AlertRuleData = { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }; export const mockAAD = { diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index 446ecfd79c8b80..d01060820ae524 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -245,6 +245,7 @@ describe('Legacy Alerts Client', () => { flappingSettings: DEFAULT_FLAPPING_SETTINGS, notifyOnActionGroupChange: true, maintenanceWindowIds: ['window-id1', 'window-id2'], + alertDelay: 5, }); expect(processAlerts).toHaveBeenCalledWith({ @@ -275,13 +276,15 @@ describe('Legacy Alerts Client', () => { }, true, 'default', + 5, {}, { '1': new Alert('1', testAlert1), '2': new Alert('2', testAlert2), }, {}, - {} + {}, + null ); expect(logAlerts).toHaveBeenCalledWith({ diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts index 0de97a8a29be68..a5cfc642a19ee8 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.ts @@ -140,6 +140,7 @@ export class LegacyAlertsClient< notifyOnActionGroupChange, flappingSettings, maintenanceWindowIds, + alertDelay, }: ProcessAlertsOpts) { const { newAlerts: processedAlertsNew, @@ -168,10 +169,12 @@ export class LegacyAlertsClient< flappingSettings, notifyOnActionGroupChange, this.options.ruleType.defaultActionGroupId, + alertDelay, processedAlertsNew, processedAlertsActive, trimmedAlertsRecovered, - processedAlertsRecoveredCurrent + processedAlertsRecoveredCurrent, + this.startedAtString ); alerts.currentRecoveredAlerts = merge(alerts.currentRecoveredAlerts, earlyRecoveredAlerts); @@ -203,11 +206,13 @@ export class LegacyAlertsClient< flappingSettings, notifyOnActionGroupChange, maintenanceWindowIds, + alertDelay, }: ProcessAndLogAlertsOpts) { this.processAlerts({ notifyOnActionGroupChange, flappingSettings, maintenanceWindowIds, + alertDelay, }); this.logAlerts({ diff --git a/x-pack/plugins/alerting/server/alerts_client/types.ts b/x-pack/plugins/alerting/server/alerts_client/types.ts index c5d02f1cd6f141..c38351126af970 100644 --- a/x-pack/plugins/alerting/server/alerts_client/types.ts +++ b/x-pack/plugins/alerting/server/alerts_client/types.ts @@ -47,6 +47,7 @@ export interface AlertRuleData { revision: number; spaceId: string; tags: string[]; + alertDelay: number; } export interface AlertRule { @@ -111,12 +112,14 @@ export interface ProcessAndLogAlertsOpts { flappingSettings: RulesSettingsFlappingProperties; notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; + alertDelay: number; } export interface ProcessAlertsOpts { flappingSettings: RulesSettingsFlappingProperties; notifyOnActionGroupChange: boolean; maintenanceWindowIds: string[]; + alertDelay: number; } export interface LogAlertsOpts { diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts index c1465d5b7a2388..4656f4377f1301 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.test.ts @@ -21,6 +21,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, { '1': alert1, }, @@ -89,6 +90,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -222,6 +224,7 @@ describe('getAlertsForNotification', () => { DISABLE_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -353,6 +356,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, false, 'default', + 0, {}, {}, { @@ -455,10 +459,11 @@ describe('getAlertsForNotification', () => { }); const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); - const { newAlerts, activeAlerts } = getAlertsForNotification( + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, { '1': alert1, }, @@ -507,6 +512,30 @@ describe('getAlertsForNotification', () => { }, } `); + expect(currentActiveAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + "2": Object { + "meta": Object { + "activeCount": 1, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-2", + }, + "state": Object {}, + }, + } + `); }); test('should reset activeCount for all recovered alerts', () => { @@ -517,6 +546,7 @@ describe('getAlertsForNotification', () => { DEFAULT_FLAPPING_SETTINGS, true, 'default', + 0, {}, {}, { @@ -574,4 +604,78 @@ describe('getAlertsForNotification', () => { } `); }); + + test('should remove the alert from newAlerts and should not return the alert in currentActiveAlerts if the activeCount is less than the rule alertDelay', () => { + const alert1 = new Alert('1', { + meta: { activeCount: 1, uuid: 'uuid-1' }, + }); + const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); + + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 5, + { + '1': alert1, + }, + { + '1': alert1, + '2': alert2, + }, + {}, + {} + ); + expect(newAlerts).toMatchInlineSnapshot(`Object {}`); + expect(activeAlerts).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "activeCount": 2, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-1", + }, + "state": Object {}, + }, + "2": Object { + "meta": Object { + "activeCount": 1, + "flappingHistory": Array [], + "maintenanceWindowIds": Array [], + "pendingRecoveredCount": 0, + "uuid": "uuid-2", + }, + "state": Object {}, + }, + } + `); + expect(currentActiveAlerts).toMatchInlineSnapshot(`Object {}`); + }); + + test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { + const alert2 = new Alert('2', { meta: { uuid: 'uuid-2' } }); + + const { newAlerts, activeAlerts, currentActiveAlerts } = getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + true, + 'default', + 1, + {}, + { + '2': alert2, + }, + {}, + {} + ); + expect(newAlerts['2'].getState().duration).toBe('0'); + expect(newAlerts['2'].getState().start).toBeTruthy(); + + expect(activeAlerts['2'].getState().duration).toBe('0'); + expect(activeAlerts['2'].getState().start).toBeTruthy(); + + expect(currentActiveAlerts['2'].getState().duration).toBe('0'); + expect(currentActiveAlerts['2'].getState().start).toBeTruthy(); + }); }); diff --git a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts index 63e95402549a4a..593d92b35383ba 100644 --- a/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts +++ b/x-pack/plugins/alerting/server/lib/get_alerts_for_notification.ts @@ -19,10 +19,12 @@ export function getAlertsForNotification< flappingSettings: RulesSettingsFlappingProperties, notifyOnActionGroupChange: boolean, actionGroupId: string, + alertDelay: number, newAlerts: Record> = {}, activeAlerts: Record> = {}, recoveredAlerts: Record> = {}, - currentRecoveredAlerts: Record> = {} + currentRecoveredAlerts: Record> = {}, + startedAt?: string | null ) { const currentActiveAlerts: Record> = {}; @@ -30,7 +32,22 @@ export function getAlertsForNotification< const alert = activeAlerts[id]; alert.incrementActiveCount(); alert.resetPendingRecoveredCount(); - currentActiveAlerts[id] = alert; + // do not trigger an action notification if the number of consecutive + // active alerts is less than the rule alertDelay threshold + if (alert.getActiveCount() < alertDelay) { + // remove from new alerts + delete newAlerts[id]; + } else { + currentActiveAlerts[id] = alert; + // if the active count is equal to the alertDelay it is considered a new alert + if (alert.getActiveCount() === alertDelay) { + const currentTime = startedAt ?? new Date().toISOString(); + const state = alert.getState(); + // keep the state and update the start time and duration + alert.replaceState({ ...state, start: currentTime, duration: '0' }); + newAlerts[id] = alert; + } + } } for (const id of keys(currentRecoveredAlerts)) { diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index 9fe8f1a8202771..a742afb152b198 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -2052,165 +2052,6 @@ describe('Execution Handler', () => { `); }); - test('does not schedule actions for alerts with activeCount less than the alertDelay.active threshold', async () => { - const executionHandler = new ExecutionHandler( - generateExecutionParams({ - ...defaultExecutionParams, - rule: { - ...defaultExecutionParams.rule, - alertDelay: { - active: 3, - }, - }, - }) - ); - - await executionHandler.run({ - ...generateAlert({ id: 1 }), - ...generateAlert({ id: 2, activeCount: 2 }), - }); - - expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled(); - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(2); - - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 0 is less than the rule alertDelay.active: 3 threshold.' - ); - expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith( - 'no scheduling of action "1" for rule "1": the alert activeCount: 2 is less than the rule alertDelay.active: 3 threshold.' - ); - }); - - test('schedules actions for alerts with activeCount greater than or equal the alertDelay.active threshold', async () => { - const executionHandler = new ExecutionHandler( - generateExecutionParams({ - ...defaultExecutionParams, - rule: { - ...defaultExecutionParams.rule, - alertDelay: { - active: 3, - }, - }, - }) - ); - - await executionHandler.run({ - ...generateAlert({ id: 1, activeCount: 3 }), - ...generateAlert({ id: 2, activeCount: 4 }), - }); - - expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); - expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 2 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); - }); - - test('schedules actions if alertDelay.active threshold is not defined', async () => { - const executionHandler = new ExecutionHandler(generateExecutionParams()); - - await executionHandler.run({ - ...generateAlert({ id: 1, activeCount: 1 }), - }); - - expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1); - expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Array [ - Object { - "actionTypeId": "test", - "apiKey": "MTIzOmFiYw==", - "consumer": "rule-consumer", - "executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28", - "id": "1", - "params": Object { - "alertVal": "My 1 name-of-alert test1 tag-A,tag-B 1 goes here", - "contextVal": "My goes here", - "foo": true, - "stateVal": "My goes here", - }, - "relatedSavedObjects": Array [ - Object { - "id": "1", - "namespace": "test1", - "type": "alert", - "typeId": "test", - }, - ], - "source": Object { - "source": Object { - "id": "1", - "type": "alert", - }, - "type": "SAVED_OBJECT", - }, - "spaceId": "test1", - }, - ], - ] - `); - }); - describe('rule url', () => { const ruleWithUrl = { ...rule, diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts index 10a4d7484b0c6a..ec690bb8ba0f5f 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.ts @@ -628,19 +628,6 @@ export class ExecutionHandler< continue; } - if (this.rule.alertDelay && alert.getActiveCount() < this.rule.alertDelay.active) { - this.logger.debug( - `no scheduling of action "${action.id}" for rule "${ - this.taskInstance.params.alertId - }": the alert activeCount: ${alert.getActiveCount()} is less than the rule alertDelay.active: ${ - this.rule.alertDelay.active - } threshold.` - ); - continue; - } else { - alert.resetActiveCount(); - } - const actionGroup = this.getActionGroup(alert); if (!this.ruleTypeActionGroups!.has(actionGroup)) { diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 3d647966414f5c..b2a984ea5768fa 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -454,7 +454,7 @@ export const generateAlertInstance = ( flapping: false, maintenanceWindowIds: maintenanceWindowIds || [], pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { bar: false, 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 d14d44010252d8..e4afa351d4f146 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 @@ -2954,7 +2954,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', @@ -3125,7 +3125,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', @@ -3143,7 +3143,7 @@ describe('Task Runner', () => { maintenanceWindowIds: [], flapping: false, pendingRecoveredCount: 0, - activeCount: 0, + activeCount: 1, }, state: { duration: '0', 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 208e46b88a1f66..f3c0f39b1e0a1a 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -257,6 +257,7 @@ export class TaskRunner< revision: rule.revision, spaceId, tags: rule.tags, + alertDelay: rule.alertDelay?.active ?? 0, }; } @@ -582,6 +583,7 @@ export class TaskRunner< notifyWhen === RuleNotifyWhen.CHANGE || some(actions, (action) => action.frequency?.notifyWhen === RuleNotifyWhen.CHANGE), maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds, + alertDelay: rule.alertDelay?.active ?? 0, }); }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts index afed418ec8d3da..2367167b496971 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.test.ts @@ -208,6 +208,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -216,6 +217,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -358,6 +360,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -366,6 +369,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -490,6 +494,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -498,6 +503,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -732,6 +738,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -740,6 +747,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -850,6 +858,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -858,6 +867,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlerts: {}, @@ -963,6 +973,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -971,6 +982,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1077,6 +1089,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: { @@ -1087,6 +1100,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }, @@ -1258,6 +1272,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1266,6 +1281,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1388,6 +1404,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1396,6 +1413,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1570,6 +1588,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: flapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1578,6 +1597,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [false, false], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_2: { alertId: 'TEST_ALERT_2', @@ -1586,6 +1606,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: flapping, flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_3: { alertId: 'TEST_ALERT_3', @@ -1594,6 +1615,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [false, false], flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1604,6 +1626,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlerts).toEqual({ TEST_ALERT_0: { + activeCount: 1, alertId: 'TEST_ALERT_0', alertUuid: 'TEST_ALERT_0_UUID', flapping: true, @@ -1612,6 +1635,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_1: { + activeCount: 1, alertId: 'TEST_ALERT_1', alertUuid: 'TEST_ALERT_1_UUID', flapping: false, @@ -1620,6 +1644,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-02T12:00:00.000Z', }, TEST_ALERT_2: { + activeCount: 1, alertId: 'TEST_ALERT_2', alertUuid: 'TEST_ALERT_2_UUID', flapping: true, @@ -1628,6 +1653,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_3: { + activeCount: 1, alertId: 'TEST_ALERT_3', alertUuid: 'TEST_ALERT_3_UUID', flapping: true, @@ -1786,6 +1812,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [true, true, true, true], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_1: { alertId: 'TEST_ALERT_1', @@ -1794,6 +1821,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: notFlapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_2: { alertId: 'TEST_ALERT_2', @@ -1802,6 +1830,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: [true, true], flapping: true, pendingRecoveredCount: 0, + activeCount: 0, }, TEST_ALERT_3: { alertId: 'TEST_ALERT_3', @@ -1810,6 +1839,7 @@ describe('createLifecycleExecutor', () => { flappingHistory: notFlapping, flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -1820,6 +1850,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlerts).toEqual({ TEST_ALERT_2: { + activeCount: 0, alertId: 'TEST_ALERT_2', alertUuid: 'TEST_ALERT_2_UUID', flapping: true, @@ -1831,6 +1862,7 @@ describe('createLifecycleExecutor', () => { expect(serializedAlerts.state.trackedAlertsRecovered).toEqual({ TEST_ALERT_0: { + activeCount: 0, alertId: 'TEST_ALERT_0', alertUuid: 'TEST_ALERT_0_UUID', flapping: true, @@ -1839,6 +1871,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-01T12:00:00.000Z', }, TEST_ALERT_1: { + activeCount: 0, alertId: 'TEST_ALERT_1', alertUuid: 'TEST_ALERT_1_UUID', flapping: false, @@ -1847,6 +1880,7 @@ describe('createLifecycleExecutor', () => { started: '2020-01-02T12:00:00.000Z', }, TEST_ALERT_3: { + activeCount: 0, alertId: 'TEST_ALERT_3', alertUuid: 'TEST_ALERT_3_UUID', flapping: false, diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 91d30fae7b3dc9..994187484f9719 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -290,7 +290,7 @@ export const createLifecycleExecutor = trackedAlertRecoveredIds ); - const { alertUuid, started, flapping, pendingRecoveredCount } = !isNew + const { alertUuid, started, flapping, pendingRecoveredCount, activeCount } = !isNew ? state.trackedAlerts[alertId] : { alertUuid: lifecycleAlertServices.getAlertUuid(alertId), @@ -299,6 +299,7 @@ export const createLifecycleExecutor = ? state.trackedAlertsRecovered[alertId].flapping : false, pendingRecoveredCount: 0, + activeCount: 0, }; const event: ParsedTechnicalFields & ParsedExperimentalFields = { @@ -342,16 +343,21 @@ export const createLifecycleExecutor = flappingHistory, flapping, pendingRecoveredCount, + activeCount, }; }); const trackedEventsToIndex = makeEventsDataMapFor(trackedAlertIds); const newEventsToIndex = makeEventsDataMapFor(newAlertIds); const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds); - const allEventsToIndex = [ - ...getAlertsForNotification(flappingSettings, trackedEventsToIndex), - ...newEventsToIndex, - ]; + const allEventsToIndex = getAlertsForNotification( + commonRuleFields[TIMESTAMP], + flappingSettings, + rule.alertDelay?.active ?? 0, + trackedEventsToIndex, + newEventsToIndex, + maintenanceWindowIds + ); // Only write alerts if: // - writing is enabled @@ -392,18 +398,34 @@ export const createLifecycleExecutor = } const nextTrackedAlerts = Object.fromEntries( - allEventsToIndex + [...allEventsToIndex, ...trackedEventsToIndex] .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); const nextTrackedAlertsRecovered = Object.fromEntries( @@ -416,16 +438,32 @@ export const createLifecycleExecutor = event[ALERT_STATUS] === ALERT_STATUS_RECOVERED && (flapping || flappingHistory.filter((f: boolean) => f).length > 0) ) - .map(({ event, flappingHistory, flapping: isCurrentlyFlapping, pendingRecoveredCount }) => { - const alertId = event[ALERT_INSTANCE_ID]!; - const alertUuid = event[ALERT_UUID]!; - const started = new Date(event[ALERT_START]!).toISOString(); - const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); - return [ - alertId, - { alertId, alertUuid, started, flappingHistory, flapping, pendingRecoveredCount }, - ]; - }) + .map( + ({ + event, + flappingHistory, + flapping: isCurrentlyFlapping, + pendingRecoveredCount, + activeCount, + }) => { + const alertId = event[ALERT_INSTANCE_ID]!; + const alertUuid = event[ALERT_UUID]!; + const started = new Date(event[ALERT_START]!).toISOString(); + const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping); + return [ + alertId, + { + alertId, + alertUuid, + started, + flappingHistory, + flapping, + pendingRecoveredCount, + activeCount, + }, + ]; + } + ) ); return { diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts index b3047303bcb08f..9fa82fcf739ab5 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts @@ -20,6 +20,7 @@ describe('getAlertsForNotification', () => { }, flapping: true, pendingRecoveredCount: 3, + activeCount: 3, }; const alert2 = { event: { @@ -40,13 +41,32 @@ describe('getAlertsForNotification', () => { pendingRecoveredCount: 4, flappingHistory: [true, true], }; + const alert5 = { + event: { + 'kibana.alert.status': ALERT_STATUS_ACTIVE, + }, + activeCount: 1, + pendingRecoveredCount: 0, + flappingHistory: [], + }; test('should set pendingRecoveredCount to zero for all active alerts', () => { - const trackedEvents = [alert4]; - expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents)) - .toMatchInlineSnapshot(` + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, newEvents) + ).toMatchInlineSnapshot(` Array [ Object { + "activeCount": 2, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, + Object { + "activeCount": 1, "event": Object { "kibana.alert.status": "active", }, @@ -62,10 +82,11 @@ describe('getAlertsForNotification', () => { test('should not remove alerts if the num of recovered alerts is not at the limit', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents)) + expect(getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [])) .toMatchInlineSnapshot(` Array [ Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -73,12 +94,14 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, "flapping": false, }, Object { + "activeCount": 0, "event": Object { "event.action": "active", "kibana.alert.status": "active", @@ -92,10 +115,11 @@ describe('getAlertsForNotification', () => { test('should reset counts and not modify alerts if flapping is disabled', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, trackedEvents)) + expect(getAlertsForNotification('timestamp', DISABLE_FLAPPING_SETTINGS, 0, trackedEvents, [])) .toMatchInlineSnapshot(` Array [ Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -103,6 +127,7 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, @@ -110,12 +135,107 @@ describe('getAlertsForNotification', () => { "pendingRecoveredCount": 0, }, Object { + "activeCount": 0, + "event": Object { + "kibana.alert.status": "recovered", + }, + "flapping": true, + "pendingRecoveredCount": 0, + }, + ] + `); + }); + + test('should increment activeCount for all active alerts', () => { + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, newEvents) + ).toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 2, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, + Object { + "activeCount": 1, + "event": Object { + "kibana.alert.status": "active", + }, + "flappingHistory": Array [ + true, + true, + ], + "pendingRecoveredCount": 0, + }, + ] + `); + }); + + test('should reset activeCount for all recovered alerts', () => { + const trackedEvents = cloneDeep([alert1, alert2]); + expect(getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [])) + .toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 0, "event": Object { "kibana.alert.status": "recovered", }, "flapping": true, "pendingRecoveredCount": 0, }, + Object { + "activeCount": 0, + "event": Object { + "kibana.alert.status": "recovered", + }, + "flapping": false, + }, + ] + `); + }); + + test('should not return active alerts if the activeCount is less than the rule alertDelay', () => { + const trackedEvents = cloneDeep([alert4]); + const newEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 5, trackedEvents, newEvents) + ).toMatchInlineSnapshot(`Array []`); + }); + + test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { + const trackedEvents = cloneDeep([alert5]); + expect( + getAlertsForNotification( + 'timestamp', + DEFAULT_FLAPPING_SETTINGS, + 2, + trackedEvents, + [], + ['maintenance-window-id'] + ) + ).toMatchInlineSnapshot(` + Array [ + Object { + "activeCount": 2, + "event": Object { + "event.action": "open", + "kibana.alert.maintenance_window_ids": Array [ + "maintenance-window-id", + ], + "kibana.alert.status": "active", + "kibana.alert.time_range": Object { + "gte": "timestamp", + }, + }, + "flappingHistory": Array [], + "pendingRecoveredCount": 0, + }, ] `); }); diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index 878db2a9180227..c9ac8a954c2c65 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -12,31 +12,54 @@ import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, EVENT_ACTION, + ALERT_TIME_RANGE, + ALERT_MAINTENANCE_WINDOW_IDS, } from '@kbn/rule-data-utils'; export function getAlertsForNotification( + timestamp: string, flappingSettings: RulesSettingsFlappingProperties, - trackedEventsToIndex: any[] + alertDelay: number, + trackedEventsToIndex: any[], + newEventsToIndex: any[], + maintenanceWindowIds?: string[] ) { - return trackedEventsToIndex.map((trackedEvent) => { - if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { + const events: any[] = []; + for (const trackedEvent of [...newEventsToIndex, ...trackedEventsToIndex]) { + if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) { + const count = trackedEvent.activeCount || 0; + trackedEvent.activeCount = count + 1; trackedEvent.pendingRecoveredCount = 0; - } else if ( - flappingSettings.enabled && - trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED - ) { - if (trackedEvent.flapping) { - const count = trackedEvent.pendingRecoveredCount || 0; - trackedEvent.pendingRecoveredCount = count + 1; - if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { - trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE; - trackedEvent.event[EVENT_ACTION] = 'active'; - delete trackedEvent.event[ALERT_END]; - } else { - trackedEvent.pendingRecoveredCount = 0; + if (trackedEvent.activeCount < alertDelay) { + continue; + } else { + if (trackedEvent.activeCount === alertDelay) { + trackedEvent.event[ALERT_TIME_RANGE] = { gte: timestamp }; + trackedEvent.event[EVENT_ACTION] = 'open'; + if (maintenanceWindowIds?.length) { + trackedEvent.event[ALERT_MAINTENANCE_WINDOW_IDS] = maintenanceWindowIds; + } } } + } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) { + trackedEvent.activeCount = 0; + if (flappingSettings.enabled) { + if (trackedEvent.flapping) { + const count = trackedEvent.pendingRecoveredCount || 0; + trackedEvent.pendingRecoveredCount = count + 1; + if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) { + trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE; + trackedEvent.event[EVENT_ACTION] = 'active'; + delete trackedEvent.event[ALERT_END]; + } else { + trackedEvent.pendingRecoveredCount = 0; + } + } + } else { + trackedEvent.pendingRecoveredCount = 0; + } } - return trackedEvent; - }); + events.push(trackedEvent); + } + return events; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index e65f8c90bd03d6..6570e79a6f6c59 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -834,7 +834,7 @@ export const RuleForm = ({ label={[ , } />, From 468f94d7c09b6165f8b5c7b2e806e23df0f71fad Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Tue, 30 Jan 2024 13:46:10 -0800 Subject: [PATCH 11/15] Updating tests --- .../alerts_client/lib/format_rule.test.ts | 1 + .../alerts_service/alerts_service.test.ts | 13 + .../server/task_runner/task_runner.ts | 4 +- .../server/utils/create_lifecycle_executor.ts | 5 +- .../utils/get_alerts_for_notification.test.ts | 54 +- .../utils/get_alerts_for_notification.ts | 17 +- .../tests/alerting/group1/event_log.ts | 135 ++-- .../tests/alerting/group4/alert_delay.ts | 29 +- .../alerts_as_data_alert_delay.ts | 660 ++++++++++++++++++ .../alerting/group4/alerts_as_data/index.ts | 1 + 10 files changed, 787 insertions(+), 132 deletions(-) create mode 100644 x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts index 9f335f8266b214..c9cc7fbefbfa41 100644 --- a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts @@ -61,6 +61,7 @@ describe('formatRule', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, ruleType, }) diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index b73f671ab0695c..0823e327e0adbc 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -1475,6 +1475,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1495,6 +1496,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1528,6 +1530,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1576,6 +1579,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1674,6 +1678,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }), alertsService.createAlertsClient({ @@ -1691,6 +1696,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }), ]); @@ -1781,6 +1787,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -1801,6 +1808,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1865,6 +1873,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); }; @@ -1961,6 +1970,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); }; @@ -2026,6 +2036,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -2091,6 +2102,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); @@ -2154,6 +2166,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, }); 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 f3c0f39b1e0a1a..93f655965e92a2 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -312,6 +312,7 @@ export class TaskRunner< muteAll, revision, snoozeSchedule, + alertDelay, } = rule; const { params: { alertId: ruleId, spaceId }, @@ -526,6 +527,7 @@ export class TaskRunner< notifyWhen, muteAll, snoozeSchedule, + alertDelay, }, logger: this.logger, flappingSettings, @@ -583,7 +585,7 @@ export class TaskRunner< notifyWhen === RuleNotifyWhen.CHANGE || some(actions, (action) => action.frequency?.notifyWhen === RuleNotifyWhen.CHANGE), maintenanceWindowIds: maintenanceWindowsWithoutScopedQueryIds, - alertDelay: rule.alertDelay?.active ?? 0, + alertDelay: alertDelay?.active ?? 0, }); }); diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts index 994187484f9719..4bd3b912ae67df 100644 --- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts +++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts @@ -351,12 +351,11 @@ export const createLifecycleExecutor = const newEventsToIndex = makeEventsDataMapFor(newAlertIds); const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds); const allEventsToIndex = getAlertsForNotification( - commonRuleFields[TIMESTAMP], flappingSettings, rule.alertDelay?.active ?? 0, trackedEventsToIndex, newEventsToIndex, - maintenanceWindowIds + { maintenanceWindowIds, timestamp: commonRuleFields[TIMESTAMP] } ); // Only write alerts if: @@ -398,7 +397,7 @@ export const createLifecycleExecutor = } const nextTrackedAlerts = Object.fromEntries( - [...allEventsToIndex, ...trackedEventsToIndex] + [...newEventsToIndex, ...trackedEventsToIndex] .filter(({ event }) => event[ALERT_STATUS] !== ALERT_STATUS_RECOVERED) .map( ({ diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts index 9fa82fcf739ab5..abb9ebba6d016a 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts @@ -14,6 +14,10 @@ import { cloneDeep } from 'lodash'; import { getAlertsForNotification } from './get_alerts_for_notification'; describe('getAlertsForNotification', () => { + const newEventParams = { + maintenanceWindowIds: ['maintenance-window-id'], + timestamp: 'timestamp', + }; const alert1 = { event: { 'kibana.alert.status': ALERT_STATUS_RECOVERED, @@ -54,7 +58,13 @@ describe('getAlertsForNotification', () => { const trackedEvents = cloneDeep([alert4]); const newEvents = cloneDeep([alert5]); expect( - getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, newEvents) + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 0, + trackedEvents, + newEvents, + newEventParams + ) ).toMatchInlineSnapshot(` Array [ Object { @@ -82,8 +92,9 @@ describe('getAlertsForNotification', () => { test('should not remove alerts if the num of recovered alerts is not at the limit', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [])) - .toMatchInlineSnapshot(` + expect( + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` Array [ Object { "activeCount": 0, @@ -115,8 +126,9 @@ describe('getAlertsForNotification', () => { test('should reset counts and not modify alerts if flapping is disabled', () => { const trackedEvents = cloneDeep([alert1, alert2, alert3]); - expect(getAlertsForNotification('timestamp', DISABLE_FLAPPING_SETTINGS, 0, trackedEvents, [])) - .toMatchInlineSnapshot(` + expect( + getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` Array [ Object { "activeCount": 0, @@ -150,7 +162,13 @@ describe('getAlertsForNotification', () => { const trackedEvents = cloneDeep([alert4]); const newEvents = cloneDeep([alert5]); expect( - getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, newEvents) + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 0, + trackedEvents, + newEvents, + newEventParams + ) ).toMatchInlineSnapshot(` Array [ Object { @@ -178,8 +196,9 @@ describe('getAlertsForNotification', () => { test('should reset activeCount for all recovered alerts', () => { const trackedEvents = cloneDeep([alert1, alert2]); - expect(getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [])) - .toMatchInlineSnapshot(` + expect( + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 0, trackedEvents, [], newEventParams) + ).toMatchInlineSnapshot(` Array [ Object { "activeCount": 0, @@ -204,30 +223,31 @@ describe('getAlertsForNotification', () => { const trackedEvents = cloneDeep([alert4]); const newEvents = cloneDeep([alert5]); expect( - getAlertsForNotification('timestamp', DEFAULT_FLAPPING_SETTINGS, 5, trackedEvents, newEvents) + getAlertsForNotification( + DEFAULT_FLAPPING_SETTINGS, + 5, + trackedEvents, + newEvents, + newEventParams + ) ).toMatchInlineSnapshot(`Array []`); }); test('should update active alert to look like a new alert if the activeCount is equal to the rule alertDelay', () => { const trackedEvents = cloneDeep([alert5]); expect( - getAlertsForNotification( - 'timestamp', - DEFAULT_FLAPPING_SETTINGS, - 2, - trackedEvents, - [], - ['maintenance-window-id'] - ) + getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, 2, trackedEvents, [], newEventParams) ).toMatchInlineSnapshot(` Array [ Object { "activeCount": 2, "event": Object { "event.action": "open", + "kibana.alert.duration.us": 0, "kibana.alert.maintenance_window_ids": Array [ "maintenance-window-id", ], + "kibana.alert.start": "timestamp", "kibana.alert.status": "active", "kibana.alert.time_range": Object { "gte": "timestamp", diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts index c9ac8a954c2c65..5ec0e5b835eec3 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts @@ -11,18 +11,23 @@ import { ALERT_STATUS, ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, + ALERT_START, + ALERT_DURATION, EVENT_ACTION, ALERT_TIME_RANGE, ALERT_MAINTENANCE_WINDOW_IDS, } from '@kbn/rule-data-utils'; export function getAlertsForNotification( - timestamp: string, flappingSettings: RulesSettingsFlappingProperties, alertDelay: number, trackedEventsToIndex: any[], newEventsToIndex: any[], - maintenanceWindowIds?: string[] + newEventParams: { + // values used to create a new event + maintenanceWindowIds?: string[]; + timestamp: string; + } ) { const events: any[] = []; for (const trackedEvent of [...newEventsToIndex, ...trackedEventsToIndex]) { @@ -30,10 +35,18 @@ export function getAlertsForNotification( const count = trackedEvent.activeCount || 0; trackedEvent.activeCount = count + 1; trackedEvent.pendingRecoveredCount = 0; + // do not index the event if the number of consecutive + // active alerts is less than the rule alertDelay threshold if (trackedEvent.activeCount < alertDelay) { + // remove from array of events to index continue; } else { + const { timestamp, maintenanceWindowIds } = newEventParams; + // if the active count is equal to the alertDelay it is considered a new event if (trackedEvent.activeCount === alertDelay) { + // update the event to look like a new event + trackedEvent.event[ALERT_DURATION] = 0; + trackedEvent.event[ALERT_START] = timestamp; trackedEvent.event[ALERT_TIME_RANGE] = { gte: timestamp }; trackedEvent.event[EVENT_ACTION] = 'open'; if (maintenanceWindowIds?.length) { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts index 4231439c892392..da3752e098de22 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/event_log.ts @@ -7,6 +7,7 @@ import moment from 'moment'; import expect from '@kbn/expect'; +import { get } from 'lodash'; import { IValidatedEvent, nanosToMillis } from '@kbn/event-log-plugin/server'; import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; import { ES_TEST_INDEX_NAME, ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; @@ -1850,6 +1851,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); it('should generate expected events with a alertDelay', async () => { + const ACTIVE_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.active'; + const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new'; + const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; + const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; + const { body: createdAction } = await supertest .post(`${getUrlPrefix(space.id)}/api/actions/connector`) .set('kbn-xsrf', 'foo') @@ -1863,7 +1869,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { // pattern of when the alert should fire const pattern = { - instance: [true, true, true, false, true], + instance: [true, true, true, true, false, true], }; const response = await supertest @@ -1874,6 +1880,7 @@ export default function eventLogTests({ getService }: FtrProviderContext) { rule_type_id: 'test.patternFiring', schedule: { interval: '1s' }, throttle: null, + notify_when: null, params: { pattern, }, @@ -1882,6 +1889,11 @@ export default function eventLogTests({ getService }: FtrProviderContext) { id: createdAction.id, group: 'default', params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, }, ], alert_delay: { @@ -1904,101 +1916,48 @@ export default function eventLogTests({ getService }: FtrProviderContext) { provider: 'alerting', actions: new Map([ // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 1 }], + ['execute-start', { equal: 6 }], + ['execute', { equal: 6 }], + ['new-instance', { equal: 1 }], + ['active-instance', { equal: 2 }], ['recovered-instance', { equal: 1 }], ]), }); }); - const actualTriggeredActions = events - .filter((event) => event?.event?.action === 'execute') - .reduce( - (acc, event) => - acc + - (event?.kibana?.alert?.rule?.execution?.metrics - ?.number_of_triggered_actions as number), - 0 - ); - expect(actualTriggeredActions).to.eql(1); - }); + const executeEvents = events.filter((event) => event?.event?.action === 'execute'); - it('should generate expected events with a alertDelay with AAD', async () => { - const { body: createdAction } = await supertest - .post(`${getUrlPrefix(space.id)}/api/actions/connector`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'MY action', - connector_type_id: 'test.noop', - config: {}, - secrets: {}, - }) - .expect(200); - - // pattern of when the alert should fire - const pattern = { - instance: [true, true, true, false, true], - }; - - const response = await supertest - .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) - .set('kbn-xsrf', 'foo') - .send( - getTestRuleData({ - rule_type_id: 'test.patternFiringAad', - schedule: { interval: '1s' }, - throttle: null, - params: { - pattern, - }, - actions: [ - { - id: createdAction.id, - group: 'default', - params: {}, - }, - ], - alert_delay: { - active: 3, - }, - }) - ); - - expect(response.status).to.eql(200); - const alertId = response.body.id; - objectRemover.add(space.id, alertId, 'rule', 'alerting'); - - // get the events we're expecting - const events = await retry.try(async () => { - return await getEventLog({ - getService, - spaceId: space.id, - type: 'alert', - id: alertId, - provider: 'alerting', - actions: new Map([ - // make sure the counts of the # of events per type are as expected - ['execute-start', { gte: 5 }], - ['execute', { gte: 5 }], - ['new-instance', { equal: 2 }], - ['active-instance', { gte: 1 }], - ['recovered-instance', { equal: 1 }], - ]), - }); + // first two executions do not create the active alert + executeEvents.slice(0, 1).forEach((event) => { + expect(get(event, ACTIVE_PATH)).to.be(0); + expect(get(event, NEW_PATH)).to.be(0); + expect(get(event, RECOVERED_PATH)).to.be(0); + expect(get(event, ACTION_PATH)).to.be(0); }); - const actualTriggeredActions = events - .filter((event) => event?.event?.action === 'execute') - .reduce( - (acc, event) => - acc + - (event?.kibana?.alert?.rule?.execution?.metrics - ?.number_of_triggered_actions as number), - 0 - ); - expect(actualTriggeredActions).to.eql(1); + // third executions creates the delayed active alert and triggers actions + expect(get(executeEvents[2], ACTIVE_PATH)).to.be(1); + expect(get(executeEvents[2], NEW_PATH)).to.be(1); + expect(get(executeEvents[2], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[2], ACTION_PATH)).to.be(1); + + // fourth execution + expect(get(executeEvents[3], ACTIVE_PATH)).to.be(1); + expect(get(executeEvents[3], NEW_PATH)).to.be(0); + expect(get(executeEvents[3], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[3], ACTION_PATH)).to.be(0); + + // fifth recovered execution + expect(get(executeEvents[4], ACTIVE_PATH)).to.be(0); + expect(get(executeEvents[4], NEW_PATH)).to.be(0); + expect(get(executeEvents[4], RECOVERED_PATH)).to.be(1); + expect(get(executeEvents[4], ACTION_PATH)).to.be(0); + + // sixth execution does not create the active alert + expect(get(executeEvents[5], ACTIVE_PATH)).to.be(0); + expect(get(executeEvents[5], NEW_PATH)).to.be(0); + expect(get(executeEvents[5], RECOVERED_PATH)).to.be(0); + expect(get(executeEvents[5], ACTION_PATH)).to.be(0); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts index 30429e9f433a3c..7062c1c65fd9c5 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alert_delay.ts @@ -37,20 +37,7 @@ export default function createAlertDelayTests({ getService }: FtrProviderContext afterEach(() => objectRemover.removeAll()); - it('should clear the activeCount if the alertDelay is not configured for the rule', async () => { - const start = new Date().toISOString(); - const pattern = { - instance: [true], - }; - - const ruleId = await createRule(actionId, pattern); - objectRemover.add(space.id, ruleId, 'rule', 'alerting'); - - const state = await getAlertState(start, ruleId, 0); - expect(get(state, ACTIVE_PATH)).to.eql(0); - }); - - it('should update the activeCount when alert is active and clear when recovered if the alertDelay is configured for the rule', async () => { + it('should update the activeCount when alert is active and clear when recovered', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, false, true], @@ -79,7 +66,7 @@ export default function createAlertDelayTests({ getService }: FtrProviderContext expect(get(state, ACTIVE_PATH)).to.eql(1); }); - it('should reset the activeCount when count of consecutive active alerts exceeds the alertDelay count', async () => { + it('should continue incrementing the activeCount when count of consecutive active alerts exceeds the alertDelay count', async () => { let start = new Date().toISOString(); const pattern = { instance: [true, true, true, true, true], @@ -96,16 +83,16 @@ export default function createAlertDelayTests({ getService }: FtrProviderContext expect(get(state, ACTIVE_PATH)).to.eql(2); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 0, true); - expect(get(state, ACTIVE_PATH)).to.eql(0); + state = await getAlertState(start, ruleId, 3, true); + expect(get(state, ACTIVE_PATH)).to.eql(3); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 1, true); - expect(get(state, ACTIVE_PATH)).to.eql(1); + state = await getAlertState(start, ruleId, 4, true); + expect(get(state, ACTIVE_PATH)).to.eql(4); start = new Date().toISOString(); - state = await getAlertState(start, ruleId, 2, true); - expect(get(state, ACTIVE_PATH)).to.eql(2); + state = await getAlertState(start, ruleId, 5, true); + expect(get(state, ACTIVE_PATH)).to.eql(5); }); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts new file mode 100644 index 00000000000000..a5039d439b72e0 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts @@ -0,0 +1,660 @@ +/* + * 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 expect from '@kbn/expect'; +import { get } from 'lodash'; +import { SearchHit } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IValidatedEvent } from '@kbn/event-log-plugin/server'; +import type { Alert } from '@kbn/alerts-as-data-utils'; +import { + ALERT_ACTION_GROUP, + ALERT_DURATION, + ALERT_END, + ALERT_RULE_CATEGORY, + ALERT_RULE_CONSUMER, + ALERT_RULE_EXECUTION_UUID, + ALERT_RULE_NAME, + ALERT_RULE_PARAMETERS, + ALERT_RULE_PRODUCER, + ALERT_RULE_TAGS, + ALERT_RULE_TYPE_ID, + ALERT_RULE_UUID, + ALERT_START, + ALERT_STATUS, + ALERT_TIME_RANGE, + ALERT_UUID, + ALERT_WORKFLOW_STATUS, + EVENT_ACTION, + EVENT_KIND, + SPACE_IDS, +} from '@kbn/rule-data-utils'; +import { RuleNotifyWhen } from '@kbn/alerting-plugin/common'; +import { ES_TEST_INDEX_NAME, ESTestIndexTool } from '@kbn/alerting-api-integration-helpers'; +import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; +import { Spaces } from '../../../../scenarios'; +import { + getEventLog, + getTestRuleData, + getUrlPrefix, + ObjectRemover, + TaskManagerDoc, +} from '../../../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export default function createAlertsAsDataAlertDelayInstallResourcesTest({ + getService, +}: FtrProviderContext) { + const ACTIVE_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.active'; + const NEW_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.new'; + const RECOVERED_PATH = 'kibana.alert.rule.execution.metrics.alert_counts.recovered'; + const ACTION_PATH = 'kibana.alert.rule.execution.metrics.number_of_triggered_actions'; + const UUID_PATH = 'kibana.alert.rule.execution.uuid'; + + const es = getService('es'); + const retry = getService('retry'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const objectRemover = new ObjectRemover(supertestWithoutAuth); + const esTestIndexTool = new ESTestIndexTool(es, retry); + + type PatternFiringAlert = Alert & { patternIndex: number; instancePattern: boolean[] }; + // type AlwaysFiringAlert = Alert & { patternIndex: number; instancePattern: boolean[] }; + + const alertsAsDataIndex = '.alerts-test.patternfiring.alerts-default'; + const alwaysFiringAlertsAsDataIndex = + '.internal.alerts-observability.test.alerts.alerts-default-000001'; + const timestampPattern = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/; + + describe('alerts as data', () => { + before(async () => { + await esTestIndexTool.destroy(); + await esTestIndexTool.setup(); + }); + afterEach(() => objectRemover.removeAll()); + after(async () => { + await objectRemover.removeAll(); + await esTestIndexTool.destroy(); + await es.deleteByQuery({ + index: [alertsAsDataIndex, alwaysFiringAlertsAsDataIndex], + query: { match_all: {} }, + conflicts: 'proceed', + }); + }); + + it('should generate expected events with a alertDelay with AAD', async () => { + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + // pattern of when the alert should fire + const pattern = { + instance: [true, true, true, true, false, true], + }; + + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.patternFiringAad', + schedule: { interval: '1d' }, + throttle: null, + notify_when: null, + params: { + pattern, + }, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + alert_delay: { + active: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // -------------------------- + // RUN 1 - 0 new alerts + // -------------------------- + let events: IValidatedEvent[] = await waitForEventLogDocs( + ruleId, + new Map([['execute', { equal: 1 }]]) + ); + let executeEvent = events[0]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun1 = await queryForAlertDocs(); + + // Get alert state from task document + let state: any = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(1); + expect(state.alertInstances.instance.state.patternIndex).to.equal(0); + + // After the first run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun1.length).to.equal(0); + + // -------------------------- + // RUN 2 - 0 new alerts + // -------------------------- + let runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 2 }]])); + executeEvent = events[1]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun2 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(2); + expect(state.alertInstances.instance.state.patternIndex).to.equal(1); + + // After the second run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun2.length).to.equal(0); + + // -------------------------- + // RUN 3 - 1 new alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 3 }]])); + executeEvent = events[2]; + let executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(1); + expect(get(executeEvent, NEW_PATH)).to.be(1); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(1); + + // Query for alerts + const alertDocsRun3 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(3); + expect(state.alertInstances.instance.state.patternIndex).to.equal(2); + + // After the third run, we should have 1 alert docs for the 1 active alert + expect(alertDocsRun3.length).to.equal(1); + + testExpectRuleData(alertDocsRun3, ruleId, { pattern }, executionUuid!); + let source: PatternFiringAlert = alertDocsRun3[0]._source!; + + // Each doc should have active status and default action group id + expect(source[ALERT_ACTION_GROUP]).to.equal('default'); + // patternIndex should be 2 for the third run + expect(source.patternIndex).to.equal(2); + // alert UUID should equal doc id + expect(source[ALERT_UUID]).to.equal(alertDocsRun3[0]._id); + // duration should be 0 since this is a new alert + expect(source[ALERT_DURATION]).to.equal(0); + // start should be defined + expect(source[ALERT_START]).to.match(timestampPattern); + // time_range.gte should be same as start + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(source[ALERT_START]); + // timestamp should be defined + expect(source['@timestamp']).to.match(timestampPattern); + // status should be active + expect(source[ALERT_STATUS]).to.equal('active'); + // workflow status should be 'open' + expect(source[ALERT_WORKFLOW_STATUS]).to.equal('open'); + // event.action should be 'open' + expect(source[EVENT_ACTION]).to.equal('open'); + // event.kind should be 'signal' + expect(source[EVENT_KIND]).to.equal('signal'); + // tags should equal rule tags because rule type doesn't set any tags + expect(source.tags).to.eql(['foo']); + + // -------------------------- + // RUN 4 - 1 active alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 4 }]])); + executeEvent = events[3]; + executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(1); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun4 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(4); + expect(state.alertInstances.instance.state.patternIndex).to.equal(3); + + // After the fourth run, we should have 1 alert docs for the 1 active alert + expect(alertDocsRun4.length).to.equal(1); + + testExpectRuleData(alertDocsRun4, ruleId, { pattern }, executionUuid!); + source = alertDocsRun4[0]._source!; + const run3Source = alertDocsRun3[0]._source!; + + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // patternIndex should be 3 for the fourth run + expect(source.patternIndex).to.equal(3); + expect(source[ALERT_ACTION_GROUP]).to.equal('default'); + // start time should be defined and the same as prior run + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // status should still be active + expect(source[ALERT_STATUS]).to.equal('active'); + // event.action set to active + expect(source[EVENT_ACTION]).to.eql('active'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + + // -------------------------- + // RUN 5 - 1 recovered alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 5 }]])); + executeEvent = events[4]; + executionUuid = get(executeEvent, UUID_PATH); + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(1); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun5 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertRecoveredInstances.instance.meta.activeCount).to.equal(0); + + // After the fourth run, we should have 1 alert docs for the 1 recovered alert + expect(alertDocsRun5.length).to.equal(1); + + testExpectRuleData(alertDocsRun5, ruleId, { pattern }, executionUuid!); + source = alertDocsRun5[0]._source!; + + // action group should be set to recovered + expect(source[ALERT_ACTION_GROUP]).to.be('recovered'); + // rule type AAD payload should be set to recovery values + expect(source.instancePattern).to.eql([]); + expect(source.patternIndex).to.eql(-1); + // uuid is the same + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // start time should be defined and the same as before + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // end time should be defined + expect(source[ALERT_END]).to.match(timestampPattern); + // status should be set to recovered + expect(source[ALERT_STATUS]).to.equal('recovered'); + // event.action set to close + expect(source[EVENT_ACTION]).to.eql('close'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + // time_range.lte should be set to end time + expect(source[ALERT_TIME_RANGE]?.lte).to.equal(source[ALERT_END]); + + // -------------------------- + // RUN 6 - 0 new alerts + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 6 }]])); + executeEvent = events[5]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun6 = await queryForAlertDocs(); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances.instance.meta.activeCount).to.equal(1); + expect(state.alertInstances.instance.state.patternIndex).to.equal(5); + + // After the sixth run, we should have 1 alert docs for the previously recovered alert + expect(alertDocsRun6.length).to.equal(1); + }); + + it('should generate expected events with a alertDelay with AAD (rule registry)', async () => { + const params = { + index: ES_TEST_INDEX_NAME, + reference: 'test', + }; + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'MY action', + connector_type_id: 'test.noop', + config: {}, + secrets: {}, + }) + .expect(200); + + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/api/alerting/rule`) + .set('kbn-xsrf', 'foo') + .send( + getTestRuleData({ + rule_type_id: 'test.always-firing-alert-as-data', + schedule: { interval: '1d' }, + throttle: null, + notify_when: null, + params, + actions: [ + { + id: createdAction.id, + group: 'default', + params: {}, + frequency: { + summary: false, + throttle: null, + notify_when: RuleNotifyWhen.CHANGE, + }, + }, + ], + alert_delay: { + active: 3, + }, + }) + ); + + expect(response.status).to.eql(200); + const ruleId = response.body.id; + objectRemover.add(Spaces.space1.id, ruleId, 'rule', 'alerting'); + + // -------------------------- + // RUN 1 - 0 new alerts + // -------------------------- + let events: IValidatedEvent[] = await waitForEventLogDocs( + ruleId, + new Map([['execute', { equal: 1 }]]) + ); + let executeEvent = events[0]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun1 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + let state: any = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(1); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(1); + + // After the first run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun1.length).to.equal(0); + + // -------------------------- + // RUN 2 - 0 new alerts + // -------------------------- + let runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 2 }]])); + executeEvent = events[1]; + expect(get(executeEvent, ACTIVE_PATH)).to.be(0); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun2 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(2); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(2); + + // After the second run, we should have 0 alert docs for the 0 active alerts + expect(alertDocsRun2.length).to.equal(0); + + // -------------------------- + // RUN 3 - 1 new alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 3 }]])); + executeEvent = events[2]; + let executionUuid = get(executeEvent, UUID_PATH); + // Note: the rule creates 2 alerts but we will only look at one + expect(get(executeEvent, ACTIVE_PATH)).to.be(2); + expect(get(executeEvent, NEW_PATH)).to.be(2); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(2); + + // Query for alerts + const alertDocsRun3 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(3); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(3); + + // After the third run, we should have 2 alert docs for the 2 active alerts but we will only look at one + expect(alertDocsRun3.length).to.equal(2); + + let source: Alert = alertDocsRun3[0]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal('Test: Always Firing Alert As Data'); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.always-firing-alert-as-data'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(params); + expect(source[SPACE_IDS]).to.eql(['space1']); + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + // alert UUID should equal doc id + expect(source[ALERT_UUID]).to.equal(alertDocsRun3[0]._id); + // duration should be 0 since this is a new alert + expect(source[ALERT_DURATION]).to.equal(0); + // start should be defined + expect(source[ALERT_START]).to.match(timestampPattern); + // time_range.gte should be same as start + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(source[ALERT_START]); + // timestamp should be defined + expect(source['@timestamp']).to.match(timestampPattern); + // status should be active + expect(source[ALERT_STATUS]).to.equal('active'); + // workflow status should be 'open' + expect(source[ALERT_WORKFLOW_STATUS]).to.equal('open'); + // event.action should be 'open' + expect(source[EVENT_ACTION]).to.equal('open'); + // event.kind should be 'signal' + expect(source[EVENT_KIND]).to.equal('signal'); + // tags should equal rule tags because rule type doesn't set any tags + expect(source.tags).to.eql(['foo']); + + // -------------------------- + // RUN 4 - 1 active alert + // -------------------------- + runSoon = await supertestWithoutAuth + .post(`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rule/${ruleId}/_run_soon`) + .set('kbn-xsrf', 'foo'); + expect(runSoon.status).to.eql(204); + + events = await waitForEventLogDocs(ruleId, new Map([['execute', { equal: 4 }]])); + executeEvent = events[3]; + executionUuid = get(executeEvent, UUID_PATH); + // Note: the rule creates 2 alerts but we will only look at one + expect(get(executeEvent, ACTIVE_PATH)).to.be(2); + expect(get(executeEvent, NEW_PATH)).to.be(0); + expect(get(executeEvent, RECOVERED_PATH)).to.be(0); + expect(get(executeEvent, ACTION_PATH)).to.be(0); + + // Query for alerts + const alertDocsRun4 = await queryForAlertDocs(alwaysFiringAlertsAsDataIndex); + + // Get alert state from task document + state = await getTaskState(ruleId); + expect(state.alertInstances['1'].meta.activeCount).to.equal(4); + expect(state.alertTypeState.trackedAlerts['1'].activeCount).to.equal(4); + + // After the fourth run, we should have 2 alert docs for the 2 active alerts but we will only look at one + expect(alertDocsRun4.length).to.equal(2); + + source = alertDocsRun4[0]._source!; + const run3Source = alertDocsRun3[0]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal('Test: Always Firing Alert As Data'); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.always-firing-alert-as-data'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(params); + expect(source[SPACE_IDS]).to.eql(['space1']); + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + expect(source[ALERT_UUID]).to.equal(run3Source[ALERT_UUID]); + // start time should be defined and the same as prior run + expect(source[ALERT_START]).to.match(timestampPattern); + expect(source[ALERT_START]).to.equal(run3Source[ALERT_START]); + // timestamp should be defined and not the same as prior run + expect(source['@timestamp']).to.match(timestampPattern); + expect(source['@timestamp']).not.to.equal(run3Source['@timestamp']); + // status should still be active + expect(source[ALERT_STATUS]).to.equal('active'); + // event.action set to active + expect(source[EVENT_ACTION]).to.eql('active'); + expect(source.tags).to.eql(['foo']); + // these values should be the same as previous run + expect(source[EVENT_KIND]).to.eql(run3Source[EVENT_KIND]); + expect(source[ALERT_WORKFLOW_STATUS]).to.eql(run3Source[ALERT_WORKFLOW_STATUS]); + expect(source[ALERT_TIME_RANGE]?.gte).to.equal(run3Source[ALERT_TIME_RANGE]?.gte); + }); + }); + + function testExpectRuleData( + alertDocs: Array>, + ruleId: string, + ruleParameters: unknown, + executionUuid?: string + ) { + for (let i = 0; i < alertDocs.length; ++i) { + const source: PatternFiringAlert = alertDocs[i]._source!; + + // Each doc should have a copy of the rule data + expect(source[ALERT_RULE_CATEGORY]).to.equal( + 'Test: Firing on a Pattern and writing Alerts as Data' + ); + expect(source[ALERT_RULE_CONSUMER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_NAME]).to.equal('abc'); + expect(source[ALERT_RULE_PRODUCER]).to.equal('alertsFixture'); + expect(source[ALERT_RULE_TAGS]).to.eql(['foo']); + expect(source[ALERT_RULE_TYPE_ID]).to.equal('test.patternFiringAad'); + expect(source[ALERT_RULE_UUID]).to.equal(ruleId); + expect(source[ALERT_RULE_PARAMETERS]).to.eql(ruleParameters); + expect(source[SPACE_IDS]).to.eql(['space1']); + + if (executionUuid) { + expect(source[ALERT_RULE_EXECUTION_UUID]).to.equal(executionUuid); + } + } + } + + async function queryForAlertDocs( + index: string = alertsAsDataIndex + ): Promise>> { + const searchResult = await es.search({ + index, + body: { query: { match_all: {} } }, + }); + return searchResult.hits.hits as Array>; + } + + async function getTaskState(ruleId: string) { + const task = await es.get({ + id: `task:${ruleId}`, + index: '.kibana_task_manager', + }); + + return JSON.parse(task._source!.task.state); + } + + async function waitForEventLogDocs( + id: string, + actions: Map + ) { + return await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id, + provider: 'alerting', + actions, + }); + }); + } +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts index 20342e053016dc..e1a29d1c4bf3e6 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/index.ts @@ -14,5 +14,6 @@ export default function alertsAsDataTests({ loadTestFile }: FtrProviderContext) loadTestFile(require.resolve('./alerts_as_data')); loadTestFile(require.resolve('./alerts_as_data_flapping')); loadTestFile(require.resolve('./alerts_as_data_conflicts')); + loadTestFile(require.resolve('./alerts_as_data_alert_delay')); }); } From 134fae2d63e0c3268ef7f04986b2ef3edca93e75 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Tue, 30 Jan 2024 16:22:02 -0800 Subject: [PATCH 12/15] Fixing test failures --- .../server/alerts_service/alerts_service.test.ts | 3 +++ .../alerting_event_logger/alerting_event_logger.ts | 2 ++ .../lib/create_alert_event_log_record_object.ts | 3 +++ .../alerting/server/routes/update_rule.test.ts | 2 +- .../server/rules_client/tests/update.test.ts | 12 ++++++------ .../task_runner/task_runner_alerts_client.test.ts | 2 ++ .../utils/get_updated_flapping_history.test.ts | 5 +++++ .../alerts_as_data/alerts_as_data_alert_delay.ts | 5 +++++ 8 files changed, 27 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 0823e327e0adbc..def19570f7e0a5 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -1614,6 +1614,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1731,6 +1732,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); @@ -1901,6 +1903,7 @@ describe('Alerts Service', () => { revision: 0, spaceId: 'default', tags: ['rule-', '-tags'], + alertDelay: 0, }, kibanaVersion: '8.8.0', }); diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts index 680279f5dbac7f..2c177dac75cd29 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts @@ -53,6 +53,7 @@ interface AlertOpts { state?: AlertInstanceState; flapping: boolean; maintenanceWindowIds?: string[]; + alertCreationDelay?: boolean; } interface ActionOpts { @@ -269,6 +270,7 @@ export function createAlertRecord(context: RuleContextOpts, alert: AlertOpts) { flapping: alert.flapping, maintenanceWindowIds: alert.maintenanceWindowIds, ruleRevision: context.ruleRevision, + alertCreationDelay: alert.alertCreationDelay, }); } diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts index 251df68e5267f7..86add440656ba4 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts @@ -44,6 +44,7 @@ interface CreateAlertEventLogRecordParams { }; maintenanceWindowIds?: string[]; ruleRevision?: number; + alertCreationDelay?: boolean; } export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecordParams): Event { @@ -64,6 +65,7 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor alertSummary, maintenanceWindowIds, ruleRevision, + alertCreationDelay, } = params; const alerting = params.instanceId || group || alertSummary @@ -98,6 +100,7 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor ...(flapping !== undefined ? { flapping } : {}), ...(maintenanceWindowIds ? { maintenance_window_ids: maintenanceWindowIds } : {}), ...(alertUuid ? { uuid: alertUuid } : {}), + ...(alertCreationDelay !== undefined ? { alert_creation_delay: alertCreationDelay } : {}), rule: { revision: ruleRevision, rule_type_id: ruleType.id, diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index c942546ba8faef..b48e6d72bef3f6 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -142,10 +142,10 @@ describe('updateRuleRoute', () => { "uuid": "1234-5678", }, ], - "name": "abc", "alertDelay": Object { "active": 10, }, + "name": "abc", "notifyWhen": "onActionGroupChange", "params": Object { "otherField": false, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index ceab7ad16f01c1..7384ab467a8e97 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -370,12 +370,12 @@ describe('update()', () => { }, }, ], - "createdAt": 2019-02-12T21:01:22.479Z, - "enabled": true, - "id": "1", "alertDelay": Object { "active": 5, }, + "createdAt": 2019-02-12T21:01:22.479Z, + "enabled": true, + "id": "1", "notifyWhen": "onActiveAlert", "params": Object { "bar": true, @@ -431,6 +431,9 @@ describe('update()', () => { "uuid": "102", }, ], + "alertDelay": Object { + "active": 10, + }, "alertTypeId": "myType", "apiKey": null, "apiKeyCreatedByUser": null, @@ -445,9 +448,6 @@ describe('update()', () => { "versionApiKeyLastmodified": "v7.10.0", }, "name": "abc", - "alertDelay": Object { - "active": 10, - }, "notifyWhen": "onActiveAlert", "params": Object { "bar": true, diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts index ebf81f5bf050ec..bd47acbbdb8c13 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_alerts_client.test.ts @@ -302,6 +302,7 @@ describe('Task Runner', () => { ruleType: ruleTypeWithAlerts, namespace: 'default', rule: { + alertDelay: 0, consumer: 'bar', executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28', id: '1', @@ -800,6 +801,7 @@ describe('Task Runner', () => { expect(alertsClientToUse.processAlerts).toHaveBeenCalledWith({ notifyOnActionGroupChange: false, + alertDelay: 0, flappingSettings: { enabled: true, lookBackWindow: 20, diff --git a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts index 52467f168e641c..84685779186d97 100644 --- a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts +++ b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts @@ -49,6 +49,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -81,6 +82,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlerts: {}, @@ -115,6 +117,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, trackedAlertsRecovered: {}, @@ -150,6 +153,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }; @@ -184,6 +188,7 @@ describe('getUpdatedFlappingHistory', () => { flappingHistory: [], flapping: false, pendingRecoveredCount: 0, + activeCount: 0, }, }, }; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts index a5039d439b72e0..f7e2876e9775b2 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group4/alerts_as_data/alerts_as_data_alert_delay.ts @@ -72,6 +72,11 @@ export default function createAlertsAsDataAlertDelayInstallResourcesTest({ before(async () => { await esTestIndexTool.destroy(); await esTestIndexTool.setup(); + await es.deleteByQuery({ + index: [alertsAsDataIndex, alwaysFiringAlertsAsDataIndex], + query: { match_all: {} }, + conflicts: 'proceed', + }); }); afterEach(() => objectRemover.removeAll()); after(async () => { From 8bfda464a0afcbb5ac78aa1d6b153badccaa6e43 Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Wed, 31 Jan 2024 12:39:37 -0800 Subject: [PATCH 13/15] Remove event log changes --- .../server/lib/alerting_event_logger/alerting_event_logger.ts | 2 -- .../server/lib/create_alert_event_log_record_object.ts | 3 --- 2 files changed, 5 deletions(-) diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts index 2c177dac75cd29..680279f5dbac7f 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.ts @@ -53,7 +53,6 @@ interface AlertOpts { state?: AlertInstanceState; flapping: boolean; maintenanceWindowIds?: string[]; - alertCreationDelay?: boolean; } interface ActionOpts { @@ -270,7 +269,6 @@ export function createAlertRecord(context: RuleContextOpts, alert: AlertOpts) { flapping: alert.flapping, maintenanceWindowIds: alert.maintenanceWindowIds, ruleRevision: context.ruleRevision, - alertCreationDelay: alert.alertCreationDelay, }); } diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts index 86add440656ba4..251df68e5267f7 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.ts @@ -44,7 +44,6 @@ interface CreateAlertEventLogRecordParams { }; maintenanceWindowIds?: string[]; ruleRevision?: number; - alertCreationDelay?: boolean; } export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecordParams): Event { @@ -65,7 +64,6 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor alertSummary, maintenanceWindowIds, ruleRevision, - alertCreationDelay, } = params; const alerting = params.instanceId || group || alertSummary @@ -100,7 +98,6 @@ export function createAlertEventLogRecordObject(params: CreateAlertEventLogRecor ...(flapping !== undefined ? { flapping } : {}), ...(maintenanceWindowIds ? { maintenance_window_ids: maintenanceWindowIds } : {}), ...(alertUuid ? { uuid: alertUuid } : {}), - ...(alertCreationDelay !== undefined ? { alert_creation_delay: alertCreationDelay } : {}), rule: { revision: ruleRevision, rule_type_id: ruleType.id, From 75a4e50160945ef08e661554a2325ef57624be1f Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Wed, 31 Jan 2024 13:09:09 -0800 Subject: [PATCH 14/15] Remove ui changes --- .../server/routes/lib/rewrite_rule.test.ts | 3 - .../server/routes/lib/rewrite_rule.ts | 2 - .../server/routes/update_rule.test.ts | 10 -- .../alerting/server/routes/update_rule.ts | 10 +- .../server/rules_client/methods/update.ts | 3 +- .../server/rules_client/tests/update.test.ts | 12 -- .../lib/rule_api/common_transformations.ts | 2 - .../application/lib/rule_api/create.test.ts | 9 -- .../public/application/lib/rule_api/create.ts | 2 - .../application/lib/rule_api/update.test.ts | 5 +- .../public/application/lib/rule_api/update.ts | 15 +- .../sections/rule_form/rule_form.test.tsx | 21 --- .../sections/rule_form/rule_form.tsx | 141 ++++++------------ .../sections/rule_form/rule_reducer.test.ts | 17 --- .../sections/rule_form/rule_reducer.ts | 29 ---- 15 files changed, 51 insertions(+), 230 deletions(-) diff --git a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts index 826ee952a6bb65..7a348e583ac6c4 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.test.ts @@ -62,9 +62,6 @@ const sampleRule: SanitizedRule & { activeSnoozes?: string[] } = }, nextRun: DATE_2020, revision: 0, - alertDelay: { - active: 10, - }, }; describe('rewriteRule', () => { 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 d0e59278b13c5b..953211a5ef4f77 100644 --- a/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts +++ b/x-pack/plugins/alerting/server/routes/lib/rewrite_rule.ts @@ -37,7 +37,6 @@ export const rewriteRule = ({ activeSnoozes, lastRun, nextRun, - alertDelay, ...rest }: SanitizedRule & { activeSnoozes?: string[] }) => ({ ...rest, @@ -79,5 +78,4 @@ export const rewriteRule = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), - ...(alertDelay !== undefined ? { alert_delay: alertDelay } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/update_rule.test.ts b/x-pack/plugins/alerting/server/routes/update_rule.test.ts index b48e6d72bef3f6..5a4b3a19c0d7ce 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.test.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.test.ts @@ -59,9 +59,6 @@ describe('updateRuleRoute', () => { }, ], notifyWhen: RuleNotifyWhen.CHANGE, - alertDelay: { - active: 10, - }, }; const updateRequest: AsApiContract['data']> = { @@ -76,9 +73,6 @@ describe('updateRuleRoute', () => { alerts_filter: mockedAlert.actions[0].alertsFilter, }, ], - alert_delay: { - active: 10, - }, }; const updateResult: AsApiContract> = { @@ -92,7 +86,6 @@ describe('updateRuleRoute', () => { connector_type_id: actionTypeId, alerts_filter: alertsFilter, })), - alert_delay: mockedAlert.alertDelay, }; it('updates a rule with proper parameters', async () => { @@ -142,9 +135,6 @@ describe('updateRuleRoute', () => { "uuid": "1234-5678", }, ], - "alertDelay": Object { - "active": 10, - }, "name": "abc", "notifyWhen": "onActionGroupChange", "params": Object { diff --git a/x-pack/plugins/alerting/server/routes/update_rule.ts b/x-pack/plugins/alerting/server/routes/update_rule.ts index 9419d84d063413..d24af256de6139 100644 --- a/x-pack/plugins/alerting/server/routes/update_rule.ts +++ b/x-pack/plugins/alerting/server/routes/update_rule.ts @@ -52,22 +52,16 @@ const bodySchema = schema.object({ ) ) ), - alert_delay: schema.maybe( - schema.object({ - active: schema.number(), - }) - ), }); const rewriteBodyReq: RewriteRequestCase> = (result) => { - const { notify_when: notifyWhen, alert_delay: alertDelay, actions, ...rest } = result.data; + const { notify_when: notifyWhen, actions, ...rest } = result.data; return { ...result, data: { ...rest, notifyWhen, actions: rewriteActionsReq(actions), - alertDelay, }, }; }; @@ -89,7 +83,6 @@ const rewriteBodyRes: RewriteResponseCase> = ({ isSnoozedUntil, lastRun, nextRun, - alertDelay, ...rest }) => ({ ...rest, @@ -122,7 +115,6 @@ const rewriteBodyRes: RewriteResponseCase> = ({ ...(lastRun ? { last_run: rewriteRuleLastRun(lastRun) } : {}), ...(nextRun ? { next_run: nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { api_key_created_by_user: apiKeyCreatedByUser } : {}), - ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export const updateRuleRoute = ( diff --git a/x-pack/plugins/alerting/server/rules_client/methods/update.ts b/x-pack/plugins/alerting/server/rules_client/methods/update.ts index 1255173beefe49..33afee4c20d26f 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/update.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/update.ts @@ -17,7 +17,7 @@ import { } from '../../types'; import { validateRuleTypeParams, getRuleNotifyWhenType } from '../../lib'; import { WriteOperations, AlertingAuthorizationEntity } from '../../authorization'; -import { parseDuration, getRuleCircuitBreakerErrorMessage, AlertDelay } from '../../../common'; +import { parseDuration, getRuleCircuitBreakerErrorMessage } from '../../../common'; import { retryIfConflicts } from '../../lib/retry_if_conflicts'; import { bulkMarkApiKeysForInvalidation } from '../../invalidate_pending_api_keys/bulk_mark_api_keys_for_invalidation'; import { ruleAuditEvent, RuleAuditAction } from '../common/audit_events'; @@ -51,7 +51,6 @@ export interface UpdateOptions { params: Params; throttle?: string | null; notifyWhen?: RuleNotifyWhenType | null; - alertDelay?: AlertDelay; }; allowMissingConnectorSecrets?: boolean; shouldIncrementRevision?: ShouldIncrementRevision; diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 7384ab467a8e97..be3221c8ed2f17 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -279,9 +279,6 @@ describe('update()', () => { scheduledTaskId: 'task-123', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), - alertDelay: { - active: 5, - }, }, references: [ { @@ -337,9 +334,6 @@ describe('update()', () => { }, }, ], - alertDelay: { - active: 10, - }, }, }); expect(result).toMatchInlineSnapshot(` @@ -370,9 +364,6 @@ describe('update()', () => { }, }, ], - "alertDelay": Object { - "active": 5, - }, "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", @@ -431,9 +422,6 @@ describe('update()', () => { "uuid": "102", }, ], - "alertDelay": Object { - "active": 10, - }, "alertTypeId": "myType", "apiKey": null, "apiKeyCreatedByUser": null, 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 b00b874b079ef5..64949d9014c507 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 @@ -77,7 +77,6 @@ export const transformRule: RewriteRequestCase = ({ active_snoozes: activeSnoozes, last_run: lastRun, next_run: nextRun, - alert_delay: alertDelay, ...rest }: any) => ({ ruleTypeId, @@ -100,7 +99,6 @@ export const transformRule: RewriteRequestCase = ({ ...(lastRun ? { lastRun: transformLastRun(lastRun) } : {}), ...(nextRun ? { nextRun } : {}), ...(apiKeyCreatedByUser !== undefined ? { apiKeyCreatedByUser } : {}), - ...(alertDelay ? { alertDelay } : {}), ...rest, }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts index b27d9cad0c0560..76e5fd7f09207f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.test.ts @@ -52,9 +52,6 @@ describe('createRule', () => { execution_status: { status: 'pending', last_execution_date: '2021-04-01T21:33:13.250Z' }, create_at: '2021-04-01T21:33:13.247Z', updated_at: '2021-04-01T21:33:13.247Z', - alert_delay: { - active: 10, - }, }; const ruleToCreate: Omit< RuleUpdates, @@ -99,9 +96,6 @@ describe('createRule', () => { updatedAt: new Date('2021-04-01T21:33:13.247Z'), apiKeyOwner: '', revision: 0, - alertDelay: { - active: 10, - }, }; http.post.mockResolvedValueOnce(resolvedValue); @@ -154,9 +148,6 @@ describe('createRule', () => { tags: [], updatedAt: '2021-04-01T21:33:13.247Z', updatedBy: undefined, - alertDelay: { - active: 10, - }, }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts index 48fa1783f3c1ff..2304ee8c489301 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/create.ts @@ -23,7 +23,6 @@ type RuleCreateBody = Omit< const rewriteBodyRequest: RewriteResponseCase = ({ ruleTypeId, actions, - alertDelay, ...res }): any => ({ ...res, @@ -44,7 +43,6 @@ const rewriteBodyRequest: RewriteResponseCase = ({ : {}), }) ), - ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export async function createRule({ diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts index 591cdc83e86cf3..8b3ebc3f96e523 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.test.ts @@ -27,9 +27,6 @@ describe('updateRule', () => { apiKey: null, apiKeyOwner: null, revision: 0, - alertDelay: { - active: 10, - }, }; const resolvedValue: Rule = { ...ruleToUpdate, @@ -54,7 +51,7 @@ describe('updateRule', () => { Array [ "/api/alerting/rule/12%2F3", Object { - "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"alert_delay\\":{\\"active\\":10}}", + "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[]}", }, ] `); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts index 80346ff2f65daf..52158bfa2f0343 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/update.ts @@ -13,13 +13,9 @@ import { transformRule } from './common_transformations'; type RuleUpdatesBody = Pick< RuleUpdates, - 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'throttle' | 'notifyWhen' | 'alertDelay' + 'name' | 'tags' | 'schedule' | 'actions' | 'params' | 'throttle' | 'notifyWhen' >; -const rewriteBodyRequest: RewriteResponseCase = ({ - actions, - alertDelay, - ...res -}): any => ({ +const rewriteBodyRequest: RewriteResponseCase = ({ actions, ...res }): any => ({ ...res, actions: actions.map( ({ group, id, params, frequency, uuid, alertsFilter, useAlertDataForTemplate }) => ({ @@ -38,7 +34,6 @@ const rewriteBodyRequest: RewriteResponseCase = ({ ...(uuid && { uuid }), }) ), - ...(alertDelay ? { alert_delay: alertDelay } : {}), }); export async function updateRule({ @@ -47,16 +42,14 @@ export async function updateRule({ id, }: { http: HttpSetup; - rule: Pick; + rule: Pick; id: string; }): Promise { const res = await http.put>( `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}`, { body: JSON.stringify( - rewriteBodyRequest( - pick(rule, ['name', 'tags', 'schedule', 'params', 'actions', 'alertDelay']) - ) + rewriteBodyRequest(pick(rule, ['name', 'tags', 'schedule', 'params', 'actions'])) ), } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx index 8902b4d472ad2d..24592566d54655 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx @@ -375,9 +375,6 @@ describe('rule_form', () => { enabled: false, mutedInstanceIds: [], ...(!showRulesList ? { ruleTypeId: ruleType.id } : {}), - alertDelay: { - active: 1, - }, } as unknown as Rule; wrapper = mountWithIntl( @@ -1037,24 +1034,6 @@ describe('rule_form', () => { expect(wrapper.find(ActionForm).props().hasFieldsForAAD).toEqual(true); }); - - it('renders rule alert delay', async () => { - const getAlertDelayInput = () => { - return wrapper.find('[data-test-subj="alertDelayInput"] input').first(); - }; - - await setup(); - expect(getAlertDelayInput().props().value).toEqual(1); - - getAlertDelayInput().simulate('change', { target: { value: '2' } }); - expect(getAlertDelayInput().props().value).toEqual(2); - - getAlertDelayInput().simulate('change', { target: { value: '20' } }); - expect(getAlertDelayInput().props().value).toEqual(20); - - getAlertDelayInput().simulate('change', { target: { value: '999' } }); - expect(getAlertDelayInput().props().value).toEqual(999); - }); }); describe('rule_form create rule non ruleing consumer and producer', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index 6570e79a6f6c59..fc8af27e416a4f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -215,7 +215,6 @@ export const RuleForm = ({ ? getDurationUnitValue(rule.schedule.interval) : defaultScheduleIntervalUnit ); - const [alertDelay, setAlertDelay] = useState(rule.alertDelay?.active); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); const [availableRuleTypes, setAvailableRuleTypes] = useState([]); @@ -329,12 +328,6 @@ export const RuleForm = ({ } }, [rule.schedule.interval, defaultScheduleInterval, defaultScheduleIntervalUnit]); - useEffect(() => { - if (rule.alertDelay) { - setAlertDelay(rule.alertDelay.active); - } - }, [rule.alertDelay]); - useEffect(() => { if (!flyoutBodyOverflowRef.current) { // We're using this as a reliable way to reset the scroll position @@ -400,10 +393,6 @@ export const RuleForm = ({ [dispatch] ); - const setAlertDelayProperty = (key: string, value: any) => { - dispatch({ command: { type: 'setAlertDelayProperty' }, payload: { key, value } }); - }; - useEffect(() => { const searchValue = searchText ? searchText.trim().toLocaleLowerCase() : null; setFilteredRuleTypes( @@ -777,94 +766,52 @@ export const RuleForm = ({ ) : null} {hideInterval !== true && ( - <> - - 0} - error={errors['schedule.interval']} - > - - - 0} - value={ruleInterval || ''} - name="interval" - data-test-subj="intervalInput" - onChange={(e) => { - const value = e.target.value; - if (value === '' || INTEGER_REGEX.test(value)) { - const parsedValue = value === '' ? '' : parseInt(value, 10); - setRuleInterval(parsedValue || undefined); - setScheduleProperty('interval', `${parsedValue}${ruleIntervalUnit}`); - } - }} - /> - - - { - setRuleIntervalUnit(e.target.value); - setScheduleProperty('interval', `${ruleInterval}${e.target.value}`); - }} - data-test-subj="intervalInputUnit" - /> - - - - - - + + 0} + error={errors['schedule.interval']} + > + + + 0} + value={ruleInterval || ''} + name="interval" + data-test-subj="intervalInput" + onChange={(e) => { + const value = e.target.value; + if (value === '' || INTEGER_REGEX.test(value)) { + const parsedValue = value === '' ? '' : parseInt(value, 10); + setRuleInterval(parsedValue || undefined); + setScheduleProperty('interval', `${parsedValue}${ruleIntervalUnit}`); + } + }} + /> + + + { + setRuleIntervalUnit(e.target.value); + setScheduleProperty('interval', `${ruleInterval}${e.target.value}`); + }} + data-test-subj="intervalInputUnit" + /> + + + + )} - - , - - } - />, - ]} - > - { - const value = e.target.value; - if (value === '' || INTEGER_REGEX.test(value)) { - const parsedValue = value === '' ? '' : parseInt(value, 10); - setAlertDelayProperty('active', parsedValue || 0); - setAlertDelay(parsedValue || undefined); - } - }} - /> - - {shouldShowConsumerSelect && ( <> diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts index 6eadf1fce5ff43..996676b73d59e8 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.test.ts @@ -21,9 +21,6 @@ describe('rule reducer', () => { actions: [], tags: [], notifyWhen: 'onActionGroupChange', - alertDelay: { - active: 5, - }, } as unknown as Rule; }); @@ -214,18 +211,4 @@ describe('rule reducer', () => { ); expect(updatedRule.rule.actions[0].frequency?.notifyWhen).toBe('onThrottleInterval'); }); - - test('if initial alert delay property was updated', () => { - const updatedRule = ruleReducer( - { rule: initialRule }, - { - command: { type: 'setAlertDelayProperty' }, - payload: { - key: 'active', - value: 10, - }, - } - ); - expect(updatedRule.rule.alertDelay?.active).toBe(10); - }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts index 257df764ebc1ef..54f3871928fb35 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_reducer.ts @@ -12,7 +12,6 @@ import { RuleActionParam, IntervalSchedule, RuleActionAlertsFilterProperty, - AlertDelay, } from '@kbn/alerting-plugin/common'; import { isEmpty } from 'lodash/fp'; import { Rule, RuleAction } from '../../../types'; @@ -31,7 +30,6 @@ interface CommandType< | 'setRuleActionProperty' | 'setRuleActionFrequency' | 'setRuleActionAlertsFilter' - | 'setAlertDelayProperty' > { type: T; } @@ -64,12 +62,6 @@ interface RuleSchedulePayload { index?: number; } -interface AlertDelayPayload { - key: Key; - value: AlertDelay[Key] | null; - index?: number; -} - export type RuleReducerAction = | { command: CommandType<'setRule'>; @@ -102,10 +94,6 @@ export type RuleReducerAction = | { command: CommandType<'setRuleActionAlertsFilter'>; payload: Payload; - } - | { - command: CommandType<'setAlertDelayProperty'>; - payload: AlertDelayPayload; }; export type InitialRuleReducer = Reducer<{ rule: InitialRule }, RuleReducerAction>; @@ -293,22 +281,5 @@ export const ruleReducer = ( }; } } - case 'setAlertDelayProperty': { - const { key, value } = action.payload as Payload; - if (rule.alertDelay && isEqual(rule.alertDelay[key], value)) { - return state; - } else { - return { - ...state, - rule: { - ...rule, - alertDelay: { - ...rule.alertDelay, - [key]: value, - }, - }, - }; - } - } } }; From 26b6a564625d27c8581931b82389122e4781fa8b Mon Sep 17 00:00:00 2001 From: Alexandra Doak Date: Wed, 31 Jan 2024 13:15:30 -0800 Subject: [PATCH 15/15] Remove new line --- .../public/application/sections/rule_form/rule_form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index fc8af27e416a4f..98e2547dabdd3a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -811,7 +811,6 @@ export const RuleForm = ({ )} - {shouldShowConsumerSelect && ( <>