kibana/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts
Maryam Saeidi bb68c20d99
Fix email connector rule URL (#162954)
Closes #162953

## Summary

This PR fixes the issue of adding basePath (`/kibana` in the following
example) twice when generating the rule URL for the email connector.

**Before**
```
1. 48e02d00-3106-11ee-99f4-39221cc3f357
2. e36f8b70-3135-11ee-9808-139f80c124ca
```
**After**
```
1. 48e02d00-3106-11ee-99f4-39221cc3f357
2. e36f8b70-3135-11ee-9808-139f80c124ca
```
2023-08-03 02:51:19 -07:00

1923 lines
61 KiB
TypeScript

/*
* 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 { ExecutionHandler } from './execution_handler';
import { loggingSystemMock } from '@kbn/core/server/mocks';
import {
actionsClientMock,
actionsMock,
renderActionParameterTemplatesDefault,
} from '@kbn/actions-plugin/server/mocks';
import { KibanaRequest } from '@kbn/core/server';
import { InjectActionParamsOpts, injectActionParams } from './inject_action_params';
import { NormalizedRuleType } from '../rule_type_registry';
import {
ActionsCompletion,
ThrottledActions,
RuleTypeParams,
RuleTypeState,
SanitizedRule,
GetViewInAppRelativeUrlFnOpts,
} from '../types';
import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store';
import { alertingEventLoggerMock } from '../lib/alerting_event_logger/alerting_event_logger.mock';
import { TaskRunnerContext } from './task_runner_factory';
import { ConcreteTaskInstance } from '@kbn/task-manager-plugin/server';
import { Alert } from '../alert';
import { AlertInstanceState, AlertInstanceContext } from '../../common';
import { asSavedObjectExecutionSource } from '@kbn/actions-plugin/server';
import sinon from 'sinon';
import { mockAAD } from './fixtures';
import { schema } from '@kbn/config-schema';
import { alertsClientMock } from '../alerts_client/alerts_client.mock';
jest.mock('./inject_action_params', () => ({
injectActionParams: jest.fn(),
}));
const injectActionParamsMock = injectActionParams as jest.Mock;
const alertingEventLogger = alertingEventLoggerMock.create();
const actionsClient = actionsClientMock.create();
const alertsClient = alertsClientMock.create();
const mockActionsPlugin = actionsMock.createStart();
const apiKey = Buffer.from('123:abc').toString('base64');
const ruleType: NormalizedRuleType<
RuleTypeParams,
RuleTypeParams,
RuleTypeState,
AlertInstanceState,
AlertInstanceContext,
'default' | 'other-group',
'recovered',
{}
> = {
id: 'test',
name: 'Test',
actionGroups: [
{ id: 'default', name: 'Default' },
{ id: 'recovered', name: 'Recovered' },
{ id: 'other-group', name: 'Other Group' },
],
defaultActionGroupId: 'default',
minimumLicenseRequired: 'basic',
isExportable: true,
recoveryActionGroup: {
id: 'recovered',
name: 'Recovered',
},
executor: jest.fn(),
producer: 'alerts',
validate: {
params: schema.any(),
},
alerts: {
context: 'context',
mappings: { fieldMap: { field: { type: 'fieldType', required: false } } },
},
autoRecoverAlerts: false,
};
const rule = {
id: '1',
name: 'name-of-alert',
tags: ['tag-A', 'tag-B'],
mutedInstanceIds: [],
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
schedule: { interval: '1m' },
notifyWhen: 'onActiveAlert',
actions: [
{
id: '1',
group: 'default',
actionTypeId: 'test',
params: {
foo: true,
contextVal: 'My {{context.value}} goes here',
stateVal: 'My {{state.value}} goes here',
alertVal:
'My {{rule.id}} {{rule.name}} {{rule.spaceId}} {{rule.tags}} {{alert.id}} goes here',
},
uuid: '111-111',
},
],
} as unknown as SanitizedRule<RuleTypeParams>;
const defaultExecutionParams = {
rule,
ruleType,
logger: loggingSystemMock.create().get(),
taskRunnerContext: {
actionsConfigMap: {
default: {
max: 1000,
},
},
actionsPlugin: mockActionsPlugin,
} as unknown as TaskRunnerContext,
apiKey,
ruleConsumer: 'rule-consumer',
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
alertUuid: 'uuid-1',
ruleLabel: 'rule-label',
request: {} as KibanaRequest,
alertingEventLogger,
previousStartedAt: null,
taskInstance: {
params: { spaceId: 'test1', alertId: '1' },
} as unknown as ConcreteTaskInstance,
actionsClient,
alertsClient,
};
let ruleRunMetricsStore: RuleRunMetricsStore;
let clock: sinon.SinonFakeTimers;
type ActiveActionGroup = 'default' | 'other-group';
const generateAlert = ({
id,
group = 'default',
context,
state,
scheduleActions = true,
throttledActions = {},
lastScheduledActionsGroup = 'default',
maintenanceWindowIds,
}: {
id: number;
group?: ActiveActionGroup | 'recovered';
context?: AlertInstanceContext;
state?: AlertInstanceState;
scheduleActions?: boolean;
throttledActions?: ThrottledActions;
lastScheduledActionsGroup?: string;
maintenanceWindowIds?: string[];
}) => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, 'default' | 'other-group'>(
String(id),
{
state: state || { test: true },
meta: {
maintenanceWindowIds,
lastScheduledActions: {
date: new Date(),
group: lastScheduledActionsGroup,
actions: throttledActions,
},
},
}
);
if (scheduleActions) {
alert.scheduleActions(group as ActiveActionGroup);
}
if (context) {
alert.setContext(context);
}
return { [id]: alert };
};
const generateRecoveredAlert = ({ id, state }: { id: number; state?: AlertInstanceState }) => {
const alert = new Alert<AlertInstanceState, AlertInstanceContext, 'recovered'>(String(id), {
state: state || { test: true },
meta: {
lastScheduledActions: {
date: new Date(),
group: 'recovered',
actions: {},
},
},
});
return { [id]: alert };
};
// @ts-ignore
const generateExecutionParams = (params = {}) => {
return {
...defaultExecutionParams,
...params,
ruleRunMetricsStore,
};
};
const DATE_1970 = new Date('1970-01-01T00:00:00.000Z');
describe('Execution Handler', () => {
beforeEach(() => {
jest.resetAllMocks();
jest
.requireMock('./inject_action_params')
.injectActionParams.mockImplementation(
({ actionParams }: InjectActionParamsOpts) => actionParams
);
mockActionsPlugin.isActionTypeEnabled.mockReturnValue(true);
mockActionsPlugin.isActionExecutable.mockReturnValue(true);
mockActionsPlugin.getActionsClientWithRequest.mockResolvedValue(actionsClient);
mockActionsPlugin.renderActionParameterTemplates.mockImplementation(
renderActionParameterTemplatesDefault
);
ruleRunMetricsStore = new RuleRunMetricsStore();
});
beforeAll(() => {
clock = sinon.useFakeTimers();
});
afterAll(() => clock.restore());
test('enqueues execution per selected action', async () => {
const alerts = generateAlert({ id: 1 });
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run(alerts);
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"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",
},
],
]
`);
expect(alertingEventLogger.logAction).toHaveBeenCalledTimes(1);
expect(alertingEventLogger.logAction).toHaveBeenNthCalledWith(1, {
id: '1',
typeId: 'test',
alertId: '1',
alertGroup: 'default',
});
expect(jest.requireMock('./inject_action_params').injectActionParams).toHaveBeenCalledWith({
actionTypeId: 'test',
actionParams: {
alertVal: 'My 1 name-of-alert test1 tag-A,tag-B 1 goes here',
contextVal: 'My goes here',
foo: true,
stateVal: 'My goes here',
},
});
expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE);
});
test(`doesn't call actionsPlugin.execute for disabled actionTypes`, async () => {
// Mock two calls, one for check against actions[0] and the second for actions[1]
mockActionsPlugin.isActionExecutable.mockReturnValueOnce(false);
mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(false);
mockActionsPlugin.isActionTypeEnabled.mockReturnValueOnce(true);
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
actions: [
{
id: '2',
group: 'default',
actionTypeId: 'test2',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
{
id: '2',
group: 'default',
actionTypeId: 'test2',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
],
},
})
);
await executionHandler.run(generateAlert({ id: 1 }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith([
{
consumer: 'rule-consumer',
id: '2',
params: {
foo: true,
contextVal: 'My other goes here',
stateVal: 'My other goes here',
},
source: asSavedObjectExecutionSource({
id: '1',
type: 'alert',
}),
relatedSavedObjects: [
{
id: '1',
namespace: 'test1',
type: 'alert',
typeId: 'test',
},
],
spaceId: 'test1',
apiKey,
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
},
]);
});
test('throw error message when action type is disabled', async () => {
mockActionsPlugin.inMemoryConnectors = [];
mockActionsPlugin.isActionExecutable.mockReturnValue(false);
mockActionsPlugin.isActionTypeEnabled.mockReturnValue(false);
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
actions: [
{
id: '2',
group: 'default',
actionTypeId: '.slack',
params: {
foo: true,
},
},
{
id: '3',
group: 'default',
actionTypeId: '.slack',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
],
},
})
);
await executionHandler.run(generateAlert({ id: 2 }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(2);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
mockActionsPlugin.isActionExecutable.mockImplementation(() => true);
const executionHandlerForPreconfiguredAction = new ExecutionHandler({
...defaultExecutionParams,
ruleRunMetricsStore,
});
await executionHandlerForPreconfiguredAction.run(generateAlert({ id: 2 }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
});
test('limits actionsPlugin.execute per action group', async () => {
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run(generateAlert({ id: 2, group: 'other-group' }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0);
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
});
test('context attribute gets parameterized', async () => {
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run(generateAlert({ id: 2, context: { value: 'context-val' } }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(1);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(1);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"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 context-val 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('state attribute gets parameterized', async () => {
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"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 state-val 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(`logs an error when action group isn't part of actionGroups available for the ruleType`, async () => {
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run(
generateAlert({ id: 2, group: 'invalid-group' as 'default' | 'other-group' })
);
expect(defaultExecutionParams.logger.error).toHaveBeenCalledWith(
'Invalid action group "invalid-group" for rule "test".'
);
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0);
expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE);
});
test('Stops triggering actions when the number of total triggered actions is reached the number of max executable actions', async () => {
const actions = [
{
id: '1',
group: 'default',
actionTypeId: 'test2',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
{
id: '2',
group: 'default',
actionTypeId: 'test2',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
{
id: '3',
group: 'default',
actionTypeId: 'test3',
params: {
foo: true,
contextVal: '{{context.value}} goes here',
stateVal: '{{state.value}} goes here',
},
},
];
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
actionsConfigMap: {
default: {
max: 2,
},
},
},
rule: {
...defaultExecutionParams.rule,
actions,
},
})
);
await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(2);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(3);
expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL);
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
});
test('Skips triggering actions for a specific action type when it reaches the limit for that specific action type', async () => {
const actions = [
...defaultExecutionParams.rule.actions,
{
id: '2',
group: 'default',
actionTypeId: 'test-action-type-id',
params: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
},
{
id: '3',
group: 'default',
actionTypeId: 'test-action-type-id',
params: {
foo: true,
contextVal: '{{context.value}} goes here',
stateVal: '{{state.value}} goes here',
},
},
{
id: '4',
group: 'default',
actionTypeId: 'another-action-type-id',
params: {
foo: true,
contextVal: '{{context.value}} goes here',
stateVal: '{{state.value}} goes here',
},
},
{
id: '5',
group: 'default',
actionTypeId: 'another-action-type-id',
params: {
foo: true,
contextVal: '{{context.value}} goes here',
stateVal: '{{state.value}} goes here',
},
},
];
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
actionsConfigMap: {
default: {
max: 4,
},
'test-action-type-id': {
max: 1,
},
},
},
rule: {
...defaultExecutionParams.rule,
actions,
},
})
);
await executionHandler.run(generateAlert({ id: 2, state: { value: 'state-val' } }));
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(4);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(5);
expect(ruleRunMetricsStore.getStatusByConnectorType('test').numberOfTriggeredActions).toBe(1);
expect(
ruleRunMetricsStore.getStatusByConnectorType('test-action-type-id').numberOfTriggeredActions
).toBe(1);
expect(
ruleRunMetricsStore.getStatusByConnectorType('another-action-type-id')
.numberOfTriggeredActions
).toBe(2);
expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.PARTIAL);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
});
test('schedules alerts with recovered actions', async () => {
const actions = [
{
id: '1',
group: 'recovered',
actionTypeId: 'test',
params: {
foo: true,
contextVal: 'My {{context.value}} goes here',
stateVal: 'My {{state.value}} goes here',
alertVal:
'My {{rule.id}} {{rule.name}} {{rule.spaceId}} {{rule.tags}} {{alert.id}} goes here',
},
},
];
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
actions,
},
})
);
await executionHandler.run(generateRecoveredAlert({ id: 1 }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"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",
},
],
]
`);
});
test('does not schedule alerts with recovered actions that are muted', async () => {
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['1'],
actions: [
{
id: '1',
group: 'recovered',
actionTypeId: 'test',
params: {
foo: true,
contextVal: 'My {{context.value}} goes here',
stateVal: 'My {{state.value}} goes here',
alertVal:
'My {{rule.id}} {{rule.name}} {{rule.spaceId}} {{rule.tags}} {{alert.id}} goes here',
},
},
],
},
})
);
await executionHandler.run(generateRecoveredAlert({ id: 1 }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
expect(defaultExecutionParams.logger.debug).nthCalledWith(
1,
`skipping scheduling of actions for '1' in rule ${defaultExecutionParams.ruleLabel}: rule is muted`
);
});
test('does not schedule active alerts that are throttled', async () => {
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
notifyWhen: 'onThrottleInterval',
throttle: '1m',
},
})
);
await executionHandler.run(generateAlert({ id: 1 }));
clock.tick(30000);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
expect(defaultExecutionParams.logger.debug).nthCalledWith(
1,
`skipping scheduling of actions for '1' in rule ${defaultExecutionParams.ruleLabel}: rule is throttled`
);
});
test('does not schedule actions that are throttled', async () => {
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
actions: [
{
...defaultExecutionParams.rule.actions[0],
frequency: {
summary: false,
notifyWhen: 'onThrottleInterval',
throttle: '1h',
},
},
],
},
})
);
await executionHandler.run(
generateAlert({
id: 1,
throttledActions: { '111-111': { date: new Date(DATE_1970) } },
})
);
clock.tick(30000);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
expect(defaultExecutionParams.logger.debug).nthCalledWith(
1,
`skipping scheduling of actions for '1' in rule ${defaultExecutionParams.ruleLabel}: rule is throttled`
);
});
test('schedule actions that are throttled but alert has a changed action group', async () => {
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
actions: [
{
...defaultExecutionParams.rule.actions[0],
frequency: {
summary: false,
notifyWhen: 'onThrottleInterval',
throttle: '1h',
},
},
],
},
})
);
await executionHandler.run(generateAlert({ id: 1, lastScheduledActionsGroup: 'recovered' }));
clock.tick(30000);
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(alertingEventLogger.logAction).toHaveBeenCalledTimes(1);
});
test('does not schedule active alerts that are muted', async () => {
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['1'],
},
})
);
await executionHandler.run(generateAlert({ id: 1 }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(0);
expect(defaultExecutionParams.logger.debug).nthCalledWith(
1,
`skipping scheduling of actions for '1' in rule ${defaultExecutionParams.ruleLabel}: rule is muted`
);
});
test('triggers summary actions (per rule run)', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 1,
data: [mockAAD],
},
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
},
],
},
})
);
await executionHandler.run(generateAlert({ id: 1 }));
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
executionUuid: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
ruleId: '1',
spaceId: 'test1',
excludedAlertInstanceIds: ['foo'],
});
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"apiKey": "MTIzOmFiYw==",
"consumer": "rule-consumer",
"executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28",
"id": "1",
"params": Object {
"message": "New: 1 Ongoing: 0 Recovered: 0",
},
"relatedSavedObjects": Array [
Object {
"id": "1",
"namespace": "test1",
"type": "alert",
"typeId": "test",
},
],
"source": Object {
"source": Object {
"id": "1",
"type": "alert",
},
"type": "SAVED_OBJECT",
},
"spaceId": "test1",
},
],
]
`);
expect(alertingEventLogger.logAction).toBeCalledWith({
alertSummary: { new: 1, ongoing: 0, recovered: 0 },
id: '1',
typeId: 'testActionTypeId',
});
});
test('skips summary actions (per rule run) when there is no alerts', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: { count: 0, data: [] },
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
actions: [
{
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
alertsFilter: { query: { kql: 'test:1', dsl: '{}', filters: [] } },
},
],
},
})
);
await executionHandler.run({});
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
});
test('triggers summary actions (custom interval)', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 1,
data: [mockAAD],
},
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: '1d',
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
uuid: '111-111',
},
],
},
})
);
const result = await executionHandler.run({});
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
start: new Date('1969-12-31T00:01:30.000Z'),
end: new Date(),
ruleId: '1',
spaceId: 'test1',
excludedAlertInstanceIds: ['foo'],
});
expect(result).toEqual({
throttledSummaryActions: {
'111-111': {
date: new Date(),
},
},
});
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"apiKey": "MTIzOmFiYw==",
"consumer": "rule-consumer",
"executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28",
"id": "1",
"params": Object {
"message": "New: 1 Ongoing: 0 Recovered: 0",
},
"relatedSavedObjects": Array [
Object {
"id": "1",
"namespace": "test1",
"type": "alert",
"typeId": "test",
},
],
"source": Object {
"source": Object {
"id": "1",
"type": "alert",
},
"type": "SAVED_OBJECT",
},
"spaceId": "test1",
},
],
]
`);
expect(alertingEventLogger.logAction).toBeCalledWith({
alertSummary: { new: 1, ongoing: 0, recovered: 0 },
id: '1',
typeId: 'testActionTypeId',
});
});
test('does not trigger summary actions if it is still being throttled (custom interval)', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: { count: 0, alerts: [] },
ongoing: { count: 0, alerts: [] },
recovered: { count: 0, alerts: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
actions: [
{
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: '1d',
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
uuid: '111-111',
},
],
},
taskInstance: {
...defaultExecutionParams.taskInstance,
state: {
...defaultExecutionParams.taskInstance.state,
summaryActions: { '111-111': { date: new Date() } },
},
} as unknown as ConcreteTaskInstance,
})
);
await executionHandler.run({});
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(1);
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith(
"skipping scheduling the action 'testActionTypeId:1', summary action is still being throttled"
);
expect(alertsClient.getSummarizedAlerts).not.toHaveBeenCalled();
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
});
test('removes the obsolete actions from the task state', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: { count: 0, data: [] },
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
actions: [
{
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: '1d',
},
params: {
message: 'New: {{alerts.new.count}}',
},
uuid: '111-111',
},
{
id: '2',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: '10d',
},
uuid: '222-222',
},
],
},
taskInstance: {
...defaultExecutionParams.taskInstance,
state: {
...defaultExecutionParams.taskInstance.state,
summaryActions: {
'111-111': { date: new Date() },
'222-222': { date: new Date() },
'333-333': { date: new Date() }, // does not exist in the actions list
},
},
} as unknown as ConcreteTaskInstance,
})
);
const result = await executionHandler.run({});
expect(result).toEqual({
throttledSummaryActions: {
'111-111': {
date: new Date(),
},
'222-222': {
date: new Date(),
},
},
});
});
test(`skips scheduling actions if the ruleType doesn't have alerts mapping`, async () => {
const { alerts, ...ruleTypeWithoutAlertsMapping } = ruleType;
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
ruleType: ruleTypeWithoutAlertsMapping,
rule: {
...defaultExecutionParams.rule,
actions: [
{
...defaultExecutionParams.rule.actions[0],
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: null,
},
},
],
},
})
);
await executionHandler.run(generateAlert({ id: 2 }));
expect(defaultExecutionParams.logger.error).toHaveBeenCalledWith(
'Skipping action "1" for rule "1" because the rule type "Test" does not support alert-as-data.'
);
expect(ruleRunMetricsStore.getNumberOfTriggeredActions()).toBe(0);
expect(ruleRunMetricsStore.getNumberOfGeneratedActions()).toBe(0);
expect(ruleRunMetricsStore.getTriggeredActionsStatus()).toBe(ActionsCompletion.COMPLETE);
});
test('schedules alerts with multiple recovered actions', async () => {
const actions = [
{
id: '1',
group: 'recovered',
actionTypeId: 'test',
params: {
foo: true,
contextVal: 'My {{context.value}} goes here',
stateVal: 'My {{state.value}} goes here',
alertVal:
'My {{rule.id}} {{rule.name}} {{rule.spaceId}} {{rule.tags}} {{alert.id}} goes here',
},
},
{
id: '2',
group: 'recovered',
actionTypeId: 'test',
params: {
foo: true,
contextVal: 'My {{context.value}} goes here',
stateVal: 'My {{state.value}} goes here',
alertVal:
'My {{rule.id}} {{rule.name}} {{rule.spaceId}} {{rule.tags}} {{alert.id}} goes here',
},
},
];
const executionHandler = new ExecutionHandler(
generateExecutionParams({
...defaultExecutionParams,
rule: {
...defaultExecutionParams.rule,
actions,
},
})
);
await executionHandler.run(generateRecoveredAlert({ id: 1 }));
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledTimes(1);
expect(actionsClient.bulkEnqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Array [
Object {
"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 {
"apiKey": "MTIzOmFiYw==",
"consumer": "rule-consumer",
"executionId": "5f6aa57d-3e22-484e-bae8-cbed868f4d28",
"id": "2",
"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",
},
],
]
`);
});
test('does not schedule actions for the summarized alerts that are filtered out (for each alert)', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 0,
data: [],
},
ongoing: {
count: 0,
data: [],
},
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
id: '1',
uuid: '111',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
alertsFilter: {
query: { kql: 'kibana.alert.rule.name:foo', dsl: '{}', filters: [] },
},
},
],
},
})
);
await executionHandler.run({
...generateAlert({ id: 1 }),
...generateAlert({ id: 2 }),
});
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
executionUuid: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
ruleId: '1',
spaceId: 'test1',
excludedAlertInstanceIds: ['foo'],
alertsFilter: {
query: { kql: 'kibana.alert.rule.name:foo', dsl: '{}', filters: [] },
},
});
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(1);
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith(
'(2) alerts have been filtered out for: testActionTypeId:111'
);
});
test('does not schedule actions for the summarized alerts that are filtered out (summary of alerts onThrottleInterval)', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 0,
data: [],
},
ongoing: {
count: 0,
data: [],
},
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
id: '1',
uuid: '111',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onThrottleInterval',
throttle: '1h',
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
alertsFilter: {
query: { kql: 'kibana.alert.rule.name:foo', dsl: '{}', filters: [] },
},
},
],
},
})
);
await executionHandler.run({
...generateAlert({ id: 1 }),
...generateAlert({ id: 2 }),
});
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
ruleId: '1',
spaceId: 'test1',
end: new Date('1970-01-01T00:01:30.000Z'),
start: new Date('1969-12-31T23:01:30.000Z'),
excludedAlertInstanceIds: ['foo'],
alertsFilter: {
query: { kql: 'kibana.alert.rule.name:foo', dsl: '{}', filters: [] },
},
});
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(alertingEventLogger.logAction).not.toHaveBeenCalled();
});
test('does not schedule actions for the for-each type alerts that are filtered out', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 1,
data: [{ ...mockAAD, kibana: { alert: { uuid: '1' } } }],
},
ongoing: {
count: 0,
data: [],
},
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
id: '1',
uuid: '111',
group: 'default',
actionTypeId: 'testActionTypeId',
frequency: {
summary: false,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {},
alertsFilter: {
query: { kql: 'kibana.alert.instance.id:1', dsl: '{}', filters: [] },
},
},
],
},
})
);
await executionHandler.run({
...generateAlert({ id: 1 }),
...generateAlert({ id: 2 }),
...generateAlert({ id: 3 }),
});
expect(alertsClient.getSummarizedAlerts).toHaveBeenCalledWith({
executionUuid: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
ruleId: '1',
spaceId: 'test1',
excludedAlertInstanceIds: ['foo'],
alertsFilter: {
query: { kql: 'kibana.alert.instance.id:1', dsl: '{}', filters: [] },
},
});
expect(actionsClient.bulkEnqueueExecution).toHaveBeenCalledWith([
{
apiKey: 'MTIzOmFiYw==',
consumer: 'rule-consumer',
executionId: '5f6aa57d-3e22-484e-bae8-cbed868f4d28',
id: '1',
params: {},
relatedSavedObjects: [{ id: '1', namespace: 'test1', type: 'alert', typeId: 'test' }],
source: { source: { id: '1', type: 'alert' }, type: 'SAVED_OBJECT' },
spaceId: 'test1',
},
]);
expect(alertingEventLogger.logAction).toHaveBeenCalledWith({
alertGroup: 'default',
alertId: '1',
id: '1',
typeId: 'testActionTypeId',
});
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(1);
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledWith(
'(2) alerts have been filtered out for: testActionTypeId:111'
);
});
test('does not schedule summary actions when there is an active maintenance window', async () => {
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 2,
data: [
{ ...mockAAD, kibana: { alert: { uuid: '1' } } },
{ ...mockAAD, kibana: { alert: { uuid: '2' } } },
],
},
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const executionHandler = new ExecutionHandler(
generateExecutionParams({
rule: {
...defaultExecutionParams.rule,
mutedInstanceIds: ['foo'],
actions: [
{
uuid: '1',
id: '1',
group: null,
actionTypeId: 'testActionTypeId',
frequency: {
summary: true,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {
message:
'New: {{alerts.new.count}} Ongoing: {{alerts.ongoing.count}} Recovered: {{alerts.recovered.count}}',
},
},
],
},
maintenanceWindowIds: ['test-id-active'],
})
);
await executionHandler.run({
...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }),
...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }),
...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }),
});
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(2);
expect(defaultExecutionParams.logger.debug).toHaveBeenNthCalledWith(
1,
'(1) alert has been filtered out for: testActionTypeId:1'
);
expect(defaultExecutionParams.logger.debug).toHaveBeenNthCalledWith(
2,
'no scheduling of summary actions "1" for rule "1": has active maintenance windows test-id-active.'
);
});
test('does not schedule actions for alerts with maintenance window IDs', async () => {
const executionHandler = new ExecutionHandler(generateExecutionParams());
await executionHandler.run({
...generateAlert({ id: 1, maintenanceWindowIds: ['test-id-1'] }),
...generateAlert({ id: 2, maintenanceWindowIds: ['test-id-2'] }),
...generateAlert({ id: 3, maintenanceWindowIds: ['test-id-3'] }),
});
expect(actionsClient.bulkEnqueueExecution).not.toHaveBeenCalled();
expect(defaultExecutionParams.logger.debug).toHaveBeenCalledTimes(3);
expect(defaultExecutionParams.logger.debug).toHaveBeenNthCalledWith(
1,
'no scheduling of actions "1" for rule "1": has active maintenance windows test-id-1.'
);
expect(defaultExecutionParams.logger.debug).toHaveBeenNthCalledWith(
2,
'no scheduling of actions "1" for rule "1": has active maintenance windows test-id-2.'
);
expect(defaultExecutionParams.logger.debug).toHaveBeenNthCalledWith(
3,
'no scheduling of actions "1" for rule "1": has active maintenance windows test-id-3.'
);
});
describe('rule url', () => {
const ruleWithUrl = {
...rule,
actions: [
{
id: '1',
group: 'default',
actionTypeId: 'test',
params: {
val: 'rule url: {{rule.url}}',
},
},
],
} as unknown as SanitizedRule<RuleTypeParams>;
const summaryRuleWithUrl = {
...rule,
actions: [
{
id: '1',
group: null,
actionTypeId: 'test',
frequency: {
summary: true,
notifyWhen: 'onActiveAlert',
throttle: null,
},
params: {
val: 'rule url: {{rule.url}}',
},
},
],
} as unknown as SanitizedRule<RuleTypeParams>;
it('populates the rule.url in the action params when the base url and rule id are specified', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345',
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
"ruleUrl": Object {
"absoluteUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
"basePathname": "",
"kibanaBaseUrl": "http://localhost:12345",
"relativePath": "/app/management/insightsAndAlerting/triggersActions/rule/1",
"spaceIdSegment": "/s/test1",
},
},
]
`);
});
it('populates the rule.url in the action params when the base url contains pathname', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345/kbn',
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0][0].actionParams).toEqual({
val: 'rule url: http://localhost:12345/kbn/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1',
});
});
it('populates the rule.url with start and stop time when available', async () => {
clock.reset();
clock.tick(90000);
alertsClient.getSummarizedAlerts.mockResolvedValue({
new: {
count: 2,
data: [
mockAAD,
{
...mockAAD,
'@timestamp': '2022-12-07T15:45:41.4672Z',
alert: { instance: { id: 'all' } },
},
],
},
ongoing: { count: 0, data: [] },
recovered: { count: 0, data: [] },
});
const execParams = {
...defaultExecutionParams,
ruleType: {
...ruleType,
getViewInAppRelativeUrl: (opts: GetViewInAppRelativeUrlFnOpts<RuleTypeParams>) =>
`/app/test/rule/${opts.rule.id}?start=${opts.start ?? 0}&end=${opts.end ?? 0}`,
},
rule: summaryRuleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345/basePath',
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: http://localhost:12345/basePath/s/test1/app/test/rule/1?start=30000&end=90000",
},
"actionTypeId": "test",
"ruleUrl": Object {
"absoluteUrl": "http://localhost:12345/basePath/s/test1/app/test/rule/1?start=30000&end=90000",
"basePathname": "/basePath",
"kibanaBaseUrl": "http://localhost:12345/basePath",
"relativePath": "/app/test/rule/1?start=30000&end=90000",
"spaceIdSegment": "/s/test1",
},
},
]
`);
});
it('populates the rule.url without the space specifier when the spaceId is the string "default"', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345',
},
taskInstance: {
params: { spaceId: 'default', alertId: '1' },
} as unknown as ConcreteTaskInstance,
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
"ruleUrl": Object {
"absoluteUrl": "http://localhost:12345/app/management/insightsAndAlerting/triggersActions/rule/1",
"basePathname": "",
"kibanaBaseUrl": "http://localhost:12345",
"relativePath": "/app/management/insightsAndAlerting/triggersActions/rule/1",
"spaceIdSegment": "",
},
},
]
`);
});
it('populates the rule.url in the action params when the base url has a trailing slash and removes the trailing slash', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345/',
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
},
"actionTypeId": "test",
"ruleUrl": Object {
"absoluteUrl": "http://localhost:12345/s/test1/app/management/insightsAndAlerting/triggersActions/rule/1",
"basePathname": "",
"kibanaBaseUrl": "http://localhost:12345/",
"relativePath": "/app/management/insightsAndAlerting/triggersActions/rule/1",
"spaceIdSegment": "/s/test1",
},
},
]
`);
});
it('does not populate the rule.url when the base url is not specified', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: undefined,
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: ",
},
"actionTypeId": "test",
"ruleUrl": undefined,
},
]
`);
});
it('does not populate the rule.url when base url is not a valid url', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'localhost12345',
},
taskInstance: {
params: { spaceId: 'test1', alertId: '1' },
} as unknown as ConcreteTaskInstance,
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: ",
},
"actionTypeId": "test",
"ruleUrl": undefined,
},
]
`);
});
it('does not populate the rule.url when base url is a number', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 1,
},
taskInstance: {
params: { spaceId: 'test1', alertId: '1' },
} as unknown as ConcreteTaskInstance,
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: ",
},
"actionTypeId": "test",
"ruleUrl": undefined,
},
]
`);
});
it('sets the rule.url to the value from getViewInAppRelativeUrl when the rule type has it defined', async () => {
const execParams = {
...defaultExecutionParams,
rule: ruleWithUrl,
taskRunnerContext: {
...defaultExecutionParams.taskRunnerContext,
kibanaBaseUrl: 'http://localhost:12345',
},
ruleType: {
...ruleType,
getViewInAppRelativeUrl() {
return '/app/management/some/other/place';
},
},
};
const executionHandler = new ExecutionHandler(generateExecutionParams(execParams));
await executionHandler.run(generateAlert({ id: 1 }));
expect(injectActionParamsMock.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"actionParams": Object {
"val": "rule url: http://localhost:12345/s/test1/app/management/some/other/place",
},
"actionTypeId": "test",
"ruleUrl": Object {
"absoluteUrl": "http://localhost:12345/s/test1/app/management/some/other/place",
"basePathname": "",
"kibanaBaseUrl": "http://localhost:12345",
"relativePath": "/app/management/some/other/place",
"spaceIdSegment": "/s/test1",
},
},
]
`);
});
});
});