mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[RAM] Add toggle for AAD fields in alert templating (#170162)
## Summary Reopen of https://github.com/elastic/kibana/pull/161213 Closes https://github.com/elastic/kibana/issues/160838 Feature can be activated via feature flag: `xpack.trigger_actions_ui.enableExperimental: ['isMustacheAutocompleteOn', 'showMustacheAutocompleteSwitch']` <img width="605" alt="Screenshot 2023-10-30 at 5 52 39 PM" src="da24b419
-3b08-4014-be2f-99692773755f"> <img width="583" alt="Screenshot 2023-10-30 at 5 52 22 PM" src="fc5b8a1e
-8202-4491-b4fb-694b70809f4d"> ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Julian Gernun <17549662+jcger@users.noreply.github.com> Co-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a46923cae3
commit
603a0454a9
42 changed files with 537 additions and 183 deletions
|
@ -63,6 +63,7 @@ export const actionSchema = schema.object({
|
|||
params: schema.recordOf(schema.string(), schema.maybe(schema.any()), { defaultValue: {} }),
|
||||
frequency: schema.maybe(actionFrequencySchema),
|
||||
alerts_filter: schema.maybe(actionAlertsFilterSchema),
|
||||
use_alert_data_for_template: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
||||
export const createBodySchema = schema.object({
|
||||
|
|
|
@ -116,6 +116,7 @@ export interface RuleAction {
|
|||
params: RuleActionParams;
|
||||
frequency?: RuleActionFrequency;
|
||||
alertsFilter?: AlertsFilter;
|
||||
useAlertDataForTemplate?: boolean;
|
||||
}
|
||||
|
||||
export interface RuleLastRun {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { v4 as uuidV4 } from 'uuid';
|
||||
import { AADAlert } from '@kbn/alerts-as-data-utils';
|
||||
import { get, isEmpty } from 'lodash';
|
||||
import { MutableAlertInstanceMeta } from '@kbn/alerting-state-types';
|
||||
import { ALERT_UUID } from '@kbn/rule-data-utils';
|
||||
|
@ -36,7 +37,7 @@ export type PublicAlert<
|
|||
Context extends AlertInstanceContext = AlertInstanceContext,
|
||||
ActionGroupIds extends string = DefaultActionGroupId
|
||||
> = Pick<
|
||||
Alert<State, Context, ActionGroupIds>,
|
||||
Alert<State, Context, ActionGroupIds, AADAlert>,
|
||||
| 'getContext'
|
||||
| 'getState'
|
||||
| 'getUuid'
|
||||
|
@ -50,13 +51,15 @@ export type PublicAlert<
|
|||
export class Alert<
|
||||
State extends AlertInstanceState = AlertInstanceState,
|
||||
Context extends AlertInstanceContext = AlertInstanceContext,
|
||||
ActionGroupIds extends string = never
|
||||
ActionGroupIds extends string = never,
|
||||
AlertAsData extends AADAlert = AADAlert
|
||||
> {
|
||||
private scheduledExecutionOptions?: ScheduledExecutionOptions<State, Context, ActionGroupIds>;
|
||||
private meta: MutableAlertInstanceMeta;
|
||||
private state: State;
|
||||
private context: Context;
|
||||
private readonly id: string;
|
||||
private alertAsData: AlertAsData | undefined;
|
||||
|
||||
constructor(id: string, { state, meta = {} }: RawAlertInstance = {}) {
|
||||
this.id = id;
|
||||
|
@ -78,6 +81,18 @@ export class Alert<
|
|||
return this.meta.uuid!;
|
||||
}
|
||||
|
||||
isAlertAsData() {
|
||||
return this.alertAsData !== undefined;
|
||||
}
|
||||
|
||||
setAlertAsData(alertAsData: AlertAsData) {
|
||||
this.alertAsData = alertAsData;
|
||||
}
|
||||
|
||||
getAlertAsData() {
|
||||
return this.alertAsData;
|
||||
}
|
||||
|
||||
getStart(): string | null {
|
||||
return this.state.start ? `${this.state.start}` : null;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export const createRuleDataSchema = schema.object({
|
|||
),
|
||||
uuid: schema.maybe(schema.string()),
|
||||
alertsFilter: schema.maybe(actionAlertsFilterSchema),
|
||||
useAlertDataForTemplate: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
),
|
||||
|
|
|
@ -65,6 +65,7 @@ export const actionDomainSchema = schema.object({
|
|||
params: actionParamsSchema,
|
||||
frequency: schema.maybe(actionFrequencySchema),
|
||||
alertsFilter: schema.maybe(actionDomainAlertsFilterSchema),
|
||||
useAlertDataAsTemplate: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -89,4 +90,5 @@ export const actionSchema = schema.object({
|
|||
params: actionParamsSchema,
|
||||
frequency: schema.maybe(actionFrequencySchema),
|
||||
alertsFilter: schema.maybe(actionAlertsFilterSchema),
|
||||
useAlertDataForTemplate: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
|
|
@ -68,6 +68,7 @@ export const actionsSchema = schema.arrayOf(
|
|||
),
|
||||
})
|
||||
),
|
||||
use_alert_data_for_template: schema.maybe(schema.boolean()),
|
||||
}),
|
||||
{ defaultValue: [] }
|
||||
);
|
||||
|
|
|
@ -15,20 +15,28 @@ export const rewriteActionsReq = (
|
|||
): NormalizedAlertAction[] => {
|
||||
if (!actions) return [];
|
||||
|
||||
return actions.map(({ frequency, alerts_filter: alertsFilter, ...action }) => {
|
||||
return {
|
||||
...action,
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
...omit(frequency, 'notify_when'),
|
||||
notifyWhen: frequency.notify_when,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(alertsFilter ? { alertsFilter } : {}),
|
||||
};
|
||||
});
|
||||
return actions.map(
|
||||
({
|
||||
frequency,
|
||||
alerts_filter: alertsFilter,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
...action
|
||||
}) => {
|
||||
return {
|
||||
...action,
|
||||
useAlertDataForTemplate,
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
...omit(frequency, 'notify_when'),
|
||||
notifyWhen: frequency.notify_when,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(alertsFilter ? { alertsFilter } : {}),
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const rewriteActionsRes = (actions?: RuleAction[]) => {
|
||||
|
@ -37,14 +45,17 @@ export const rewriteActionsRes = (actions?: RuleAction[]) => {
|
|||
notify_when: notifyWhen,
|
||||
});
|
||||
if (!actions) return [];
|
||||
return actions.map(({ actionTypeId, frequency, alertsFilter, ...action }) => ({
|
||||
...action,
|
||||
connector_type_id: actionTypeId,
|
||||
...(frequency ? { frequency: rewriteFrequency(frequency) } : {}),
|
||||
...(alertsFilter
|
||||
? {
|
||||
alerts_filter: alertsFilter,
|
||||
}
|
||||
: {}),
|
||||
}));
|
||||
return actions.map(
|
||||
({ actionTypeId, frequency, alertsFilter, useAlertDataForTemplate, ...action }) => ({
|
||||
...action,
|
||||
connector_type_id: actionTypeId,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
...(frequency ? { frequency: rewriteFrequency(frequency) } : {}),
|
||||
...(alertsFilter
|
||||
? {
|
||||
alerts_filter: alertsFilter,
|
||||
}
|
||||
: {}),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -114,6 +114,7 @@ describe('bulkEditRulesRoute', () => {
|
|||
foo: true,
|
||||
},
|
||||
uuid: '123-456',
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
|
|
@ -124,6 +124,7 @@ describe('createRuleRoute', () => {
|
|||
},
|
||||
connector_type_id: 'test',
|
||||
uuid: '123-456',
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
@ -198,6 +199,7 @@ describe('createRuleRoute', () => {
|
|||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
|
@ -314,6 +316,7 @@ describe('createRuleRoute', () => {
|
|||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
|
@ -431,6 +434,7 @@ describe('createRuleRoute', () => {
|
|||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
|
@ -548,6 +552,7 @@ describe('createRuleRoute', () => {
|
|||
"params": Object {
|
||||
"foo": true,
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
},
|
||||
],
|
||||
"alertTypeId": "1",
|
||||
|
|
|
@ -15,25 +15,33 @@ import type { RuleParams } from '../../../../../../application/rule/types';
|
|||
const transformCreateBodyActions = (actions: CreateRuleActionV1[]): CreateRuleData['actions'] => {
|
||||
if (!actions) return [];
|
||||
|
||||
return actions.map(({ frequency, alerts_filter: alertsFilter, ...action }) => {
|
||||
return {
|
||||
group: action.group,
|
||||
id: action.id,
|
||||
params: action.params,
|
||||
actionTypeId: action.actionTypeId,
|
||||
...(action.uuid ? { uuid: action.uuid } : {}),
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
summary: frequency.summary,
|
||||
throttle: frequency.throttle,
|
||||
notifyWhen: frequency.notify_when,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(alertsFilter ? { alertsFilter } : {}),
|
||||
};
|
||||
});
|
||||
return actions.map(
|
||||
({
|
||||
frequency,
|
||||
alerts_filter: alertsFilter,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
...action
|
||||
}) => {
|
||||
return {
|
||||
group: action.group,
|
||||
id: action.id,
|
||||
params: action.params,
|
||||
actionTypeId: action.actionTypeId,
|
||||
useAlertDataForTemplate,
|
||||
...(action.uuid ? { uuid: action.uuid } : {}),
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
summary: frequency.summary,
|
||||
throttle: frequency.throttle,
|
||||
notifyWhen: frequency.notify_when,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(alertsFilter ? { alertsFilter } : {}),
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const transformCreateBody = <Params extends RuleParams = never>(
|
||||
|
|
|
@ -45,6 +45,7 @@ describe('resolveRuleRoute', () => {
|
|||
foo: true,
|
||||
},
|
||||
uuid: '123-456',
|
||||
useAlertDataForTemplate: false,
|
||||
},
|
||||
],
|
||||
consumer: 'bar',
|
||||
|
@ -101,6 +102,7 @@ describe('resolveRuleRoute', () => {
|
|||
params: mockedRule.actions[0].params,
|
||||
connector_type_id: mockedRule.actions[0].actionTypeId,
|
||||
uuid: mockedRule.actions[0].uuid,
|
||||
use_alert_data_for_template: mockedRule.actions[0].useAlertDataForTemplate,
|
||||
},
|
||||
],
|
||||
outcome: 'aliasMatch',
|
||||
|
|
|
@ -49,11 +49,21 @@ export const transformRuleToRuleResponse = <Params extends RuleParams = never>(
|
|||
consumer: rule.consumer,
|
||||
schedule: rule.schedule,
|
||||
actions: rule.actions.map(
|
||||
({ group, id, actionTypeId, params, frequency, uuid, alertsFilter }) => ({
|
||||
({
|
||||
group,
|
||||
id,
|
||||
actionTypeId,
|
||||
params,
|
||||
frequency,
|
||||
uuid,
|
||||
alertsFilter,
|
||||
useAlertDataForTemplate,
|
||||
}) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
connector_type_id: actionTypeId,
|
||||
use_alert_data_for_template: useAlertDataForTemplate ?? false,
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
|
|
|
@ -132,6 +132,7 @@ describe('updateRuleRoute', () => {
|
|||
"params": Object {
|
||||
"baz": true,
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
"uuid": "1234-5678",
|
||||
},
|
||||
],
|
||||
|
|
|
@ -20,12 +20,16 @@ import { ActionsClient } from '@kbn/actions-plugin/server/actions_client';
|
|||
import { chunk } from 'lodash';
|
||||
import { GetSummarizedAlertsParams, IAlertsClient } from '../alerts_client/types';
|
||||
import { AlertingEventLogger } from '../lib/alerting_event_logger/alerting_event_logger';
|
||||
import { parseDuration, CombinedSummarizedAlerts, ThrottledActions } from '../types';
|
||||
import { AlertHit, parseDuration, CombinedSummarizedAlerts, ThrottledActions } from '../types';
|
||||
import { RuleRunMetricsStore } from '../lib/rule_run_metrics_store';
|
||||
import { injectActionParams } from './inject_action_params';
|
||||
import { Executable, ExecutionHandlerOptions, RuleTaskInstance } from './types';
|
||||
import { TaskRunnerContext } from './task_runner_factory';
|
||||
import { transformActionParams, transformSummaryActionParams } from './transform_action_params';
|
||||
import {
|
||||
transformActionParams,
|
||||
TransformActionParamsOptions,
|
||||
transformSummaryActionParams,
|
||||
} from './transform_action_params';
|
||||
import { Alert } from '../alert';
|
||||
import { NormalizedRuleType } from '../rule_type_registry';
|
||||
import {
|
||||
|
@ -292,33 +296,40 @@ export class ExecutionHandler<
|
|||
};
|
||||
} else {
|
||||
const ruleUrl = this.buildRuleUrl(spaceId);
|
||||
const executableAlert = alert!;
|
||||
const transformActionParamsOptions: TransformActionParamsOptions = {
|
||||
actionsPlugin,
|
||||
alertId: ruleId,
|
||||
alertType: this.ruleType.id,
|
||||
actionTypeId,
|
||||
alertName: this.rule.name,
|
||||
spaceId,
|
||||
tags: this.rule.tags,
|
||||
alertInstanceId: executableAlert.getId(),
|
||||
alertUuid: executableAlert.getUuid(),
|
||||
alertActionGroup: actionGroup,
|
||||
alertActionGroupName: this.ruleTypeActionGroups!.get(actionGroup)!,
|
||||
context: executableAlert.getContext(),
|
||||
actionId: action.id,
|
||||
state: executableAlert.getState(),
|
||||
kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl,
|
||||
alertParams: this.rule.params,
|
||||
actionParams: action.params,
|
||||
flapping: executableAlert.getFlapping(),
|
||||
ruleUrl: ruleUrl?.absoluteUrl,
|
||||
};
|
||||
|
||||
if (executableAlert.isAlertAsData()) {
|
||||
transformActionParamsOptions.aadAlert = executableAlert.getAlertAsData();
|
||||
}
|
||||
|
||||
const actionToRun = {
|
||||
...action,
|
||||
params: injectActionParams({
|
||||
actionTypeId,
|
||||
ruleUrl,
|
||||
ruleName: this.rule.name,
|
||||
actionParams: transformActionParams({
|
||||
actionsPlugin,
|
||||
alertId: ruleId,
|
||||
alertType: this.ruleType.id,
|
||||
actionTypeId,
|
||||
alertName: this.rule.name,
|
||||
spaceId,
|
||||
tags: this.rule.tags,
|
||||
alertInstanceId: alert.getId(),
|
||||
alertUuid: alert.getUuid(),
|
||||
alertActionGroup: actionGroup,
|
||||
alertActionGroupName: this.ruleTypeActionGroups!.get(actionGroup)!,
|
||||
context: alert.getContext(),
|
||||
actionId: action.id,
|
||||
state: alert.getState(),
|
||||
kibanaBaseUrl: this.taskRunnerContext.kibanaBaseUrl,
|
||||
alertParams: this.rule.params,
|
||||
actionParams: action.params,
|
||||
flapping: alert.getFlapping(),
|
||||
ruleUrl: ruleUrl?.absoluteUrl,
|
||||
}),
|
||||
actionParams: transformActionParams(transformActionParamsOptions),
|
||||
}),
|
||||
};
|
||||
|
||||
|
@ -570,7 +581,6 @@ export class ExecutionHandler<
|
|||
for (const action of this.rule.actions) {
|
||||
const alertsArray = Object.entries(alerts);
|
||||
let summarizedAlerts = null;
|
||||
|
||||
if (this.shouldGetSummarizedAlerts({ action, throttledSummaryActions })) {
|
||||
summarizedAlerts = await this.getSummarizedAlerts({
|
||||
action,
|
||||
|
@ -634,6 +644,15 @@ export class ExecutionHandler<
|
|||
continue;
|
||||
}
|
||||
|
||||
if (summarizedAlerts) {
|
||||
const alertAsData = summarizedAlerts.all.data.find(
|
||||
(alertHit: AlertHit) => alertHit._id === alert.getUuid()
|
||||
);
|
||||
if (alertAsData) {
|
||||
alert.setAlertAsData(alertAsData);
|
||||
}
|
||||
}
|
||||
|
||||
if (action.group === actionGroup && !this.isAlertMuted(alertId)) {
|
||||
if (
|
||||
this.isRecoveredAlert(action.group) ||
|
||||
|
@ -667,12 +686,13 @@ export class ExecutionHandler<
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (action.useAlertDataForTemplate) {
|
||||
return true;
|
||||
}
|
||||
// we fetch summarizedAlerts to filter alerts in memory as well
|
||||
if (!isSummaryAction(action) && !action.alertsFilter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isSummaryAction(action) &&
|
||||
isSummaryActionThrottled({
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
|
||||
import { AADAlert } from '@kbn/alerts-as-data-utils';
|
||||
import { mapKeys, snakeCase } from 'lodash/fp';
|
||||
import {
|
||||
RuleActionParams,
|
||||
|
@ -15,7 +16,7 @@ import {
|
|||
SanitizedRule,
|
||||
} from '../types';
|
||||
|
||||
interface TransformActionParamsOptions {
|
||||
export interface TransformActionParamsOptions {
|
||||
actionsPlugin: ActionsPluginStartContract;
|
||||
alertId: string;
|
||||
alertType: string;
|
||||
|
@ -35,6 +36,7 @@ interface TransformActionParamsOptions {
|
|||
context: AlertInstanceContext;
|
||||
ruleUrl?: string;
|
||||
flapping: boolean;
|
||||
aadAlert?: AADAlert;
|
||||
}
|
||||
|
||||
interface SummarizedAlertsWithAll {
|
||||
|
@ -76,40 +78,45 @@ export function transformActionParams({
|
|||
alertParams,
|
||||
ruleUrl,
|
||||
flapping,
|
||||
aadAlert,
|
||||
}: TransformActionParamsOptions): RuleActionParams {
|
||||
// when the list of variables we pass in here changes,
|
||||
// the UI will need to be updated as well; see:
|
||||
// x-pack/plugins/triggers_actions_ui/public/application/lib/action_variables.ts
|
||||
const variables = {
|
||||
alertId,
|
||||
alertName,
|
||||
spaceId,
|
||||
tags,
|
||||
alertInstanceId,
|
||||
alertActionGroup,
|
||||
alertActionGroupName,
|
||||
context,
|
||||
date: new Date().toISOString(),
|
||||
state,
|
||||
kibanaBaseUrl,
|
||||
params: alertParams,
|
||||
rule: {
|
||||
params: alertParams,
|
||||
id: alertId,
|
||||
name: alertName,
|
||||
type: alertType,
|
||||
spaceId,
|
||||
tags,
|
||||
url: ruleUrl,
|
||||
},
|
||||
alert: {
|
||||
id: alertInstanceId,
|
||||
uuid: alertUuid,
|
||||
actionGroup: alertActionGroup,
|
||||
actionGroupName: alertActionGroupName,
|
||||
flapping,
|
||||
},
|
||||
};
|
||||
const variables =
|
||||
aadAlert !== undefined
|
||||
? aadAlert
|
||||
: {
|
||||
alertId,
|
||||
alertName,
|
||||
spaceId,
|
||||
tags,
|
||||
alertInstanceId,
|
||||
alertActionGroup,
|
||||
alertActionGroupName,
|
||||
context,
|
||||
date: new Date().toISOString(),
|
||||
state,
|
||||
kibanaBaseUrl,
|
||||
params: alertParams,
|
||||
rule: {
|
||||
params: alertParams,
|
||||
id: alertId,
|
||||
name: alertName,
|
||||
type: alertType,
|
||||
spaceId,
|
||||
tags,
|
||||
url: ruleUrl,
|
||||
},
|
||||
alert: {
|
||||
id: alertInstanceId,
|
||||
uuid: alertUuid,
|
||||
actionGroup: alertActionGroup,
|
||||
actionGroupName: alertActionGroupName,
|
||||
flapping,
|
||||
},
|
||||
};
|
||||
|
||||
return actionsPlugin.renderActionParameterTemplates(
|
||||
actionTypeId,
|
||||
actionId,
|
||||
|
|
|
@ -12,7 +12,7 @@ export type ExperimentalFeatures = typeof allowedExperimentalValues;
|
|||
* This object is then used to validate and parse the value entered.
|
||||
*/
|
||||
export const allowedExperimentalValues = Object.freeze({
|
||||
isMustacheAutocompleteOn: false,
|
||||
isMustacheAutocompleteOn: true,
|
||||
sentinelOneConnectorOn: false,
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiComboBox, EuiButtonEmpty, EuiFormRow } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -15,8 +15,6 @@ import {
|
|||
TextAreaWithMessageVariables,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { EmailActionParams } from '../types';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features';
|
||||
import { TextAreaWithAutocomplete } from '../../components/text_area_with_autocomplete';
|
||||
|
||||
const noop = () => {};
|
||||
|
||||
|
@ -32,8 +30,8 @@ export const EmailParamsFields = ({
|
|||
onBlur = noop,
|
||||
showEmailSubjectAndMessage = true,
|
||||
useDefaultMessage,
|
||||
ruleTypeId,
|
||||
}: ActionParamsProps<EmailActionParams>) => {
|
||||
const isMustacheAutocompleteOn = getIsExperimentalFeatureEnabled('isMustacheAutocompleteOn');
|
||||
const { to, cc, bcc, subject, message } = actionParams;
|
||||
const toOptions = to ? to.map((label: string) => ({ label })) : [];
|
||||
const ccOptions = cc ? cc.map((label: string) => ({ label })) : [];
|
||||
|
@ -64,10 +62,6 @@ export const EmailParamsFields = ({
|
|||
const isBCCInvalid: boolean =
|
||||
errors.bcc !== undefined && errors.bcc.length > 0 && bcc !== undefined;
|
||||
|
||||
const TextAreaComponent = useMemo(() => {
|
||||
return isMustacheAutocompleteOn ? TextAreaWithAutocomplete : TextAreaWithMessageVariables;
|
||||
}, [isMustacheAutocompleteOn]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFormRow
|
||||
|
@ -239,7 +233,7 @@ export const EmailParamsFields = ({
|
|||
</EuiFormRow>
|
||||
)}
|
||||
{showEmailSubjectAndMessage && (
|
||||
<TextAreaComponent
|
||||
<TextAreaWithMessageVariables
|
||||
index={index}
|
||||
editAction={editAction}
|
||||
messageVariables={messageVariables}
|
||||
|
|
|
@ -13,7 +13,6 @@ import { ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public';
|
|||
import { MockCodeEditor } from '@kbn/triggers-actions-ui-plugin/public/application/code_editor.mock';
|
||||
import { OpsgenieSubActions } from '../../../common';
|
||||
import type { OpsgenieActionParams } from '../../../server/connector_types';
|
||||
|
||||
const kibanaReactPath = '../../../../../../src/plugins/kibana_react/public';
|
||||
|
||||
jest.mock(kibanaReactPath, () => {
|
||||
|
|
|
@ -37,7 +37,7 @@ export const ServerLogParamsFields: React.FunctionComponent<
|
|||
editAction('level', 'info', index);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [actionParams.level]);
|
||||
|
||||
const [[isUsingDefault, defaultMessageUsed], setDefaultMessageUsage] = useState<
|
||||
[boolean, string | undefined]
|
||||
|
|
|
@ -16,6 +16,7 @@ import { IToasts } from '@kbn/core/public';
|
|||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import { getConnectorType as getSlackConnectorType } from './slack';
|
||||
import { getSlackApiConnectorType } from '../slack_api';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
jest.mock('@kbn/triggers-actions-ui-plugin/public/common/lib/kibana');
|
||||
jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
|
||||
|
@ -51,6 +52,14 @@ const { loadActionTypes } = jest.requireMock(
|
|||
'@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api/connector_types'
|
||||
);
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
cacheTime: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// GET api/actions/connector_types?feature_id=alerting
|
||||
loadActionTypes.mockResolvedValue([
|
||||
|
@ -95,6 +104,7 @@ actionTypeRegistry.register(getSlackApiConnectorType());
|
|||
const baseProps = {
|
||||
actions: [],
|
||||
defaultActionGroupId: 'metrics.inventory_threshold.fired',
|
||||
ruleTypeId: 'metrics.inventory_threshold',
|
||||
hasAlertsMappings: true,
|
||||
featureId: 'alerting',
|
||||
recoveryActionGroup: 'recovered',
|
||||
|
@ -170,7 +180,9 @@ describe('ActionForm - Slack API Connector', () => {
|
|||
|
||||
render(
|
||||
<IntlProvider locale="en">
|
||||
<ActionForm {...testProps} />
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ActionForm {...testProps} />
|
||||
</QueryClientProvider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
// have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636
|
||||
"server/**/*.json",
|
||||
"common/**/*",
|
||||
"public/**/*"
|
||||
"public/**/*",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
|
@ -33,7 +33,6 @@
|
|||
"@kbn/core-saved-objects-common",
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/core-saved-objects-api-server-mocks",
|
||||
"@kbn/alerts-ui-shared",
|
||||
"@kbn/alerting-plugin",
|
||||
"@kbn/securitysolution-ecs",
|
||||
"@kbn/ui-theme",
|
||||
|
|
|
@ -19,6 +19,8 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
rulesDetailLogs: true,
|
||||
ruleUseExecutionStatus: false,
|
||||
ruleKqlBar: false,
|
||||
isMustacheAutocompleteOn: false,
|
||||
showMustacheAutocompleteSwitch: false,
|
||||
});
|
||||
|
||||
type ExperimentalConfigKeys = Array<keyof ExperimentalFeatures>;
|
||||
|
@ -29,7 +31,8 @@ const allowedKeys = Object.keys(allowedExperimentalValues) as Readonly<Experimen
|
|||
|
||||
/**
|
||||
* Parses the string value used in `xpack.trigger_actions_ui.enableExperimental` kibana configuration,
|
||||
* which should be a string of values delimited by a comma (`,`)
|
||||
* which should be a string of values delimited by a comma (`,`):
|
||||
* xpack.trigger_actions_ui.enableExperimental: ['ruleStatusFilter', 'ruleTagFilter']
|
||||
*
|
||||
* @param configValue
|
||||
* @throws TriggersActionsUIInvalidExperimentalValue
|
||||
|
|
|
@ -22,8 +22,8 @@ import {
|
|||
import { ActionVariable } from '@kbn/alerting-plugin/common';
|
||||
import { AddMessageVariables } from '@kbn/alerts-ui-shared';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { filterSuggestions } from '../lib/filter_suggestions_for_autocomplete';
|
||||
import { templateActionVariable } from '../lib/template_action_variable';
|
||||
import { filterSuggestions } from './lib/filter_suggestions_for_autocomplete';
|
||||
import { templateActionVariable } from './lib/template_action_variable';
|
||||
|
||||
export interface TextAreaWithAutocompleteProps {
|
||||
editAction: (property: string, value: any, index: number) => void;
|
||||
|
@ -291,7 +291,6 @@ export const TextAreaWithAutocomplete: React.FunctionComponent<TextAreaWithAutoc
|
|||
}
|
||||
}, [editAction, index, inputTargetValue, isListOpen, paramsProperty]);
|
||||
const onClick = useCallback(() => closeList(), [closeList]);
|
||||
|
||||
const onScroll = useCallback(
|
||||
(evt) => {
|
||||
// FUTURE ENGINEER -> we need to make sure to not close the autocomplete option list
|
|
@ -9,6 +9,8 @@ import React, { useState } from 'react';
|
|||
import { EuiTextArea, EuiFormRow } from '@elastic/eui';
|
||||
import { ActionVariable } from '@kbn/alerting-plugin/common';
|
||||
import { AddMessageVariables } from '@kbn/alerts-ui-shared';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../common/get_experimental_features';
|
||||
import { TextAreaWithAutocomplete } from './text_area_with_autocomplete';
|
||||
import { templateActionVariable } from '../lib';
|
||||
|
||||
interface Props {
|
||||
|
@ -23,7 +25,7 @@ interface Props {
|
|||
errors?: string[];
|
||||
}
|
||||
|
||||
export const TextAreaWithMessageVariables: React.FunctionComponent<Props> = ({
|
||||
const TextAreaWithMessageVariablesLegacy: React.FunctionComponent<Props> = ({
|
||||
messageVariables,
|
||||
paramsProperty,
|
||||
index,
|
||||
|
@ -87,3 +89,15 @@ export const TextAreaWithMessageVariables: React.FunctionComponent<Props> = ({
|
|||
</EuiFormRow>
|
||||
);
|
||||
};
|
||||
|
||||
export const TextAreaWithMessageVariables = (props: Props) => {
|
||||
let isMustacheAutocompleteOn;
|
||||
try {
|
||||
isMustacheAutocompleteOn = getIsExperimentalFeatureEnabled('isMustacheAutocompleteOn');
|
||||
} catch (e) {
|
||||
isMustacheAutocompleteOn = false;
|
||||
}
|
||||
|
||||
if (isMustacheAutocompleteOn) return TextAreaWithAutocomplete(props);
|
||||
return TextAreaWithMessageVariablesLegacy(props);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import { DataViewField } from '@kbn/data-views-plugin/common';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common';
|
||||
import { ActionVariable } from '@kbn/alerting-plugin/common';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { EcsFlat } from '@kbn/ecs';
|
||||
import { EcsMetadata } from '@kbn/alerts-as-data-utils/src/field_maps/types';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
export const getDescription = (fieldName: string, ecsFlat: Record<string, EcsMetadata>) => {
|
||||
let ecsField = ecsFlat[fieldName];
|
||||
if (isEmpty(ecsField?.description ?? '') && fieldName.includes('kibana.alert.')) {
|
||||
ecsField = ecsFlat[fieldName.replace('kibana.alert.', '')];
|
||||
}
|
||||
return ecsField?.description ?? '';
|
||||
};
|
||||
|
||||
async function loadRuleTypeAadTemplateFields({
|
||||
http,
|
||||
ruleTypeId,
|
||||
}: {
|
||||
http: HttpStart;
|
||||
ruleTypeId: string;
|
||||
}): Promise<DataViewField[]> {
|
||||
if (!ruleTypeId || !http) return [];
|
||||
const fields = await http.get<DataViewField[]>(`${BASE_RAC_ALERTS_API_PATH}/aad_fields`, {
|
||||
query: { ruleTypeId },
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
export function useRuleTypeAadTemplateFields(
|
||||
http: HttpStart,
|
||||
ruleTypeId: string | undefined,
|
||||
enabled: boolean
|
||||
): { isLoading: boolean; fields: ActionVariable[] } {
|
||||
// Reimplement useQuery here; this hook is sometimes called in contexts without a QueryClientProvider
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [data, setData] = useState<DataViewField[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (enabled && data.length === 0 && ruleTypeId) {
|
||||
setIsLoading(true);
|
||||
loadRuleTypeAadTemplateFields({ http, ruleTypeId }).then((res) => {
|
||||
setData(res);
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [data, enabled, http, ruleTypeId]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isLoading,
|
||||
fields: data.map<ActionVariable>((d) => ({
|
||||
name: d.name,
|
||||
description: getDescription(d.name, EcsFlat),
|
||||
})),
|
||||
}),
|
||||
[data, isLoading]
|
||||
);
|
||||
}
|
|
@ -75,6 +75,7 @@ describe('cloneRule', () => {
|
|||
"level": "info",
|
||||
"message": "alert ",
|
||||
},
|
||||
"useAlertDataForTemplate": undefined,
|
||||
"uuid": "123456",
|
||||
},
|
||||
],
|
||||
|
|
|
@ -16,11 +16,13 @@ const transformAction: RewriteRequestCase<RuleAction> = ({
|
|||
params,
|
||||
frequency,
|
||||
alerts_filter: alertsFilter,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
}) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
actionTypeId,
|
||||
useAlertDataForTemplate,
|
||||
...(frequency
|
||||
? {
|
||||
frequency: {
|
||||
|
|
|
@ -27,17 +27,20 @@ const rewriteBodyRequest: RewriteResponseCase<RuleCreateBody> = ({
|
|||
}): any => ({
|
||||
...res,
|
||||
rule_type_id: ruleTypeId,
|
||||
actions: actions.map(({ group, id, params, frequency, alertsFilter }) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
frequency: {
|
||||
notify_when: frequency!.notifyWhen,
|
||||
throttle: frequency!.throttle,
|
||||
summary: frequency!.summary,
|
||||
},
|
||||
alerts_filter: alertsFilter,
|
||||
})),
|
||||
actions: actions.map(
|
||||
({ group, id, params, frequency, alertsFilter, useAlertDataForTemplate }) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
frequency: {
|
||||
notify_when: frequency!.notifyWhen,
|
||||
throttle: frequency!.throttle,
|
||||
summary: frequency!.summary,
|
||||
},
|
||||
alerts_filter: alertsFilter,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export async function createRule({
|
||||
|
|
|
@ -17,18 +17,21 @@ type RuleUpdatesBody = Pick<
|
|||
>;
|
||||
const rewriteBodyRequest: RewriteResponseCase<RuleUpdatesBody> = ({ actions, ...res }): any => ({
|
||||
...res,
|
||||
actions: actions.map(({ group, id, params, frequency, uuid, alertsFilter }) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
frequency: {
|
||||
notify_when: frequency!.notifyWhen,
|
||||
throttle: frequency!.throttle,
|
||||
summary: frequency!.summary,
|
||||
},
|
||||
alerts_filter: alertsFilter,
|
||||
...(uuid && { uuid }),
|
||||
})),
|
||||
actions: actions.map(
|
||||
({ group, id, params, frequency, uuid, alertsFilter, useAlertDataForTemplate }) => ({
|
||||
group,
|
||||
id,
|
||||
params,
|
||||
frequency: {
|
||||
notify_when: frequency!.notifyWhen,
|
||||
throttle: frequency!.throttle,
|
||||
summary: frequency!.summary,
|
||||
},
|
||||
alerts_filter: alertsFilter,
|
||||
use_alert_data_for_template: useAlertDataForTemplate,
|
||||
...(uuid && { uuid }),
|
||||
})
|
||||
),
|
||||
});
|
||||
|
||||
export async function updateRule({
|
||||
|
|
|
@ -60,6 +60,7 @@ export interface ActionAccordionFormProps {
|
|||
defaultActionMessage?: string;
|
||||
setActionIdByIndex: (id: string, index: number) => void;
|
||||
setActionGroupIdByIndex?: (group: string, index: number) => void;
|
||||
setActionUseAlertDataForTemplate?: (enabled: boolean, index: number) => void;
|
||||
setActions: (actions: RuleAction[]) => void;
|
||||
setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void;
|
||||
setActionFrequencyProperty: (key: string, value: RuleActionParam, index: number) => void;
|
||||
|
@ -70,6 +71,7 @@ export interface ActionAccordionFormProps {
|
|||
) => void;
|
||||
featureId: string;
|
||||
producerId: string;
|
||||
ruleTypeId?: string;
|
||||
messageVariables?: ActionVariables;
|
||||
summaryMessageVariables?: ActionVariables;
|
||||
setHasActionsDisabled?: (value: boolean) => void;
|
||||
|
@ -84,7 +86,6 @@ export interface ActionAccordionFormProps {
|
|||
minimumThrottleInterval?: [number | undefined, string];
|
||||
notifyWhenSelectOptions?: NotifyWhenSelectOptions[];
|
||||
defaultRuleFrequency?: RuleActionFrequency;
|
||||
ruleTypeId?: string;
|
||||
hasFieldsForAAD?: boolean;
|
||||
disableErrorMessages?: boolean;
|
||||
}
|
||||
|
@ -99,6 +100,7 @@ export const ActionForm = ({
|
|||
defaultActionGroupId,
|
||||
setActionIdByIndex,
|
||||
setActionGroupIdByIndex,
|
||||
setActionUseAlertDataForTemplate,
|
||||
setActions,
|
||||
setActionParamsProperty,
|
||||
setActionFrequencyProperty,
|
||||
|
@ -437,6 +439,7 @@ export const ActionForm = ({
|
|||
actionConnector={actionConnector}
|
||||
index={index}
|
||||
key={`action-form-action-at-${actionItem.uuid}`}
|
||||
setActionUseAlertDataForTemplate={setActionUseAlertDataForTemplate}
|
||||
setActionParamsProperty={setActionParamsProperty}
|
||||
setActionFrequencyProperty={setActionFrequencyProperty}
|
||||
setActionAlertsFilterProperty={setActionAlertsFilterProperty}
|
||||
|
|
|
@ -64,6 +64,19 @@ jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
|
|||
useUiSetting: jest.fn().mockImplementation((_, defaultValue) => defaultValue),
|
||||
}));
|
||||
|
||||
jest.mock('../../../common/get_experimental_features', () => ({
|
||||
getIsExperimentalFeatureEnabled() {
|
||||
return true;
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('../../hooks/use_rule_aad_template_fields', () => ({
|
||||
useRuleTypeAadTemplateFields: () => ({
|
||||
isLoading: false,
|
||||
fields: [],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('action_type_form', () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -402,6 +415,59 @@ describe('action_type_form', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('clears the default message when the user toggles the "Use template fields from alerts index" switch ', async () => {
|
||||
const setActionParamsProperty = jest.fn();
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
id: '.pagerduty',
|
||||
iconClass: 'test',
|
||||
selectMessage: 'test',
|
||||
validateParams: (): Promise<GenericValidationResult<unknown>> => {
|
||||
const validationResult = { errors: {} };
|
||||
return Promise.resolve(validationResult);
|
||||
},
|
||||
actionConnectorFields: null,
|
||||
actionParamsFields: mockedActionParamsFields,
|
||||
defaultActionParams: {
|
||||
dedupKey: '{{rule.id}}:{{alert.id}}',
|
||||
eventAction: 'resolve',
|
||||
},
|
||||
});
|
||||
actionTypeRegistry.get.mockReturnValue(actionType);
|
||||
|
||||
const wrapper = render(
|
||||
<IntlProvider locale="en">
|
||||
{getActionTypeForm({
|
||||
index: 1,
|
||||
ruleTypeId: 'test',
|
||||
setActionParamsProperty,
|
||||
actionItem: {
|
||||
id: '123',
|
||||
actionTypeId: '.pagerduty',
|
||||
group: 'recovered',
|
||||
params: {
|
||||
eventAction: 'recovered',
|
||||
dedupKey: '232323',
|
||||
summary: '2323',
|
||||
source: 'source',
|
||||
severity: '1',
|
||||
timestamp: new Date().toISOString(),
|
||||
component: 'test',
|
||||
group: 'group',
|
||||
class: 'test class',
|
||||
},
|
||||
},
|
||||
})}
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
expect(wrapper.getByTestId('mustacheAutocompleteSwitch')).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
wrapper.getByTestId('mustacheAutocompleteSwitch').click();
|
||||
});
|
||||
expect(setActionParamsProperty).toHaveBeenCalledWith('dedupKey', '', 1);
|
||||
});
|
||||
|
||||
describe('Customize notify when options', () => {
|
||||
it('should not have "On status changes" notify when option for summary actions', async () => {
|
||||
const actionType = actionTypeRegistryMock.createMockActionTypeModel({
|
||||
|
@ -523,6 +589,7 @@ function getActionTypeForm({
|
|||
onAddConnector,
|
||||
onDeleteAction,
|
||||
onConnectorSelected,
|
||||
setActionParamsProperty,
|
||||
setActionFrequencyProperty,
|
||||
setActionAlertsFilterProperty,
|
||||
hasAlertsMappings = true,
|
||||
|
@ -530,6 +597,7 @@ function getActionTypeForm({
|
|||
summaryMessageVariables = { context: [], state: [], params: [] },
|
||||
notifyWhenSelectOptions,
|
||||
defaultNotifyWhenValue,
|
||||
ruleTypeId,
|
||||
}: {
|
||||
index?: number;
|
||||
actionConnector?: ActionConnector<Record<string, unknown>, Record<string, unknown>>;
|
||||
|
@ -541,12 +609,14 @@ function getActionTypeForm({
|
|||
onDeleteAction?: () => void;
|
||||
onConnectorSelected?: (id: string) => void;
|
||||
setActionFrequencyProperty?: () => void;
|
||||
setActionParamsProperty?: () => void;
|
||||
setActionAlertsFilterProperty?: () => void;
|
||||
hasAlertsMappings?: boolean;
|
||||
messageVariables?: ActionVariables;
|
||||
summaryMessageVariables?: ActionVariables;
|
||||
notifyWhenSelectOptions?: NotifyWhenSelectOptions[];
|
||||
defaultNotifyWhenValue?: RuleNotifyWhenType;
|
||||
ruleTypeId?: string;
|
||||
}) {
|
||||
const actionConnectorDefault = {
|
||||
actionTypeId: '.pagerduty',
|
||||
|
@ -628,7 +698,7 @@ function getActionTypeForm({
|
|||
onDeleteAction={onDeleteAction ?? jest.fn()}
|
||||
onConnectorSelected={onConnectorSelected ?? jest.fn()}
|
||||
defaultActionGroupId={defaultActionGroupId ?? 'default'}
|
||||
setActionParamsProperty={jest.fn()}
|
||||
setActionParamsProperty={setActionParamsProperty ?? jest.fn()}
|
||||
setActionFrequencyProperty={setActionFrequencyProperty ?? jest.fn()}
|
||||
setActionAlertsFilterProperty={setActionAlertsFilterProperty ?? jest.fn()}
|
||||
index={index ?? 1}
|
||||
|
@ -641,6 +711,7 @@ function getActionTypeForm({
|
|||
defaultNotifyWhenValue={defaultNotifyWhenValue}
|
||||
producerId="infrastructure"
|
||||
featureId="infrastructure"
|
||||
ruleTypeId={ruleTypeId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import {
|
|||
EuiSplitPanel,
|
||||
useEuiTheme,
|
||||
EuiCallOut,
|
||||
EuiSwitch,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty, partition, some } from 'lodash';
|
||||
import {
|
||||
|
@ -42,6 +43,8 @@ import {
|
|||
getDurationUnitValue,
|
||||
parseDuration,
|
||||
} from '@kbn/alerting-plugin/common/parse_duration';
|
||||
import { SavedObjectAttribute } from '@kbn/core-saved-objects-api-server';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../common/get_experimental_features';
|
||||
import { betaBadgeProps } from './beta_badge_props';
|
||||
import {
|
||||
IErrorObject,
|
||||
|
@ -64,6 +67,7 @@ import { validateParamsForWarnings } from '../../lib/validate_params_for_warning
|
|||
import { ActionAlertsFilterTimeframe } from './action_alerts_filter_timeframe';
|
||||
import { ActionAlertsFilterQuery } from './action_alerts_filter_query';
|
||||
import { validateActionFilterQuery } from '../../lib/value_validators';
|
||||
import { useRuleTypeAadTemplateFields } from '../../hooks/use_rule_aad_template_fields';
|
||||
|
||||
export type ActionTypeFormProps = {
|
||||
actionItem: RuleAction;
|
||||
|
@ -72,6 +76,7 @@ export type ActionTypeFormProps = {
|
|||
onAddConnector: () => void;
|
||||
onConnectorSelected: (id: string) => void;
|
||||
onDeleteAction: () => void;
|
||||
setActionUseAlertDataForTemplate?: (enabled: boolean, index: number) => void;
|
||||
setActionParamsProperty: (key: string, value: RuleActionParam, index: number) => void;
|
||||
setActionFrequencyProperty: (key: string, value: RuleActionParam, index: number) => void;
|
||||
setActionAlertsFilterProperty: (
|
||||
|
@ -120,6 +125,7 @@ export const ActionTypeForm = ({
|
|||
onAddConnector,
|
||||
onConnectorSelected,
|
||||
onDeleteAction,
|
||||
setActionUseAlertDataForTemplate,
|
||||
setActionParamsProperty,
|
||||
setActionFrequencyProperty,
|
||||
setActionAlertsFilterProperty,
|
||||
|
@ -148,7 +154,7 @@ export const ActionTypeForm = ({
|
|||
}: ActionTypeFormProps) => {
|
||||
const {
|
||||
application: { capabilities },
|
||||
http: { basePath },
|
||||
http,
|
||||
} = useKibana().services;
|
||||
const { euiTheme } = useEuiTheme();
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
|
@ -178,6 +184,53 @@ export const ActionTypeForm = ({
|
|||
|
||||
const isSummaryAction = actionItem.frequency?.summary;
|
||||
|
||||
const [useAadTemplateFields, setUseAadTemplateField] = useState(
|
||||
actionItem?.useAlertDataForTemplate ?? false
|
||||
);
|
||||
const [storedActionParamsForAadToggle, setStoredActionParamsForAadToggle] = useState<
|
||||
Record<string, SavedObjectAttribute>
|
||||
>({});
|
||||
|
||||
const { fields: aadTemplateFields } = useRuleTypeAadTemplateFields(
|
||||
http,
|
||||
ruleTypeId,
|
||||
useAadTemplateFields
|
||||
);
|
||||
|
||||
const templateFields = useMemo(
|
||||
() => (useAadTemplateFields ? aadTemplateFields : availableActionVariables),
|
||||
[aadTemplateFields, availableActionVariables, useAadTemplateFields]
|
||||
);
|
||||
|
||||
let showMustacheAutocompleteSwitch;
|
||||
try {
|
||||
showMustacheAutocompleteSwitch =
|
||||
getIsExperimentalFeatureEnabled('showMustacheAutocompleteSwitch') && ruleTypeId;
|
||||
} catch (e) {
|
||||
showMustacheAutocompleteSwitch = false;
|
||||
}
|
||||
|
||||
const handleUseAadTemplateFields = useCallback(() => {
|
||||
setUseAadTemplateField((prevVal) => {
|
||||
if (setActionUseAlertDataForTemplate) {
|
||||
setActionUseAlertDataForTemplate(!prevVal, index);
|
||||
}
|
||||
return !prevVal;
|
||||
});
|
||||
const currentActionParams = { ...actionItem.params };
|
||||
for (const key of Object.keys(currentActionParams)) {
|
||||
setActionParamsProperty(key, storedActionParamsForAadToggle[key] ?? '', index);
|
||||
}
|
||||
setStoredActionParamsForAadToggle(currentActionParams);
|
||||
}, [
|
||||
setActionUseAlertDataForTemplate,
|
||||
storedActionParamsForAadToggle,
|
||||
setStoredActionParamsForAadToggle,
|
||||
setActionParamsProperty,
|
||||
actionItem.params,
|
||||
index,
|
||||
]);
|
||||
|
||||
const getDefaultParams = async () => {
|
||||
const connectorType = await actionTypeRegistry.get(actionItem.actionTypeId);
|
||||
let defaultParams;
|
||||
|
@ -227,9 +280,15 @@ export const ActionTypeForm = ({
|
|||
const defaultParams = await getDefaultParams();
|
||||
if (defaultParams) {
|
||||
for (const [key, paramValue] of Object.entries(defaultParams)) {
|
||||
const defaultAADParams: typeof defaultParams = {};
|
||||
if (actionItem.params[key] === undefined || actionItem.params[key] === null) {
|
||||
setActionParamsProperty(key, paramValue, index);
|
||||
// Add default param to AAD defaults only if it does not contain any template code
|
||||
if (typeof paramValue !== 'string' || !paramValue.match(/{{.*?}}/g)) {
|
||||
defaultAADParams[key] = paramValue;
|
||||
}
|
||||
}
|
||||
setStoredActionParamsForAadToggle(defaultAADParams);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
@ -240,9 +299,14 @@ export const ActionTypeForm = ({
|
|||
(async () => {
|
||||
const defaultParams = await getDefaultParams();
|
||||
if (defaultParams && actionGroup) {
|
||||
const defaultAADParams: typeof defaultParams = {};
|
||||
for (const [key, paramValue] of Object.entries(defaultParams)) {
|
||||
setActionParamsProperty(key, paramValue, index);
|
||||
if (!paramValue.match(/{{.*?}}/g)) {
|
||||
defaultAADParams[key] = paramValue;
|
||||
}
|
||||
}
|
||||
setStoredActionParamsForAadToggle(defaultAADParams);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@ -273,6 +337,12 @@ export const ActionTypeForm = ({
|
|||
})();
|
||||
}, [actionItem, disableErrorMessages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmpty(storedActionParamsForAadToggle) && actionItem.params.subAction) {
|
||||
setStoredActionParamsForAadToggle(actionItem.params);
|
||||
}
|
||||
}, [actionItem.params, storedActionParamsForAadToggle]);
|
||||
|
||||
const canSave = hasSaveActionsCapability(capabilities);
|
||||
|
||||
const actionGroupDisplay = (
|
||||
|
@ -463,39 +533,54 @@ export const ActionTypeForm = ({
|
|||
<EuiSplitPanel.Inner color="plain">
|
||||
{ParamsFieldsComponent ? (
|
||||
<EuiErrorBoundary>
|
||||
<Suspense fallback={null}>
|
||||
<ParamsFieldsComponent
|
||||
actionParams={actionItem.params as any}
|
||||
index={index}
|
||||
errors={actionParamsErrors.errors}
|
||||
editAction={(key: string, value: RuleActionParam, i: number) => {
|
||||
setWarning(
|
||||
validateParamsForWarnings(
|
||||
value,
|
||||
basePath.publicBaseUrl,
|
||||
availableActionVariables
|
||||
)
|
||||
);
|
||||
setActionParamsProperty(key, value, i);
|
||||
}}
|
||||
messageVariables={availableActionVariables}
|
||||
defaultMessage={
|
||||
// if action is a summary action, show the default summary message
|
||||
isSummaryAction
|
||||
? defaultSummaryMessage
|
||||
: selectedActionGroup?.defaultActionMessage ?? defaultActionMessage
|
||||
}
|
||||
useDefaultMessage={useDefaultMessage}
|
||||
actionConnector={actionConnector}
|
||||
executionMode={ActionConnectorMode.ActionForm}
|
||||
/>
|
||||
{warning ? (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut size="s" color="warning" title={warning} />
|
||||
</>
|
||||
) : null}
|
||||
</Suspense>
|
||||
<EuiFlexGroup gutterSize="m" direction="column">
|
||||
{showMustacheAutocompleteSwitch && (
|
||||
<EuiFlexItem>
|
||||
<EuiSwitch
|
||||
label="Use template fields from alerts index"
|
||||
checked={useAadTemplateFields}
|
||||
onChange={handleUseAadTemplateFields}
|
||||
data-test-subj="mustacheAutocompleteSwitch"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem>
|
||||
<Suspense fallback={null}>
|
||||
<ParamsFieldsComponent
|
||||
actionParams={actionItem.params as any}
|
||||
errors={actionParamsErrors.errors}
|
||||
index={index}
|
||||
editAction={(key: string, value: RuleActionParam, i: number) => {
|
||||
setWarning(
|
||||
validateParamsForWarnings(
|
||||
value,
|
||||
http.basePath.publicBaseUrl,
|
||||
availableActionVariables
|
||||
)
|
||||
);
|
||||
setActionParamsProperty(key, value, i);
|
||||
}}
|
||||
messageVariables={templateFields}
|
||||
defaultMessage={
|
||||
// if action is a summary action, show the default summary message
|
||||
isSummaryAction
|
||||
? defaultSummaryMessage
|
||||
: selectedActionGroup?.defaultActionMessage ?? defaultActionMessage
|
||||
}
|
||||
useDefaultMessage={useDefaultMessage}
|
||||
actionConnector={actionConnector}
|
||||
executionMode={ActionConnectorMode.ActionForm}
|
||||
ruleTypeId={ruleTypeId}
|
||||
/>
|
||||
{warning ? (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut size="s" color="warning" title={warning} />
|
||||
</>
|
||||
) : null}
|
||||
</Suspense>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiErrorBoundary>
|
||||
) : null}
|
||||
</EuiSplitPanel.Inner>
|
||||
|
|
|
@ -839,6 +839,9 @@ export const RuleForm = ({
|
|||
: { ...actionGroup, defaultActionMessage: ruleTypeModel?.defaultActionMessage }
|
||||
)}
|
||||
recoveryActionGroup={recoveryActionGroup}
|
||||
setActionUseAlertDataForTemplate={(enabled: boolean, index: number) => {
|
||||
setActionProperty('useAlertDataForTemplate', enabled, index);
|
||||
}}
|
||||
setActionIdByIndex={(id: string, index: number) => setActionProperty('id', id, index)}
|
||||
setActionGroupIdByIndex={(group: string, index: number) =>
|
||||
setActionProperty('group', group, index)
|
||||
|
|
|
@ -217,6 +217,7 @@ export interface ActionParamsProps<TParams> {
|
|||
index: number;
|
||||
editAction: (key: string, value: RuleActionParam, index: number) => void;
|
||||
errors: IErrorObject;
|
||||
ruleTypeId?: string;
|
||||
messageVariables?: ActionVariable[];
|
||||
defaultMessage?: string;
|
||||
useDefaultMessage?: boolean;
|
||||
|
|
|
@ -55,7 +55,8 @@
|
|||
"@kbn/dashboard-plugin",
|
||||
"@kbn/licensing-plugin",
|
||||
"@kbn/expressions-plugin",
|
||||
"@kbn/serverless",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/serverless"
|
||||
],
|
||||
"exclude": ["target/**/*"]
|
||||
}
|
||||
|
|
|
@ -102,6 +102,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
group: 'default',
|
||||
params: {},
|
||||
uuid: response.body.actions[0].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
|
|
|
@ -116,6 +116,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
connector_type_id: 'test.noop',
|
||||
uuid: response.body.rules[0].actions[0].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
]);
|
||||
expect(response.statusCode).to.eql(200);
|
||||
|
|
|
@ -78,6 +78,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
group: 'default',
|
||||
params: {},
|
||||
uuid: response.body.actions[0].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
|
@ -181,6 +182,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
group: 'default',
|
||||
params: {},
|
||||
uuid: response.body.actions[0].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
{
|
||||
id: 'my-slack1',
|
||||
|
@ -190,6 +192,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
message: 'something important happened!',
|
||||
},
|
||||
uuid: response.body.actions[1].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
{
|
||||
id: 'system-connector-test.system-action',
|
||||
|
@ -197,6 +200,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) {
|
|||
connector_type_id: 'test.system-action',
|
||||
params: {},
|
||||
uuid: response.body.actions[2].uuid,
|
||||
use_alert_data_for_template: false,
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue