Alerting: Show a warning when a template has been potentially misconfigured (#94698)

pull/92393/head^2
Tom Ratcliffe 7 months ago committed by GitHub
parent 016dea1143
commit 0841497cad
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      public/app/features/alerting/unified/components/common/TextVariants.tsx
  2. 7
      public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx
  3. 7
      public/app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json
  4. 4
      public/app/features/alerting/unified/components/contact-points/useNotificationTemplates.ts
  5. 6
      public/app/features/alerting/unified/components/receivers/NewReceiverView.test.tsx
  6. 28
      public/app/features/alerting/unified/components/receivers/TemplatesTable.tsx
  7. 205
      public/app/features/alerting/unified/components/receivers/__snapshots__/NewReceiverView.test.tsx.snap
  8. 3
      public/app/features/alerting/unified/components/receivers/form/fields/TemplateSelector.test.tsx
  9. 5
      public/locales/en-US/grafana.json
  10. 5
      public/locales/pseudo-LOCALE/grafana.json

@ -6,3 +6,4 @@
import { Text } from '@grafana/ui';
export const PrimaryText = ({ content }: { content: string }) => <Text color="primary">{content}</Text>;
export const CodeText = ({ content }: { content: string }) => <Text variant="code">{content}</Text>;

@ -126,6 +126,13 @@ describe('contact points', () => {
});
});
describe('templates tab', () => {
it('shows a warning when a template is misconfigured', async () => {
renderWithProvider(<ContactPointsPageContents />, { initialEntries: ['/?tab=templates'] });
expect((await screen.findAllByText(/^misconfigured$/i))[0]).toBeInTheDocument();
});
});
it('should show / hide loading states, have all actions enabled', async () => {
renderWithProvider(<ContactPointsPageContents />);

@ -3,10 +3,13 @@
"slack-template": "{{ define \"slack-template\" }} Custom slack template {{ end }}",
"custom-email": "{{ define \"custom-email\" }} Custom email template {{ end }}",
"provisioned-template": "{{ define \"provisioned-template\" }} Custom provisioned template {{ end }}",
"template with spaces": "{{ define \"template with spaces\" }} Custom template with spaces in the name {{ end }}"
"template with spaces": "{{ define \"template with spaces\" }} Custom template with spaces in the name {{ end }}",
"misconfigured-template": "{{ define \"misconfigured template\" }} Template that is defined in template_files but not templates {{ end }}",
"misconfigured and provisioned": "{{ define \"misconfigured and provisioned template\" }} Provisioned template that is defined in template_files but not templates {{ end }}"
},
"template_file_provenances": {
"provisioned-template": "api"
"provisioned-template": "api",
"misconfigured and provisioned": "api"
},
"alertmanager_config": {
"route": {

@ -26,6 +26,7 @@ export interface NotificationTemplate {
title: string;
content: string;
provenance: string;
missing?: boolean;
}
const { useGetAlertmanagerConfigurationQuery, useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
@ -84,12 +85,15 @@ function templateGroupToTemplate(
}
function amConfigToTemplates(config: AlertManagerCortexConfig): NotificationTemplate[] {
const { alertmanager_config } = config;
const { templates = [] } = alertmanager_config;
return Object.entries(config.template_files).map(([title, content]) => ({
uid: title,
title,
content,
// Undefined, null or empty string should be converted to PROVENANCE_NONE
provenance: (config.template_file_provenances ?? {})[title] || PROVENANCE_NONE,
missing: !templates.includes(title),
}));
}

@ -97,7 +97,11 @@ describe('alerting API server disabled', () => {
);
const testBody = await testRequest?.json();
const saveBody = await saveRequest?.json();
const fullSaveBody = await saveRequest?.json();
// Only snapshot and check the receivers, as we don't want other tests to break this
// just because we added something new to the mock config
const saveBody = fullSaveBody.alertmanager_config.receivers;
expect([testBody]).toMatchSnapshot();
expect([saveBody]).toMatchSnapshot();

@ -1,8 +1,10 @@
import { Fragment, useState } from 'react';
import { logError } from '@grafana/runtime';
import { ConfirmModal, useStyles2 } from '@grafana/ui';
import { Badge, ConfirmModal, Tooltip, useStyles2 } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { t, Trans } from 'app/core/internationalization';
import { CodeText } from 'app/features/alerting/unified/components/common/TextVariants';
import { Authorize } from '../../components/Authorize';
import { AlertmanagerAction } from '../../hooks/useAbilities';
@ -116,8 +118,8 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
const [isExpanded, setIsExpanded] = useState(false);
const { isProvisioned } = useNotificationTemplateMetadata(notificationTemplate);
const { uid, title: name, content: template } = notificationTemplate;
const { uid, title: name, content: template, missing } = notificationTemplate;
const misconfiguredBadgeText = t('alerting.templates.misconfigured-badge-text', 'Misconfigured');
return (
<Fragment key={uid}>
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
@ -125,7 +127,25 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
<CollapseToggle isCollapsed={!isExpanded} onToggle={() => setIsExpanded(!isExpanded)} />
</td>
<td>
{name} {isProvisioned && <ProvisioningBadge />}
{name} {isProvisioned && <ProvisioningBadge />}{' '}
{missing && (
<Tooltip
content={
<>
<Trans i18nKey="alerting.templates.misconfigured-warning">This template is misconfigured.</Trans>
<br />
<Trans i18nKey="alerting.templates.misconfigured-warning-details">
Templates must be defined in both the <CodeText content="template_files" /> and{' '}
<CodeText content="templates" /> sections of your alertmanager configuration.
</Trans>
</>
}
>
<span>
<Badge text={misconfiguredBadgeText} color="orange" />
</span>
</Tooltip>
)}
</td>
<td className={tableStyles.actionsCell}>
{isProvisioned && (

@ -34,142 +34,113 @@ exports[`alerting API server disabled should be able to test and save a receiver
exports[`alerting API server disabled should be able to test and save a receiver 2`] = `
[
{
"alertmanager_config": {
"mute_time_intervals": [],
"receivers": [
[
{
"grafana_managed_receiver_configs": [
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "grafana-default-email",
"secureFields": {},
"settings": {
"addresses": "gilles.demey@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "xeKQrBrnk",
},
],
"disableResolveMessage": false,
"name": "grafana-default-email",
"secureFields": {},
"settings": {
"addresses": "gilles.demey@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "xeKQrBrnk",
},
],
"name": "grafana-default-email",
},
{
"grafana_managed_receiver_configs": [
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "provisioned-contact-point",
"provenance": "api",
"secureFields": {},
"settings": {
"addresses": "gilles.demey@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "s8SdCVjnk",
},
],
"disableResolveMessage": false,
"name": "provisioned-contact-point",
"provenance": "api",
"secureFields": {},
"settings": {
"addresses": "gilles.demey@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "s8SdCVjnk",
},
],
"name": "provisioned-contact-point",
},
{
"grafana_managed_receiver_configs": [
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "lotsa-emails",
"secureFields": {},
"settings": {
"addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "af306c96-35a2-4d6e-908a-4993e245dbb2",
},
],
"disableResolveMessage": false,
"name": "lotsa-emails",
"secureFields": {},
"settings": {
"addresses": "gilles.demey+1@grafana.com, gilles.demey+2@grafana.com, gilles.demey+3@grafana.com, gilles.demey+4@grafana.com",
"singleEmail": false,
},
"type": "email",
"uid": "af306c96-35a2-4d6e-908a-4993e245dbb2",
},
],
"name": "lotsa-emails",
},
{
"grafana_managed_receiver_configs": [
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "Slack with multiple channels",
"secureFields": {
"token": true,
},
"settings": {
"recipient": "test-alerts",
},
"type": "slack",
"uid": "c02ad56a-31da-46b9-becb-4348ec0890fd",
},
{
"disableResolveMessage": false,
"name": "Slack with multiple channels",
"secureFields": {
"token": true,
},
"settings": {
"recipient": "test-alerts2",
},
"type": "slack",
"uid": "b286a3be-f690-49e2-8605-b075cbace2df",
},
],
"disableResolveMessage": false,
"name": "Slack with multiple channels",
"secureFields": {
"token": true,
},
"settings": {
"recipient": "test-alerts",
},
"type": "slack",
"uid": "c02ad56a-31da-46b9-becb-4348ec0890fd",
},
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "Oncall-integration",
"settings": {
"url": "https://oncall-endpoint.example.com",
},
"type": "oncall",
},
],
"name": "OnCall Conctact point",
"disableResolveMessage": false,
"name": "Slack with multiple channels",
"secureFields": {
"token": true,
},
"settings": {
"recipient": "test-alerts2",
},
"type": "slack",
"uid": "b286a3be-f690-49e2-8605-b075cbace2df",
},
],
"name": "Slack with multiple channels",
},
{
"grafana_managed_receiver_configs": [
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "my new receiver",
"secureSettings": {},
"settings": {
"addresses": "tester@grafana.com",
"singleEmail": false,
},
"type": "email",
},
],
"name": "my new receiver",
"disableResolveMessage": false,
"name": "Oncall-integration",
"settings": {
"url": "https://oncall-endpoint.example.com",
},
"type": "oncall",
},
],
"route": {
"receiver": "grafana-default-email",
"routes": [
{
"receiver": "provisioned-contact-point",
"name": "OnCall Conctact point",
},
{
"grafana_managed_receiver_configs": [
{
"disableResolveMessage": false,
"name": "my new receiver",
"secureSettings": {},
"settings": {
"addresses": "tester@grafana.com",
"singleEmail": false,
},
],
},
"templates": [
"slack-template",
"custom-email",
"provisioned-template",
"template with spaces",
"type": "email",
},
],
"time_intervals": [],
},
"template_file_provenances": {
"provisioned-template": "api",
"name": "my new receiver",
},
"template_files": {
"custom-email": "{{ define "custom-email" }} Custom email template {{ end }}",
"provisioned-template": "{{ define "provisioned-template" }} Custom provisioned template {{ end }}",
"slack-template": "{{ define "slack-template" }} Custom slack template {{ end }}",
"template with spaces": "{{ define "template with spaces" }} Custom template with spaces in the name {{ end }}",
},
},
],
]
`;

@ -2,6 +2,7 @@ import { ReactNode } from 'react';
import { render, screen, userEvent } from 'test/test-utils';
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
import alertmanagerConfigMock from 'app/features/alerting/unified/components/contact-points/__mocks__/alertmanager.config.mock.json';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
@ -98,7 +99,7 @@ describe('TemplatesPicker', () => {
const input = screen.getByRole('combobox');
expect(screen.queryByText('slack-template')).not.toBeInTheDocument();
await userEvent.click(input);
expect(screen.getAllByRole('option')).toHaveLength(7); // 4 templates in mock plus 3 in the default template
expect(screen.getAllByRole('option')).toHaveLength(Object.keys(alertmanagerConfigMock.template_files).length + 3); // 4 templates in mock plus 3 in the default template
const template = screen.getByRole('option', { name: 'slack-template' });
await userEvent.click(template);
expect(screen.getByText('slack-template')).toBeInTheDocument();

@ -284,6 +284,11 @@
"ofQuery": {
"To": "TO"
}
},
"templates": {
"misconfigured-badge-text": "Misconfigured",
"misconfigured-warning": "This template is misconfigured.",
"misconfigured-warning-details": "Templates must be defined in both the <1></1> and <4></4> sections of your alertmanager configuration."
}
},
"annotations": {

@ -284,6 +284,11 @@
"ofQuery": {
"To": "ŦØ"
}
},
"templates": {
"misconfigured-badge-text": "Mįşčőʼnƒįģūřęđ",
"misconfigured-warning": "Ŧĥįş ŧęmpľäŧę įş mįşčőʼnƒįģūřęđ.",
"misconfigured-warning-details": "Ŧęmpľäŧęş mūşŧ þę đęƒįʼnęđ įʼn þőŧĥ ŧĥę <1></1> äʼnđ <4></4> şęčŧįőʼnş őƒ yőūř äľęřŧmäʼnäģęř čőʼnƒįģūřäŧįőʼn."
}
},
"annotations": {

Loading…
Cancel
Save