From 1960fa6f216858952a2ded62077ea964e4129f16 Mon Sep 17 00:00:00 2001 From: Ersin Erdal <92688503+ersin-erdal@users.noreply.github.com> Date: Wed, 9 Feb 2022 01:39:46 +0100 Subject: [PATCH] Alert doesn't fire action if it's muted or throttled (#124775) * Alert doesn't fire action if it's muted or throttled (cherry picked from commit e4dd6e769c493becefcac1290cd5c4e955b0f64b) # Conflicts: # x-pack/plugins/alerting/server/task_runner/task_runner.ts --- .../server/task_runner/task_runner.test.ts | 124 +++++++++++++++++- .../server/task_runner/task_runner.ts | 52 ++++---- 2 files changed, 148 insertions(+), 28 deletions(-) diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index 402cc3951d39b..f572bd3466418 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 @@ -41,6 +41,7 @@ import { omit } from 'lodash'; import { UntypedNormalizedRuleType } from '../rule_type_registry'; import { ruleTypeRegistryMock } from '../rule_type_registry.mock'; import { ExecuteOptions } from '../../../actions/server/create_execute_function'; +import moment from 'moment'; jest.mock('uuid', () => ({ v4: () => '5f6aa57d-3e22-484e-bae8-cbed868f4d28', @@ -978,7 +979,128 @@ describe('Task Runner', () => { } ); - test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert alert state does not change', async () => { + test.each(ephemeralTestParams)( + 'skips firing actions for active alert if alert is throttled %s', + async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => { + ( + customTaskRunnerFactoryInitializerParams as TaskRunnerFactoryInitializerParamsType + ).actionsPlugin.isActionTypeEnabled.mockReturnValue(true); + customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue( + true + ); + actionsClient.ephemeralEnqueuedExecution.mockResolvedValue(mockRunNowResponse); + ruleType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + executorServices.alertInstanceFactory('2').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + ruleType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '2': { + meta: { + lastScheduledActions: { date: moment().toISOString(), group: 'default' }, + }, + state: { + bar: false, + start: '1969-12-31T00:00:00.000Z', + duration: 86400000000000, + }, + }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + rulesClient.get.mockResolvedValue({ + ...mockedRuleTypeSavedObject, + throttle: '1d', + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, + }, + references: [], + }); + await taskRunner.run(); + // expect(enqueueFunction).toHaveBeenCalledTimes(1); + + const logger = customTaskRunnerFactoryInitializerParams.logger; + // expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).nthCalledWith( + 3, + `skipping scheduling of actions for '2' in rule test:1: 'rule-name': rule is throttled` + ); + } + ); + + test.each(ephemeralTestParams)( + 'skips firing actions for active alert when alert is muted even if notifyWhen === onActionGroupChange %s', + async (nameExtension, customTaskRunnerFactoryInitializerParams, enqueueFunction) => { + customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue( + true + ); + ruleType.executor.mockImplementation( + async ({ + services: executorServices, + }: AlertExecutorOptions< + AlertTypeParams, + AlertTypeState, + AlertInstanceState, + AlertInstanceContext, + string + >) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + executorServices.alertInstanceFactory('2').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + ruleType, + mockedTaskInstance, + customTaskRunnerFactoryInitializerParams + ); + rulesClient.get.mockResolvedValue({ + ...mockedRuleTypeSavedObject, + mutedInstanceIds: ['2'], + notifyWhen: 'onActionGroupChange', + }); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + enabled: true, + }, + references: [], + }); + await taskRunner.run(); + expect(enqueueFunction).toHaveBeenCalledTimes(1); + const logger = customTaskRunnerFactoryInitializerParams.logger; + expect(logger.debug).toHaveBeenCalledTimes(5); + expect(logger.debug).nthCalledWith( + 3, + `skipping scheduling of actions for '2' in rule test:1: 'rule-name': rule is muted` + ); + } + ); + + test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert state does not change', async () => { taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true); taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true); ruleType.executor.mockImplementation( 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 785a68e1a24b9..4312167aea048 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -475,34 +475,32 @@ export class TaskRunner< triggeredActions = concat(triggeredActions, scheduledActionsForRecoveredAlerts); - const alertsToExecute = - notifyWhen === 'onActionGroupChange' - ? Object.entries(alertsWithScheduledActions).filter( - ([alertName, alert]: [string, AlertInstance]) => { - const shouldExecuteAction = alert.scheduledActionGroupOrSubgroupHasChanged(); - if (!shouldExecuteAction) { - this.logger.debug( - `skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: alert is active but action group has not changed` - ); - } - return shouldExecuteAction; - } - ) - : Object.entries(alertsWithScheduledActions).filter( - ([alertName, alert]: [string, AlertInstance]) => { - const throttled = alert.isThrottled(throttle); - const muted = mutedAlertIdsSet.has(alertName); - const shouldExecuteAction = !throttled && !muted; - if (!shouldExecuteAction) { - this.logger.debug( - `skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: rule is ${ - muted ? 'muted' : 'throttled' - }` - ); - } - return shouldExecuteAction; - } + const alertsToExecute = Object.entries(alertsWithScheduledActions).filter( + ([alertName, alert]: [string, AlertInstance]) => { + const throttled = alert.isThrottled(throttle); + const muted = mutedAlertIdsSet.has(alertName); + let shouldExecuteAction = true; + + if (throttled || muted) { + shouldExecuteAction = false; + this.logger.debug( + `skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: rule is ${ + muted ? 'muted' : 'throttled' + }` ); + } else if ( + notifyWhen === 'onActionGroupChange' && + !alert.scheduledActionGroupOrSubgroupHasChanged() + ) { + shouldExecuteAction = false; + this.logger.debug( + `skipping scheduling of actions for '${alertName}' in rule ${ruleLabel}: alert is active but action group has not changed` + ); + } + + return shouldExecuteAction; + } + ); const allTriggeredActions = await Promise.all( alertsToExecute.map(