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:
parent
18df677da7
commit
badbfa0eb5
34 changed files with 228 additions and 69 deletions
|
@ -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"`
|
||||
|
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -119,5 +119,6 @@ function normalizedActionVariables(actionVariables: AlertType['actionVariables']
|
|||
return {
|
||||
context: actionVariables?.context ?? [],
|
||||
state: actionVariables?.state ?? [],
|
||||
params: actionVariables?.params ?? [],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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[];
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 =>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.messageVariablesPanel {
|
||||
@include euiYScrollWithShadows;
|
||||
max-height: $euiSize * 20;
|
||||
max-width: $euiSize * 20;
|
||||
}
|
|
@ -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>
|
||||
));
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -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' }],
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -68,6 +68,7 @@ describe('alert_add', () => {
|
|||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
params: [],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ export {
|
|||
ActionType,
|
||||
ActionTypeRegistryContract,
|
||||
AlertTypeParamsExpressionProps,
|
||||
ActionVariable,
|
||||
} from './types';
|
||||
export {
|
||||
ConnectorAddFlyout,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in a new issue