Alerting: Add support for configuring avatar URL for the Discord notifier (#33355)

Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com>
This commit is contained in:
Chip Wolf ‮ 2021-05-28 22:00:21 +01:00 committed by GitHub
parent 66e2624ae0
commit badec6c6ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 71 additions and 2 deletions

View File

@ -437,6 +437,14 @@ The following sections detail the supported settings and secure settings for eac
| sound | | | sound | |
| okSound | | | okSound | |
#### Alert notification `discord`
| Name | Secure setting |
| -------------- | -------------- |
| url | yes |
| avatar_url | |
| message | |
#### Alert notification `slack` #### Alert notification `slack`
| Name | Secure setting | | Name | Secure setting |

View File

@ -52,7 +52,7 @@ Alert rule evaluation interval | Send reminders every | Reminder sent every (aft
Name | Type | Supports images | Support alert rule tags Name | Type | Supports images | Support alert rule tags
-----|------|---------------- | ----------------------- -----|------|---------------- | -----------------------
[DingDing](#dingdingdingtalk) | `dingding` | yes, external only | no [DingDing](#dingdingdingtalk) | `dingding` | yes, external only | no
Discord | `discord` | yes | no [Discord](#discord) | `discord` | yes | no
[Email](#email) | `email` | yes | no [Email](#email) | `email` | yes | no
[Google Hangouts Chat](#google-hangouts-chat) | `googlechat` | yes, external only | no [Google Hangouts Chat](#google-hangouts-chat) | `googlechat` | yes, external only | no
Hipchat | `hipchat` | 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". 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 ### 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). 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).

View File

@ -25,6 +25,13 @@ func init() {
Factory: newDiscordNotifier, Factory: newDiscordNotifier,
Heading: "Discord settings", Heading: "Discord settings",
Options: []alerting.NotifierOption{ 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", Label: "Message Content",
Description: "Mention a group using @ or a user using <@ID> when notifying in a channel", 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) { func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
avatar := model.Settings.Get("avatar_url").MustString()
content := model.Settings.Get("content").MustString() content := model.Settings.Get("content").MustString()
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@ -54,6 +62,7 @@ func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err
return &DiscordNotifier{ return &DiscordNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Content: content, Content: content,
AvatarURL: avatar,
WebhookURL: url, WebhookURL: url,
log: log.New("alerting.notifier.discord"), log: log.New("alerting.notifier.discord"),
}, nil }, nil
@ -64,6 +73,7 @@ func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, err
type DiscordNotifier struct { type DiscordNotifier struct {
NotifierBase NotifierBase
Content string Content string
AvatarURL string
WebhookURL string WebhookURL string
log log.Logger log log.Logger
} }
@ -85,6 +95,10 @@ func (dn *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("content", dn.Content) bodyJSON.Set("content", dn.Content)
} }
if dn.AvatarURL != "" {
bodyJSON.Set("avatar_url", dn.AvatarURL)
}
fields := make([]map[string]interface{}, 0) fields := make([]map[string]interface{}, 0)
for _, evt := range evalContext.EvalMatches { for _, evt := range evalContext.EvalMatches {

View File

@ -28,6 +28,7 @@ func TestDiscordNotifier(t *testing.T) {
Convey("settings should trigger incident", func() { Convey("settings should trigger incident", func() {
json := ` json := `
{ {
"avatar_url": "https://grafana.com/img/fav32.png",
"content": "@everyone Please check this notification", "content": "@everyone Please check this notification",
"url": "https://web.hook/" "url": "https://web.hook/"
}` }`
@ -45,6 +46,7 @@ func TestDiscordNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(discordNotifier.Name, ShouldEqual, "discord_testing") So(discordNotifier.Name, ShouldEqual, "discord_testing")
So(discordNotifier.Type, ShouldEqual, "discord") 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.Content, ShouldEqual, "@everyone Please check this notification")
So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/") So(discordNotifier.WebhookURL, ShouldEqual, "https://web.hook/")
}) })

View File

@ -688,6 +688,12 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
PropertyName: "url", PropertyName: "url",
Required: true, Required: true,
}, },
{
Label: "Avatar URL",
Element: alerting.ElementTypeInput,
InputType: alerting.InputTypeText,
PropertyName: "avatar_url",
},
}, },
}, },
{ {

View File

@ -24,6 +24,7 @@ type DiscordNotifier struct {
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template
Content string Content string
AvatarURL string
WebhookURL string WebhookURL string
} }
@ -32,6 +33,8 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template)
return nil, alerting.ValidationError{Reason: "No Settings Supplied"} return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
} }
avatarURL := model.Settings.Get("avatar_url").MustString()
discordURL := model.Settings.Get("url").MustString() discordURL := model.Settings.Get("url").MustString()
if discordURL == "" { if discordURL == "" {
return nil, alerting.ValidationError{Reason: "Could not find webhook url property in settings"} 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, SecureSettings: model.SecureSettings,
}), }),
Content: content, Content: content,
AvatarURL: avatarURL,
WebhookURL: discordURL, WebhookURL: discordURL,
log: log.New("alerting.notifier.discord"), log: log.New("alerting.notifier.discord"),
tmpl: t, tmpl: t,
@ -70,6 +74,10 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
bodyJSON.Set("content", tmpl(d.Content)) bodyJSON.Set("content", tmpl(d.Content))
} }
if d.AvatarURL != "" {
bodyJSON.Set("avatar_url", tmpl(d.AvatarURL))
}
footer := map[string]interface{}{ footer := map[string]interface{}{
"text": "Grafana v" + setting.BuildVersion, "text": "Grafana v" + setting.BuildVersion,
"icon_url": "https://grafana.com/assets/img/fav32.png", "icon_url": "https://grafana.com/assets/img/fav32.png",

View File

@ -64,6 +64,7 @@ func TestDiscordNotifier(t *testing.T) {
{ {
name: "Custom config with multiple alerts", name: "Custom config with multiple alerts",
settings: `{ settings: `{
"avatar_url": "https://grafana.com/assets/img/fav32.png",
"url": "http://localhost", "url": "http://localhost",
"message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved" "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{}{ 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{}{ "embeds": []interface{}{map[string]interface{}{
"color": 1.4037554e+07, "color": 1.4037554e+07,
"footer": map[string]interface{}{ "footer": map[string]interface{}{

View File

@ -1385,6 +1385,22 @@ var expAvailableChannelJsonOutput = `
"required": true, "required": true,
"validationRule": "", "validationRule": "",
"secure": false "secure": false
},
{
"label": "Avatar URL",
"description": "",
"element": "input",
"inputType": "text",
"placeholder": "",
"propertyName": "avatar_url",
"selectOptions": null,
"showWhen": {
"field": "",
"is": ""
},
"required": false,
"validationRule": "",
"secure": false
} }
] ]
}, },