Alert doesn't fire action if it's muted or throttled (#124775) (#125562)

* Alert doesn't fire action if it's muted or throttled

(cherry picked from commit e4dd6e769c)

# Conflicts:
#	x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
#	x-pack/plugins/alerting/server/task_runner/task_runner.ts
This commit is contained in:
Ersin Erdal 2022-02-14 20:52:26 +01:00 committed by GitHub
parent bbefaf052e
commit 4313930141
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 163 additions and 35 deletions

View file

@ -40,6 +40,7 @@ import { omit } from 'lodash';
import { UntypedNormalizedAlertType } from '../rule_type_registry';
import { ruleTypeRegistryMock } from '../rule_type_registry.mock';
import { ExecuteOptions } from '../../../actions/server/create_execute_function';
import moment from 'moment';
const alertType: jest.Mocked<UntypedNormalizedAlertType> = {
id: 'test',
@ -55,6 +56,10 @@ const alertType: jest.Mocked<UntypedNormalizedAlertType> = {
let fakeTimer: sinon.SinonFakeTimers;
export const mockRunNowResponse = {
id: 1,
} as jest.ResolvedValue<unknown>;
describe('Task Runner', () => {
let mockedTaskInstance: ConcreteTaskInstance;
@ -865,7 +870,136 @@ describe('Task Runner', () => {
}
);
test('actionsPlugin.execute is not called when notifyWhen=onActionGroupChange and alert instance state does not change', async () => {
testAgainstEphemeralSupport(
'skips firing actions for active alert if alert is throttled %s',
(
customTaskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType,
enqueueFunction: (options: ExecuteOptions) => Promise<void | RunNowResult>
) =>
async () => {
(
customTaskRunnerFactoryInitializerParams as TaskRunnerFactoryInitializerParamsType
).actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(
true
);
actionsClient.ephemeralEnqueuedExecution.mockResolvedValue(mockRunNowResponse);
alertType.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(
alertType,
{
...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({
...mockedAlertTypeSavedObject,
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(4);
expect(logger.debug).nthCalledWith(
3,
`skipping scheduling of actions for '2' in alert test:1: 'alert-name': instance is throttled`
);
}
);
testAgainstEphemeralSupport(
'skips firing actions for active alert when alert is muted even if notifyWhen === onActionGroupChange %s',
(
customTaskRunnerFactoryInitializerParams: TaskRunnerFactoryInitializerParamsType,
enqueueFunction: (options: ExecuteOptions) => Promise<void | RunNowResult>
) =>
async () => {
customTaskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(
true
);
alertType.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(
alertType,
mockedTaskInstance,
customTaskRunnerFactoryInitializerParams
);
rulesClient.get.mockResolvedValue({
...mockedAlertTypeSavedObject,
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(4);
expect(logger.debug).nthCalledWith(
3,
`skipping scheduling of actions for '2' in alert test:1: 'alert-name': instance 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);
alertType.executor.mockImplementation(

View file

@ -379,41 +379,35 @@ export class TaskRunner<
alertLabel,
});
const instancesToExecute =
notifyWhen === 'onActionGroupChange'
? Object.entries(instancesWithScheduledActions).filter(
([alertInstanceName, alertInstance]: [
string,
AlertInstance<InstanceState, InstanceContext>
]) => {
const shouldExecuteAction =
alertInstance.scheduledActionGroupOrSubgroupHasChanged();
if (!shouldExecuteAction) {
this.logger.debug(
`skipping scheduling of actions for '${alertInstanceName}' in alert ${alertLabel}: instance is active but action group has not changed`
);
}
return shouldExecuteAction;
}
)
: Object.entries(instancesWithScheduledActions).filter(
([alertInstanceName, alertInstance]: [
string,
AlertInstance<InstanceState, InstanceContext>
]) => {
const throttled = alertInstance.isThrottled(throttle);
const muted = mutedInstanceIdsSet.has(alertInstanceName);
const shouldExecuteAction = !throttled && !muted;
if (!shouldExecuteAction) {
this.logger.debug(
`skipping scheduling of actions for '${alertInstanceName}' in alert ${alertLabel}: instance is ${
muted ? 'muted' : 'throttled'
}`
);
}
return shouldExecuteAction;
}
const instancesToExecute = Object.entries(instancesWithScheduledActions).filter(
([alertInstanceName, alertInstance]: [
string,
AlertInstance<InstanceState, InstanceContext>
]) => {
const throttled = alertInstance.isThrottled(throttle);
const muted = mutedInstanceIdsSet.has(alertInstanceName);
let shouldExecuteAction = true;
if (throttled || muted) {
shouldExecuteAction = false;
this.logger.debug(
`skipping scheduling of actions for '${alertInstanceName}' in alert ${alertLabel}: instance is ${
muted ? 'muted' : 'throttled'
}`
);
} else if (
notifyWhen === 'onActionGroupChange' &&
!alertInstance.scheduledActionGroupOrSubgroupHasChanged()
) {
shouldExecuteAction = false;
this.logger.debug(
`skipping scheduling of actions for '${alertInstanceName}' in alert ${alertLabel}: instance is active but action group has not changed`
);
}
return shouldExecuteAction;
}
);
await Promise.all(
instancesToExecute.map(