diff --git a/public/components/Notifications/NotificationForm.tsx b/public/components/Notifications/NotificationForm.tsx index 06354941..25a86021 100644 --- a/public/components/Notifications/NotificationForm.tsx +++ b/public/components/Notifications/NotificationForm.tsx @@ -16,16 +16,19 @@ import { EuiCompressedSwitch, EuiText, EuiCompressedTextArea, + EuiCompressedCheckbox, } from '@elastic/eui'; -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { NOTIFICATIONS_HREF } from '../../utils/constants'; import { NotificationsCallOut } from '../NotificationsCallOut'; import { NotificationChannelOption, NotificationChannelTypeOptions, TriggerAction, + TriggerContext, } from '../../../types'; import { getIsNotificationPluginInstalled } from '../../utils/helpers'; +import { render } from 'mustache'; export interface NotificationFormProps { allNotificationChannels: NotificationChannelTypeOptions[]; @@ -37,10 +40,12 @@ export interface NotificationFormProps { onMessageBodyChange: (message: string) => void; onMessageSubjectChange: (subject: string) => void; onNotificationToggle?: (enabled: boolean) => void; + context: TriggerContext; } export const NotificationForm: React.FC = ({ action, + context, allNotificationChannels, loadingNotifications, prepareMessage, @@ -53,6 +58,10 @@ export const NotificationForm: React.FC = ({ const hasNotificationPlugin = getIsNotificationPluginInstalled(); const [shouldSendNotification, setShouldSendNotification] = useState(!!action?.destination_id); const selectedNotificationChannelOption: NotificationChannelOption[] = []; + const [displayPreview, setDisplayPreview] = useState(false); + const onDisplayPreviewChange = useCallback((e) => setDisplayPreview(e.target.checked), [ + displayPreview, + ]); if (shouldSendNotification && action?.destination_id) { allNotificationChannels.forEach((typeOption) => { const matchingChannel = typeOption.options.find( @@ -61,7 +70,17 @@ export const NotificationForm: React.FC = ({ if (matchingChannel) selectedNotificationChannelOption.push(matchingChannel); }); } - + let preview = ''; + try { + preview = `${render(action?.subject_template.source, context)}\n\n${render( + action?.message_template.source, + context + )}`; + } catch (err) { + preview = `There was an error rendering message preview: ${err.message}`; + console.error('There was an error rendering mustache template', err); + } + ``; return ( <> = ({ /> - {prepareMessage && ( + + + + + {displayPreview && ( - - prepareMessage(true /* updateMessage */)} - > - Generate message - + + )} diff --git a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx index 62707764..f49c2fd4 100644 --- a/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx +++ b/public/pages/CreateDetector/components/ConfigureAlerts/components/AlertCondition/AlertConditionPanel.tsx @@ -28,9 +28,10 @@ import { Detector, NotificationChannelOption, NotificationChannelTypeOptions, + TriggerContext, } from '../../../../../../../types'; import { NotificationForm } from '../../../../../../components/Notifications/NotificationForm'; -import { ALERT_SEVERITY_OPTIONS } from '../../../../../../utils/constants'; +import { ALERT_SEVERITY_OPTIONS, DEFAULT_MESSAGE_SOURCE } from '../../../../../../utils/constants'; interface AlertConditionPanelProps extends RouteComponentProps { alertCondition: AlertCondition; @@ -99,6 +100,29 @@ export default class AlertConditionPanel extends Component< }); } + getTriggerContext = (): TriggerContext => { + const lineBreakAndTab = '\n\t'; + const { alertCondition, detector } = this.props; + const detectorInput = detector.inputs[0].detector_input; + const detectorIndices = `${lineBreakAndTab}${detectorInput.indices.join( + `,${lineBreakAndTab}` + )}`; + return { + ctx: { + trigger: { + name: alertCondition.name, + severity: + parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity, + }, + detector: { + name: detector.name, + description: detectorInput.description, + datasources: detectorIndices, + }, + }, + }; + }; + // When component mounts, we prepare message but at this point we don't want to emit the // trigger changed metric since it is not user initiated. So we use the onMount flag to determine that // and pass it downstream accordingly. @@ -113,7 +137,7 @@ export default class AlertConditionPanel extends Component< parseAlertSeverityToOption(alertCondition.severity)?.label || alertCondition.severity }`; const detectorName = `Threat detector: ${detector.name}`; - const defaultSubject = [alertConditionName, alertConditionSeverity, detectorName].join(' - '); + const defaultSubject = DEFAULT_MESSAGE_SOURCE.MESSAGE_SUBJECT; if (updateMessage || !alertCondition.actions[0]?.subject_template.source) this.onMessageSubjectChange(defaultSubject, !onMount); @@ -157,7 +181,7 @@ export default class AlertConditionPanel extends Component< if (alertConditionSelections.length) defaultMessageBody = defaultMessageBody + lineBreak + lineBreak + alertConditionSelections.join(lineBreak); - this.onMessageBodyChange(defaultMessageBody, !onMount); + this.onMessageBodyChange(DEFAULT_MESSAGE_SOURCE.MESSAGE_BODY, !onMount); } }; @@ -537,6 +561,7 @@ export default class AlertConditionPanel extends Component<
+
+
+ Preview message +
@@ -1707,28 +1701,22 @@ Object { class="euiFlexItem" >
+
+
+ Preview message +
diff --git a/public/utils/constants.ts b/public/utils/constants.ts index 25ab3da8..dec93b83 100644 --- a/public/utils/constants.ts +++ b/public/utils/constants.ts @@ -320,3 +320,12 @@ const LocalCluster: DataSourceOption = { export const dataSourceObservable = new BehaviorSubject({}); export const DATA_SOURCE_NOT_SET_ERROR = 'Data source is not set'; + +export const DEFAULT_MESSAGE_SOURCE = { + MESSAGE_BODY: `- Triggered alert condition: {{ctx.trigger.name}} + - Severity: {{ctx.trigger.severity}} + - Threat detector: {{ctx.detector.name}} + - Description: {{ctx.detector.description}} + - Detector data sources: {{ctx.detector.datasources}}`, + MESSAGE_SUBJECT: `Triggered alert condition: {{ctx.trigger.name}} - Severity: {{ctx.trigger.severity}} - Threat detector: {{ctx.detector.name}}`, +}; diff --git a/types/Alert.ts b/types/Alert.ts index 5e600c57..c7b22a8b 100644 --- a/types/Alert.ts +++ b/types/Alert.ts @@ -43,6 +43,20 @@ export interface TriggerAction { }; } +export interface TriggerContext { + ctx: { + trigger: { + name: string; + severity: string; + }; + detector: { + name: string; + description: string; + datasources: string; + }; + }; +} + /** * API interfaces */