From 42d9b191ecb8933303ae3c9c13035fd6b89bf35b Mon Sep 17 00:00:00 2001 From: Patrick Mueller Date: Tue, 10 Mar 2020 02:54:12 -0400 Subject: [PATCH] [Alerting] extend Alert Type with names/descriptions of action variables resolves https://github.com/elastic/kibana/issues/58529 This PR extends alertType with an `actionVariables` property, which should be an object with optionial properties `context` and `state`. These properties should be typed as optional `Record` values. The keys are the names of the relevant action variables, and the values are the localized descriptions of the variables. --- x-pack/plugins/alerting/README.md | 41 +++- .../server/alert_type_registry.test.ts | 4 + .../alerting/server/alert_type_registry.ts | 8 + x-pack/plugins/alerting/server/types.ts | 9 + .../index_threshold/action_context.test.ts | 21 +- .../index_threshold/action_context.ts | 10 +- .../index_threshold/alert_type.test.ts | 27 +++ .../alert_types/index_threshold/alert_type.ts | 44 +++++ .../application/lib/action_variables.test.ts | 185 ++++++++++++++++++ .../application/lib/action_variables.ts | 66 +++++++ .../public/application/lib/alert_api.test.ts | 5 +- .../components/alert_details.test.tsx | 16 -- .../sections/alert_form/alert_form.tsx | 3 +- .../triggers_actions_ui/public/types.ts | 12 +- .../common/fixtures/plugins/alerts/index.ts | 26 +++ .../tests/alerting/list_alert_types.ts | 4 + .../tests/alerting/list_alert_types.ts | 47 ++++- 17 files changed, 485 insertions(+), 43 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts diff --git a/x-pack/plugins/alerting/README.md b/x-pack/plugins/alerting/README.md index f5d0d2cd071f46..fa2e5c8e2faa1f 100644 --- a/x-pack/plugins/alerting/README.md +++ b/x-pack/plugins/alerting/README.md @@ -86,6 +86,7 @@ The following table describes the properties of the `options` object. |id|Unique identifier for the alert type. For convention purposes, ids starting with `.` are reserved for built in alert types. We recommend using a convention like `.mySpecialAlert` for your alert types to avoid conflicting with another plugin.|string| |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}>| +|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| @@ -112,11 +113,25 @@ This is the primary function for an alert type. Whenever the alert needs to exec |createdBy|The userid that created this alert.| |updatedBy|The userid that last updated this alert.| +### The `actionVariables` property + +This property should contain the **flattened** names of the state and context variables available when an executor calls `alertInstance.scheduleActions(groupName, context)`. These names are meant to be used in prompters in the alerting user interface, are used as text values for display, and can be inserted into to an action parameter text entry field via UI gesture (eg, clicking a menu item from a menu built with these names). They should be flattened, so if a state or context variable is an object with properties, these should be listed with the "parent" property/properties in the name, separated by a `.` (period). + +For example, if the `context` has one variable `foo` which is an object that has one property `bar`, and there are no `state` variables, the `actionVariables` value would be in the following shape: + +```js +{ + context: [ + { name: 'foo.bar', description: 'the ultra-exciting bar property' }, + ] +} +``` + ### Example This example receives server and threshold as parameters. It will read the CPU usage of the server and schedule actions to be executed (asynchronously by the task manager) if the reading is greater than the threshold. -``` +```typescript import { schema } from '@kbn/config-schema'; ... server.newPlatform.setup.plugins.alerting.registerType({ @@ -128,6 +143,15 @@ server.newPlatform.setup.plugins.alerting.registerType({ threshold: schema.number({ min: 0, max: 1 }), }), }, + actionVariables: { + context: [ + { name: 'server', description: 'the server' }, + { name: 'hasCpuUsageIncreased', description: 'boolean indicating if the cpu usage has increased' }, + ], + state: [ + { name: 'cpuUsage', description: 'CPU usage' }, + ], + }, async executor({ alertId, startedAt, @@ -136,7 +160,8 @@ server.newPlatform.setup.plugins.alerting.registerType({ params, state, }: AlertExecutorOptions) { - const { server, threshold } = params; // Let's assume params is { server: 'server_1', threshold: 0.8 } + // Let's assume params is { server: 'server_1', threshold: 0.8 } + const { server, threshold } = params; // Call a function to get the server's current CPU usage const currentCpuUsage = await getCpuUsage(server); @@ -177,7 +202,7 @@ server.newPlatform.setup.plugins.alerting.registerType({ This example only receives threshold as a parameter. It will read the CPU usage of all the servers and schedule individual actions if the reading for a server is greater than the threshold. This is a better implementation than above as only one query is performed for all the servers instead of one query per server. -``` +```typescript server.newPlatform.setup.plugins.alerting.registerType({ id: 'my-alert-type', name: 'My alert type', @@ -186,6 +211,15 @@ server.newPlatform.setup.plugins.alerting.registerType({ threshold: schema.number({ min: 0, max: 1 }), }), }, + actionVariables: { + context: [ + { name: 'server', description: 'the server' }, + { name: 'hasCpuUsageIncreased', description: 'boolean indicating if the cpu usage has increased' }, + ], + state: [ + { name: 'cpuUsage', description: 'CPU usage' }, + ], + }, async executor({ alertId, startedAt, @@ -446,3 +480,4 @@ The templating system will take the alert and alert type as described above and ``` There are limitations that we are aware of using only templates, and we are gathering feedback and use cases for these. (for example passing an array of strings to an action). + diff --git a/x-pack/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/plugins/alerting/server/alert_type_registry.test.ts index f4749772c70040..4c7dd56714a306 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.test.ts @@ -173,6 +173,10 @@ describe('list()', () => { "name": "Test Action Group", }, ], + "actionVariables": Object { + "context": Array [], + "state": Array [], + }, "defaultActionGroupId": "testActionGroup", "id": "test", "name": "Test", diff --git a/x-pack/plugins/alerting/server/alert_type_registry.ts b/x-pack/plugins/alerting/server/alert_type_registry.ts index d9045fb986745f..0ae2ee9d1ae631 100644 --- a/x-pack/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/plugins/alerting/server/alert_type_registry.ts @@ -71,6 +71,14 @@ export class AlertTypeRegistry { name: alertType.name, actionGroups: alertType.actionGroups, defaultActionGroupId: alertType.defaultActionGroupId, + actionVariables: normalizedActionVariables(alertType.actionVariables), })); } } + +function normalizedActionVariables(actionVariables: any) { + return { + context: actionVariables?.context ?? [], + state: actionVariables?.state ?? [], + }; +} diff --git a/x-pack/plugins/alerting/server/types.ts b/x-pack/plugins/alerting/server/types.ts index 635cf0cbd13716..739a0d0aece249 100644 --- a/x-pack/plugins/alerting/server/types.ts +++ b/x-pack/plugins/alerting/server/types.ts @@ -52,6 +52,11 @@ export interface AlertExecutorOptions { updatedBy: string | null; } +export interface ActionVariable { + name: string; + description: string; +} + export interface AlertType { id: string; name: string; @@ -61,6 +66,10 @@ export interface AlertType { actionGroups: ActionGroup[]; defaultActionGroupId: ActionGroup['id']; executor: ({ services, params, state }: AlertExecutorOptions) => Promise; + actionVariables?: { + context?: ActionVariable[]; + state?: ActionVariable[]; + }; } export interface RawAlertAction extends SavedObjectAttributes { diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.test.ts index d924f5492f88d9..a72a7343c59048 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.test.ts @@ -26,11 +26,8 @@ describe('ActionContext', () => { thresholdComparator: '>', threshold: [4], }); - const alertInfo = { - name: '[alert-name]', - }; - const context = addMessages(alertInfo, base, params); - expect(context.subject).toMatchInlineSnapshot( + const context = addMessages({ name: '[alert-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot( `"alert [alert-name] group [group] exceeded threshold"` ); expect(context.message).toMatchInlineSnapshot( @@ -57,11 +54,8 @@ describe('ActionContext', () => { thresholdComparator: '>', threshold: [4.2], }); - const alertInfo = { - name: '[alert-name]', - }; - const context = addMessages(alertInfo, base, params); - expect(context.subject).toMatchInlineSnapshot( + const context = addMessages({ name: '[alert-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot( `"alert [alert-name] group [group] exceeded threshold"` ); expect(context.message).toMatchInlineSnapshot( @@ -87,11 +81,8 @@ describe('ActionContext', () => { thresholdComparator: 'between', threshold: [4, 5], }); - const alertInfo = { - name: '[alert-name]', - }; - const context = addMessages(alertInfo, base, params); - expect(context.subject).toMatchInlineSnapshot( + const context = addMessages({ name: '[alert-name]' }, base, params); + expect(context.title).toMatchInlineSnapshot( `"alert [alert-name] group [group] exceeded threshold"` ); expect(context.message).toMatchInlineSnapshot( diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts index 4a4965db91071e..15139ae34c93d8 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/action_context.ts @@ -13,9 +13,9 @@ import { AlertExecutorOptions } from '../../../../alerting/server'; type AlertInfo = Pick; export interface ActionContext extends BaseActionContext { - // a short generic message which may be used in an action message - subject: string; - // a longer generic message which may be used in an action message + // a short pre-constructed message which may be used in an action field + title: string; + // a longer pre-constructed message which may be used in an action field message: string; } @@ -34,7 +34,7 @@ export function addMessages( baseContext: BaseActionContext, params: Params ): ActionContext { - const subject = i18n.translate( + const title = i18n.translate( 'xpack.alertingBuiltins.indexThreshold.alertTypeContextSubjectTitle', { defaultMessage: 'alert {name} group {group} exceeded threshold', @@ -65,5 +65,5 @@ export function addMessages( } ); - return { ...baseContext, subject, message }; + return { ...baseContext, title, message }; } diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts index 5034b1ee0cd018..5c15c398dbdcd9 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.test.ts @@ -22,6 +22,33 @@ describe('alertType', () => { expect(alertType.id).toBe('.index-threshold'); expect(alertType.name).toBe('Index Threshold'); expect(alertType.actionGroups).toEqual([{ id: 'threshold met', name: 'Threshold Met' }]); + + expect(alertType.actionVariables).toMatchInlineSnapshot(` + Object { + "context": Array [ + Object { + "description": "A pre-constructed message for the alert.", + "name": "message", + }, + Object { + "description": "A pre-constructed title for the alert.", + "name": "title", + }, + Object { + "description": "The group that exceeded the threshold.", + "name": "group", + }, + Object { + "description": "The date the alert exceeded the threshold.", + "name": "date", + }, + Object { + "description": "The value that exceeded the threshold.", + "name": "value", + }, + ], + } + `); }); it('validator succeeds with valid params', async () => { diff --git a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts index bc5fcd970bd9b6..b79321a8803fad 100644 --- a/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts +++ b/x-pack/plugins/alerting_builtins/server/alert_types/index_threshold/alert_type.ts @@ -32,6 +32,41 @@ export function getAlertType(service: Service): AlertType { } ); + const actionVariableContextGroupLabel = i18n.translate( + 'xpack.alertingBuiltins.indexThreshold.actionVariableContextGroupLabel', + { + defaultMessage: 'The group that exceeded the threshold.', + } + ); + + const actionVariableContextDateLabel = i18n.translate( + 'xpack.alertingBuiltins.indexThreshold.actionVariableContextDateLabel', + { + defaultMessage: 'The date the alert exceeded the threshold.', + } + ); + + const actionVariableContextValueLabel = i18n.translate( + 'xpack.alertingBuiltins.indexThreshold.actionVariableContextValueLabel', + { + defaultMessage: 'The value that exceeded the threshold.', + } + ); + + const actionVariableContextMessageLabel = i18n.translate( + 'xpack.alertingBuiltins.indexThreshold.actionVariableContextMessageLabel', + { + defaultMessage: 'A pre-constructed message for the alert.', + } + ); + + const actionVariableContextTitleLabel = i18n.translate( + 'xpack.alertingBuiltins.indexThreshold.actionVariableContextTitleLabel', + { + defaultMessage: 'A pre-constructed title for the alert.', + } + ); + return { id: ID, name: alertTypeName, @@ -40,6 +75,15 @@ export function getAlertType(service: Service): AlertType { validate: { params: ParamsSchema, }, + actionVariables: { + context: [ + { name: 'message', description: actionVariableContextMessageLabel }, + { name: 'title', description: actionVariableContextTitleLabel }, + { name: 'group', description: actionVariableContextGroupLabel }, + { name: 'date', description: actionVariableContextDateLabel }, + { name: 'value', description: actionVariableContextValueLabel }, + ], + }, executor, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts new file mode 100644 index 00000000000000..6b136724fc8135 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.test.ts @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertType, ActionVariables } from '../../types'; +import { actionVariablesFromAlertType } from './action_variables'; + +beforeEach(() => jest.resetAllMocks()); + +describe('actionVariablesFromAlertType', () => { + test('should return correct variables when no state or context provided', async () => { + const alertType = getAlertType(); + expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(` + Array [ + Object { + "description": "The id of the alert.", + "name": "alertId", + }, + Object { + "description": "The name of the alert.", + "name": "alertName", + }, + Object { + "description": "The spaceId of the alert.", + "name": "spaceId", + }, + Object { + "description": "The tags of the alert.", + "name": "tags", + }, + Object { + "description": "The alert instance id that scheduled actions for the alert.", + "name": "alertInstanceId", + }, + ] + `); + }); + + test('should return correct variables when no state provided', async () => { + const alertType = getAlertType({ + context: [ + { name: 'foo', description: 'foo-description' }, + { name: 'bar', description: 'bar-description' }, + ], + }); + expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(` + Array [ + Object { + "description": "The id of the alert.", + "name": "alertId", + }, + Object { + "description": "The name of the alert.", + "name": "alertName", + }, + Object { + "description": "The spaceId of the alert.", + "name": "spaceId", + }, + Object { + "description": "The tags of the alert.", + "name": "tags", + }, + Object { + "description": "The alert instance id that scheduled actions for the alert.", + "name": "alertInstanceId", + }, + Object { + "description": "foo-description", + "name": "context.foo", + }, + Object { + "description": "bar-description", + "name": "context.bar", + }, + ] + `); + }); + + test('should return correct variables when no context provided', async () => { + const alertType = getAlertType({ + state: [ + { name: 'foo', description: 'foo-description' }, + { name: 'bar', description: 'bar-description' }, + ], + }); + expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(` + Array [ + Object { + "description": "The id of the alert.", + "name": "alertId", + }, + Object { + "description": "The name of the alert.", + "name": "alertName", + }, + Object { + "description": "The spaceId of the alert.", + "name": "spaceId", + }, + Object { + "description": "The tags of the alert.", + "name": "tags", + }, + Object { + "description": "The alert instance id that scheduled actions for the alert.", + "name": "alertInstanceId", + }, + Object { + "description": "foo-description", + "name": "state.foo", + }, + Object { + "description": "bar-description", + "name": "state.bar", + }, + ] + `); + }); + + test('should return correct variables when both context and state provided', async () => { + const alertType = getAlertType({ + context: [ + { name: 'fooC', description: 'fooC-description' }, + { name: 'barC', description: 'barC-description' }, + ], + state: [ + { name: 'fooS', description: 'fooS-description' }, + { name: 'barS', description: 'barS-description' }, + ], + }); + expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(` + Array [ + Object { + "description": "The id of the alert.", + "name": "alertId", + }, + Object { + "description": "The name of the alert.", + "name": "alertName", + }, + Object { + "description": "The spaceId of the alert.", + "name": "spaceId", + }, + Object { + "description": "The tags of the alert.", + "name": "tags", + }, + Object { + "description": "The alert instance id that scheduled actions for the alert.", + "name": "alertInstanceId", + }, + Object { + "description": "fooC-description", + "name": "context.fooC", + }, + Object { + "description": "barC-description", + "name": "context.barC", + }, + Object { + "description": "fooS-description", + "name": "state.fooS", + }, + Object { + "description": "barS-description", + "name": "state.barS", + }, + ] + `); + }); +}); + +function getAlertType(actionVariables?: ActionVariables): AlertType { + return { + id: 'test', + name: 'Test', + actionVariables, + actionGroups: [{ id: 'default', name: 'Default' }], + defaultActionGroupId: 'default', + }; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts new file mode 100644 index 00000000000000..0e530829132cc4 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { AlertType, ActionVariable } from '../../types'; + +// return a "flattened" list of action variables for an alertType +export function actionVariablesFromAlertType(alertType: AlertType): ActionVariable[] { + const alwaysProvidedVars = getAlwaysProvidedActionVariables(); + const contextVars = prefixKeys(alertType.actionVariables?.context ?? [], 'context.'); + const stateVars = prefixKeys(alertType.actionVariables?.state ?? [], 'state.'); + + return alwaysProvidedVars.concat(contextVars, stateVars); +} + +function prefixKeys(actionVariables: ActionVariable[], prefix: string): ActionVariable[] { + return actionVariables.map(actionVariable => { + return { name: `${prefix}${actionVariable.name}`, description: actionVariable.description }; + }); +} + +// this list should be the same as in: +// x-pack/plugins/alerting/server/task_runner/transform_action_params.ts +function getAlwaysProvidedActionVariables(): ActionVariable[] { + const result: ActionVariable[] = []; + + result.push({ + name: 'alertId', + description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertIdLabel', { + defaultMessage: 'The id of the alert.', + }), + }); + + result.push({ + name: 'alertName', + description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertNameLabel', { + defaultMessage: 'The name of the alert.', + }), + }); + + result.push({ + name: 'spaceId', + description: i18n.translate('xpack.triggersActionsUI.actionVariables.spaceIdLabel', { + defaultMessage: 'The spaceId of the alert.', + }), + }); + + result.push({ + name: 'tags', + description: i18n.translate('xpack.triggersActionsUI.actionVariables.tagsLabel', { + defaultMessage: 'The tags of the alert.', + }), + }); + + result.push({ + name: 'alertInstanceId', + description: i18n.translate('xpack.triggersActionsUI.actionVariables.alertInstanceIdLabel', { + defaultMessage: 'The alert instance id that scheduled actions for the alert.', + }), + }); + + return result; +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts index ebbfb0fc4b76fc..0b06982828446c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_api.test.ts @@ -38,7 +38,10 @@ describe('loadAlertTypes', () => { { id: 'test', name: 'Test', - actionVariables: ['var1'], + actionVariables: { + context: [{ name: 'var1', description: 'val1' }], + state: [{ name: 'var2', description: 'val2' }], + }, actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index 64069009f6589c..b8f5dab91d9a56 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -54,7 +54,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; expect( @@ -87,7 +86,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; expect( @@ -115,7 +113,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const actionTypes: ActionType[] = [ @@ -165,7 +162,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const actionTypes: ActionType[] = [ { @@ -216,7 +212,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; expect( @@ -241,7 +236,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; expect( @@ -266,7 +260,6 @@ describe('alert_details', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; expect( @@ -296,7 +289,6 @@ describe('enable button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableButton = shallow( @@ -322,7 +314,6 @@ describe('enable button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableButton = shallow( @@ -348,7 +339,6 @@ describe('enable button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const disableAlert = jest.fn(); @@ -383,7 +373,6 @@ describe('enable button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableAlert = jest.fn(); @@ -421,7 +410,6 @@ describe('mute button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableButton = shallow( @@ -448,7 +436,6 @@ describe('mute button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableButton = shallow( @@ -475,7 +462,6 @@ describe('mute button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const muteAlert = jest.fn(); @@ -511,7 +497,6 @@ describe('mute button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const unmuteAlert = jest.fn(); @@ -547,7 +532,6 @@ describe('mute button', () => { name: 'No Op', actionGroups: [{ id: 'default', name: 'Default' }], defaultActionGroupId: 'default', - actionVariables: [], }; const enableButton = shallow( diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index f90a05e2b766ec..aa56b565ef3246 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -25,6 +25,7 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { loadAlertTypes } from '../../lib/alert_api'; +import { actionVariablesFromAlertType } from '../../lib/action_variables'; import { AlertReducerAction } from './alert_reducer'; import { AlertTypeModel, Alert, IErrorObject, AlertAction, AlertTypeIndex } from '../../../types'; import { getTimeOptions } from '../../../common/lib/get_time_options'; @@ -213,7 +214,7 @@ export const AlertForm = ({ alert, canChangeTrigger = true, dispatch, errors }: actions={alert.actions} messageVariables={ alertTypesIndex && alertTypesIndex[alert.alertTypeId] - ? alertTypesIndex[alert.alertTypeId].actionVariables + ? actionVariablesFromAlertType(alertTypesIndex[alert.alertTypeId]).map(av => av.name) : undefined } defaultActionGroupId={defaultActionGroupId} diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 2119c08bedc31b..94d6c0a4dd604c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -71,11 +71,21 @@ export interface ActionConnectorTableItem extends ActionConnector { actionType: ActionType['name']; } +export interface ActionVariable { + name: string; + description: string; +} + +export interface ActionVariables { + context?: ActionVariable[]; + state?: ActionVariable[]; +} + export interface AlertType { id: string; name: string; actionGroups: ActionGroup[]; - actionVariables: string[]; + actionVariables?: ActionVariables; defaultActionGroupId: ActionGroup['id']; } diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index 2e7674f2b3eb7d..58f7a49720007f 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -207,6 +207,10 @@ export default function(kibana: any) { { id: 'other', name: 'Other' }, ], defaultActionGroupId: 'default', + actionVariables: { + state: [{ name: 'instanceStateValue', description: 'the instance state value' }], + context: [{ name: 'instanceContextValue', description: 'the instance context value' }], + }, async executor(alertExecutorOptions: AlertExecutorOptions) { const { services, @@ -419,6 +423,26 @@ export default function(kibana: any) { defaultActionGroupId: 'default', async executor({ services, params, state }: AlertExecutorOptions) {}, }; + const onlyContextVariablesAlertType: AlertType = { + id: 'test.onlyContextVariables', + name: 'Test: Only Context Variables', + actionGroups: [{ id: 'default', name: 'Default' }], + defaultActionGroupId: 'default', + actionVariables: { + context: [{ name: 'aContextVariable', description: 'this is a context variable' }], + }, + async executor(opts: AlertExecutorOptions) {}, + }; + const onlyStateVariablesAlertType: AlertType = { + id: 'test.onlyStateVariables', + name: 'Test: Only State Variables', + actionGroups: [{ id: 'default', name: 'Default' }], + defaultActionGroupId: 'default', + actionVariables: { + state: [{ name: 'aStateVariable', description: 'this is a state variable' }], + }, + async executor(opts: AlertExecutorOptions) {}, + }; server.newPlatform.setup.plugins.alerting.registerType(alwaysFiringAlertType); server.newPlatform.setup.plugins.alerting.registerType(cumulativeFiringAlertType); server.newPlatform.setup.plugins.alerting.registerType(neverFiringAlertType); @@ -426,6 +450,8 @@ export default function(kibana: any) { server.newPlatform.setup.plugins.alerting.registerType(validationAlertType); server.newPlatform.setup.plugins.alerting.registerType(authorizationAlertType); server.newPlatform.setup.plugins.alerting.registerType(noopAlertType); + server.newPlatform.setup.plugins.alerting.registerType(onlyContextVariablesAlertType); + server.newPlatform.setup.plugins.alerting.registerType(onlyStateVariablesAlertType); }, }); } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts index 30c1548b7db2a5..1199e8cbd69f84 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/list_alert_types.ts @@ -44,6 +44,10 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { defaultActionGroupId: 'default', id: 'test.noop', name: 'Test: Noop', + actionVariables: { + state: {}, + context: {}, + }, }); break; default: diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts index 590de1ea7ce0b7..fbcf744b96916d 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/list_alert_types.ts @@ -16,7 +16,6 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { describe('list_alert_types', () => { it('should return 200 with list of alert types', async () => { const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); - expect(response.statusCode).to.eql(200); const fixtureAlertType = response.body.find((alertType: any) => alertType.id === 'test.noop'); expect(fixtureAlertType).to.eql({ @@ -24,6 +23,52 @@ export default function listAlertTypes({ getService }: FtrProviderContext) { defaultActionGroupId: 'default', id: 'test.noop', name: 'Test: Noop', + actionVariables: { + state: [], + context: [], + }, + }); + }); + + it('should return actionVariables with both context and state', async () => { + const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + expect(response.statusCode).to.eql(200); + + const fixtureAlertType = response.body.find( + (alertType: any) => alertType.id === 'test.always-firing' + ); + + expect(fixtureAlertType.actionVariables).to.eql({ + state: [{ name: 'instanceStateValue', description: 'the instance state value' }], + context: [{ name: 'instanceContextValue', description: 'the instance context value' }], + }); + }); + + it('should return actionVariables with just context', async () => { + const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + expect(response.statusCode).to.eql(200); + + const fixtureAlertType = response.body.find( + (alertType: any) => alertType.id === 'test.onlyContextVariables' + ); + + expect(fixtureAlertType.actionVariables).to.eql({ + state: [], + context: [{ name: 'aContextVariable', description: 'this is a context variable' }], + }); + }); + + it('should return actionVariables with just state', async () => { + const response = await supertest.get(`${getUrlPrefix(Spaces.space1.id)}/api/alert/types`); + expect(response.statusCode).to.eql(200); + + const fixtureAlertType = response.body.find( + (alertType: any) => alertType.id === 'test.onlyStateVariables' + ); + + expect(fixtureAlertType.actionVariables).to.eql({ + state: [{ name: 'aStateVariable', description: 'this is a state variable' }], + context: [], }); }); });