diff --git a/public/app/features/alerting/unified/api/templateApi.ts b/public/app/features/alerting/unified/api/templateApi.ts index ccfc5f62042..76bbaaef4f5 100644 --- a/public/app/features/alerting/unified/api/templateApi.ts +++ b/public/app/features/alerting/unified/api/templateApi.ts @@ -41,9 +41,11 @@ generatedTemplatesApi.enhanceEndpoints({ }, }); +export type TemplatesTestPayload = { template: string; alerts: AlertField[]; name: string }; + export const templatesApi = generatedTemplatesApi.injectEndpoints({ endpoints: (build) => ({ - previewTemplate: build.mutation({ + previewTemplate: build.mutation({ query: ({ template, alerts, name }) => ({ url: previewTemplateUrl, data: { template: template, alerts: alerts, name: name }, diff --git a/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx new file mode 100644 index 00000000000..477955062bc --- /dev/null +++ b/public/app/features/alerting/unified/components/contact-points/EditContactPoint.test.tsx @@ -0,0 +1,76 @@ +import 'core-js/stable/structured-clone'; +import { Routes, Route } from 'react-router-dom-v5-compat'; +import { clickSelectOption } from 'test/helpers/selectOptionInTest'; +import { render, screen } from 'test/test-utils'; + +import EditContactPoint from 'app/features/alerting/unified/components/contact-points/EditContactPoint'; +import { AccessControlAction } from 'app/types'; + +import { setupMswServer } from '../../mockApi'; +import { grantUserPermissions } from '../../mocks'; + +setupMswServer(); + +const Index = () => { + return
redirected
; +}; + +const renderEditContactPoint = (contactPointUid: string) => + render( + + } /> + } /> + , + { + historyOptions: { initialEntries: [`/alerting/notifications/receivers/${contactPointUid}/edit`] }, + } + ); + +beforeEach(() => { + grantUserPermissions([AccessControlAction.AlertingNotificationsRead, AccessControlAction.AlertingNotificationsWrite]); +}); + +const getTemplatePreviewContent = async () => + await screen.findByRole('presentation', { description: /Preview with the default payload/i }); + +const templatesSelectorTestId = 'existing-templates-selector'; + +describe('Edit contact point', () => { + it('can edit a contact point with existing template field values', async () => { + const { user } = renderEditContactPoint('lotsa-emails'); + + // Expand settings and open "edit message template" drawer + await user.click(await screen.findByText(/optional email settings/i)); + await user.click(await screen.findByRole('button', { name: /edit message/i })); + expect(await screen.findByRole('dialog', { name: /edit message/i })).toBeInTheDocument(); + expect(await getTemplatePreviewContent()).toHaveTextContent(/some example preview for slack-template/i); + + // Change the preset template and check that the preview updates correctly + await clickSelectOption(screen.getByTestId(templatesSelectorTestId), 'custom-email'); + expect(await getTemplatePreviewContent()).toHaveTextContent(/some example preview for custom-email/i); + + // Close the drawer + await user.click(screen.getByRole('button', { name: /^save$/i })); + + // Check a setting that has an existing custom value, and change it to a preset template + await user.click(await screen.findByRole('button', { name: /edit subject/i })); + expect(await screen.findByRole('dialog', { name: /edit subject/i })).toBeInTheDocument(); + // If this isn't correct, then we haven't set the correct initial state for the radio buttons/tabs + expect(await screen.findByLabelText(/custom template value/i)).toHaveValue('some custom value'); + + await user.click(screen.getByRole('radio', { name: /select existing template/i })); + await clickSelectOption(screen.getByTestId(templatesSelectorTestId), 'slack-template'); + + expect(await getTemplatePreviewContent()).toHaveTextContent(/some example preview for slack-template/i); + + // Close the drawer + await user.click(screen.getByRole('button', { name: /^save$/i })); + + expect(await screen.findByText(/template: custom-email/i)).toBeInTheDocument(); + expect(await screen.findByText(/template: slack-template/i)).toBeInTheDocument(); + + await user.click(screen.getByRole('button', { name: /save contact point/i })); + + expect(await screen.findByText(/redirected/i)).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap b/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap index 14aa20dad9a..00d2cbdd9e3 100644 --- a/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap +++ b/public/app/features/alerting/unified/components/contact-points/__snapshots__/useContactPoints.test.tsx.snap @@ -49,7 +49,9 @@ exports[`useContactPoints should return contact points with status 1`] = ` "secureFields": {}, "settings": { "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com", + "message": "{{ template "slack-template" . }}", "singleEmail": false, + "subject": "some custom value", }, "type": "email", "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2", @@ -243,7 +245,9 @@ exports[`useContactPoints when having oncall plugin installed and no alert manag "secureFields": {}, "settings": { "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com", + "message": "{{ template "slack-template" . }}", "singleEmail": false, + "subject": "some custom value", }, "type": "email", "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2", 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 63369188507..bd0d77a2a15 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 @@ -4,12 +4,16 @@ import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { Label, Stack, useStyles2 } from '@grafana/ui'; -export function EditorColumnHeader({ label, actions }: { label: string; actions?: React.ReactNode }) { +type Props = { label: string; actions?: React.ReactNode; id?: string }; + +export function EditorColumnHeader({ label, actions, id }: Props) { const styles = useStyles2(editorColumnStyles); return (
- + {actions} @@ -17,7 +21,7 @@ export function EditorColumnHeader({ label, actions }: { label: string; actions? ); } -export const editorColumnStyles = (theme: GrafanaTheme2) => ({ +const editorColumnStyles = (theme: GrafanaTheme2) => ({ container: css({ display: 'flex', flexDirection: 'row', diff --git a/public/app/features/alerting/unified/components/receivers/__snapshots__/NewReceiverView.test.tsx.snap b/public/app/features/alerting/unified/components/receivers/__snapshots__/NewReceiverView.test.tsx.snap index ededb25feb5..9141fd5246f 100644 --- a/public/app/features/alerting/unified/components/receivers/__snapshots__/NewReceiverView.test.tsx.snap +++ b/public/app/features/alerting/unified/components/receivers/__snapshots__/NewReceiverView.test.tsx.snap @@ -76,7 +76,9 @@ exports[`alerting API server disabled should be able to test and save a receiver "secureFields": {}, "settings": { "addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com", + "message": "{{ template "slack-template" . }}", "singleEmail": false, + "subject": "some custom value", }, "type": "email", "uid": "af306c96-35a2-4d6e-908a-4993e245dbb2", diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateContentAndPreview.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateContentAndPreview.tsx index 419f0b51d77..74d64d7be89 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateContentAndPreview.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateContentAndPreview.tsx @@ -35,6 +35,8 @@ export function TemplateContentAndPreview({ const { data, error } = usePreviewTemplate(templateContent, templateName, payload, setPayloadFormatError); const previewToRender = getPreviewResults(error, payloadFormatError, data); + const templatePreviewId = 'template-preview'; + return (
@@ -58,9 +60,15 @@ export function TemplateContentAndPreview({ {isGrafanaAlertManager && (
- + -
{previewToRender}
+
+ {previewToRender} +
)} diff --git a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx index 1472fb111ea..185e5f34b25 100644 --- a/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.tsx @@ -8,7 +8,7 @@ import { Button, Drawer, IconButton, - Input, + Label, RadioButtonGroup, Select, Stack, @@ -16,6 +16,7 @@ import { TextArea, useStyles2, } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; import { trackEditInputWithTemplate, trackUseCustomInputInTemplate, @@ -34,6 +35,8 @@ import { defaultPayloadString } from '../../TemplateForm'; import { TemplateContentAndPreview } from './TemplateContentAndPreview'; import { getTemplateName, getUseTemplateText, matchesOnlyOneTemplate, parseTemplates } from './utils'; +const { useGetDefaultTemplatesQuery } = templatesApi; + interface TemplatesPickerProps { onSelect: (temnplate: string) => void; option: NotificationChannelOption; @@ -45,27 +48,23 @@ export function TemplatesPicker({ onSelect, option, valueInForm }: TemplatesPick setShowTemplates(true); trackEditInputWithTemplate(); }; + const handleClose = () => setShowTemplates(false); + return ( <> {showTemplates && ( - setShowTemplates(false)}> - setShowTemplates(false)} - option={option} - valueInForm={valueInForm} - /> + + )} @@ -102,10 +101,6 @@ export function getTemplateOptions(templateFiles: NotificationTemplate[], defaul // return the sum of default and custom templates return Array.from(templateMap.values()); } -function getContentFromOptions(name: string, options: Array>) { - const template = options.find((option) => option.label === name); - return template?.value?.content ?? ''; -} export interface Template { name: string; @@ -117,40 +112,44 @@ interface TemplateSelectorProps { option: NotificationChannelOption; valueInForm: string; } + function TemplateSelector({ onSelect, onClose, option, valueInForm }: TemplateSelectorProps) { const styles = useStyles2(getStyles); - const useGetDefaultTemplatesQuery = templatesApi.endpoints.getDefaultTemplates.useQuery; - const [template, setTemplate] = useState