Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 135 additions & 1 deletion x-pack/plugins/alerting/server/task_runner/task_runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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;

Expand Down Expand Up @@ -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(
Expand Down
62 changes: 28 additions & 34 deletions x-pack/plugins/alerting/server/task_runner/task_runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down