[Alerting] Enables AlertTypes to define the custom recovery action groups (#84408)
In this PR we introduce a new `recoveryActionGroup` field on AlertTypes which allows an implementor to specify a custom action group which the framework will use when an alert instance goes from _active_ to _inactive_. By default all alert types will use the existing `RecoveryActionGroup`, but when `recoveryActionGroup` is specified, this group is used instead. This is applied across the UI, event log and underlying object model, rather than just being a label change. To support this we also introduced the `alertActionGroupName` message variable which is the human readable version of existing `alertActionGroup` variable.
This commit is contained in:
parent
b9b2704832
commit
249a1a41aa
|
@ -43,6 +43,10 @@ export const alertType: AlertType = {
|
|||
name: 'People In Space Right Now',
|
||||
actionGroups: [{ id: 'default', name: 'default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: {
|
||||
id: 'hasLandedBackOnEarth',
|
||||
name: 'Has landed back on Earth',
|
||||
},
|
||||
async executor({ services, params }) {
|
||||
const { outerSpaceCapacity, craft: craftToTriggerBy, op } = params;
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ The following table describes the properties of the `options` object.
|
|||
|name|A user-friendly name for the alert type. These will be displayed in dropdowns when choosing alert types.|string|
|
||||
|actionGroups|An explicit list of groups the alert type may schedule actions for, each specifying the ActionGroup's unique ID and human readable name. Alert `actions` validation will use this configuartion to ensure groups are valid. We highly encourage using `kbn-i18n` to translate the names of actionGroup when registering the AlertType. |Array<{id:string, name:string}>|
|
||||
|defaultActionGroupId|Default ID value for the group of the alert type.|string|
|
||||
|recoveryActionGroup|An action group to use when an alert instance goes from an active state, to an inactive one. This action group should not be specified under the `actionGroups` property. If no recoveryActionGroup is specified, the default `recovered` action group will be used. |{id:string, name:string}|
|
||||
|actionVariables|An explicit list of action variables the alert type makes available via context and state in action parameter templates, and a short human readable description. Alert UI will use this to display prompts for the users for these variables, in action parameter editors. We highly encourage using `kbn-i18n` to translate the descriptions. |{ context: Array<{name:string, description:string}, state: Array<{name:string, description:string}>|
|
||||
|validate.params|When developing an alert type, you can choose to accept a series of parameters. You may also have the parameters validated before they are passed to the `executor` function or created as an alert saved object. In order to do this, provide a `@kbn/config-schema` schema that we will use to validate the `params` attribute.|@kbn/config-schema|
|
||||
|executor|This is where the code of the alert type lives. This is a function to be called when executing an alert on an interval basis. For full details, see executor section below.|Function|
|
||||
|
|
|
@ -8,6 +8,7 @@ export interface AlertType {
|
|||
id: string;
|
||||
name: string;
|
||||
actionGroups: ActionGroup[];
|
||||
recoveryActionGroup: ActionGroup;
|
||||
actionVariables: string[];
|
||||
defaultActionGroupId: ActionGroup['id'];
|
||||
producer: string;
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { ActionGroup } from './alert_type';
|
||||
|
||||
export const RecoveredActionGroup: ActionGroup = {
|
||||
export const RecoveredActionGroup: Readonly<ActionGroup> = {
|
||||
id: 'recovered',
|
||||
name: i18n.translate('xpack.alerts.builtinActionGroups.recovered', {
|
||||
defaultMessage: 'Recovered',
|
||||
}),
|
||||
};
|
||||
|
||||
export function getBuiltinActionGroups(): ActionGroup[] {
|
||||
return [RecoveredActionGroup];
|
||||
export function getBuiltinActionGroups(customRecoveryGroup?: ActionGroup): ActionGroup[] {
|
||||
return [customRecoveryGroup ?? Object.freeze(RecoveredActionGroup)];
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertType } from '../common';
|
||||
import { AlertType, RecoveredActionGroup } from '../common';
|
||||
import { httpServiceMock } from '../../../../src/core/public/mocks';
|
||||
import { loadAlert, loadAlertType, loadAlertTypes } from './alert_api';
|
||||
import uuid from 'uuid';
|
||||
|
@ -22,6 +22,7 @@ describe('loadAlertTypes', () => {
|
|||
actionVariables: ['var1'],
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
},
|
||||
];
|
||||
|
@ -45,6 +46,7 @@ describe('loadAlertType', () => {
|
|||
actionVariables: ['var1'],
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
};
|
||||
http.get.mockResolvedValueOnce([alertType]);
|
||||
|
@ -65,6 +67,7 @@ describe('loadAlertType', () => {
|
|||
actionVariables: [],
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
};
|
||||
http.get.mockResolvedValueOnce([alertType]);
|
||||
|
@ -80,6 +83,7 @@ describe('loadAlertType', () => {
|
|||
actionVariables: [],
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { AlertNavigationRegistry } from './alert_navigation_registry';
|
||||
import { AlertType, SanitizedAlert } from '../../common';
|
||||
import { AlertType, RecoveredActionGroup, SanitizedAlert } from '../../common';
|
||||
import uuid from 'uuid';
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
@ -14,6 +14,7 @@ const mockAlertType = (id: string): AlertType => ({
|
|||
id,
|
||||
name: id,
|
||||
actionGroups: [],
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
actionVariables: [],
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerts',
|
||||
|
|
|
@ -122,6 +122,71 @@ describe('register()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('allows an AlertType to specify a custom recovery group', () => {
|
||||
const alertType = {
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: {
|
||||
id: 'backToAwesome',
|
||||
name: 'Back To Awesome',
|
||||
},
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
};
|
||||
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
|
||||
registry.register(alertType);
|
||||
expect(registry.get('test').actionGroups).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"id": "default",
|
||||
"name": "Default",
|
||||
},
|
||||
Object {
|
||||
"id": "backToAwesome",
|
||||
"name": "Back To Awesome",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('throws if the custom recovery group is contained in the AlertType action groups', () => {
|
||||
const alertType = {
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
{
|
||||
id: 'backToAwesome',
|
||||
name: 'Back To Awesome',
|
||||
},
|
||||
],
|
||||
recoveryActionGroup: {
|
||||
id: 'backToAwesome',
|
||||
name: 'Back To Awesome',
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
};
|
||||
const registry = new AlertTypeRegistry(alertTypeRegistryParams);
|
||||
|
||||
expect(() => registry.register(alertType)).toThrowError(
|
||||
new Error(
|
||||
`Alert type [id="${alertType.id}"] cannot be registered. Action group [backToAwesome] cannot be used as both a recovery and an active action group.`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
test('registers the executor with the task manager', () => {
|
||||
const alertType = {
|
||||
id: 'test',
|
||||
|
@ -243,6 +308,10 @@ describe('get()', () => {
|
|||
"id": "test",
|
||||
"name": "Test",
|
||||
"producer": "alerts",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
@ -300,6 +369,10 @@ describe('list()', () => {
|
|||
"id": "test",
|
||||
"name": "Test",
|
||||
"producer": "alerts",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -9,7 +9,6 @@ import { i18n } from '@kbn/i18n';
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import typeDetect from 'type-detect';
|
||||
import { intersection } from 'lodash';
|
||||
import _ from 'lodash';
|
||||
import { RunContext, TaskManagerSetupContract } from '../../task_manager/server';
|
||||
import { TaskRunnerFactory } from './task_runner';
|
||||
import {
|
||||
|
@ -18,9 +17,8 @@ import {
|
|||
AlertTypeState,
|
||||
AlertInstanceState,
|
||||
AlertInstanceContext,
|
||||
ActionGroup,
|
||||
} from './types';
|
||||
import { getBuiltinActionGroups } from '../common';
|
||||
import { RecoveredActionGroup, getBuiltinActionGroups } from '../common';
|
||||
|
||||
interface ConstructorOptions {
|
||||
taskManager: TaskManagerSetupContract;
|
||||
|
@ -29,8 +27,13 @@ interface ConstructorOptions {
|
|||
|
||||
export interface RegistryAlertType
|
||||
extends Pick<
|
||||
AlertType,
|
||||
'name' | 'actionGroups' | 'defaultActionGroupId' | 'actionVariables' | 'producer'
|
||||
NormalizedAlertType,
|
||||
| 'name'
|
||||
| 'actionGroups'
|
||||
| 'recoveryActionGroup'
|
||||
| 'defaultActionGroupId'
|
||||
| 'actionVariables'
|
||||
| 'producer'
|
||||
> {
|
||||
id: string;
|
||||
}
|
||||
|
@ -55,9 +58,17 @@ const alertIdSchema = schema.string({
|
|||
},
|
||||
});
|
||||
|
||||
export type NormalizedAlertType<
|
||||
Params extends AlertTypeParams = AlertTypeParams,
|
||||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
> = Omit<AlertType<Params, State, InstanceState, InstanceContext>, 'recoveryActionGroup'> &
|
||||
Pick<Required<AlertType<Params, State, InstanceState, InstanceContext>>, 'recoveryActionGroup'>;
|
||||
|
||||
export class AlertTypeRegistry {
|
||||
private readonly taskManager: TaskManagerSetupContract;
|
||||
private readonly alertTypes: Map<string, AlertType> = new Map();
|
||||
private readonly alertTypes: Map<string, NormalizedAlertType> = new Map();
|
||||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
|
||||
constructor({ taskManager, taskRunnerFactory }: ConstructorOptions) {
|
||||
|
@ -86,14 +97,15 @@ export class AlertTypeRegistry {
|
|||
);
|
||||
}
|
||||
alertType.actionVariables = normalizedActionVariables(alertType.actionVariables);
|
||||
validateActionGroups(alertType.id, alertType.actionGroups);
|
||||
alertType.actionGroups = [...alertType.actionGroups, ..._.cloneDeep(getBuiltinActionGroups())];
|
||||
this.alertTypes.set(alertIdSchema.validate(alertType.id), { ...alertType } as AlertType);
|
||||
|
||||
const normalizedAlertType = augmentActionGroupsWithReserved(alertType as AlertType);
|
||||
|
||||
this.alertTypes.set(alertIdSchema.validate(alertType.id), normalizedAlertType);
|
||||
this.taskManager.registerTaskDefinitions({
|
||||
[`alerting:${alertType.id}`]: {
|
||||
title: alertType.name,
|
||||
createTaskRunner: (context: RunContext) =>
|
||||
this.taskRunnerFactory.create({ ...alertType } as AlertType, context),
|
||||
this.taskRunnerFactory.create(normalizedAlertType, context),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -103,7 +115,7 @@ export class AlertTypeRegistry {
|
|||
State extends AlertTypeState = AlertTypeState,
|
||||
InstanceState extends AlertInstanceState = AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext = AlertInstanceContext
|
||||
>(id: string): AlertType<Params, State, InstanceState, InstanceContext> {
|
||||
>(id: string): NormalizedAlertType<Params, State, InstanceState, InstanceContext> {
|
||||
if (!this.has(id)) {
|
||||
throw Boom.badRequest(
|
||||
i18n.translate('xpack.alerts.alertTypeRegistry.get.missingAlertTypeError', {
|
||||
|
@ -114,19 +126,32 @@ export class AlertTypeRegistry {
|
|||
})
|
||||
);
|
||||
}
|
||||
return this.alertTypes.get(id)! as AlertType<Params, State, InstanceState, InstanceContext>;
|
||||
return this.alertTypes.get(id)! as NormalizedAlertType<
|
||||
Params,
|
||||
State,
|
||||
InstanceState,
|
||||
InstanceContext
|
||||
>;
|
||||
}
|
||||
|
||||
public list(): Set<RegistryAlertType> {
|
||||
return new Set(
|
||||
Array.from(this.alertTypes).map(
|
||||
([id, { name, actionGroups, defaultActionGroupId, actionVariables, producer }]: [
|
||||
string,
|
||||
AlertType
|
||||
]) => ({
|
||||
([
|
||||
id,
|
||||
{
|
||||
name,
|
||||
actionGroups,
|
||||
recoveryActionGroup,
|
||||
defaultActionGroupId,
|
||||
actionVariables,
|
||||
producer,
|
||||
},
|
||||
]: [string, NormalizedAlertType]) => ({
|
||||
id,
|
||||
name,
|
||||
actionGroups,
|
||||
recoveryActionGroup,
|
||||
defaultActionGroupId,
|
||||
actionVariables,
|
||||
producer,
|
||||
|
@ -144,21 +169,52 @@ function normalizedActionVariables(actionVariables: AlertType['actionVariables']
|
|||
};
|
||||
}
|
||||
|
||||
function validateActionGroups(alertTypeId: string, actionGroups: ActionGroup[]) {
|
||||
const reservedActionGroups = intersection(
|
||||
actionGroups.map((item) => item.id),
|
||||
getBuiltinActionGroups().map((item) => item.id)
|
||||
function augmentActionGroupsWithReserved<
|
||||
Params extends AlertTypeParams,
|
||||
State extends AlertTypeState,
|
||||
InstanceState extends AlertInstanceState,
|
||||
InstanceContext extends AlertInstanceContext
|
||||
>(
|
||||
alertType: AlertType<Params, State, InstanceState, InstanceContext>
|
||||
): NormalizedAlertType<Params, State, InstanceState, InstanceContext> {
|
||||
const reservedActionGroups = getBuiltinActionGroups(alertType.recoveryActionGroup);
|
||||
const { id, actionGroups, recoveryActionGroup } = alertType;
|
||||
|
||||
const activeActionGroups = new Set(actionGroups.map((item) => item.id));
|
||||
const intersectingReservedActionGroups = intersection(
|
||||
[...activeActionGroups.values()],
|
||||
reservedActionGroups.map((item) => item.id)
|
||||
);
|
||||
if (reservedActionGroups.length > 0) {
|
||||
if (recoveryActionGroup && activeActionGroups.has(recoveryActionGroup.id)) {
|
||||
throw new Error(
|
||||
i18n.translate(
|
||||
'xpack.alerts.alertTypeRegistry.register.customRecoveryActionGroupUsageError',
|
||||
{
|
||||
defaultMessage:
|
||||
'Alert type [id="{id}"] cannot be registered. Action group [{actionGroup}] cannot be used as both a recovery and an active action group.',
|
||||
values: {
|
||||
actionGroup: recoveryActionGroup.id,
|
||||
id,
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
} else if (intersectingReservedActionGroups.length > 0) {
|
||||
throw new Error(
|
||||
i18n.translate('xpack.alerts.alertTypeRegistry.register.reservedActionGroupUsageError', {
|
||||
defaultMessage:
|
||||
'Alert type [id="{alertTypeId}"] cannot be registered. Action groups [{actionGroups}] are reserved by the framework.',
|
||||
'Alert type [id="{id}"] cannot be registered. Action groups [{actionGroups}] are reserved by the framework.',
|
||||
values: {
|
||||
actionGroups: reservedActionGroups.join(', '),
|
||||
alertTypeId,
|
||||
actionGroups: intersectingReservedActionGroups.join(', '),
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
...alertType,
|
||||
actionGroups: [...actionGroups, ...reservedActionGroups],
|
||||
recoveryActionGroup: recoveryActionGroup ?? RecoveredActionGroup,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { AlertsAuthorization } from '../../authorization/alerts_authorization';
|
|||
import { ActionsAuthorization } from '../../../../actions/server';
|
||||
import { getBeforeSetup, setGlobalDate } from './lib';
|
||||
import { AlertExecutionStatusValues } from '../../types';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
|
@ -53,6 +54,7 @@ describe('aggregate()', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myType',
|
||||
name: 'myType',
|
||||
producer: 'myApp',
|
||||
|
@ -102,6 +104,7 @@ describe('aggregate()', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
authorizedConsumers: {
|
||||
myApp: { read: true, all: true },
|
||||
|
|
|
@ -15,6 +15,7 @@ import { AlertsAuthorization } from '../../authorization/alerts_authorization';
|
|||
import { ActionsAuthorization, ActionsClient } from '../../../../actions/server';
|
||||
import { TaskStatus } from '../../../../task_manager/server';
|
||||
import { getBeforeSetup, setGlobalDate } from './lib';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
|
@ -683,6 +684,7 @@ describe('create()', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
|
|
|
@ -15,6 +15,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
|
|||
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
|
||||
import { ActionsAuthorization } from '../../../../actions/server';
|
||||
import { getBeforeSetup, setGlobalDate } from './lib';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
|
@ -52,6 +53,7 @@ describe('find()', () => {
|
|||
const listedTypes = new Set([
|
||||
{
|
||||
actionGroups: [],
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
id: 'myType',
|
||||
|
@ -108,6 +110,7 @@ describe('find()', () => {
|
|||
id: 'myType',
|
||||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerts',
|
||||
authorizedConsumers: {
|
||||
|
|
|
@ -9,6 +9,7 @@ import { actionsClientMock } from '../../../../actions/server/mocks';
|
|||
import { ConstructorOptions } from '../alerts_client';
|
||||
import { eventLogClientMock } from '../../../../event_log/server/mocks';
|
||||
import { AlertTypeRegistry } from '../../alert_type_registry';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
|
||||
export const mockedDateString = '2019-02-12T21:01:22.479Z';
|
||||
|
||||
|
@ -82,6 +83,7 @@ export function getBeforeSetup(
|
|||
id: '123',
|
||||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
defaultActionGroupId: 'default',
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
|
|
|
@ -13,6 +13,7 @@ import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
|
|||
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
|
||||
import { ActionsAuthorization } from '../../../../actions/server';
|
||||
import { getBeforeSetup } from './lib';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
|
||||
const taskManager = taskManagerMock.createStart();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
|
@ -50,6 +51,7 @@ describe('listAlertTypes', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'alertingAlertType',
|
||||
name: 'alertingAlertType',
|
||||
producer: 'alerts',
|
||||
|
@ -58,6 +60,7 @@ describe('listAlertTypes', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -96,6 +99,7 @@ describe('listAlertTypes', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myType',
|
||||
name: 'myType',
|
||||
producer: 'myApp',
|
||||
|
@ -105,6 +109,7 @@ describe('listAlertTypes', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
},
|
||||
]);
|
||||
|
@ -119,6 +124,7 @@ describe('listAlertTypes', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'alerts',
|
||||
authorizedConsumers: {
|
||||
myApp: { read: true, all: true },
|
||||
|
|
|
@ -11,6 +11,7 @@ import { taskManagerMock } from '../../../../task_manager/server/mocks';
|
|||
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
|
||||
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
|
||||
import { IntervalSchedule, InvalidatePendingApiKey } from '../../types';
|
||||
import { RecoveredActionGroup } from '../../../common';
|
||||
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
|
||||
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
|
||||
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
|
||||
|
@ -97,6 +98,7 @@ describe('update()', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
});
|
||||
|
@ -676,6 +678,7 @@ describe('update()', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
validate: {
|
||||
params: schema.object({
|
||||
param1: schema.string(),
|
||||
|
@ -1021,6 +1024,7 @@ describe('update()', () => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import { ActionsAuthorization } from '../../actions/server';
|
|||
import { SavedObjectsErrorHelpers } from '../../../../src/core/server';
|
||||
import { RetryForConflictsAttempts } from './lib/retry_if_conflicts';
|
||||
import { TaskStatus } from '../../../plugins/task_manager/server/task';
|
||||
import { RecoveredActionGroup } from '../common';
|
||||
|
||||
let alertsClient: AlertsClient;
|
||||
|
||||
|
@ -331,6 +332,7 @@ beforeEach(() => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
}));
|
||||
|
@ -340,6 +342,7 @@ beforeEach(() => {
|
|||
name: 'Test',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
async executor() {},
|
||||
producer: 'alerts',
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ import { AlertsAuthorization, WriteOperations, ReadOperations } from './alerts_a
|
|||
import { alertsAuthorizationAuditLoggerMock } from './audit_logger.mock';
|
||||
import { AlertsAuthorizationAuditLogger, AuthorizationResult } from './audit_logger';
|
||||
import uuid from 'uuid';
|
||||
import { RecoveredActionGroup } from '../../common';
|
||||
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
const features: jest.Mocked<FeaturesStartContract> = featuresPluginMock.createStart();
|
||||
|
@ -172,6 +173,7 @@ beforeEach(() => {
|
|||
name: 'My Alert Type',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
async executor() {},
|
||||
producer: 'myApp',
|
||||
}));
|
||||
|
@ -534,6 +536,7 @@ describe('AlertsAuthorization', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myOtherAppAlertType',
|
||||
name: 'myOtherAppAlertType',
|
||||
producer: 'alerts',
|
||||
|
@ -542,6 +545,7 @@ describe('AlertsAuthorization', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -550,6 +554,7 @@ describe('AlertsAuthorization', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'mySecondAppAlertType',
|
||||
name: 'mySecondAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -824,6 +829,7 @@ describe('AlertsAuthorization', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myOtherAppAlertType',
|
||||
name: 'myOtherAppAlertType',
|
||||
producer: 'myOtherApp',
|
||||
|
@ -832,6 +838,7 @@ describe('AlertsAuthorization', () => {
|
|||
actionGroups: [],
|
||||
actionVariables: undefined,
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -880,6 +887,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myAppAlertType",
|
||||
"name": "myAppAlertType",
|
||||
"producer": "myApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionGroups": Array [],
|
||||
|
@ -906,6 +917,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myOtherAppAlertType",
|
||||
"name": "myOtherAppAlertType",
|
||||
"producer": "myOtherApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
@ -972,6 +987,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myOtherAppAlertType",
|
||||
"name": "myOtherAppAlertType",
|
||||
"producer": "myOtherApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionGroups": Array [],
|
||||
|
@ -994,6 +1013,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myAppAlertType",
|
||||
"name": "myAppAlertType",
|
||||
"producer": "myApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
@ -1055,6 +1078,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myAppAlertType",
|
||||
"name": "myAppAlertType",
|
||||
"producer": "myApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
@ -1145,6 +1172,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myOtherAppAlertType",
|
||||
"name": "myOtherAppAlertType",
|
||||
"producer": "myOtherApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"actionGroups": Array [],
|
||||
|
@ -1167,6 +1198,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myAppAlertType",
|
||||
"name": "myAppAlertType",
|
||||
"producer": "myApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
@ -1241,6 +1276,10 @@ describe('AlertsAuthorization', () => {
|
|||
"id": "myOtherAppAlertType",
|
||||
"name": "myOtherAppAlertType",
|
||||
"producer": "myOtherApp",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { esKuery } from '../../../../../src/plugins/data/server';
|
||||
import { RecoveredActionGroup } from '../../common';
|
||||
import {
|
||||
asFiltersByAlertTypeAndConsumer,
|
||||
ensureFieldIsSafeForQuery,
|
||||
|
@ -17,6 +18,7 @@ describe('asFiltersByAlertTypeAndConsumer', () => {
|
|||
{
|
||||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -40,6 +42,7 @@ describe('asFiltersByAlertTypeAndConsumer', () => {
|
|||
{
|
||||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -65,6 +68,7 @@ describe('asFiltersByAlertTypeAndConsumer', () => {
|
|||
{
|
||||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myAppAlertType',
|
||||
name: 'myAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
@ -78,6 +82,7 @@ describe('asFiltersByAlertTypeAndConsumer', () => {
|
|||
{
|
||||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'myOtherAppAlertType',
|
||||
name: 'myOtherAppAlertType',
|
||||
producer: 'alerts',
|
||||
|
@ -91,6 +96,7 @@ describe('asFiltersByAlertTypeAndConsumer', () => {
|
|||
{
|
||||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
id: 'mySecondAppAlertType',
|
||||
name: 'mySecondAppAlertType',
|
||||
producer: 'myApp',
|
||||
|
|
|
@ -10,6 +10,7 @@ import { mockLicenseState } from '../lib/license_state.mock';
|
|||
import { verifyApiAccess } from '../lib/license_api_access';
|
||||
import { mockHandlerArguments } from './_mock_handler_arguments';
|
||||
import { alertsClientMock } from '../alerts_client.mock';
|
||||
import { RecoveredActionGroup } from '../../common';
|
||||
|
||||
const alertsClient = alertsClientMock.create();
|
||||
|
||||
|
@ -43,6 +44,7 @@ describe('listAlertTypesRoute', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
|
@ -74,6 +76,10 @@ describe('listAlertTypesRoute', () => {
|
|||
"id": "1",
|
||||
"name": "name",
|
||||
"producer": "test",
|
||||
"recoveryActionGroup": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
@ -107,6 +113,7 @@ describe('listAlertTypesRoute', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
|
@ -156,6 +163,7 @@ describe('listAlertTypesRoute', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
|
|
|
@ -20,6 +20,10 @@ const alertType: AlertType = {
|
|||
{ id: 'other-group', name: 'Other Group' },
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
};
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { map } from 'lodash';
|
||||
import { Logger, KibanaRequest } from '../../../../../src/core/server';
|
||||
import { transformActionParams } from './transform_action_params';
|
||||
import {
|
||||
|
@ -58,7 +57,9 @@ export function createExecutionHandler({
|
|||
request,
|
||||
alertParams,
|
||||
}: CreateExecutionHandlerOptions) {
|
||||
const alertTypeActionGroups = new Set(map(alertType.actionGroups, 'id'));
|
||||
const alertTypeActionGroups = new Map(
|
||||
alertType.actionGroups.map((actionGroup) => [actionGroup.id, actionGroup.name])
|
||||
);
|
||||
return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => {
|
||||
if (!alertTypeActionGroups.has(actionGroup)) {
|
||||
logger.error(`Invalid action group "${actionGroup}" for alert "${alertType.id}".`);
|
||||
|
@ -76,6 +77,7 @@ export function createExecutionHandler({
|
|||
tags,
|
||||
alertInstanceId,
|
||||
alertActionGroup: actionGroup,
|
||||
alertActionGroupName: alertTypeActionGroups.get(actionGroup)!,
|
||||
context,
|
||||
actionParams: action.params,
|
||||
state,
|
||||
|
|
|
@ -33,6 +33,7 @@ const alertType = {
|
|||
name: 'My test alert',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }, RecoveredActionGroup],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
};
|
||||
|
@ -590,6 +591,109 @@ describe('Task Runner', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('fire actions under a custom recovery group when specified on an alert type for alertInstances which are in the recovered state', async () => {
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionTypeEnabled.mockReturnValue(true);
|
||||
taskRunnerFactoryInitializerParams.actionsPlugin.isActionExecutable.mockReturnValue(true);
|
||||
|
||||
const recoveryActionGroup = {
|
||||
id: 'customRecovered',
|
||||
name: 'Custom Recovered',
|
||||
};
|
||||
const alertTypeWithCustomRecovery = {
|
||||
...alertType,
|
||||
recoveryActionGroup,
|
||||
actionGroups: [{ id: 'default', name: 'Default' }, recoveryActionGroup],
|
||||
};
|
||||
|
||||
alertTypeWithCustomRecovery.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
executorServices.alertInstanceFactory('1').scheduleActions('default');
|
||||
}
|
||||
);
|
||||
const taskRunner = new TaskRunner(
|
||||
alertTypeWithCustomRecovery,
|
||||
{
|
||||
...mockedTaskInstance,
|
||||
state: {
|
||||
...mockedTaskInstance.state,
|
||||
alertInstances: {
|
||||
'1': { meta: {}, state: { bar: false } },
|
||||
'2': { meta: {}, state: { bar: false } },
|
||||
},
|
||||
},
|
||||
},
|
||||
taskRunnerFactoryInitializerParams
|
||||
);
|
||||
alertsClient.get.mockResolvedValue({
|
||||
...mockedAlertTypeSavedObject,
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '1',
|
||||
actionTypeId: 'action',
|
||||
params: {
|
||||
foo: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
group: recoveryActionGroup.id,
|
||||
id: '2',
|
||||
actionTypeId: 'action',
|
||||
params: {
|
||||
isResolved: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValue({
|
||||
id: '1',
|
||||
type: 'alert',
|
||||
attributes: {
|
||||
apiKey: Buffer.from('123:abc').toString('base64'),
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
const runnerResult = await taskRunner.run();
|
||||
expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"1": Object {
|
||||
"meta": Object {
|
||||
"lastScheduledActions": Object {
|
||||
"date": 1970-01-01T00:00:00.000Z,
|
||||
"group": "default",
|
||||
},
|
||||
},
|
||||
"state": Object {
|
||||
"bar": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const eventLogger = taskRunnerFactoryInitializerParams.eventLogger;
|
||||
expect(eventLogger.logEvent).toHaveBeenCalledTimes(5);
|
||||
expect(actionsClient.enqueueExecution).toHaveBeenCalledTimes(2);
|
||||
expect(actionsClient.enqueueExecution.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"apiKey": "MTIzOmFiYw==",
|
||||
"id": "2",
|
||||
"params": Object {
|
||||
"isResolved": true,
|
||||
},
|
||||
"source": Object {
|
||||
"source": Object {
|
||||
"id": "1",
|
||||
"type": "alert",
|
||||
},
|
||||
"type": "SAVED_OBJECT",
|
||||
},
|
||||
"spaceId": undefined,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => {
|
||||
alertType.executor.mockImplementation(
|
||||
({ services: executorServices }: AlertExecutorOptions) => {
|
||||
|
|
|
@ -20,7 +20,6 @@ import {
|
|||
ErrorWithReason,
|
||||
} from '../lib';
|
||||
import {
|
||||
AlertType,
|
||||
RawAlert,
|
||||
IntervalSchedule,
|
||||
Services,
|
||||
|
@ -39,7 +38,8 @@ import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_l
|
|||
import { isAlertSavedObjectNotFoundError } from '../lib/is_alert_not_found_error';
|
||||
import { AlertsClient } from '../alerts_client';
|
||||
import { partiallyUpdateAlert } from '../saved_objects';
|
||||
import { RecoveredActionGroup } from '../../common';
|
||||
import { ActionGroup } from '../../common';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
|
||||
const FALLBACK_RETRY_INTERVAL = '5m';
|
||||
|
||||
|
@ -58,10 +58,10 @@ export class TaskRunner {
|
|||
private context: TaskRunnerContext;
|
||||
private logger: Logger;
|
||||
private taskInstance: AlertTaskInstance;
|
||||
private alertType: AlertType;
|
||||
private alertType: NormalizedAlertType;
|
||||
|
||||
constructor(
|
||||
alertType: AlertType,
|
||||
alertType: NormalizedAlertType,
|
||||
taskInstance: ConcreteTaskInstance,
|
||||
context: TaskRunnerContext
|
||||
) {
|
||||
|
@ -230,6 +230,7 @@ export class TaskRunner {
|
|||
|
||||
if (!muteAll) {
|
||||
scheduleActionsForRecoveredInstances(
|
||||
this.alertType.recoveryActionGroup,
|
||||
alertInstances,
|
||||
executionHandler,
|
||||
originalAlertInstances,
|
||||
|
@ -499,6 +500,7 @@ function generateNewAndRecoveredInstanceEvents(
|
|||
}
|
||||
|
||||
function scheduleActionsForRecoveredInstances(
|
||||
recoveryActionGroup: ActionGroup,
|
||||
alertInstancesMap: Record<string, AlertInstance>,
|
||||
executionHandler: ReturnType<typeof createExecutionHandler>,
|
||||
originalAlertInstances: Record<string, AlertInstance>,
|
||||
|
@ -514,15 +516,15 @@ function scheduleActionsForRecoveredInstances(
|
|||
);
|
||||
for (const id of recoveredIds) {
|
||||
const instance = alertInstancesMap[id];
|
||||
instance.updateLastScheduledActions(RecoveredActionGroup.id);
|
||||
instance.updateLastScheduledActions(recoveryActionGroup.id);
|
||||
instance.unscheduleActions();
|
||||
executionHandler({
|
||||
actionGroup: RecoveredActionGroup.id,
|
||||
actionGroup: recoveryActionGroup.id,
|
||||
context: {},
|
||||
state: {},
|
||||
alertInstanceId: id,
|
||||
});
|
||||
instance.scheduleActions(RecoveredActionGroup.id);
|
||||
instance.scheduleActions(recoveryActionGroup.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,10 @@ const alertType = {
|
|||
name: 'My test alert',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
executor: jest.fn(),
|
||||
producer: 'alerts',
|
||||
};
|
||||
|
|
|
@ -13,10 +13,11 @@ import {
|
|||
import { RunContext } from '../../../task_manager/server';
|
||||
import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server';
|
||||
import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server';
|
||||
import { AlertType, GetServicesFunction, SpaceIdToNamespaceFunction } from '../types';
|
||||
import { GetServicesFunction, SpaceIdToNamespaceFunction } from '../types';
|
||||
import { TaskRunner } from './task_runner';
|
||||
import { IEventLogger } from '../../../event_log/server';
|
||||
import { AlertsClient } from '../alerts_client';
|
||||
import { NormalizedAlertType } from '../alert_type_registry';
|
||||
|
||||
export interface TaskRunnerContext {
|
||||
logger: Logger;
|
||||
|
@ -42,7 +43,7 @@ export class TaskRunnerFactory {
|
|||
this.taskRunnerContext = taskRunnerContext;
|
||||
}
|
||||
|
||||
public create(alertType: AlertType, { taskInstance }: RunContext) {
|
||||
public create(alertType: NormalizedAlertType, { taskInstance }: RunContext) {
|
||||
if (!this.isInitialized) {
|
||||
throw new Error('TaskRunnerFactory not initialized');
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ test('skips non string parameters', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {
|
||||
foo: 'test',
|
||||
},
|
||||
|
@ -56,6 +57,7 @@ test('missing parameters get emptied out', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -80,6 +82,7 @@ test('context parameters are passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -103,6 +106,7 @@ test('state parameters are passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -126,6 +130,7 @@ test('alertId is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -149,6 +154,7 @@ test('alertName is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -172,6 +178,7 @@ test('tags is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -194,6 +201,7 @@ test('undefined tags is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -217,6 +225,7 @@ test('empty tags is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -240,6 +249,7 @@ test('spaceId is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -263,6 +273,7 @@ test('alertInstanceId is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -286,6 +297,7 @@ test('alertActionGroup is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -295,6 +307,30 @@ test('alertActionGroup is passed to templates', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('alertActionGroupName is passed to templates', () => {
|
||||
const actionParams = {
|
||||
message: 'Value "{{alertActionGroupName}}" exists',
|
||||
};
|
||||
const result = transformActionParams({
|
||||
actionParams,
|
||||
state: {},
|
||||
context: {},
|
||||
alertId: '1',
|
||||
alertName: 'alert-name',
|
||||
tags: ['tag-A', 'tag-B'],
|
||||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"message": "Value \\"Action Group\\" exists",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
test('date is passed to templates', () => {
|
||||
const actionParams = {
|
||||
message: '{{date}}',
|
||||
|
@ -310,6 +346,7 @@ test('date is passed to templates', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
const dateAfter = Date.now();
|
||||
|
@ -335,6 +372,7 @@ test('works recursively', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
@ -362,6 +400,7 @@ test('works recursively with arrays', () => {
|
|||
spaceId: 'spaceId-A',
|
||||
alertInstanceId: '2',
|
||||
alertActionGroup: 'action-group',
|
||||
alertActionGroupName: 'Action Group',
|
||||
alertParams: {},
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -20,6 +20,7 @@ interface TransformActionParamsOptions {
|
|||
tags?: string[];
|
||||
alertInstanceId: string;
|
||||
alertActionGroup: string;
|
||||
alertActionGroupName: string;
|
||||
actionParams: AlertActionParams;
|
||||
alertParams: AlertTypeParams;
|
||||
state: AlertInstanceState;
|
||||
|
@ -33,6 +34,7 @@ export function transformActionParams({
|
|||
tags,
|
||||
alertInstanceId,
|
||||
alertActionGroup,
|
||||
alertActionGroupName,
|
||||
context,
|
||||
actionParams,
|
||||
state,
|
||||
|
@ -51,6 +53,7 @@ export function transformActionParams({
|
|||
tags,
|
||||
alertInstanceId,
|
||||
alertActionGroup,
|
||||
alertActionGroupName,
|
||||
context,
|
||||
date: new Date().toISOString(),
|
||||
state,
|
||||
|
|
|
@ -96,6 +96,7 @@ export interface AlertType<
|
|||
};
|
||||
actionGroups: ActionGroup[];
|
||||
defaultActionGroupId: ActionGroup['id'];
|
||||
recoveryActionGroup?: ActionGroup;
|
||||
executor: ({
|
||||
services,
|
||||
params,
|
||||
|
|
|
@ -63,7 +63,7 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
|
|||
[field.setValue, actions]
|
||||
);
|
||||
|
||||
const setAlertProperty = useCallback(
|
||||
const setAlertActionsProperty = useCallback(
|
||||
(updatedActions: AlertAction[]) => field.setValue(updatedActions),
|
||||
[field]
|
||||
);
|
||||
|
@ -119,7 +119,7 @@ export const RuleActionsField: React.FC<Props> = ({ field, messageVariables }) =
|
|||
messageVariables={messageVariables}
|
||||
defaultActionGroupId={DEFAULT_ACTION_GROUP_ID}
|
||||
setActionIdByIndex={setActionIdByIndex}
|
||||
setAlertProperty={setAlertProperty}
|
||||
setActions={setAlertActionsProperty}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
actionTypes={supportedActionTypes}
|
||||
|
|
|
@ -32,7 +32,7 @@ export const AddMessageVariables: React.FunctionComponent<Props> = ({
|
|||
messageVariables?.map((variable: ActionVariable, i: number) => (
|
||||
<EuiContextMenuItem
|
||||
key={variable.name}
|
||||
data-test-subj={`variableMenuButton-${i}`}
|
||||
data-test-subj={`variableMenuButton-${variable.name}`}
|
||||
icon="empty"
|
||||
onClick={() => {
|
||||
onSelectEventHandler(variable.name);
|
||||
|
|
|
@ -43,6 +43,10 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
@ -86,6 +90,10 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "foo-description",
|
||||
"name": "context.foo",
|
||||
|
@ -137,6 +145,10 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "foo-description",
|
||||
"name": "state.foo",
|
||||
|
@ -191,6 +203,10 @@ describe('transformActionVariables', () => {
|
|||
"description": "The alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroup",
|
||||
},
|
||||
Object {
|
||||
"description": "The human readable name of the alert action group that was used to scheduled actions for the alert.",
|
||||
"name": "alertActionGroupName",
|
||||
},
|
||||
Object {
|
||||
"description": "fooC-description",
|
||||
"name": "context.fooC",
|
||||
|
@ -223,6 +239,7 @@ function getAlertType(actionVariables: ActionVariables): AlertType {
|
|||
actionVariables,
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
authorizedConsumers: {},
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
};
|
||||
|
|
|
@ -87,5 +87,16 @@ function getAlwaysProvidedActionVariables(): ActionVariable[] {
|
|||
}),
|
||||
});
|
||||
|
||||
result.push({
|
||||
name: 'alertActionGroupName',
|
||||
description: i18n.translate(
|
||||
'xpack.triggersActionsUI.actionVariables.alertActionGroupNameLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'The human readable name of the alert action group that was used to scheduled actions for the alert.',
|
||||
}
|
||||
),
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ describe('loadAlertTypes', () => {
|
|||
},
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
defaultActionGroupId: 'default',
|
||||
authorizedConsumers: {},
|
||||
},
|
||||
|
|
|
@ -10,16 +10,20 @@ import { getDefaultsForActionParams } from './get_defaults_for_action_params';
|
|||
|
||||
describe('getDefaultsForActionParams', () => {
|
||||
test('pagerduty defaults', async () => {
|
||||
expect(getDefaultsForActionParams('.pagerduty', 'test')).toEqual({
|
||||
expect(getDefaultsForActionParams(() => false)('.pagerduty', 'test')).toEqual({
|
||||
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
|
||||
eventAction: 'trigger',
|
||||
});
|
||||
});
|
||||
|
||||
test('pagerduty defaults for recovered action group', async () => {
|
||||
expect(getDefaultsForActionParams('.pagerduty', RecoveredActionGroup.id)).toEqual({
|
||||
const isRecoveryActionGroup = jest.fn().mockReturnValue(true);
|
||||
expect(
|
||||
getDefaultsForActionParams(isRecoveryActionGroup)('.pagerduty', RecoveredActionGroup.id)
|
||||
).toEqual({
|
||||
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
|
||||
eventAction: 'resolve',
|
||||
});
|
||||
expect(isRecoveryActionGroup).toHaveBeenCalledWith(RecoveredActionGroup.id);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { AlertActionParam, RecoveredActionGroup } from '../../../../alerts/common';
|
||||
import { AlertActionParam } from '../../../../alerts/common';
|
||||
import { EventActionOptions } from '../components/builtin_action_types/types';
|
||||
import { AlertProvidedActionVariables } from './action_variables';
|
||||
|
||||
export type DefaultActionParamsGetter = ReturnType<typeof getDefaultsForActionParams>;
|
||||
export type DefaultActionParams = ReturnType<DefaultActionParamsGetter>;
|
||||
export const getDefaultsForActionParams = (
|
||||
isRecoveryActionGroup: (actionGroupId: string) => boolean
|
||||
) => (
|
||||
actionTypeId: string,
|
||||
actionGroupId: string
|
||||
): Record<string, AlertActionParam> | undefined => {
|
||||
|
@ -18,7 +22,7 @@ export const getDefaultsForActionParams = (
|
|||
dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`,
|
||||
eventAction: EventActionOptions.TRIGGER,
|
||||
};
|
||||
if (actionGroupId === RecoveredActionGroup.id) {
|
||||
if (isRecoveryActionGroup(actionGroupId)) {
|
||||
pagerDutyDefaults.eventAction = EventActionOptions.RESOLVE;
|
||||
}
|
||||
return pagerDutyDefaults;
|
||||
|
|
|
@ -10,9 +10,7 @@ import { act } from 'react-dom/test-utils';
|
|||
import { actionTypeRegistryMock } from '../../action_type_registry.mock';
|
||||
import { ValidationResult, Alert, AlertAction } from '../../../types';
|
||||
import ActionForm from './action_form';
|
||||
import { RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { EuiScreenReaderOnly } from '@elastic/eui';
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
jest.mock('../../lib/action_connector_api', () => ({
|
||||
loadAllActions: jest.fn(),
|
||||
|
@ -212,6 +210,7 @@ describe('action_form', () => {
|
|||
mutedInstanceIds: [],
|
||||
} as unknown) as Alert;
|
||||
|
||||
const defaultActionMessage = 'Alert [{{context.metadata.name}}] has exceeded the threshold';
|
||||
const wrapper = mountWithIntl(
|
||||
<ActionForm
|
||||
actions={initialAlert.actions}
|
||||
|
@ -228,19 +227,18 @@ describe('action_form', () => {
|
|||
initialAlert.actions[index].id = id;
|
||||
}}
|
||||
actionGroups={[
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'default', name: 'Default', defaultActionMessage },
|
||||
{ id: 'recovered', name: 'Recovered' },
|
||||
]}
|
||||
setActionGroupIdByIndex={(group: string, index: number) => {
|
||||
initialAlert.actions[index].group = group;
|
||||
}}
|
||||
setAlertProperty={(_updatedActions: AlertAction[]) => {}}
|
||||
setActions={(_updatedActions: AlertAction[]) => {}}
|
||||
setActionParamsProperty={(key: string, value: any, index: number) =>
|
||||
(initialAlert.actions[index] = { ...initialAlert.actions[index], [key]: value })
|
||||
}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
defaultActionMessage={'Alert [{{ctx.metadata.name}}] has exceeded the threshold'}
|
||||
actionTypes={[
|
||||
{
|
||||
id: actionType.id,
|
||||
|
@ -356,45 +354,6 @@ describe('action_form', () => {
|
|||
`);
|
||||
});
|
||||
|
||||
it('renders selected Recovered action group', async () => {
|
||||
const wrapper = await setup([
|
||||
{
|
||||
group: RecoveredActionGroup.id,
|
||||
id: 'test',
|
||||
actionTypeId: actionType.id,
|
||||
params: {
|
||||
message: '',
|
||||
},
|
||||
},
|
||||
]);
|
||||
const actionOption = wrapper.find(
|
||||
`[data-test-subj="${actionType.id}-ActionTypeSelectOption"]`
|
||||
);
|
||||
actionOption.first().simulate('click');
|
||||
const actionGroupsSelect = wrapper.find(
|
||||
`[data-test-subj="addNewActionConnectorActionGroup-0"]`
|
||||
);
|
||||
expect((actionGroupsSelect.first().props() as any).options).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"data-test-subj": "addNewActionConnectorActionGroup-0-option-default",
|
||||
"inputDisplay": "Default",
|
||||
"value": "default",
|
||||
},
|
||||
Object {
|
||||
"data-test-subj": "addNewActionConnectorActionGroup-0-option-recovered",
|
||||
"inputDisplay": "Recovered",
|
||||
"value": "recovered",
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(actionGroupsSelect.first().find(EuiScreenReaderOnly).text()).toEqual(
|
||||
'Select an option: Recovered, is selected'
|
||||
);
|
||||
expect(actionGroupsSelect.first().find('button').first().text()).toEqual('Recovered');
|
||||
});
|
||||
|
||||
it('renders available connectors for the selected action type', async () => {
|
||||
const wrapper = await setup();
|
||||
const actionOption = wrapper.find(
|
||||
|
|
|
@ -37,21 +37,28 @@ import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_en
|
|||
import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants';
|
||||
import { ActionGroup, AlertActionParam } from '../../../../../alerts/common';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params';
|
||||
|
||||
export interface ActionGroupWithMessageVariables extends ActionGroup {
|
||||
omitOptionalMessageVariables?: boolean;
|
||||
defaultActionMessage?: string;
|
||||
}
|
||||
|
||||
export interface ActionAccordionFormProps {
|
||||
actions: AlertAction[];
|
||||
defaultActionGroupId: string;
|
||||
actionGroups?: ActionGroup[];
|
||||
actionGroups?: ActionGroupWithMessageVariables[];
|
||||
defaultActionMessage?: string;
|
||||
setActionIdByIndex: (id: string, index: number) => void;
|
||||
setActionGroupIdByIndex?: (group: string, index: number) => void;
|
||||
setAlertProperty: (actions: AlertAction[]) => void;
|
||||
setActions: (actions: AlertAction[]) => void;
|
||||
setActionParamsProperty: (key: string, value: AlertActionParam, index: number) => void;
|
||||
actionTypes?: ActionType[];
|
||||
messageVariables?: ActionVariables;
|
||||
defaultActionMessage?: string;
|
||||
setHasActionsDisabled?: (value: boolean) => void;
|
||||
setHasActionsWithBrokenConnector?: (value: boolean) => void;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
getDefaultActionParams?: DefaultActionParamsGetter;
|
||||
}
|
||||
|
||||
interface ActiveActionConnectorState {
|
||||
|
@ -62,17 +69,18 @@ interface ActiveActionConnectorState {
|
|||
export const ActionForm = ({
|
||||
actions,
|
||||
defaultActionGroupId,
|
||||
actionGroups,
|
||||
setActionIdByIndex,
|
||||
setActionGroupIdByIndex,
|
||||
setAlertProperty,
|
||||
setActions,
|
||||
setActionParamsProperty,
|
||||
actionTypes,
|
||||
messageVariables,
|
||||
actionGroups,
|
||||
defaultActionMessage,
|
||||
setHasActionsDisabled,
|
||||
setHasActionsWithBrokenConnector,
|
||||
actionTypeRegistry,
|
||||
getDefaultActionParams,
|
||||
}: ActionAccordionFormProps) => {
|
||||
const {
|
||||
http,
|
||||
|
@ -303,7 +311,7 @@ export const ActionForm = ({
|
|||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setActions(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id)
|
||||
.length === 0
|
||||
|
@ -333,9 +341,10 @@ export const ActionForm = ({
|
|||
actionTypesIndex={actionTypesIndex}
|
||||
connectors={connectors}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
defaultActionMessage={defaultActionMessage}
|
||||
messageVariables={messageVariables}
|
||||
actionGroups={actionGroups}
|
||||
defaultActionMessage={defaultActionMessage}
|
||||
defaultParams={getDefaultActionParams?.(actionItem.actionTypeId, actionItem.group)}
|
||||
setActionGroupIdByIndex={setActionGroupIdByIndex}
|
||||
onAddConnector={() => {
|
||||
setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index });
|
||||
|
@ -349,7 +358,7 @@ export const ActionForm = ({
|
|||
const updatedActions = actions.filter(
|
||||
(_item: AlertAction, i: number) => i !== index
|
||||
);
|
||||
setAlertProperty(updatedActions);
|
||||
setActions(updatedActions);
|
||||
setIsAddActionPanelOpen(
|
||||
updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length ===
|
||||
0
|
||||
|
|
|
@ -26,7 +26,8 @@ import {
|
|||
EuiBadge,
|
||||
EuiErrorBoundary,
|
||||
} from '@elastic/eui';
|
||||
import { AlertActionParam, RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
import { pick } from 'lodash';
|
||||
import { AlertActionParam } from '../../../../../alerts/common';
|
||||
import {
|
||||
IErrorObject,
|
||||
AlertAction,
|
||||
|
@ -35,14 +36,14 @@ import {
|
|||
ActionVariables,
|
||||
ActionVariable,
|
||||
ActionTypeRegistryContract,
|
||||
REQUIRED_ACTION_VARIABLES,
|
||||
} from '../../../types';
|
||||
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
|
||||
import { hasSaveActionsCapability } from '../../lib/capabilities';
|
||||
import { ActionAccordionFormProps } from './action_form';
|
||||
import { ActionAccordionFormProps, ActionGroupWithMessageVariables } from './action_form';
|
||||
import { transformActionVariables } from '../../lib/action_variables';
|
||||
import { recoveredActionGroupMessage } from '../../constants';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params';
|
||||
import { DefaultActionParams } from '../../lib/get_defaults_for_action_params';
|
||||
|
||||
export type ActionTypeFormProps = {
|
||||
actionItem: AlertAction;
|
||||
|
@ -58,6 +59,7 @@ export type ActionTypeFormProps = {
|
|||
actionTypesIndex: ActionTypeIndex;
|
||||
connectors: ActionConnector[];
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
defaultParams: DefaultActionParams;
|
||||
} & Pick<
|
||||
ActionAccordionFormProps,
|
||||
| 'defaultActionGroupId'
|
||||
|
@ -92,31 +94,28 @@ export const ActionTypeForm = ({
|
|||
actionGroups,
|
||||
setActionGroupIdByIndex,
|
||||
actionTypeRegistry,
|
||||
defaultParams,
|
||||
}: ActionTypeFormProps) => {
|
||||
const {
|
||||
application: { capabilities },
|
||||
} = useKibana().services;
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const [availableActionVariables, setAvailableActionVariables] = useState<ActionVariable[]>([]);
|
||||
const [availableDefaultActionMessage, setAvailableDefaultActionMessage] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId);
|
||||
const selectedActionGroup =
|
||||
actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup;
|
||||
|
||||
useEffect(() => {
|
||||
setAvailableActionVariables(getAvailableActionVariables(messageVariables, actionItem.group));
|
||||
const res =
|
||||
actionItem.group === RecoveredActionGroup.id
|
||||
? recoveredActionGroupMessage
|
||||
: defaultActionMessage;
|
||||
setAvailableDefaultActionMessage(res);
|
||||
const paramsDefaults = getDefaultsForActionParams(actionItem.actionTypeId, actionItem.group);
|
||||
if (paramsDefaults) {
|
||||
for (const [key, paramValue] of Object.entries(paramsDefaults)) {
|
||||
setAvailableActionVariables(
|
||||
messageVariables ? getAvailableActionVariables(messageVariables, selectedActionGroup) : []
|
||||
);
|
||||
if (defaultParams) {
|
||||
for (const [key, paramValue] of Object.entries(defaultParams)) {
|
||||
setActionParamsProperty(key, paramValue, index);
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [actionItem.group]);
|
||||
}, [actionItem.group, defaultParams]);
|
||||
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
const getSelectedOptions = (actionItemId: string) => {
|
||||
|
@ -167,10 +166,6 @@ export const ActionTypeForm = ({
|
|||
connectors.filter((connector) => connector.isPreconfigured)
|
||||
);
|
||||
|
||||
const defaultActionGroup = actionGroups?.find(({ id }) => id === defaultActionGroupId);
|
||||
const selectedActionGroup =
|
||||
actionGroups?.find(({ id }) => id === actionItem.group) ?? defaultActionGroup;
|
||||
|
||||
const accordionContent = checkEnabledResult.isEnabled ? (
|
||||
<Fragment>
|
||||
{actionGroups && selectedActionGroup && setActionGroupIdByIndex && (
|
||||
|
@ -275,7 +270,7 @@ export const ActionTypeForm = ({
|
|||
errors={actionParamsErrors.errors}
|
||||
editAction={setActionParamsProperty}
|
||||
messageVariables={availableActionVariables}
|
||||
defaultMessage={availableDefaultActionMessage}
|
||||
defaultMessage={selectedActionGroup?.defaultActionMessage ?? defaultActionMessage}
|
||||
actionConnector={actionConnector}
|
||||
/>
|
||||
</Suspense>
|
||||
|
@ -367,18 +362,12 @@ export const ActionTypeForm = ({
|
|||
};
|
||||
|
||||
function getAvailableActionVariables(
|
||||
actionVariables: ActionVariables | undefined,
|
||||
actionGroup: string
|
||||
actionVariables: ActionVariables,
|
||||
actionGroup?: ActionGroupWithMessageVariables
|
||||
) {
|
||||
if (!actionVariables) {
|
||||
return [];
|
||||
}
|
||||
const filteredActionVariables =
|
||||
actionGroup === RecoveredActionGroup.id
|
||||
? { params: actionVariables.params, state: actionVariables.state }
|
||||
: actionVariables;
|
||||
|
||||
return transformActionVariables(filteredActionVariables).sort((a, b) =>
|
||||
a.name.toUpperCase().localeCompare(b.name.toUpperCase())
|
||||
);
|
||||
return transformActionVariables(
|
||||
actionGroup?.omitOptionalMessageVariables
|
||||
? pick(actionVariables, ...REQUIRED_ACTION_VARIABLES)
|
||||
: actionVariables
|
||||
).sort((a, b) => a.name.toUpperCase().localeCompare(b.name.toUpperCase()));
|
||||
}
|
||||
|
|
|
@ -47,8 +47,8 @@ const mockAlertApis = {
|
|||
const authorizedConsumers = {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
};
|
||||
const recoveryActionGroup = { id: 'recovered', name: 'Recovered' };
|
||||
|
||||
// const AlertDetails = withBulkAlertOperations(RawAlertDetails);
|
||||
describe('alert_details', () => {
|
||||
// mock Api handlers
|
||||
|
||||
|
@ -58,6 +58,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -83,6 +84,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -111,6 +113,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -145,6 +148,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -199,6 +203,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -258,6 +263,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -278,6 +284,7 @@ describe('alert_details', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -307,6 +314,7 @@ describe('disable button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -335,6 +343,7 @@ describe('disable button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -363,6 +372,7 @@ describe('disable button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -400,6 +410,7 @@ describe('disable button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -440,6 +451,7 @@ describe('mute button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -469,6 +481,7 @@ describe('mute button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -498,6 +511,7 @@ describe('mute button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -536,6 +550,7 @@ describe('mute button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -574,6 +589,7 @@ describe('mute button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
@ -639,6 +655,7 @@ describe('edit button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
|
@ -681,6 +698,7 @@ describe('edit button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
|
@ -716,6 +734,7 @@ describe('edit button', () => {
|
|||
id: '.noop',
|
||||
name: 'No Op',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup,
|
||||
actionVariables: { context: [], state: [], params: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: 'alerting',
|
||||
|
|
|
@ -307,6 +307,7 @@ function mockAlertType(overloads: Partial<AlertType> = {}): AlertType {
|
|||
params: [],
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
authorizedConsumers: {},
|
||||
producer: 'alerts',
|
||||
...overloads,
|
||||
|
|
|
@ -147,6 +147,7 @@ function mockAlertType(overloads: Partial<AlertType> = {}): AlertType {
|
|||
params: [],
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
authorizedConsumers: {},
|
||||
producer: 'alerts',
|
||||
...overloads,
|
||||
|
|
|
@ -61,6 +61,7 @@ describe('alert_add', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'testActionGroup',
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers: {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
|
|
|
@ -13,7 +13,7 @@ import { ValidationResult, Alert } from '../../../types';
|
|||
import { AlertForm } from './alert_form';
|
||||
import { AlertsContextProvider } from '../../context/alerts_context';
|
||||
import { coreMock } from 'src/core/public/mocks';
|
||||
import { ALERTS_FEATURE_ID } from '../../../../../alerts/common';
|
||||
import { ALERTS_FEATURE_ID, RecoveredActionGroup } from '../../../../../alerts/common';
|
||||
|
||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const alertTypeRegistry = alertTypeRegistryMock.create();
|
||||
|
@ -85,6 +85,7 @@ describe('alert_form', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'testActionGroup',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers: {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
|
@ -218,6 +219,7 @@ describe('alert_form', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'testActionGroup',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
authorizedConsumers: {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
|
@ -234,6 +236,7 @@ describe('alert_form', () => {
|
|||
},
|
||||
],
|
||||
defaultActionGroupId: 'testActionGroup',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
producer: 'test',
|
||||
authorizedConsumers: {
|
||||
[ALERTS_FEATURE_ID]: { read: true, all: true },
|
||||
|
|
|
@ -58,6 +58,8 @@ import { AlertActionParam, ALERTS_FEATURE_ID } from '../../../../../alerts/commo
|
|||
import { hasAllPrivilege, hasShowActionsCapability } from '../../lib/capabilities';
|
||||
import { SolutionFilter } from './solution_filter';
|
||||
import './alert_form.scss';
|
||||
import { recoveredActionGroupMessage } from '../../constants';
|
||||
import { getDefaultsForActionParams } from '../../lib/get_defaults_for_action_params';
|
||||
|
||||
const ENTER_KEY = 13;
|
||||
|
||||
|
@ -306,6 +308,7 @@ export const AlertForm = ({
|
|||
? !item.alertTypeModel.requiresAppContext
|
||||
: item.alertType!.producer === alert.consumer
|
||||
);
|
||||
const selectedAlertType = alert?.alertTypeId && alertTypesIndex?.get(alert?.alertTypeId);
|
||||
|
||||
const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : [];
|
||||
|
||||
|
@ -461,7 +464,7 @@ export const AlertForm = ({
|
|||
{AlertParamsExpressionComponent &&
|
||||
defaultActionGroupId &&
|
||||
alert.alertTypeId &&
|
||||
alertTypesIndex?.has(alert.alertTypeId) ? (
|
||||
selectedAlertType ? (
|
||||
<EuiErrorBoundary>
|
||||
<Suspense fallback={<CenterJustifiedSpinner />}>
|
||||
<AlertParamsExpressionComponent
|
||||
|
@ -473,7 +476,7 @@ export const AlertForm = ({
|
|||
setAlertProperty={setAlertProperty}
|
||||
alertsContext={alertsContext}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
actionGroups={alertTypesIndex.get(alert.alertTypeId)!.actionGroups}
|
||||
actionGroups={selectedAlertType.actionGroups}
|
||||
/>
|
||||
</Suspense>
|
||||
</EuiErrorBoundary>
|
||||
|
@ -482,22 +485,32 @@ export const AlertForm = ({
|
|||
defaultActionGroupId &&
|
||||
alertTypeModel &&
|
||||
alert.alertTypeId &&
|
||||
alertTypesIndex?.has(alert.alertTypeId) ? (
|
||||
selectedAlertType ? (
|
||||
<ActionForm
|
||||
actions={alert.actions}
|
||||
setHasActionsDisabled={setHasActionsDisabled}
|
||||
setHasActionsWithBrokenConnector={setHasActionsWithBrokenConnector}
|
||||
messageVariables={alertTypesIndex.get(alert.alertTypeId)!.actionVariables}
|
||||
messageVariables={selectedAlertType.actionVariables}
|
||||
defaultActionGroupId={defaultActionGroupId}
|
||||
actionGroups={alertTypesIndex.get(alert.alertTypeId)!.actionGroups}
|
||||
actionGroups={selectedAlertType.actionGroups.map((actionGroup) =>
|
||||
actionGroup.id === selectedAlertType.recoveryActionGroup.id
|
||||
? {
|
||||
...actionGroup,
|
||||
omitOptionalMessageVariables: true,
|
||||
defaultActionMessage: recoveredActionGroupMessage,
|
||||
}
|
||||
: { ...actionGroup, defaultActionMessage: alertTypeModel?.defaultActionMessage }
|
||||
)}
|
||||
getDefaultActionParams={getDefaultsForActionParams(
|
||||
(actionGroupId) => actionGroupId === selectedAlertType.recoveryActionGroup.id
|
||||
)}
|
||||
setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)}
|
||||
setActionGroupIdByIndex={(group: string, index: number) =>
|
||||
setActionProperty('group', group, index)
|
||||
}
|
||||
setAlertProperty={setActions}
|
||||
setActions={setActions}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
actionTypeRegistry={actionTypeRegistry}
|
||||
defaultActionMessage={alertTypeModel?.defaultActionMessage}
|
||||
/>
|
||||
) : null}
|
||||
</Fragment>
|
||||
|
|
|
@ -56,6 +56,7 @@ const alertTypeFromApi = {
|
|||
id: 'test_alert_type',
|
||||
name: 'some alert type',
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
actionVariables: { context: [], state: [] },
|
||||
defaultActionGroupId: 'default',
|
||||
producer: ALERTS_FEATURE_ID,
|
||||
|
|
|
@ -127,16 +127,19 @@ export interface ActionVariable {
|
|||
description: string;
|
||||
}
|
||||
|
||||
export interface ActionVariables {
|
||||
context?: ActionVariable[];
|
||||
state: ActionVariable[];
|
||||
params: ActionVariable[];
|
||||
}
|
||||
type AsActionVariables<Keys extends string> = {
|
||||
[Req in Keys]: ActionVariable[];
|
||||
};
|
||||
export const REQUIRED_ACTION_VARIABLES = ['state', 'params'] as const;
|
||||
export const OPTIONAL_ACTION_VARIABLES = ['context'] as const;
|
||||
export type ActionVariables = AsActionVariables<typeof REQUIRED_ACTION_VARIABLES[number]> &
|
||||
Partial<AsActionVariables<typeof OPTIONAL_ACTION_VARIABLES[number]>>;
|
||||
|
||||
export interface AlertType {
|
||||
id: string;
|
||||
name: string;
|
||||
actionGroups: ActionGroup[];
|
||||
recoveryActionGroup: ActionGroup;
|
||||
actionVariables: ActionVariables;
|
||||
defaultActionGroupId: ActionGroup['id'];
|
||||
authorizedConsumers: Record<string, { read: boolean; all: boolean }>;
|
||||
|
|
|
@ -18,6 +18,7 @@ export function defineAlertTypes(
|
|||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
producer: 'alertsRestrictedFixture',
|
||||
defaultActionGroupId: 'default',
|
||||
recoveryActionGroup: { id: 'restrictedRecovered', name: 'Restricted Recovery' },
|
||||
async executor({ services, params, state }: AlertExecutorOptions) {},
|
||||
};
|
||||
const noopUnrestrictedAlertType: AlertType = {
|
||||
|
|
|
@ -28,13 +28,21 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
|
|||
params: [],
|
||||
},
|
||||
producer: 'alertsFixture',
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
};
|
||||
|
||||
const expectedRestrictedNoOpType = {
|
||||
actionGroups: [
|
||||
{ id: 'default', name: 'Default' },
|
||||
{ id: 'recovered', name: 'Recovered' },
|
||||
{ id: 'restrictedRecovered', name: 'Restricted Recovery' },
|
||||
],
|
||||
recoveryActionGroup: {
|
||||
id: 'restrictedRecovered',
|
||||
name: 'Restricted Recovery',
|
||||
},
|
||||
defaultActionGroupId: 'default',
|
||||
id: 'test.restricted-noop',
|
||||
name: 'Test: Restricted Noop',
|
||||
|
|
|
@ -35,6 +35,10 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
|
|||
params: [],
|
||||
context: [],
|
||||
},
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
producer: 'alertsFixture',
|
||||
});
|
||||
expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture');
|
||||
|
|
|
@ -89,14 +89,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
|
|||
);
|
||||
await testSubjects.setValue('messageTextArea', 'test message ');
|
||||
await testSubjects.click('messageAddVariableButton');
|
||||
await testSubjects.click('variableMenuButton-0');
|
||||
await testSubjects.click('variableMenuButton-alertActionGroup');
|
||||
expect(await messageTextArea.getAttribute('value')).to.eql(
|
||||
'test message {{alertActionGroup}}'
|
||||
);
|
||||
await messageTextArea.type(' some additional text ');
|
||||
|
||||
await testSubjects.click('messageAddVariableButton');
|
||||
await testSubjects.click('variableMenuButton-1');
|
||||
await testSubjects.click('variableMenuButton-alertId');
|
||||
|
||||
expect(await messageTextArea.getAttribute('value')).to.eql(
|
||||
'test message {{alertActionGroup}} some additional text {{alertId}}'
|
||||
|
|
Loading…
Reference in a new issue