Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating Preview Message functionality while setting notifications in detector alerts #1241

Merged
merged 4 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 38 additions & 10 deletions public/components/Notifications/NotificationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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<NotificationFormProps> = ({
action,
context,
allNotificationChannels,
loadingNotifications,
prepareMessage,
Expand All @@ -53,6 +58,10 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
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(
Expand All @@ -61,7 +70,17 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
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);
amsiglan marked this conversation as resolved.
Show resolved Hide resolved
}
``;
return (
<>
<EuiCompressedSwitch
Expand Down Expand Up @@ -168,15 +187,24 @@ export const NotificationForm: React.FC<NotificationFormProps> = ({
/>
</EuiCompressedFormRow>
</EuiFlexItem>
{prepareMessage && (

<EuiFlexItem>
<EuiCompressedCheckbox
id="notification-message-preview-checkbox"
label="Preview message"
checked={displayPreview}
onChange={onDisplayPreviewChange}
/>
</EuiFlexItem>
{displayPreview && (
<EuiFlexItem>
<EuiCompressedFormRow>
<EuiSmallButton
fullWidth={false}
onClick={() => prepareMessage(true /* updateMessage */)}
>
Generate message
</EuiSmallButton>
<EuiCompressedFormRow label="Message preview" fullWidth>
<EuiCompressedTextArea
placeholder="Preview of mustache template"
fullWidth
value={preview}
readOnly
/>
</EuiCompressedFormRow>
</EuiFlexItem>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
};

Expand Down Expand Up @@ -537,6 +561,7 @@ export default class AlertConditionPanel extends Component<

<NotificationForm
action={alertCondition.actions[0]}
context={this.getTriggerContext()}
allNotificationChannels={allNotificationChannels}
loadingNotifications={loadingNotifications}
prepareMessage={this.prepareMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -838,28 +838,22 @@ Object {
class="euiFlexItem"
>
<div
class="euiFormRow euiFormRow--compressed"
id="some_html_id-row"
class="euiCheckbox euiCheckbox--compressed"
>
<input
class="euiCheckbox__input"
id="notification-message-preview-checkbox"
type="checkbox"
/>
<div
class="euiFormRow__fieldWrapper"
class="euiCheckbox__square"
/>
<label
class="euiCheckbox__label"
for="notification-message-preview-checkbox"
>
<button
class="euiButton euiButton--primary euiButton--small"
id="some_html_id"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButton__text"
>
Generate message
</span>
</span>
</button>
</div>
Preview message
</label>
</div>
</div>
</div>
Expand Down Expand Up @@ -1707,28 +1701,22 @@ Object {
class="euiFlexItem"
>
<div
class="euiFormRow euiFormRow--compressed"
id="some_html_id-row"
class="euiCheckbox euiCheckbox--compressed"
>
<input
class="euiCheckbox__input"
id="notification-message-preview-checkbox"
type="checkbox"
/>
<div
class="euiFormRow__fieldWrapper"
class="euiCheckbox__square"
/>
<label
class="euiCheckbox__label"
for="notification-message-preview-checkbox"
>
<button
class="euiButton euiButton--primary euiButton--small"
id="some_html_id"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButton__text"
>
Generate message
</span>
</span>
</button>
</div>
Preview message
</label>
</div>
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,12 @@ const LocalCluster: DataSourceOption = {
export const dataSourceObservable = new BehaviorSubject<DataSourceOption>({});

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}}`,
};
14 changes: 14 additions & 0 deletions types/Alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
Loading