Added more {{context}} fields for Index Threshold alert type (including requested 'threshold' field). Extended action variables UX with tooltip containing variable description. (#71141)

* Added more {{context}} fields for Index Threshold alert type (including requested 'threshold' field). Extended action variables UX with tooltip containing variable description.

* Fixed type checks and failing tests

* fixed type check

* Splited params variables

* Fixed tests and type checks

* Fixed styles

* Fixed type check

* fixed styles

* fixed missing type

* Fixed due to comments

* fixed variables description

* fixed type check

* Fixed due to comments

* fixed typecheck

* Merge remote-tracking branch upstream/master into alerting-additional-context-fields

* fixed type checks and tests

* fixed tests
This commit is contained in:
Yuliia Naumenko 2020-07-23 08:39:51 -07:00 committed by GitHub
parent 18df677da7
commit badbfa0eb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 228 additions and 69 deletions

View file

@ -9,11 +9,6 @@ import { ParamsSchema } from './alert_type_params';
describe('ActionContext', () => {
it('generates expected properties if aggField is null', async () => {
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 42,
};
const params = ParamsSchema.validate({
index: '[index]',
timeField: '[timeField]',
@ -26,6 +21,11 @@ describe('ActionContext', () => {
thresholdComparator: '>',
threshold: [4],
});
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 42,
};
const context = addMessages({ name: '[alert-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(
`"alert [alert-name] group [group] exceeded threshold"`
@ -36,11 +36,6 @@ describe('ActionContext', () => {
});
it('generates expected properties if aggField is not null', async () => {
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 42,
};
const params = ParamsSchema.validate({
index: '[index]',
timeField: '[timeField]',
@ -54,6 +49,11 @@ describe('ActionContext', () => {
thresholdComparator: '>',
threshold: [4.2],
});
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 42,
};
const context = addMessages({ name: '[alert-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(
`"alert [alert-name] group [group] exceeded threshold"`
@ -64,11 +64,6 @@ describe('ActionContext', () => {
});
it('generates expected properties if comparator is between', async () => {
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 4,
};
const params = ParamsSchema.validate({
index: '[index]',
timeField: '[timeField]',
@ -81,6 +76,11 @@ describe('ActionContext', () => {
thresholdComparator: 'between',
threshold: [4, 5],
});
const base: BaseActionContext = {
date: '2020-01-01T00:00:00.000Z',
group: '[group]',
value: 4,
};
const context = addMessages({ name: '[alert-name]' }, base, params);
expect(context.title).toMatchInlineSnapshot(
`"alert [alert-name] group [group] exceeded threshold"`

View file

@ -47,6 +47,52 @@ describe('alertType', () => {
"name": "value",
},
],
"params": Array [
Object {
"description": "An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one.",
"name": "threshold",
},
Object {
"description": "A comparison function to use to determine if the threshold as been met.",
"name": "thresholdComparator",
},
Object {
"description": "index",
"name": "index",
},
Object {
"description": "timeField",
"name": "timeField",
},
Object {
"description": "aggType",
"name": "aggType",
},
Object {
"description": "aggField",
"name": "aggField",
},
Object {
"description": "groupBy",
"name": "groupBy",
},
Object {
"description": "termField",
"name": "termField",
},
Object {
"description": "termSize",
"name": "termSize",
},
Object {
"description": "timeWindowSize",
"name": "timeWindowSize",
},
Object {
"description": "timeWindowUnit",
"name": "timeWindowUnit",
},
],
}
`);
});

View file

@ -14,6 +14,7 @@ import { BUILT_IN_ALERTS_FEATURE_ID } from '../../../common';
export const ID = '.index-threshold';
import { CoreQueryParamsSchemaProperties } from './lib/core_query_types';
const ActionGroupId = 'threshold met';
const ComparatorFns = getComparatorFns();
export const ComparatorFnNames = new Set(ComparatorFns.keys());
@ -67,6 +68,30 @@ export function getAlertType(service: Service): AlertType {
}
);
const actionVariableContextThresholdLabel = i18n.translate(
'xpack.alertingBuiltins.indexThreshold.actionVariableContextThresholdLabel',
{
defaultMessage:
"An array of values to use as the threshold; 'between' and 'notBetween' require two values, the others require one.",
}
);
const actionVariableContextThresholdComparatorLabel = i18n.translate(
'xpack.alertingBuiltins.indexThreshold.actionVariableContextThresholdComparatorLabel',
{
defaultMessage: 'A comparison function to use to determine if the threshold as been met.',
}
);
const alertParamsVariables = Object.keys(CoreQueryParamsSchemaProperties).map(
(propKey: string) => {
return {
name: propKey,
description: propKey,
};
}
);
return {
id: ID,
name: alertTypeName,
@ -83,6 +108,11 @@ export function getAlertType(service: Service): AlertType {
{ name: 'date', description: actionVariableContextDateLabel },
{ name: 'value', description: actionVariableContextValueLabel },
],
params: [
{ name: 'threshold', description: actionVariableContextThresholdLabel },
{ name: 'thresholdComparator', description: actionVariableContextThresholdComparatorLabel },
...alertParamsVariables,
],
},
executor,
producer: BUILT_IN_ALERTS_FEATURE_ID,

View file

@ -208,6 +208,7 @@ describe('get()', () => {
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "default",
@ -261,6 +262,7 @@ describe('list()', () => {
],
"actionVariables": Object {
"context": Array [],
"params": Array [],
"state": Array [],
},
"defaultActionGroupId": "testActionGroup",

View file

@ -119,5 +119,6 @@ function normalizedActionVariables(actionVariables: AlertType['actionVariables']
return {
context: actionVariables?.context ?? [],
state: actionVariables?.state ?? [],
params: actionVariables?.params ?? [],
};
}

View file

@ -50,6 +50,11 @@ const createExecutionHandlerParams = {
},
],
request: {} as KibanaRequest,
alertParams: {
foo: true,
contextVal: 'My other {{context.value}} goes here',
stateVal: 'My other {{state.value}} goes here',
},
};
beforeEach(() => {

View file

@ -5,7 +5,7 @@
*/
import { map } from 'lodash';
import { AlertAction, State, Context, AlertType } from '../types';
import { AlertAction, State, Context, AlertType, AlertParams } from '../types';
import { Logger, KibanaRequest } from '../../../../../src/core/server';
import { transformActionParams } from './transform_action_params';
import { PluginStartContract as ActionsPluginStartContract } from '../../../actions/server';
@ -24,6 +24,7 @@ interface CreateExecutionHandlerOptions {
logger: Logger;
eventLogger: IEventLogger;
request: KibanaRequest;
alertParams: AlertParams;
}
interface ExecutionHandlerOptions {
@ -45,6 +46,7 @@ export function createExecutionHandler({
alertType,
eventLogger,
request,
alertParams,
}: CreateExecutionHandlerOptions) {
const alertTypeActionGroups = new Set(map(alertType.actionGroups, 'id'));
return async ({ actionGroup, context, state, alertInstanceId }: ExecutionHandlerOptions) => {
@ -66,6 +68,7 @@ export function createExecutionHandler({
context,
actionParams: action.params,
state,
alertParams,
}),
};
});

View file

@ -110,7 +110,8 @@ export class TaskRunner {
tags: string[] | undefined,
spaceId: string,
apiKey: string | null,
actions: Alert['actions']
actions: Alert['actions'],
alertParams: RawAlert['params']
) {
return createExecutionHandler({
alertId,
@ -124,6 +125,7 @@ export class TaskRunner {
alertType: this.alertType,
eventLogger: this.context.eventLogger,
request: this.getFakeKibanaRequest(spaceId, apiKey),
alertParams,
});
}
@ -261,7 +263,8 @@ export class TaskRunner {
alert.tags,
spaceId,
apiKey,
alert.actions
alert.actions,
alert.params
);
return this.executeAlertInstances(services, alert, validatedParams, executionHandler, spaceId);
}

View file

@ -13,6 +13,7 @@ test('skips non string parameters', () => {
empty1: null,
empty2: undefined,
date: '2019-02-12T21:01:22.479Z',
message: 'Value "{{params.foo}}" exists',
};
const result = transformActionParams({
actionParams,
@ -23,6 +24,9 @@ test('skips non string parameters', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {
foo: 'test',
},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -30,6 +34,7 @@ test('skips non string parameters', () => {
"date": "2019-02-12T21:01:22.479Z",
"empty1": null,
"empty2": undefined,
"message": "Value \\"test\\" exists",
"number": 1,
}
`);
@ -49,6 +54,7 @@ test('missing parameters get emptied out', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -71,6 +77,7 @@ test('context parameters are passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -92,6 +99,7 @@ test('state parameters are passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -113,6 +121,7 @@ test('alertId is passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -134,6 +143,7 @@ test('alertName is passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -155,6 +165,7 @@ test('tags is passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -175,6 +186,7 @@ test('undefined tags is passed to templates', () => {
alertName: 'alert-name',
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -196,6 +208,7 @@ test('empty tags is passed to templates', () => {
tags: [],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -217,6 +230,7 @@ test('spaceId is passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -238,6 +252,7 @@ test('alertInstanceId is passed to templates', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -261,6 +276,7 @@ test('works recursively', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {
@ -286,6 +302,7 @@ test('works recursively with arrays', () => {
tags: ['tag-A', 'tag-B'],
spaceId: 'spaceId-A',
alertInstanceId: '2',
alertParams: {},
});
expect(result).toMatchInlineSnapshot(`
Object {

View file

@ -6,7 +6,7 @@
import Mustache from 'mustache';
import { isString, cloneDeepWith } from 'lodash';
import { AlertActionParams, State, Context } from '../types';
import { AlertActionParams, State, Context, AlertParams } from '../types';
interface TransformActionParamsOptions {
alertId: string;
@ -17,6 +17,7 @@ interface TransformActionParamsOptions {
actionParams: AlertActionParams;
state: State;
context: Context;
alertParams: AlertParams;
}
export function transformActionParams({
@ -28,6 +29,7 @@ export function transformActionParams({
context,
actionParams,
state,
alertParams,
}: TransformActionParamsOptions): AlertActionParams {
const result = cloneDeepWith(actionParams, (value: unknown) => {
if (!isString(value)) return;
@ -43,6 +45,7 @@ export function transformActionParams({
alertInstanceId,
context,
state,
params: alertParams,
};
return Mustache.render(value, variables);
});

View file

@ -23,6 +23,8 @@ import {
export type State = Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Context = Record<string, any>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type AlertParams = Record<string, unknown>;
export type WithoutQueryAndParams<T> = Pick<T, Exclude<keyof T, 'query' | 'params'>>;
export type GetServicesFunction = (request: KibanaRequest) => Services;
export type GetBasePathFunction = (spaceId?: string) => string;
@ -82,6 +84,7 @@ export interface AlertType {
actionVariables?: {
context?: ActionVariable[];
state?: ActionVariable[];
params?: ActionVariable[];
};
}

View file

@ -15,6 +15,7 @@ import {
import { findIndex } from 'lodash/fp';
import React, { FC, memo, useCallback, useEffect, useMemo, useState } from 'react';
import { ActionVariable } from '../../../../../../triggers_actions_ui/public';
import {
RuleStep,
RuleStepProps,
@ -36,7 +37,7 @@ import { APP_ID } from '../../../../../common/constants';
interface StepRuleActionsProps extends RuleStepProps {
defaultValues?: ActionsStepRule | null;
actionMessageParams: string[];
actionMessageParams: ActionVariable[];
}
const stepActionsDefaultValue = {

View file

@ -9,6 +9,7 @@ import moment from 'moment';
import memoizeOne from 'memoize-one';
import { useLocation } from 'react-router-dom';
import { ActionVariable } from '../../../../../../triggers_actions_ui/public';
import { RuleAlertAction, RuleType } from '../../../../../common/detection_engine/types';
import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { transformRuleToAlertAction } from '../../../../../common/detection_engine/transform_actions';
@ -326,18 +327,23 @@ export const getActionMessageRuleParams = (ruleType: RuleType): string[] => {
return ruleParamsKeys;
};
export const getActionMessageParams = memoizeOne((ruleType: RuleType | undefined): string[] => {
if (!ruleType) {
return [];
}
const actionMessageRuleParams = getActionMessageRuleParams(ruleType);
export const getActionMessageParams = memoizeOne(
(ruleType: RuleType | undefined): ActionVariable[] => {
if (!ruleType) {
return [];
}
const actionMessageRuleParams = getActionMessageRuleParams(ruleType);
return [
'state.signals_count',
'{context.results_link}',
...actionMessageRuleParams.map((param) => `context.rule.${param}`),
];
});
return [
{ name: 'state.signals_count', description: 'state.signals_count' },
{ name: '{context.results_link}', description: 'context.results_link' },
...actionMessageRuleParams.map((param) => {
const extendedParam = `context.rule.${param}`;
return { name: extendedParam, description: extendedParam };
}),
];
}
);
// typed as null not undefined as the initial state for this value is null.
export const userHasNoPermissions = (canUserCRUD: boolean | null): boolean =>

View file

@ -1294,7 +1294,7 @@ Then this dependencies will be used to embed Actions form or register your own a
return (
<ActionForm
actions={initialAlert.actions}
messageVariables={['test var1', 'test var2']}
messageVariables={[ { name: 'testVar1', description: 'test var1' } ]}
defaultActionGroupId={'default'}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;
@ -1329,7 +1329,7 @@ interface ActionAccordionFormProps {
'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError'
>;
actionTypes?: ActionType[];
messageVariables?: string[];
messageVariables?: ActionVariable[];
defaultActionMessage?: string;
consumer: string;
}

View file

@ -1,4 +1,5 @@
.messageVariablesPanel {
@include euiYScrollWithShadows;
max-height: $euiSize * 20;
max-width: $euiSize * 20;
}

View file

@ -5,11 +5,18 @@
*/
import React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiPopover, EuiButtonIcon, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui';
import {
EuiPopover,
EuiButtonIcon,
EuiContextMenuPanel,
EuiContextMenuItem,
EuiText,
} from '@elastic/eui';
import './add_message_variables.scss';
import { ActionVariable } from '../../types';
interface Props {
messageVariables: string[] | undefined;
messageVariables?: ActionVariable[];
paramsProperty: string;
onSelectEventHandler: (variable: string) => void;
}
@ -22,17 +29,22 @@ export const AddMessageVariables: React.FunctionComponent<Props> = ({
const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState<boolean>(false);
const getMessageVariables = () =>
messageVariables?.map((variable: string, i: number) => (
messageVariables?.map((variable: ActionVariable, i: number) => (
<EuiContextMenuItem
key={variable}
key={variable.name}
data-test-subj={`variableMenuButton-${i}`}
icon="empty"
onClick={() => {
onSelectEventHandler(variable);
onSelectEventHandler(variable.name);
setIsVariablesPopoverOpen(false);
}}
>
{`{{${variable}}}`}
<>
<EuiText size="m">{`{{${variable.name}}}`}</EuiText>
<EuiText size="m" color="subdued">
<div className="euiTextColor--subdued">{variable.description}</div>
</EuiText>
</>
</EuiContextMenuItem>
));

View file

@ -61,7 +61,7 @@ const ServiceNowParamsFields: React.FunctionComponent<ActionParamsProps<
if (!actionParams.subAction) {
editAction('subAction', 'pushToService', index);
}
if (!savedObjectId && messageVariables?.find((variable) => variable === 'alertId')) {
if (!savedObjectId && messageVariables?.find((variable) => variable.name === 'alertId')) {
editSubActionProperty('savedObjectId', '{{alertId}}');
}
if (!urgency) {

View file

@ -9,9 +9,10 @@ import './add_message_variables.scss';
import { useXJsonMode } from '../../../../../../src/plugins/es_ui_shared/static/ace_x_json/hooks';
import { AddMessageVariables } from './add_message_variables';
import { ActionVariable } from '../../types';
interface Props {
messageVariables: string[] | undefined;
messageVariables?: ActionVariable[];
paramsProperty: string;
inputTargetValue: string;
label: string;

View file

@ -7,9 +7,10 @@ import React, { useState } from 'react';
import { EuiTextArea, EuiFormRow } from '@elastic/eui';
import './add_message_variables.scss';
import { AddMessageVariables } from './add_message_variables';
import { ActionVariable } from '../../types';
interface Props {
messageVariables: string[] | undefined;
messageVariables?: ActionVariable[];
paramsProperty: string;
index: number;
inputTargetValue?: string;

View file

@ -7,9 +7,10 @@ import React, { useState } from 'react';
import { EuiFieldText } from '@elastic/eui';
import './add_message_variables.scss';
import { AddMessageVariables } from './add_message_variables';
import { ActionVariable } from '../../types';
interface Props {
messageVariables: string[] | undefined;
messageVariables?: ActionVariable[];
paramsProperty: string;
index: number;
inputTargetValue?: string;

View file

@ -12,7 +12,7 @@ beforeEach(() => jest.resetAllMocks());
describe('actionVariablesFromAlertType', () => {
test('should return correct variables when no state or context provided', async () => {
const alertType = getAlertType({ context: [], state: [] });
const alertType = getAlertType({ context: [], state: [], params: [] });
expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(`
Array [
Object {
@ -46,6 +46,7 @@ describe('actionVariablesFromAlertType', () => {
{ name: 'bar', description: 'bar-description' },
],
state: [],
params: [],
});
expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(`
Array [
@ -88,6 +89,7 @@ describe('actionVariablesFromAlertType', () => {
{ name: 'foo', description: 'foo-description' },
{ name: 'bar', description: 'bar-description' },
],
params: [],
});
expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(`
Array [
@ -133,6 +135,7 @@ describe('actionVariablesFromAlertType', () => {
{ name: 'fooS', description: 'fooS-description' },
{ name: 'barS', description: 'barS-description' },
],
params: [{ name: 'fooP', description: 'fooP-description' }],
});
expect(actionVariablesFromAlertType(alertType)).toMatchInlineSnapshot(`
Array [
@ -164,6 +167,10 @@ describe('actionVariablesFromAlertType', () => {
"description": "barC-description",
"name": "context.barC",
},
Object {
"description": "fooP-description",
"name": "params.fooP",
},
Object {
"description": "fooS-description",
"name": "state.fooS",

View file

@ -11,9 +11,10 @@ import { AlertType, ActionVariable } from '../../types';
export function actionVariablesFromAlertType(alertType: AlertType): ActionVariable[] {
const alwaysProvidedVars = getAlwaysProvidedActionVariables();
const contextVars = prefixKeys(alertType.actionVariables.context, 'context.');
const paramsVars = prefixKeys(alertType.actionVariables.params, 'params.');
const stateVars = prefixKeys(alertType.actionVariables.state, 'state.');
return alwaysProvidedVars.concat(contextVars, stateVars);
return alwaysProvidedVars.concat(contextVars, paramsVars, stateVars);
}
function prefixKeys(actionVariables: ActionVariable[], prefix: string): ActionVariable[] {

View file

@ -42,6 +42,7 @@ describe('loadAlertTypes', () => {
actionVariables: {
context: [{ name: 'var1', description: 'val1' }],
state: [{ name: 'var2', description: 'val2' }],
params: [{ name: 'var3', description: 'val3' }],
},
producer: ALERTS_FEATURE_ID,
actionGroups: [{ id: 'default', name: 'Default' }],

View file

@ -217,7 +217,10 @@ describe('action_form', () => {
wrapper = mountWithIntl(
<ActionForm
actions={initialAlert.actions}
messageVariables={['test var1', 'test var2']}
messageVariables={[
{ name: 'testVar1', description: 'test var1' },
{ name: 'testVar2', description: 'test var2' },
]}
defaultActionGroupId={'default'}
setActionIdByIndex={(id: string, index: number) => {
initialAlert.actions[index].id = id;

View file

@ -38,6 +38,7 @@ import {
ActionTypeIndex,
ActionConnector,
ActionType,
ActionVariable,
} from '../../../types';
import { SectionLoading } from '../../components/section_loading';
import { ConnectorAddModal } from './connector_add_modal';
@ -61,7 +62,7 @@ interface ActionAccordionFormProps {
>;
docLinks: DocLinksStart;
actionTypes?: ActionType[];
messageVariables?: string[];
messageVariables?: ActionVariable[];
defaultActionMessage?: string;
setHasActionsDisabled?: (value: boolean) => void;
capabilities: ApplicationStart['capabilities'];

View file

@ -93,7 +93,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -132,7 +132,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -162,7 +162,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -216,7 +216,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -275,7 +275,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -295,7 +295,7 @@ describe('alert_details', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -324,7 +324,7 @@ describe('disable button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -352,7 +352,7 @@ describe('disable button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -380,7 +380,7 @@ describe('disable button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -417,7 +417,7 @@ describe('disable button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -457,7 +457,7 @@ describe('mute button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -486,7 +486,7 @@ describe('mute button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -515,7 +515,7 @@ describe('mute button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -553,7 +553,7 @@ describe('mute button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -591,7 +591,7 @@ describe('mute button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: ALERTS_FEATURE_ID,
authorizedConsumers,
@ -641,7 +641,7 @@ describe('edit button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: 'alerting',
authorizedConsumers,
@ -683,7 +683,7 @@ describe('edit button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: 'alerting',
authorizedConsumers,
@ -718,7 +718,7 @@ describe('edit button', () => {
id: '.noop',
name: 'No Op',
actionGroups: [{ id: 'default', name: 'Default' }],
actionVariables: { context: [], state: [] },
actionVariables: { context: [], state: [], params: [] },
defaultActionGroupId: 'default',
producer: 'alerting',
authorizedConsumers,

View file

@ -68,6 +68,7 @@ describe('alert_add', () => {
actionVariables: {
context: [],
state: [],
params: [],
},
},
];

View file

@ -269,8 +269,8 @@ export const AlertForm = ({
setHasActionsDisabled={setHasActionsDisabled}
messageVariables={
alertTypesIndex && alertTypesIndex.has(alert.alertTypeId)
? actionVariablesFromAlertType(alertTypesIndex.get(alert.alertTypeId)!).map(
(av) => av.name
? actionVariablesFromAlertType(alertTypesIndex.get(alert.alertTypeId)!).sort((a, b) =>
a.name.toUpperCase().localeCompare(b.name.toUpperCase())
)
: undefined
}

View file

@ -19,6 +19,7 @@ export {
ActionType,
ActionTypeRegistryContract,
AlertTypeParamsExpressionProps,
ActionVariable,
} from './types';
export {
ConnectorAddFlyout,

View file

@ -41,7 +41,7 @@ export interface ActionParamsProps<TParams> {
index: number;
editAction: (property: string, value: any, index: number) => void;
errors: IErrorObject;
messageVariables?: string[];
messageVariables?: ActionVariable[];
defaultMessage?: string;
docLinks: DocLinksStart;
}
@ -94,6 +94,7 @@ export interface ActionVariable {
export interface ActionVariables {
context: ActionVariable[];
state: ActionVariable[];
params: ActionVariable[];
}
export interface AlertType {

View file

@ -26,6 +26,7 @@ export function defineAlertTypes(
defaultActionGroupId: 'default',
actionVariables: {
state: [{ name: 'instanceStateValue', description: 'the instance state value' }],
params: [{ name: 'instanceParamsValue', description: 'the instance params value' }],
context: [{ name: 'instanceContextValue', description: 'the instance context value' }],
},
async executor(alertExecutorOptions: AlertExecutorOptions) {

View file

@ -22,6 +22,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
actionVariables: {
state: [],
context: [],
params: [],
},
producer: 'alertsFixture',
};
@ -34,6 +35,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
actionVariables: {
state: [],
context: [],
params: [],
},
producer: 'alertsRestrictedFixture',
};

View file

@ -29,6 +29,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
name: 'Test: Noop',
actionVariables: {
state: [],
params: [],
context: [],
},
producer: 'alertsFixture',
@ -48,6 +49,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
expect(fixtureAlertType.actionVariables).to.eql({
state: [{ name: 'instanceStateValue', description: 'the instance state value' }],
params: [{ name: 'instanceParamsValue', description: 'the instance params value' }],
context: [{ name: 'instanceContextValue', description: 'the instance context value' }],
});
});
@ -64,6 +66,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
expect(fixtureAlertType.actionVariables).to.eql({
state: [],
params: [],
context: [{ name: 'aContextVariable', description: 'this is a context variable' }],
});
});
@ -81,6 +84,7 @@ export default function listAlertTypes({ getService }: FtrProviderContext) {
expect(fixtureAlertType.actionVariables).to.eql({
state: [{ name: 'aStateVariable', description: 'this is a state variable' }],
context: [],
params: [],
});
});
});

View file

@ -86,7 +86,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.click('variableMenuButton-1');
expect(await messageTextArea.getAttribute('value')).to.eql(
'test message {{alertId}} some additional text {{alertName}}'
'test message {{alertId}} some additional text {{alertInstanceId}}'
);
await testSubjects.click('saveAlertButton');