diff --git a/public/app/features/alerting/unified/Analytics.ts b/public/app/features/alerting/unified/Analytics.ts index e7a6f1929be..3cf5526c250 100644 --- a/public/app/features/alerting/unified/Analytics.ts +++ b/public/app/features/alerting/unified/Analytics.ts @@ -28,7 +28,7 @@ export const LogMessages = { const { logInfo, logError, logMeasurement } = createMonitoringLogger('features.alerting', { module: 'Alerting' }); -export { logInfo, logError, logMeasurement }; +export { logError, logInfo, logMeasurement }; // eslint-disable-next-line @typescript-eslint/no-explicit-any export function withPerformanceLogging Promise>( @@ -245,6 +245,16 @@ export function trackSwitchToPoliciesRouting() { reportInteraction('grafana_alerting_switch_to_policies_routing'); } +export function trackEditInputWithTemplate() { + reportInteraction('grafana_alerting_contact_point_form_edit_input_with_template'); +} +export function trackUseCustomInputInTemplate() { + reportInteraction('grafana_alerting_contact_point_form_use_custom_input_in_template'); +} +export function trackUseSingleTemplateInInput() { + reportInteraction('grafana_alerting_contact_point_form_use_single_template_in_input'); +} + export type AlertRuleTrackingProps = { user_id: number; grafana_version?: string; diff --git a/public/app/features/alerting/unified/api/templateApi.ts b/public/app/features/alerting/unified/api/templateApi.ts index 41e5f23703a..2d9f83014b9 100644 --- a/public/app/features/alerting/unified/api/templateApi.ts +++ b/public/app/features/alerting/unified/api/templateApi.ts @@ -1,3 +1,8 @@ +import { Template } from 'app/features/alerting/unified/components/receivers/form/fields/TemplateSelector'; +import { DEFAULT_TEMPLATES } from 'app/features/alerting/unified/utils/template-constants'; + +import { parseTemplates } from '../components/receivers/form/fields/utils'; + import { alertingApi } from './alertingApi'; export const previewTemplateUrl = `/api/alertmanager/grafana/config/api/v1/templates/test`; @@ -34,6 +39,12 @@ export const templatesApi = alertingApi.injectEndpoints({ method: 'POST', }), }), + getDefaultTemplates: build.query({ + queryFn: async () => { + const data = parseTemplates(DEFAULT_TEMPLATES); + return { data }; + }, + }), }), }); diff --git a/public/app/features/alerting/unified/components/contact-points/templates/EditorColumnHeader.tsx b/public/app/features/alerting/unified/components/contact-points/templates/EditorColumnHeader.tsx index 8b6e37f0047..f60257d79ac 100644 --- a/public/app/features/alerting/unified/components/contact-points/templates/EditorColumnHeader.tsx +++ b/public/app/features/alerting/unified/components/contact-points/templates/EditorColumnHeader.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { useStyles2, Stack, Label } from '@grafana/ui'; +import { Label, Stack, useStyles2 } from '@grafana/ui'; export function EditorColumnHeader({ label, actions }: { label: string; actions?: React.ReactNode }) { const styles = useStyles2(editorColumnStyles); diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index 0ec493833bf..275c69cecdd 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -471,4 +471,4 @@ const defaultPayload: TestTemplateAlert[] = [ }, ]; -const defaultPayloadString = JSON.stringify(defaultPayload, null, 2); +export const defaultPayloadString = JSON.stringify(defaultPayload, null, 2); diff --git a/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx b/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx index cd80d764661..cab1322c2d4 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplatePreview.tsx @@ -1,23 +1,18 @@ import { css, cx } from '@emotion/css'; import { compact, uniqueId } from 'lodash'; -import React, { useCallback, useEffect } from 'react'; +import React from 'react'; import { useFormContext } from 'react-hook-form'; import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme2 } from '@grafana/data'; -import { Button, useStyles2, Alert, Box } from '@grafana/ui'; +import { Alert, Box, Button, useStyles2 } from '@grafana/ui'; -import { - AlertField, - TemplatePreviewErrors, - TemplatePreviewResponse, - TemplatePreviewResult, - usePreviewTemplateMutation, -} from '../../api/templateApi'; +import { TemplatePreviewErrors, TemplatePreviewResponse, TemplatePreviewResult } from '../../api/templateApi'; import { stringifyErrorLike } from '../../utils/misc'; import { EditorColumnHeader } from '../contact-points/templates/EditorColumnHeader'; import type { TemplateFormValues } from './TemplateForm'; +import { usePreviewTemplate } from './usePreviewTemplate'; export function TemplatePreview({ payload, @@ -38,23 +33,14 @@ export function TemplatePreview({ const templateContent = watch('content'); - const [trigger, { data, error: previewError, isLoading }] = usePreviewTemplateMutation(); - + const { + data, + isLoading, + onPreview, + error: previewError, + } = usePreviewTemplate(templateContent, templateName, payload, setPayloadFormatError); const previewToRender = getPreviewResults(previewError, payloadFormatError, data); - const onPreview = useCallback(() => { - try { - const alertList: AlertField[] = JSON.parse(payload); - JSON.stringify([...alertList]); // check if it's iterable, in order to be able to add more data - trigger({ template: templateContent, alerts: alertList, name: templateName }); - setPayloadFormatError(null); - } catch (e) { - setPayloadFormatError(e instanceof Error ? e.message : 'Invalid JSON.'); - } - }, [templateContent, templateName, payload, setPayloadFormatError, trigger]); - - useEffect(() => onPreview(), [onPreview]); - return (
({ container: css({ display: 'flex', flexDirection: 'column', + height: 'inherit', }), box: css({ display: 'flex', flexDirection: 'column', borderBottom: `1px solid ${theme.colors.border.medium}`, + height: 'inherit', }), header: css({ fontSize: theme.typography.bodySmall.fontSize, diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx index 920abdd4494..05bc1c00a5d 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/OptionField.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { isEmpty } from 'lodash'; import React, { FC, useEffect } from 'react'; -import { useFormContext, FieldError, DeepMap, Controller } from 'react-hook-form'; +import { Controller, DeepMap, FieldError, useFormContext } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; import { Checkbox, Field, Input, RadioButtonList, Select, TextArea, useStyles2 } from '@grafana/ui'; @@ -11,6 +11,7 @@ import { KeyValueMapInput } from './KeyValueMapInput'; import { StringArrayInput } from './StringArrayInput'; import { SubformArrayField } from './SubformArrayField'; import { SubformField } from './SubformField'; +import { WrapWithTemplateSelection } from './TemplateSelector'; interface Props { defaultValue: any; @@ -89,7 +90,7 @@ const OptionInput: FC = ({ customValidator, }) => { const styles = useStyles2(getStyles); - const { control, register, unregister, getValues } = useFormContext(); + const { control, register, unregister, getValues, setValue } = useFormContext(); const name = `${pathPrefix}${option.propertyName}`; // workaround for https://github.com/react-hook-form/react-hook-form/issues/4993#issuecomment-829012506 @@ -99,6 +100,13 @@ const OptionInput: FC = ({ }, [unregister, name] ); + + const useTemplates = option.placeholder.includes('{{ template'); + + function onSelectTemplate(template: string) { + setValue(name, template); + } + switch (option.element) { case 'checkbox': return ( @@ -114,22 +122,29 @@ const OptionInput: FC = ({ ); case 'input': return ( - - option.validationRule ? validateOption(v, option.validationRule, option.required) : true, - customValidator: (v) => (customValidator ? customValidator(v) : true), - }, - setValueAs: option.setValueAs, - })} - placeholder={option.placeholder} - /> + + + option.validationRule ? validateOption(v, option.validationRule, option.required) : true, + customValidator: (v) => (customValidator ? customValidator(v) : true), + }, + setValueAs: option.setValueAs, + })} + placeholder={option.placeholder} + /> + ); case 'select': @@ -178,17 +193,24 @@ const OptionInput: FC = ({ ); case 'textarea': return ( -