From badec6c6ad096a6ef163da9c2d185c13262884a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chip=20Wolf=20=E2=80=AE?= Date: Fri, 28 May 2021 22:00:21 +0100 Subject: [PATCH] Alerting: Add support for configuring avatar URL for the Discord notifier (#33355) Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> --- docs/sources/administration/provisioning.md | 8 ++++++++ .../alerting/old-alerting/notifications.md | 15 ++++++++++++++- pkg/services/alerting/notifiers/discord.go | 14 ++++++++++++++ pkg/services/alerting/notifiers/discord_test.go | 2 ++ .../ngalert/notifier/available_channels.go | 6 ++++++ .../ngalert/notifier/channels/discord.go | 8 ++++++++ .../ngalert/notifier/channels/discord_test.go | 4 +++- .../api/alerting/api_available_channel_test.go | 16 ++++++++++++++++ 8 files changed, 71 insertions(+), 2 deletions(-) diff --git a/docs/sources/administration/provisioning.md b/docs/sources/administration/provisioning.md index 3a592bf6dd0..548309da3c6 100644 --- a/docs/sources/administration/provisioning.md +++ b/docs/sources/administration/provisioning.md @@ -437,6 +437,14 @@ The following sections detail the supported settings and secure settings for eac | sound | | | okSound | | +#### Alert notification `discord` + +| Name | Secure setting | +| -------------- | -------------- | +| url | yes | +| avatar_url | | +| message | | + #### Alert notification `slack` | Name | Secure setting | diff --git a/docs/sources/alerting/old-alerting/notifications.md b/docs/sources/alerting/old-alerting/notifications.md index ac4551a75b3..860f359f108 100644 --- a/docs/sources/alerting/old-alerting/notifications.md +++ b/docs/sources/alerting/old-alerting/notifications.md @@ -52,7 +52,7 @@ Alert rule evaluation interval | Send reminders every | Reminder sent every (aft Name | Type | Supports images | Support alert rule tags -----|------|---------------- | ----------------------- [DingDing](#dingdingdingtalk) | `dingding` | yes, external only | no -Discord | `discord` | yes | no +[Discord](#discord) | `discord` | yes | no [Email](#email) | `email` | yes | no [Google Hangouts Chat](#google-hangouts-chat) | `googlechat` | yes, external only | no Hipchat | `hipchat` | yes, external only | no @@ -220,6 +220,19 @@ In DingTalk PC Client: 6. There will be a Webhook URL in the panel, looks like this: https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxx. Copy this URL to the Grafana DingTalk setting page and then click "finish". +### Discord + +To set up Discord, you must create a Discord channel webhook. For instructions on how to create the channel, refer to +[Intro to Webhooks](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) f. + +Setting | Description +---------- | ----------- +Webhook URL | Discord webhook URL. +Message Content | Mention a group using @ or a user using <@ID> when notifying in a channel. +Avatar URL | Optionally, provide a URL to an image to use as the avatar for the bot's message. + +Alternately, use the [Slack](#slack) notifier by appending `/slack` to a Discord webhook URL. + ### Kafka Notifications can be sent to a Kafka topic from Grafana using the [Kafka REST Proxy](https://docs.confluent.io/1.0/kafka-rest/docs/index.html). diff --git a/pkg/services/alerting/notifiers/discord.go b/pkg/services/alerting/notifiers/discord.go index 7c8f504ebe6..36225dc2fc2 100644 --- a/pkg/services/alerting/notifiers/discord.go +++ b/pkg/services/alerting/notifiers/discord.go @@ -25,6 +25,13 @@ func init() { Factory: newDiscordNotifier, Heading: "Discord settings", Options: []alerting.NotifierOption{ + { + Label: "Avatar URL", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + Description: "Provide a URL to an image to use as the avatar for the bot's message", + PropertyName: "avatar_url", + }, { Label: "Message Content", Description: "Mention a group using @ or a user using <@ID> when notifying in a channel", @@ -45,6 +52,7 @@ func init() { } func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) { + avatar := model.Settings.Get("avatar_url").MustString() content := model.Settings.Get("content").MustString() url := model.Settings.Get("url").MustString() if url == "" { @@ -54,6 +62,7 @@ func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err return &DiscordNotifier{ NotifierBase: NewNotifierBase(model), Content: content, + AvatarURL: avatar, WebhookURL: url, log: log.New("alerting.notifier.discord"), }, nil @@ -64,6 +73,7 @@ func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err type DiscordNotifier struct { NotifierBase Content string + AvatarURL string WebhookURL string log log.Logger } @@ -85,6 +95,10 @@ func (dn *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error { bodyJSON.Set("content", dn.Content) } + if dn.AvatarURL != "" { + bodyJSON.Set("avatar_url", dn.AvatarURL) + } + fields := make([]map[string]interface{}, 0) for _, evt := range evalContext.EvalMatches { diff --git a/pkg/services/alerting/notifiers/discord_test.go b/pkg/services/alerting/notifiers/discord_test.go index fe373c42d1f..795454361cd 100644 --- a/pkg/services/alerting/notifiers/discord_test.go +++ b/pkg/services/alerting/notifiers/discord_test.go @@ -28,6 +28,7 @@ func TestDiscordNotifier(t *testing.T) { Convey("settings should trigger incident", func() { json := ` { + "avatar_url": "https://grafana.com/img/fav32.png", "content": "@everyone Please check this notification", "url": "https://web.hook/" }` @@ -45,6 +46,7 @@ func TestDiscordNotifier(t *testing.T) { So(err, ShouldBeNil) So(discordNotifier.Name, ShouldEqual, "discord_testing") So(discordNotifier.Type, ShouldEqual, "discord") + So(discordNotifier.AvatarURL, ShouldEqual, "https://grafana.com/img/fav32.png") So(discordNotifier.Content, ShouldEqual, "@everyone Please check this notification") So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/") }) diff --git a/pkg/services/ngalert/notifier/available_channels.go b/pkg/services/ngalert/notifier/available_channels.go index 8f82177e8c5..ddc79549b85 100644 --- a/pkg/services/ngalert/notifier/available_channels.go +++ b/pkg/services/ngalert/notifier/available_channels.go @@ -688,6 +688,12 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin { PropertyName: "url", Required: true, }, + { + Label: "Avatar URL", + Element: alerting.ElementTypeInput, + InputType: alerting.InputTypeText, + PropertyName: "avatar_url", + }, }, }, { diff --git a/pkg/services/ngalert/notifier/channels/discord.go b/pkg/services/ngalert/notifier/channels/discord.go index e7b1cf4b812..11a5b2e1de2 100644 --- a/pkg/services/ngalert/notifier/channels/discord.go +++ b/pkg/services/ngalert/notifier/channels/discord.go @@ -24,6 +24,7 @@ type DiscordNotifier struct { log log.Logger tmpl *template.Template Content string + AvatarURL string WebhookURL string } @@ -32,6 +33,8 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template) return nil, alerting.ValidationError{Reason: "No Settings Supplied"} } + avatarURL := model.Settings.Get("avatar_url").MustString() + discordURL := model.Settings.Get("url").MustString() if discordURL == "" { return nil, alerting.ValidationError{Reason: "Could not find webhook url property in settings"} @@ -49,6 +52,7 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template) SecureSettings: model.SecureSettings, }), Content: content, + AvatarURL: avatarURL, WebhookURL: discordURL, log: log.New("alerting.notifier.discord"), tmpl: t, @@ -70,6 +74,10 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, bodyJSON.Set("content", tmpl(d.Content)) } + if d.AvatarURL != "" { + bodyJSON.Set("avatar_url", tmpl(d.AvatarURL)) + } + footer := map[string]interface{}{ "text": "Grafana v" + setting.BuildVersion, "icon_url": "https://grafana.com/assets/img/fav32.png", diff --git a/pkg/services/ngalert/notifier/channels/discord_test.go b/pkg/services/ngalert/notifier/channels/discord_test.go index d0b3d842212..996f4d575ec 100644 --- a/pkg/services/ngalert/notifier/channels/discord_test.go +++ b/pkg/services/ngalert/notifier/channels/discord_test.go @@ -64,6 +64,7 @@ func TestDiscordNotifier(t *testing.T) { { name: "Custom config with multiple alerts", settings: `{ + "avatar_url": "https://grafana.com/assets/img/fav32.png", "url": "http://localhost", "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved" }`, @@ -81,7 +82,8 @@ func TestDiscordNotifier(t *testing.T) { }, }, expMsg: map[string]interface{}{ - "content": "2 alerts are firing, 0 are resolved", + "avatar_url": "https://grafana.com/assets/img/fav32.png", + "content": "2 alerts are firing, 0 are resolved", "embeds": []interface{}{map[string]interface{}{ "color": 1.4037554e+07, "footer": map[string]interface{}{ diff --git a/pkg/tests/api/alerting/api_available_channel_test.go b/pkg/tests/api/alerting/api_available_channel_test.go index 6f730854247..ed186b82117 100644 --- a/pkg/tests/api/alerting/api_available_channel_test.go +++ b/pkg/tests/api/alerting/api_available_channel_test.go @@ -1385,6 +1385,22 @@ var expAvailableChannelJsonOutput = ` "required": true, "validationRule": "", "secure": false + }, + { + "label": "Avatar URL", + "description": "", + "element": "input", + "inputType": "text", + "placeholder": "", + "propertyName": "avatar_url", + "selectOptions": null, + "showWhen": { + "field": "", + "is": "" + }, + "required": false, + "validationRule": "", + "secure": false } ] },