Alerting: Fix external Alertmanager settings payload (#86413)

* Remove helper properties from the AM config object

* Make the omitTemporaryIdentifiers a pure function

* Add formValuesToCloudReceiver test

* Remove immer produce to prevent object freezing
This commit is contained in:
Konrad Lalik 2024-04-18 15:55:13 +02:00 committed by GitHub
parent 817f787947
commit 344cea1725
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 162 additions and 3 deletions

View File

@ -1,8 +1,17 @@
import 'core-js/stable/structured-clone';
import { NotifierDTO } from 'app/types';
import { GrafanaChannelValues, ReceiverFormValues } from '../types/receiver-form';
import { Receiver } from '../../../../plugins/datasource/alertmanager/types';
import { CloudChannelValues, GrafanaChannelValues, ReceiverFormValues } from '../types/receiver-form';
import { formValuesToGrafanaReceiver, omitEmptyValues, omitEmptyUnlessExisting } from './receiver-form';
import {
formValuesToGrafanaReceiver,
omitEmptyValues,
omitEmptyUnlessExisting,
omitTemporaryIdentifiers,
formValuesToCloudReceiver,
} from './receiver-form';
describe('Receiver form utils', () => {
describe('omitEmptyStringValues', () => {
@ -67,6 +76,91 @@ describe('Receiver form utils', () => {
expect(omitEmptyUnlessExisting(original, existing)).toEqual(expected);
});
});
describe('omitTemporaryIdentifiers', () => {
it('should remove __id from the root object', () => {
const original = {
__id: '1',
foo: 'bar',
};
const expected = {
foo: 'bar',
};
expect(omitTemporaryIdentifiers(original)).toEqual(expected);
});
it('should remove __id from nested objects', () => {
const original = {
foo: 'bar',
nested: {
__id: '1',
baz: 'qux',
doubleNested: { __id: '2', url: 'example.com' },
},
};
const expected = {
foo: 'bar',
nested: {
baz: 'qux',
doubleNested: { url: 'example.com' },
},
};
expect(omitTemporaryIdentifiers(original)).toEqual(expected);
});
it('should remove __id from objects in an array', () => {
const original = {
foo: 'bar',
array: [
{
__id: '1',
baz: 'qux',
actions: [
{ __id: '3', type: 'email' },
{ __id: '4', type: 'slack' },
],
},
{ __id: '2', quux: 'quuz' },
],
};
const expected = {
foo: 'bar',
array: [
{
baz: 'qux',
actions: [{ type: 'email' }, { type: 'slack' }],
},
{
quux: 'quuz',
},
],
};
expect(omitTemporaryIdentifiers(original)).toEqual(expected);
});
it('should return a new object and keep the original intact', () => {
const original = {
foo: 'bar',
nested: {
__id: '1',
baz: 'qux',
doubleNested: { __id: '2', url: 'example.com' },
},
};
const withOmitted = omitTemporaryIdentifiers(original);
expect(withOmitted).not.toBe(original);
expect(original.nested.__id).toBe('1');
expect(original.nested.doubleNested.__id).toBe('2');
});
});
});
describe('formValuesToGrafanaReceiver', () => {
@ -111,3 +205,50 @@ describe('formValuesToGrafanaReceiver', () => {
expect(formValuesToGrafanaReceiver(formValues, channelMap, {}, notifiers)).toMatchSnapshot();
});
});
describe('formValuesToCloudReceiver', () => {
it('should remove temporary ids from receivers and settings', () => {
const formValues: ReceiverFormValues<CloudChannelValues> = {
name: 'my-receiver',
items: [
{
__id: '1',
type: 'slack',
settings: {
url: 'https://slack.example.com/',
actions: [{ __id: '2', text: 'Acknowledge', type: 'button' }],
fields: [{ __id: '10', title: 'priority', value: '1' }],
},
secureFields: {},
secureSettings: {},
sendResolved: true,
},
],
};
const defaults: CloudChannelValues = {
__id: '1',
type: 'slack',
settings: {
url: 'https://slack.example.com/',
},
secureFields: {},
secureSettings: {},
sendResolved: true,
};
const expected: Receiver = {
name: 'my-receiver',
slack_configs: [
{
url: 'https://slack.example.com/',
actions: [{ text: 'Acknowledge', type: 'button' }],
fields: [{ title: 'priority', value: '1' }],
send_resolved: true,
},
],
};
expect(formValuesToCloudReceiver(formValues, defaults)).toEqual(expected);
});
});

View File

@ -108,7 +108,7 @@ export function formValuesToCloudReceiver(
};
values.items.forEach(({ __id, type, settings, sendResolved }) => {
const channel = omitEmptyValues({
...settings,
...omitTemporaryIdentifiers(settings),
send_resolved: sendResolved ?? defaults.sendResolved,
});
@ -292,3 +292,21 @@ export function omitEmptyValues<T>(obj: T): T {
export function omitEmptyUnlessExisting(settings = {}, existing = {}): Record<string, unknown> {
return omitBy(settings, (value, key) => isUnacceptableValue(value) && !(key in existing));
}
export function omitTemporaryIdentifiers<T>(object: Readonly<T>): T {
function omitIdentifiers<T>(obj: T) {
if (isArray(obj)) {
obj.forEach(omitIdentifiers);
} else if (typeof obj === 'object' && obj !== null) {
if ('__id' in obj) {
delete obj.__id;
}
Object.values(obj).forEach(omitIdentifiers);
}
}
const objectCopy = structuredClone(object);
omitIdentifiers(objectCopy);
return objectCopy;
}