mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Show a warning when a template has been potentially misconfigured (#94698)
This commit is contained in:
parent
016dea1143
commit
0841497cad
@ -6,3 +6,4 @@
|
|||||||
import { Text } from '@grafana/ui';
|
import { Text } from '@grafana/ui';
|
||||||
|
|
||||||
export const PrimaryText = ({ content }: { content: string }) => <Text color="primary">{content}</Text>;
|
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 () => {
|
it('should show / hide loading states, have all actions enabled', async () => {
|
||||||
renderWithProvider(<ContactPointsPageContents />);
|
renderWithProvider(<ContactPointsPageContents />);
|
||||||
|
|
||||||
|
@ -3,10 +3,13 @@
|
|||||||
"slack-template": "{{ define \"slack-template\" }} Custom slack template {{ end }}",
|
"slack-template": "{{ define \"slack-template\" }} Custom slack template {{ end }}",
|
||||||
"custom-email": "{{ define \"custom-email\" }} Custom email template {{ end }}",
|
"custom-email": "{{ define \"custom-email\" }} Custom email template {{ end }}",
|
||||||
"provisioned-template": "{{ define \"provisioned-template\" }} Custom provisioned 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": {
|
"template_file_provenances": {
|
||||||
"provisioned-template": "api"
|
"provisioned-template": "api",
|
||||||
|
"misconfigured and provisioned": "api"
|
||||||
},
|
},
|
||||||
"alertmanager_config": {
|
"alertmanager_config": {
|
||||||
"route": {
|
"route": {
|
||||||
|
@ -26,6 +26,7 @@ export interface NotificationTemplate {
|
|||||||
title: string;
|
title: string;
|
||||||
content: string;
|
content: string;
|
||||||
provenance: string;
|
provenance: string;
|
||||||
|
missing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { useGetAlertmanagerConfigurationQuery, useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
|
const { useGetAlertmanagerConfigurationQuery, useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
|
||||||
@ -84,12 +85,15 @@ function templateGroupToTemplate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function amConfigToTemplates(config: AlertManagerCortexConfig): NotificationTemplate[] {
|
function amConfigToTemplates(config: AlertManagerCortexConfig): NotificationTemplate[] {
|
||||||
|
const { alertmanager_config } = config;
|
||||||
|
const { templates = [] } = alertmanager_config;
|
||||||
return Object.entries(config.template_files).map(([title, content]) => ({
|
return Object.entries(config.template_files).map(([title, content]) => ({
|
||||||
uid: title,
|
uid: title,
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
// Undefined, null or empty string should be converted to PROVENANCE_NONE
|
// Undefined, null or empty string should be converted to PROVENANCE_NONE
|
||||||
provenance: (config.template_file_provenances ?? {})[title] || 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 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([testBody]).toMatchSnapshot();
|
||||||
expect([saveBody]).toMatchSnapshot();
|
expect([saveBody]).toMatchSnapshot();
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { Fragment, useState } from 'react';
|
import { Fragment, useState } from 'react';
|
||||||
|
|
||||||
import { logError } from '@grafana/runtime';
|
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 { 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 { Authorize } from '../../components/Authorize';
|
||||||
import { AlertmanagerAction } from '../../hooks/useAbilities';
|
import { AlertmanagerAction } from '../../hooks/useAbilities';
|
||||||
@ -116,8 +118,8 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
|
|||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const { isProvisioned } = useNotificationTemplateMetadata(notificationTemplate);
|
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 (
|
return (
|
||||||
<Fragment key={uid}>
|
<Fragment key={uid}>
|
||||||
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
|
||||||
@ -125,7 +127,25 @@ function TemplateRow({ notificationTemplate, idx, alertManagerName, onDeleteClic
|
|||||||
<CollapseToggle isCollapsed={!isExpanded} onToggle={() => setIsExpanded(!isExpanded)} />
|
<CollapseToggle isCollapsed={!isExpanded} onToggle={() => setIsExpanded(!isExpanded)} />
|
||||||
</td>
|
</td>
|
||||||
<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>
|
||||||
<td className={tableStyles.actionsCell}>
|
<td className={tableStyles.actionsCell}>
|
||||||
{isProvisioned && (
|
{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`] = `
|
exports[`alerting API server disabled should be able to test and save a receiver 2`] = `
|
||||||
[
|
[
|
||||||
{
|
[
|
||||||
"alertmanager_config": {
|
{
|
||||||
"mute_time_intervals": [],
|
"grafana_managed_receiver_configs": [
|
||||||
"receivers": [
|
|
||||||
{
|
{
|
||||||
"grafana_managed_receiver_configs": [
|
"disableResolveMessage": false,
|
||||||
{
|
|
||||||
"disableResolveMessage": false,
|
|
||||||
"name": "grafana-default-email",
|
|
||||||
"secureFields": {},
|
|
||||||
"settings": {
|
|
||||||
"addresses": "gilles.demey@grafana.com",
|
|
||||||
"singleEmail": false,
|
|
||||||
},
|
|
||||||
"type": "email",
|
|
||||||
"uid": "xeKQrBrnk",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"name": "grafana-default-email",
|
"name": "grafana-default-email",
|
||||||
},
|
"secureFields": {},
|
||||||
{
|
"settings": {
|
||||||
"grafana_managed_receiver_configs": [
|
"addresses": "gilles.demey@grafana.com",
|
||||||
{
|
"singleEmail": false,
|
||||||
"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": [
|
|
||||||
{
|
|
||||||
"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": [
|
|
||||||
{
|
|
||||||
"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",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"name": "Slack with multiple channels",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"grafana_managed_receiver_configs": [
|
|
||||||
{
|
|
||||||
"disableResolveMessage": false,
|
|
||||||
"name": "Oncall-integration",
|
|
||||||
"settings": {
|
|
||||||
"url": "https://oncall-endpoint.example.com",
|
|
||||||
},
|
|
||||||
"type": "oncall",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"name": "OnCall Conctact point",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"grafana_managed_receiver_configs": [
|
|
||||||
{
|
|
||||||
"disableResolveMessage": false,
|
|
||||||
"name": "my new receiver",
|
|
||||||
"secureSettings": {},
|
|
||||||
"settings": {
|
|
||||||
"addresses": "tester@grafana.com",
|
|
||||||
"singleEmail": false,
|
|
||||||
},
|
|
||||||
"type": "email",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"name": "my new receiver",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"route": {
|
|
||||||
"receiver": "grafana-default-email",
|
|
||||||
"routes": [
|
|
||||||
{
|
|
||||||
"receiver": "provisioned-contact-point",
|
|
||||||
},
|
},
|
||||||
],
|
"type": "email",
|
||||||
},
|
"uid": "xeKQrBrnk",
|
||||||
"templates": [
|
},
|
||||||
"slack-template",
|
|
||||||
"custom-email",
|
|
||||||
"provisioned-template",
|
|
||||||
"template with spaces",
|
|
||||||
],
|
],
|
||||||
"time_intervals": [],
|
"name": "grafana-default-email",
|
||||||
},
|
},
|
||||||
"template_file_provenances": {
|
{
|
||||||
"provisioned-template": "api",
|
"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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "provisioned-contact-point",
|
||||||
},
|
},
|
||||||
"template_files": {
|
{
|
||||||
"custom-email": "{{ define "custom-email" }} Custom email template {{ end }}",
|
"grafana_managed_receiver_configs": [
|
||||||
"provisioned-template": "{{ define "provisioned-template" }} Custom provisioned template {{ end }}",
|
{
|
||||||
"slack-template": "{{ define "slack-template" }} Custom slack template {{ end }}",
|
"disableResolveMessage": false,
|
||||||
"template with spaces": "{{ define "template with spaces" }} Custom template with spaces in the name {{ end }}",
|
"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": [
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "Slack with multiple channels",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"grafana_managed_receiver_configs": [
|
||||||
|
{
|
||||||
|
"disableResolveMessage": false,
|
||||||
|
"name": "Oncall-integration",
|
||||||
|
"settings": {
|
||||||
|
"url": "https://oncall-endpoint.example.com",
|
||||||
|
},
|
||||||
|
"type": "oncall",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "OnCall Conctact point",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"grafana_managed_receiver_configs": [
|
||||||
|
{
|
||||||
|
"disableResolveMessage": false,
|
||||||
|
"name": "my new receiver",
|
||||||
|
"secureSettings": {},
|
||||||
|
"settings": {
|
||||||
|
"addresses": "tester@grafana.com",
|
||||||
|
"singleEmail": false,
|
||||||
|
},
|
||||||
|
"type": "email",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"name": "my new receiver",
|
||||||
|
},
|
||||||
|
],
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -2,6 +2,7 @@ import { ReactNode } from 'react';
|
|||||||
import { render, screen, userEvent } from 'test/test-utils';
|
import { render, screen, userEvent } from 'test/test-utils';
|
||||||
|
|
||||||
import { CodeEditorProps } from '@grafana/ui/src/components/Monaco/types';
|
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 { setupMswServer } from 'app/features/alerting/unified/mockApi';
|
||||||
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
import { grantUserPermissions } from 'app/features/alerting/unified/mocks';
|
||||||
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||||
@ -98,7 +99,7 @@ describe('TemplatesPicker', () => {
|
|||||||
const input = screen.getByRole('combobox');
|
const input = screen.getByRole('combobox');
|
||||||
expect(screen.queryByText('slack-template')).not.toBeInTheDocument();
|
expect(screen.queryByText('slack-template')).not.toBeInTheDocument();
|
||||||
await userEvent.click(input);
|
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' });
|
const template = screen.getByRole('option', { name: 'slack-template' });
|
||||||
await userEvent.click(template);
|
await userEvent.click(template);
|
||||||
expect(screen.getByText('slack-template')).toBeInTheDocument();
|
expect(screen.getByText('slack-template')).toBeInTheDocument();
|
||||||
|
@ -284,6 +284,11 @@
|
|||||||
"ofQuery": {
|
"ofQuery": {
|
||||||
"To": "TO"
|
"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": {
|
"annotations": {
|
||||||
|
@ -284,6 +284,11 @@
|
|||||||
"ofQuery": {
|
"ofQuery": {
|
||||||
"To": "ŦØ"
|
"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": {
|
"annotations": {
|
||||||
|
Loading…
Reference in New Issue
Block a user