diff --git a/public/app/features/alerting/unified/mockGrafanaNotifiers.ts b/public/app/features/alerting/unified/mockGrafanaNotifiers.ts index 58c20a86262..ce0c4b012aa 100644 --- a/public/app/features/alerting/unified/mockGrafanaNotifiers.ts +++ b/public/app/features/alerting/unified/mockGrafanaNotifiers.ts @@ -1,7 +1,7 @@ -import { NotifierDTO } from 'app/types'; +import { GrafanaNotifierType, NotifierDTO } from 'app/types'; -export const grafanaAlertNotifiersMock: NotifierDTO[] = [ - { +export const grafanaAlertNotifiers: Record = { + dingding: { type: 'dingding', name: 'DingDing', heading: 'DingDing settings', @@ -87,7 +87,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + kafka: { type: 'kafka', name: 'Kafka REST Proxy', heading: 'Kafka settings', @@ -242,7 +242,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + email: { type: 'email', name: 'Email', heading: 'Email settings', @@ -320,7 +320,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + pagerduty: { type: 'pagerduty', name: 'PagerDuty', heading: 'PagerDuty settings', @@ -500,7 +500,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + victorops: { type: 'victorops', name: 'VictorOps', heading: 'VictorOps settings', @@ -586,7 +586,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + pushover: { type: 'pushover', name: 'Pushover', heading: 'Pushover settings', @@ -1011,7 +1011,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + slack: { type: 'slack', name: 'Slack', heading: 'Slack settings', @@ -1242,7 +1242,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + sensugo: { type: 'sensugo', name: 'Sensu Go', heading: 'Sensu Go Settings', @@ -1370,7 +1370,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + teams: { type: 'teams', name: 'Microsoft Teams', heading: 'Teams settings', @@ -1447,7 +1447,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + telegram: { type: 'telegram', name: 'Telegram', heading: 'Telegram API settings', @@ -1592,7 +1592,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + webhook: { type: 'webhook', name: 'Webhook', heading: 'Webhook settings', @@ -1765,7 +1765,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + oncall: { type: 'oncall', name: 'Grafana OnCall', heading: 'Grafana OnCall settings', @@ -1938,7 +1938,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + wecom: { type: 'wecom', name: 'WeCom', heading: 'WeCom settings', @@ -2092,7 +2092,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + 'prometheus-alertmanager': { type: 'prometheus-alertmanager', name: 'Alertmanager', heading: 'Alertmanager Settings', @@ -2152,7 +2152,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + discord: { type: 'discord', name: 'Discord', heading: 'Discord settings', @@ -2247,7 +2247,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + googlechat: { type: 'googlechat', name: 'Google Hangouts Chat', heading: 'Google Hangouts Chat settings', @@ -2268,7 +2268,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, required: true, validationRule: '', - secure: false, + secure: true, dependsOn: '', }, { @@ -2307,7 +2307,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + LINE: { type: 'LINE', name: 'LINE', heading: 'LINE notify settings', @@ -2367,7 +2367,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + threema: { type: 'threema', name: 'Threema Gateway', heading: 'Threema Gateway settings', @@ -2461,7 +2461,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + mqtt: { type: 'mqtt', name: 'MQTT', heading: 'MQTT settings', @@ -2733,7 +2733,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + opsgenie: { type: 'opsgenie', name: 'OpsGenie', heading: 'OpsGenie settings', @@ -2874,7 +2874,7 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, - { + webex: { type: 'webex', name: 'Cisco Webex Teams', heading: 'Webex settings', @@ -2951,4 +2951,254 @@ export const grafanaAlertNotifiersMock: NotifierDTO[] = [ }, ], }, -]; + sns: { + type: 'sns', + name: 'AWS SNS', + heading: 'Webex settings', + description: 'Sends notifications to AWS Simple Notification Service', + info: '', + options: [ + { + element: 'input', + inputType: 'text', + label: 'The Amazon SNS API URL', + description: '', + placeholder: '', + propertyName: 'api_url', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'subform', + inputType: '', + label: 'SigV4 Authentication', + description: "Configures AWS's Signature Verification 4 signing process to sign requests", + placeholder: '', + propertyName: 'sigv4', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: [ + { + element: 'input', + inputType: 'text', + label: 'Region', + description: 'The AWS region. If blank, the region from the default credentials chain is used.', + placeholder: '', + propertyName: 'region', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Access Key', + description: 'The AWS API access key.', + placeholder: '', + propertyName: 'access_key', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: true, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Secret Key', + description: 'The AWS API secret key.', + placeholder: '', + propertyName: 'secret_key', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: true, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Profile', + description: 'Named AWS profile used to authenticate', + placeholder: '', + propertyName: 'profile', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Role ARN', + description: 'AWS Role ARN, an alternative to using AWS API keys', + placeholder: '', + propertyName: 'role_arn', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + ], + }, + { + element: 'input', + inputType: 'text', + label: 'SNS topic ARN', + description: + "If you don't specify this value, you must specify a value for the phone_number or target_arn. If you are using a FIFO SNS topic you should set a message group interval longer than 5 minutes to prevent messages with the same group key being deduplicated by the SNS default deduplication window.", + placeholder: '', + propertyName: 'topic_arn', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Phone number', + description: + "Phone number if message is delivered via SMS in E.164 format. If you don't specify this value, you must specify a value for the topic_arn or target_arn", + placeholder: '', + propertyName: 'phone_number', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Target ARN', + description: + "The mobile platform endpoint ARN if message is delivered via mobile notifications. If you don't specify this value, you must specify a value for the topic_arn or phone_number", + placeholder: '', + propertyName: 'target_arn', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'input', + inputType: 'text', + label: 'Subject', + description: 'Optional subject. You can use templates to customize this field', + placeholder: '{{ template "default.title" . }}', + propertyName: 'subject', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'textarea', + inputType: '', + label: 'Message', + description: + 'Optional message. You can use templates to customize this field. Using a custom message will replace the default message', + placeholder: '{{ template "default.message" . }}', + propertyName: 'message', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + { + element: 'key_value_map', + inputType: 'text', + label: 'Attributes', + description: 'SNS message attributes', + placeholder: '', + propertyName: 'attributes', + selectOptions: null, + showWhen: { + field: '', + is: '', + }, + required: false, + validationRule: '', + secure: false, + dependsOn: '', + subformOptions: undefined, + }, + ], + }, +}; + +export const grafanaAlertNotifiersMock: NotifierDTO[] = Object.values(grafanaAlertNotifiers); diff --git a/public/app/features/alerting/unified/utils/receiver-form.test.ts b/public/app/features/alerting/unified/utils/receiver-form.test.ts index 06ee33629a2..0c65cef3de9 100644 --- a/public/app/features/alerting/unified/utils/receiver-form.test.ts +++ b/public/app/features/alerting/unified/utils/receiver-form.test.ts @@ -2,7 +2,8 @@ import 'core-js/stable/structured-clone'; import { NotifierDTO } from 'app/types'; -import { Receiver } from '../../../../plugins/datasource/alertmanager/types'; +import { GrafanaManagedContactPoint, Receiver } from '../../../../plugins/datasource/alertmanager/types'; +import { grafanaAlertNotifiers, grafanaAlertNotifiersMock } from '../mockGrafanaNotifiers'; import { CloudChannelValues, GrafanaChannelValues, ReceiverFormValues } from '../types/receiver-form'; import { @@ -11,6 +12,7 @@ import { omitEmptyUnlessExisting, omitTemporaryIdentifiers, formValuesToCloudReceiver, + grafanaReceiverToFormValues, } from './receiver-form'; describe('Receiver form utils', () => { @@ -252,3 +254,78 @@ describe('formValuesToCloudReceiver', () => { expect(formValuesToCloudReceiver(formValues, defaults)).toEqual(expected); }); }); + +describe('grafanaReceiverToFormValues', () => { + const { googlechat, slack, sns } = grafanaAlertNotifiers; + + it('should convert fields from settings and secureFields', () => { + const slackReceiver: GrafanaManagedContactPoint = { + name: 'slack-receiver', + grafana_managed_receiver_configs: [ + { + type: slack.type, + settings: { + recipient: '#alerting-ops', + }, + secureFields: { + token: true, + }, + }, + ], + }; + + const [formValues, _] = grafanaReceiverToFormValues(slackReceiver, grafanaAlertNotifiersMock); + expect(formValues.items[0].type).toBe(slack.type); + expect(formValues.items[0].settings.recipient).toBe('#alerting-ops'); + expect(formValues.items[0].secureFields.token).toBe(true); + expect(formValues.items[0].secureSettings).toEqual({}); + }); + + it('should convert nested settings and secureFields', () => { + const snsReceiver: GrafanaManagedContactPoint = { + name: 'sns-receiver', + grafana_managed_receiver_configs: [ + { + type: sns.type, + settings: { + api_url: 'https://sns.example.com/', + phone_number: '+1234567890', + sigv4: { region: 'us-east-1' }, + }, + secureFields: { + 'sigv4.access_key': true, + 'sigv4.secret_key': true, + }, + }, + ], + }; + + const [formValues, _] = grafanaReceiverToFormValues(snsReceiver, grafanaAlertNotifiersMock); + + expect(formValues.items[0].settings.api_url).toBe('https://sns.example.com/'); + expect(formValues.items[0].settings.phone_number).toBe('+1234567890'); + expect(formValues.items[0].settings.sigv4.region).toBe('us-east-1'); + expect(formValues.items[0].secureFields['sigv4.access_key']).toBe(true); + expect(formValues.items[0].secureFields['sigv4.secret_key']).toBe(true); + }); + + // Some receivers have migrated options that are now marked as secure but were standard fields in the past + // We need to handle the case where the field is still present in settings but marked as secure + it('should convert fields from settings to secureSettings for migrated options', () => { + const googleChatReceiver: GrafanaManagedContactPoint = { + name: 'googlechat-receiver', + grafana_managed_receiver_configs: [ + { + type: googlechat.type, + settings: { + url: 'https://googlechat.example.com/', + }, + }, + ], + }; + + const [formValues, _] = grafanaReceiverToFormValues(googleChatReceiver, grafanaAlertNotifiersMock); + expect(formValues.items[0].secureSettings.url).toBe('https://googlechat.example.com/'); + expect(formValues.items[0].settings.url).toBeUndefined(); + }); +}); diff --git a/public/app/features/alerting/unified/utils/receiver-form.ts b/public/app/features/alerting/unified/utils/receiver-form.ts index 4ecfcd05144..283ac3e81b1 100644 --- a/public/app/features/alerting/unified/utils/receiver-form.ts +++ b/public/app/features/alerting/unified/utils/receiver-form.ts @@ -151,6 +151,13 @@ function grafanaChannelConfigToFormChannelValues( disableResolveMessage: channel.disableResolveMessage, }; + notifier?.options.forEach((option) => { + if (option.secure && values.settings[option.propertyName]) { + values.secureSettings[option.propertyName] = values.settings[option.propertyName]; + delete values.settings[option.propertyName]; + } + }); + return values; } diff --git a/public/app/types/alerting.ts b/public/app/types/alerting.ts index ac0ad119cce..ac598691607 100644 --- a/public/app/types/alerting.ts +++ b/public/app/types/alerting.ts @@ -38,9 +38,7 @@ export interface AlertRule { export type GrafanaNotifierType = | 'discord' - | 'hipchat' | 'email' - | 'sensu' | 'sensugo' | 'googlechat' | 'threema' @@ -57,7 +55,10 @@ export type GrafanaNotifierType = | 'LINE' | 'kafka' | 'wecom' - | 'mqtt'; + | 'webex' + | 'mqtt' + | 'oncall' + | 'sns'; export type CloudNotifierType = | 'oncall' // Only FE implementation for now