mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: useProduceNewAlertmanagerConfiguration for notification templates (#95290)
This commit is contained in:
parent
33b4c71cb2
commit
f0c57622f8
@ -1,17 +1,20 @@
|
|||||||
import { produce } from 'immer';
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { Validate } from 'react-hook-form';
|
import { Validate } from 'react-hook-form';
|
||||||
|
|
||||||
import { useDispatch } from 'app/types';
|
|
||||||
|
|
||||||
import { AlertManagerCortexConfig } from '../../../../../plugins/datasource/alertmanager/types';
|
import { AlertManagerCortexConfig } from '../../../../../plugins/datasource/alertmanager/types';
|
||||||
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
import { alertmanagerApi } from '../../api/alertmanagerApi';
|
||||||
import { templatesApi } from '../../api/templateApi';
|
import { templatesApi } from '../../api/templateApi';
|
||||||
|
import { useAsync } from '../../hooks/useAsync';
|
||||||
|
import { useProduceNewAlertmanagerConfiguration } from '../../hooks/useProduceNewAlertmanagerConfig';
|
||||||
import {
|
import {
|
||||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TemplateGroup,
|
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TemplateGroup,
|
||||||
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TemplateGroupList,
|
ComGithubGrafanaGrafanaPkgApisAlertingNotificationsV0Alpha1TemplateGroupList,
|
||||||
} from '../../openapi/templatesApi.gen';
|
} from '../../openapi/templatesApi.gen';
|
||||||
import { updateAlertManagerConfigAction } from '../../state/actions';
|
import {
|
||||||
|
addNotificationTemplateAction,
|
||||||
|
deleteNotificationTemplateAction,
|
||||||
|
updateNotificationTemplateAction,
|
||||||
|
} from '../../reducers/alertmanager/notificationTemplates';
|
||||||
import { K8sAnnotations, PROVENANCE_NONE } from '../../utils/k8s/constants';
|
import { K8sAnnotations, PROVENANCE_NONE } from '../../utils/k8s/constants';
|
||||||
import { getAnnotation, getK8sNamespace, shouldUseK8sApi } from '../../utils/k8s/utils';
|
import { getAnnotation, getK8sNamespace, shouldUseK8sApi } from '../../utils/k8s/utils';
|
||||||
import { ensureDefine } from '../../utils/templates';
|
import { ensureDefine } from '../../utils/templates';
|
||||||
@ -30,6 +33,7 @@ export interface NotificationTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { useGetAlertmanagerConfigurationQuery, useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
|
const { useGetAlertmanagerConfigurationQuery, useLazyGetAlertmanagerConfigurationQuery } = alertmanagerApi;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
useListNamespacedTemplateGroupQuery,
|
useListNamespacedTemplateGroupQuery,
|
||||||
useLazyReadNamespacedTemplateGroupQuery,
|
useLazyReadNamespacedTemplateGroupQuery,
|
||||||
@ -146,39 +150,17 @@ interface CreateTemplateParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useCreateNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
export function useCreateNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const [fetchAmConfig] = useLazyGetAlertmanagerConfigurationQuery();
|
|
||||||
const [createNamespacedTemplateGroup] = useCreateNamespacedTemplateGroupMutation();
|
const [createNamespacedTemplateGroup] = useCreateNamespacedTemplateGroupMutation();
|
||||||
|
const [updateAlertmanagerConfiguration] = useProduceNewAlertmanagerConfiguration();
|
||||||
|
|
||||||
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
||||||
|
|
||||||
async function createUsingConfigFileApi({ templateValues }: CreateTemplateParams) {
|
const createUsingConfigFileApi = useAsync(({ templateValues }: CreateTemplateParams) => {
|
||||||
const amConfig = await fetchAmConfig(alertmanager).unwrap();
|
const action = addNotificationTemplateAction({ template: templateValues });
|
||||||
// wrap content in "define" if it's not already wrapped, in case user did not do it/
|
return updateAlertmanagerConfiguration(action);
|
||||||
// it's not obvious that this is needed for template to work
|
});
|
||||||
const content = ensureDefine(templateValues.title, templateValues.content);
|
|
||||||
|
|
||||||
const targetTemplateExists = amConfig.template_files?.[templateValues.title] !== undefined;
|
const createUsingK8sApi = useAsync(({ templateValues }: CreateTemplateParams) => {
|
||||||
if (targetTemplateExists) {
|
|
||||||
throw new Error('target template already exists');
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedConfig = produce(amConfig, (draft) => {
|
|
||||||
draft.template_files[templateValues.title] = content;
|
|
||||||
draft.alertmanager_config.templates = [...(draft.alertmanager_config.templates ?? []), templateValues.title];
|
|
||||||
});
|
|
||||||
|
|
||||||
return dispatch(
|
|
||||||
updateAlertManagerConfigAction({
|
|
||||||
alertManagerSourceName: alertmanager,
|
|
||||||
newConfig: updatedConfig,
|
|
||||||
oldConfig: amConfig,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createUsingK8sApi({ templateValues }: CreateTemplateParams) {
|
|
||||||
const content = ensureDefine(templateValues.title, templateValues.content);
|
const content = ensureDefine(templateValues.title, templateValues.content);
|
||||||
|
|
||||||
return createNamespacedTemplateGroup({
|
return createNamespacedTemplateGroup({
|
||||||
@ -188,7 +170,7 @@ export function useCreateNotificationTemplate({ alertmanager }: BaseAlertmanager
|
|||||||
metadata: {},
|
metadata: {},
|
||||||
},
|
},
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
});
|
||||||
|
|
||||||
return k8sApiSupported ? createUsingK8sApi : createUsingConfigFileApi;
|
return k8sApiSupported ? createUsingK8sApi : createUsingConfigFileApi;
|
||||||
}
|
}
|
||||||
@ -199,43 +181,17 @@ interface UpdateTemplateParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useUpdateNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
export function useUpdateNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
||||||
const dispatch = useDispatch();
|
|
||||||
|
|
||||||
const [fetchAmConfig] = useLazyGetAlertmanagerConfigurationQuery();
|
|
||||||
const [replaceNamespacedTemplateGroup] = useReplaceNamespacedTemplateGroupMutation();
|
const [replaceNamespacedTemplateGroup] = useReplaceNamespacedTemplateGroupMutation();
|
||||||
|
const [updateAlertmanagerConfiguration] = useProduceNewAlertmanagerConfiguration();
|
||||||
|
|
||||||
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
||||||
|
|
||||||
async function updateUsingConfigFileApi({ template, patch }: UpdateTemplateParams) {
|
const updateUsingConfigFileApi = useAsync(({ template, patch }: UpdateTemplateParams) => {
|
||||||
const amConfig = await fetchAmConfig(alertmanager).unwrap();
|
const action = updateNotificationTemplateAction({ name: template.title, template: patch });
|
||||||
// wrap content in "define" if it's not already wrapped, in case user did not do it/
|
return updateAlertmanagerConfiguration(action);
|
||||||
// it's not obvious that this is needed for template to work
|
});
|
||||||
const content = ensureDefine(patch.title, patch.content);
|
|
||||||
|
|
||||||
const originalName = template.title; // For ConfigFile API name is the same as uid
|
const updateUsingK8sApi = useAsync(({ template, patch }: UpdateTemplateParams) => {
|
||||||
const nameChanged = originalName !== patch.title;
|
|
||||||
|
|
||||||
// TODO Maybe we could simplify or extract this logic
|
|
||||||
const updatedConfig = produce(amConfig, (draft) => {
|
|
||||||
if (nameChanged) {
|
|
||||||
delete draft.template_files[originalName];
|
|
||||||
draft.alertmanager_config.templates = draft.alertmanager_config.templates?.filter((t) => t !== originalName);
|
|
||||||
}
|
|
||||||
|
|
||||||
draft.template_files[patch.title] = content;
|
|
||||||
draft.alertmanager_config.templates = [...(draft.alertmanager_config.templates ?? []), patch.title];
|
|
||||||
});
|
|
||||||
|
|
||||||
return dispatch(
|
|
||||||
updateAlertManagerConfigAction({
|
|
||||||
alertManagerSourceName: alertmanager,
|
|
||||||
newConfig: updatedConfig,
|
|
||||||
oldConfig: amConfig,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateUsingK8sApi({ template, patch }: UpdateTemplateParams) {
|
|
||||||
const content = ensureDefine(patch.title, patch.content);
|
const content = ensureDefine(patch.title, patch.content);
|
||||||
|
|
||||||
return replaceNamespacedTemplateGroup({
|
return replaceNamespacedTemplateGroup({
|
||||||
@ -246,43 +202,30 @@ export function useUpdateNotificationTemplate({ alertmanager }: BaseAlertmanager
|
|||||||
metadata: { name: template.uid },
|
metadata: { name: template.uid },
|
||||||
},
|
},
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
});
|
||||||
|
|
||||||
return k8sApiSupported ? updateUsingK8sApi : updateUsingConfigFileApi;
|
return k8sApiSupported ? updateUsingK8sApi : updateUsingConfigFileApi;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
export function useDeleteNotificationTemplate({ alertmanager }: BaseAlertmanagerArgs) {
|
||||||
const dispatch = useDispatch();
|
|
||||||
const [fetchAmConfig] = useLazyGetAlertmanagerConfigurationQuery();
|
|
||||||
const [deleteNamespacedTemplateGroup] = useDeleteNamespacedTemplateGroupMutation();
|
const [deleteNamespacedTemplateGroup] = useDeleteNamespacedTemplateGroupMutation();
|
||||||
|
const [updateAlertmanagerConfiguration] = useProduceNewAlertmanagerConfiguration();
|
||||||
|
|
||||||
async function deleteUsingConfigFileApi({ uid }: { uid: string }) {
|
const deleteUsingConfigAPI = useAsync(async ({ uid }: { uid: string }) => {
|
||||||
const amConfig = await fetchAmConfig(alertmanager).unwrap();
|
const action = deleteNotificationTemplateAction({ name: uid });
|
||||||
|
return updateAlertmanagerConfiguration(action);
|
||||||
|
});
|
||||||
|
|
||||||
const updatedConfig = produce(amConfig, (draft) => {
|
const deleteUsingK8sApi = useAsync(({ uid }: { uid: string }) => {
|
||||||
delete draft.template_files[uid];
|
|
||||||
draft.alertmanager_config.templates = draft.alertmanager_config.templates?.filter((t) => t !== uid);
|
|
||||||
});
|
|
||||||
|
|
||||||
return dispatch(
|
|
||||||
updateAlertManagerConfigAction({
|
|
||||||
alertManagerSourceName: alertmanager,
|
|
||||||
newConfig: updatedConfig,
|
|
||||||
oldConfig: amConfig,
|
|
||||||
})
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteUsingK8sApi({ uid }: { uid: string }) {
|
|
||||||
return deleteNamespacedTemplateGroup({
|
return deleteNamespacedTemplateGroup({
|
||||||
namespace: getK8sNamespace(),
|
namespace: getK8sNamespace(),
|
||||||
name: uid,
|
name: uid,
|
||||||
ioK8SApimachineryPkgApisMetaV1DeleteOptions: {},
|
ioK8SApimachineryPkgApisMetaV1DeleteOptions: {},
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
}
|
});
|
||||||
|
|
||||||
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
const k8sApiSupported = shouldUseK8sApi(alertmanager);
|
||||||
return k8sApiSupported ? deleteUsingK8sApi : deleteUsingConfigFileApi;
|
return k8sApiSupported ? deleteUsingK8sApi : deleteUsingConfigAPI;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ValidateNotificationTemplateParams {
|
interface ValidateNotificationTemplateParams {
|
||||||
|
@ -10,18 +10,18 @@ import { GrafanaTheme2 } from '@grafana/data';
|
|||||||
import { isFetchError, locationService } from '@grafana/runtime';
|
import { isFetchError, locationService } from '@grafana/runtime';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
|
Drawer,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
FieldSet,
|
FieldSet,
|
||||||
|
InlineField,
|
||||||
Input,
|
Input,
|
||||||
LinkButton,
|
LinkButton,
|
||||||
Menu,
|
Menu,
|
||||||
useStyles2,
|
|
||||||
Stack,
|
Stack,
|
||||||
useSplitter,
|
useSplitter,
|
||||||
Drawer,
|
useStyles2,
|
||||||
InlineField,
|
|
||||||
Box,
|
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||||
import { useCleanup } from 'app/core/hooks/useCleanup';
|
import { useCleanup } from 'app/core/hooks/useCleanup';
|
||||||
@ -93,8 +93,8 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
|||||||
|
|
||||||
const appNotification = useAppNotification();
|
const appNotification = useAppNotification();
|
||||||
|
|
||||||
const createNewTemplate = useCreateNotificationTemplate({ alertmanager });
|
const [createNewTemplate] = useCreateNotificationTemplate({ alertmanager });
|
||||||
const updateTemplate = useUpdateNotificationTemplate({ alertmanager });
|
const [updateTemplate] = useUpdateNotificationTemplate({ alertmanager });
|
||||||
const { titleIsUnique } = useValidateNotificationTemplate({ alertmanager, originalTemplate });
|
const { titleIsUnique } = useValidateNotificationTemplate({ alertmanager, originalTemplate });
|
||||||
|
|
||||||
useCleanup((state) => (state.unifiedAlerting.saveAMConfig = initialAsyncRequestState));
|
useCleanup((state) => (state.unifiedAlerting.saveAMConfig = initialAsyncRequestState));
|
||||||
@ -149,9 +149,9 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props)
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (!originalTemplate) {
|
if (!originalTemplate) {
|
||||||
await createNewTemplate({ templateValues: values });
|
await createNewTemplate.execute({ templateValues: values });
|
||||||
} else {
|
} else {
|
||||||
await updateTemplate({ template: originalTemplate, patch: values });
|
await updateTemplate.execute({ template: originalTemplate, patch: values });
|
||||||
}
|
}
|
||||||
appNotification.success('Template saved', `Template ${values.title} has been saved`);
|
appNotification.success('Template saved', `Template ${values.title} has been saved`);
|
||||||
locationService.push(returnLink);
|
locationService.push(returnLink);
|
||||||
|
@ -30,7 +30,7 @@ interface Props {
|
|||||||
|
|
||||||
export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
|
export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
|
||||||
const appNotification = useAppNotification();
|
const appNotification = useAppNotification();
|
||||||
const deleteTemplate = useDeleteNotificationTemplate({ alertmanager: alertManagerName });
|
const [deleteTemplate] = useDeleteNotificationTemplate({ alertmanager: alertManagerName });
|
||||||
|
|
||||||
const tableStyles = useStyles2(getAlertTableStyles);
|
const tableStyles = useStyles2(getAlertTableStyles);
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export const TemplatesTable = ({ alertManagerName, templates }: Props) => {
|
|||||||
const onDeleteTemplate = async () => {
|
const onDeleteTemplate = async () => {
|
||||||
if (templateToDelete) {
|
if (templateToDelete) {
|
||||||
try {
|
try {
|
||||||
await deleteTemplate({ uid: templateToDelete.uid });
|
await deleteTemplate.execute({ uid: templateToDelete.uid });
|
||||||
appNotification.success('Template deleted', `Template ${templateToDelete.title} has been deleted`);
|
appNotification.success('Template deleted', `Template ${templateToDelete.title} has been deleted`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
appNotification.error('Error deleting template', `Error deleting template ${templateToDelete.title}`);
|
appNotification.error('Error deleting template', `Error deleting template ${templateToDelete.title}`);
|
||||||
|
@ -5,6 +5,7 @@ import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/ty
|
|||||||
|
|
||||||
import { alertmanagerApi } from '../api/alertmanagerApi';
|
import { alertmanagerApi } from '../api/alertmanagerApi';
|
||||||
import { muteTimingsReducer } from '../reducers/alertmanager/muteTimings';
|
import { muteTimingsReducer } from '../reducers/alertmanager/muteTimings';
|
||||||
|
import { notificationTemplatesReducer } from '../reducers/alertmanager/notificationTemplates';
|
||||||
import { receiversReducer } from '../reducers/alertmanager/receivers';
|
import { receiversReducer } from '../reducers/alertmanager/receivers';
|
||||||
import { useAlertmanager } from '../state/AlertmanagerContext';
|
import { useAlertmanager } from '../state/AlertmanagerContext';
|
||||||
|
|
||||||
@ -26,7 +27,12 @@ export const initialAlertmanagerConfiguration: AlertManagerCortexConfig = {
|
|||||||
template_files: {},
|
template_files: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const configurationReducer = reduceReducers(initialAlertmanagerConfiguration, muteTimingsReducer, receiversReducer);
|
const configurationReducer = reduceReducers(
|
||||||
|
initialAlertmanagerConfiguration,
|
||||||
|
muteTimingsReducer,
|
||||||
|
receiversReducer,
|
||||||
|
notificationTemplatesReducer
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This hook will make sure we are always applying actions that mutate the Alertmanager configuration
|
* This hook will make sure we are always applying actions that mutate the Alertmanager configuration
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`notification templates should add a new notification template 1`] = `
|
||||||
|
{
|
||||||
|
"alertmanager_config": {
|
||||||
|
"templates": [
|
||||||
|
"foo",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"template_files": {
|
||||||
|
"foo": "{{ define "foo" }}
|
||||||
|
foo
|
||||||
|
{{ end }}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`notification templates should allow renaming a notification template 1`] = `
|
||||||
|
{
|
||||||
|
"alertmanager_config": {
|
||||||
|
"templates": [
|
||||||
|
"rename-me-copy",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"template_files": {
|
||||||
|
"rename-me-copy": "{{ define "rename-me-copy" }}
|
||||||
|
rename me, please
|
||||||
|
{{ end }}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`notification templates should remove a notification template 1`] = `
|
||||||
|
{
|
||||||
|
"alertmanager_config": {
|
||||||
|
"templates": [],
|
||||||
|
},
|
||||||
|
"template_files": {},
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`notification templates should update a notification template without renaming 1`] = `
|
||||||
|
{
|
||||||
|
"alertmanager_config": {
|
||||||
|
"templates": [
|
||||||
|
"update-me",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"template_files": {
|
||||||
|
"update-me": "{{ define "update-me" }}
|
||||||
|
update me, please
|
||||||
|
{{ end }}",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
@ -0,0 +1,119 @@
|
|||||||
|
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
|
import { TemplateFormValues } from '../../components/receivers/TemplateForm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
addNotificationTemplateAction,
|
||||||
|
deleteNotificationTemplateAction,
|
||||||
|
notificationTemplatesReducer,
|
||||||
|
updateNotificationTemplateAction,
|
||||||
|
} from './notificationTemplates';
|
||||||
|
|
||||||
|
describe('notification templates', () => {
|
||||||
|
it('should add a new notification template', () => {
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {},
|
||||||
|
template_files: {},
|
||||||
|
};
|
||||||
|
const newNotificationTemplate: TemplateFormValues = {
|
||||||
|
title: 'foo',
|
||||||
|
content: 'foo',
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = addNotificationTemplateAction({ template: newNotificationTemplate });
|
||||||
|
expect(notificationTemplatesReducer(initialConfig, action)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add a new notification template if the name already exists', () => {
|
||||||
|
const name = 'existing';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: { templates: [name] },
|
||||||
|
template_files: {
|
||||||
|
[name]: 'foo',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const newNotificationTemplate: TemplateFormValues = {
|
||||||
|
title: name,
|
||||||
|
content: 'foo',
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = addNotificationTemplateAction({ template: newNotificationTemplate });
|
||||||
|
expect(() => notificationTemplatesReducer(initialConfig, action)).toThrow(/already exists/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update a notification template without renaming', () => {
|
||||||
|
const name = 'update-me';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
templates: [name],
|
||||||
|
},
|
||||||
|
template_files: {
|
||||||
|
[name]: 'update me',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = updateNotificationTemplateAction({ name, template: { title: name, content: 'update me, please' } });
|
||||||
|
expect(notificationTemplatesReducer(initialConfig, action)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update if target does not exist', () => {
|
||||||
|
const name = 'update-me';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {},
|
||||||
|
template_files: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = updateNotificationTemplateAction({ name, template: { title: name, content: 'update me, please' } });
|
||||||
|
expect(() => notificationTemplatesReducer(initialConfig, action)).toThrow(/did not find it/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update if renaming and new template name exist', () => {
|
||||||
|
const name = 'rename-me';
|
||||||
|
const name2 = 'rename-me-2';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
templates: [name, name2],
|
||||||
|
},
|
||||||
|
template_files: {
|
||||||
|
[name]: 'foo',
|
||||||
|
[name2]: 'bar',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = updateNotificationTemplateAction({ name, template: { title: name2, content: 'foo' } });
|
||||||
|
expect(() => notificationTemplatesReducer(initialConfig, action)).toThrow(/duplicate/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow renaming a notification template', () => {
|
||||||
|
const name = 'rename-me';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
templates: [name],
|
||||||
|
},
|
||||||
|
template_files: {
|
||||||
|
[name]: 'rename me',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = updateNotificationTemplateAction({
|
||||||
|
name,
|
||||||
|
template: { title: 'rename-me-copy', content: 'rename me, please' },
|
||||||
|
});
|
||||||
|
expect(notificationTemplatesReducer(initialConfig, action)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove a notification template', () => {
|
||||||
|
const name = 'delete-me';
|
||||||
|
const initialConfig: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {
|
||||||
|
templates: [name],
|
||||||
|
},
|
||||||
|
template_files: {
|
||||||
|
[name]: 'delete me please',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const action = deleteNotificationTemplateAction({ name });
|
||||||
|
expect(notificationTemplatesReducer(initialConfig, action)).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,83 @@
|
|||||||
|
import { createAction, createReducer } from '@reduxjs/toolkit';
|
||||||
|
import { remove, toArray, unset } from 'lodash';
|
||||||
|
|
||||||
|
import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/types';
|
||||||
|
|
||||||
|
import { TemplateFormValues } from '../../components/receivers/TemplateForm';
|
||||||
|
import { ensureDefine } from '../../utils/templates';
|
||||||
|
|
||||||
|
export const addNotificationTemplateAction = createAction<{ template: TemplateFormValues }>('notificationTemplate/add');
|
||||||
|
export const updateNotificationTemplateAction = createAction<{
|
||||||
|
name: string;
|
||||||
|
template: TemplateFormValues;
|
||||||
|
}>('notificationTemplate/update');
|
||||||
|
export const deleteNotificationTemplateAction = createAction<{ name: string }>('notificationTemplate/delete');
|
||||||
|
|
||||||
|
const initialState: AlertManagerCortexConfig = {
|
||||||
|
alertmanager_config: {},
|
||||||
|
template_files: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This reducer will manage action related to notification templates and make sure all operations on the alertmanager
|
||||||
|
* configuration happen immutably and only mutate what they need.
|
||||||
|
*/
|
||||||
|
export const notificationTemplatesReducer = createReducer(initialState, (builder) => {
|
||||||
|
builder
|
||||||
|
.addCase(addNotificationTemplateAction, (draft, { payload }) => {
|
||||||
|
const { alertmanager_config = {}, template_files = {} } = draft;
|
||||||
|
const { template } = payload;
|
||||||
|
|
||||||
|
const targetTemplateExists = template_files[template.title] !== undefined;
|
||||||
|
if (targetTemplateExists) {
|
||||||
|
throw new Error('target template already exists');
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap content in "define" if it's not already wrapped, in case user did not do it/
|
||||||
|
// it's not obvious that this is needed for template to work
|
||||||
|
const content = ensureDefine(template.title, template.content);
|
||||||
|
|
||||||
|
// add the template to the list of template files
|
||||||
|
template_files[template.title] = content;
|
||||||
|
|
||||||
|
// add the template to the alertmanager_config
|
||||||
|
alertmanager_config.templates = toArray(alertmanager_config.templates).concat(template.title);
|
||||||
|
})
|
||||||
|
.addCase(updateNotificationTemplateAction, (draft, { payload }) => {
|
||||||
|
const { alertmanager_config = {}, template_files = {} } = draft;
|
||||||
|
const { name, template } = payload;
|
||||||
|
const renaming = name !== template.title;
|
||||||
|
|
||||||
|
const targetExists = template_files[name] !== undefined;
|
||||||
|
if (!targetExists) {
|
||||||
|
throw new Error(`Expected notification template ${name} to exist, but did not find it in the config`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap content in "define" if it's not already wrapped, in case user did not do it/
|
||||||
|
// it's not obvious that this is needed for template to work
|
||||||
|
const content = ensureDefine(template.title, template.content);
|
||||||
|
|
||||||
|
if (renaming) {
|
||||||
|
const oldName = name;
|
||||||
|
const newName = template.title;
|
||||||
|
|
||||||
|
const targetExists = template_files[newName] !== undefined;
|
||||||
|
if (targetExists) {
|
||||||
|
throw new Error(`Duplicate template name ${newName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
unset(template_files, oldName);
|
||||||
|
remove(alertmanager_config.templates ?? [], (templateName) => templateName === oldName);
|
||||||
|
alertmanager_config.templates = toArray(alertmanager_config.templates).concat(template.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
template_files[template.title] = content;
|
||||||
|
})
|
||||||
|
.addCase(deleteNotificationTemplateAction, (draft, { payload }) => {
|
||||||
|
const { name } = payload;
|
||||||
|
const { alertmanager_config = {}, template_files = {} } = draft;
|
||||||
|
|
||||||
|
unset(template_files, name);
|
||||||
|
remove(alertmanager_config.templates ?? [], (templateName) => templateName === name);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user