mirror of
https://github.com/grafana/grafana.git
synced 2026-07-30 08:18:10 -05:00
Alerting: Add custom title to discord contact point (#57506)
* Add custom title to discord contact point * Update pkg/services/ngalert/notifier/channels/discord.go Co-authored-by: Santiago <santiagohernandez.1997@gmail.com> * Use simplejson Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>
This commit is contained in:
@@ -26,18 +26,15 @@ import (
|
||||
|
||||
type DiscordNotifier struct {
|
||||
*Base
|
||||
log log.Logger
|
||||
ns notifications.WebhookSender
|
||||
images ImageStore
|
||||
tmpl *template.Template
|
||||
Content string
|
||||
AvatarURL string
|
||||
WebhookURL string
|
||||
UseDiscordUsername bool
|
||||
log log.Logger
|
||||
ns notifications.WebhookSender
|
||||
images ImageStore
|
||||
tmpl *template.Template
|
||||
settings discordSettings
|
||||
}
|
||||
|
||||
type DiscordConfig struct {
|
||||
*NotificationChannelConfig
|
||||
type discordSettings struct {
|
||||
Title string
|
||||
Content string
|
||||
AvatarURL string
|
||||
WebhookURL string
|
||||
@@ -54,50 +51,44 @@ type discordAttachment struct {
|
||||
|
||||
const DiscordMaxEmbeds = 10
|
||||
|
||||
func NewDiscordConfig(config *NotificationChannelConfig) (*DiscordConfig, error) {
|
||||
discordURL := config.Settings.Get("url").MustString()
|
||||
if discordURL == "" {
|
||||
return nil, errors.New("could not find webhook url property in settings")
|
||||
}
|
||||
return &DiscordConfig{
|
||||
NotificationChannelConfig: config,
|
||||
Content: config.Settings.Get("message").MustString(DefaultMessageEmbed),
|
||||
AvatarURL: config.Settings.Get("avatar_url").MustString(),
|
||||
WebhookURL: discordURL,
|
||||
UseDiscordUsername: config.Settings.Get("use_discord_username").MustBool(false),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func DiscordFactory(fc FactoryConfig) (NotificationChannel, error) {
|
||||
cfg, err := NewDiscordConfig(fc.Config)
|
||||
dn, err := newDiscordNotifier(fc)
|
||||
if err != nil {
|
||||
return nil, receiverInitError{
|
||||
Reason: err.Error(),
|
||||
Cfg: *fc.Config,
|
||||
}
|
||||
}
|
||||
return NewDiscordNotifier(cfg, fc.NotificationService, fc.ImageStore, fc.Template), nil
|
||||
return dn, nil
|
||||
}
|
||||
|
||||
func NewDiscordNotifier(config *DiscordConfig, ns notifications.WebhookSender, images ImageStore, t *template.Template) *DiscordNotifier {
|
||||
func newDiscordNotifier(fc FactoryConfig) (*DiscordNotifier, error) {
|
||||
dUrl := fc.Config.Settings.Get("url").MustString()
|
||||
if dUrl == "" {
|
||||
return nil, errors.New("could not find webhook url property in settings")
|
||||
}
|
||||
|
||||
return &DiscordNotifier{
|
||||
Base: NewBase(&models.AlertNotification{
|
||||
Uid: config.UID,
|
||||
Name: config.Name,
|
||||
Type: config.Type,
|
||||
DisableResolveMessage: config.DisableResolveMessage,
|
||||
Settings: config.Settings,
|
||||
SecureSettings: config.SecureSettings,
|
||||
Uid: fc.Config.UID,
|
||||
Name: fc.Config.Name,
|
||||
Type: fc.Config.Type,
|
||||
DisableResolveMessage: fc.Config.DisableResolveMessage,
|
||||
Settings: fc.Config.Settings,
|
||||
SecureSettings: fc.Config.SecureSettings,
|
||||
}),
|
||||
Content: config.Content,
|
||||
AvatarURL: config.AvatarURL,
|
||||
WebhookURL: config.WebhookURL,
|
||||
log: log.New("alerting.notifier.discord"),
|
||||
ns: ns,
|
||||
images: images,
|
||||
tmpl: t,
|
||||
UseDiscordUsername: config.UseDiscordUsername,
|
||||
}
|
||||
log: log.New("alerting.notifier.discord"),
|
||||
ns: fc.NotificationService,
|
||||
images: fc.ImageStore,
|
||||
tmpl: fc.Template,
|
||||
settings: discordSettings{
|
||||
Title: fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed),
|
||||
Content: fc.Config.Settings.Get("message").MustString(DefaultMessageEmbed),
|
||||
AvatarURL: fc.Config.Settings.Get("avatar_url").MustString(),
|
||||
WebhookURL: dUrl,
|
||||
UseDiscordUsername: fc.Config.Settings.Get("use_discord_username").MustBool(false),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
@@ -105,27 +96,25 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
|
||||
bodyJSON := simplejson.New()
|
||||
|
||||
if !d.UseDiscordUsername {
|
||||
if !d.settings.UseDiscordUsername {
|
||||
bodyJSON.Set("username", "Grafana")
|
||||
}
|
||||
|
||||
var tmplErr error
|
||||
tmpl, _ := TmplText(ctx, d.tmpl, as, d.log, &tmplErr)
|
||||
|
||||
if d.Content != "" {
|
||||
bodyJSON.Set("content", tmpl(d.Content))
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord notification content", "error", tmplErr.Error())
|
||||
// Reset tmplErr for templating other fields.
|
||||
tmplErr = nil
|
||||
}
|
||||
bodyJSON.Set("content", tmpl(d.settings.Content))
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord notification content", "error", tmplErr.Error())
|
||||
// Reset tmplErr for templating other fields.
|
||||
tmplErr = nil
|
||||
}
|
||||
|
||||
if d.AvatarURL != "" {
|
||||
bodyJSON.Set("avatar_url", tmpl(d.AvatarURL))
|
||||
if d.settings.AvatarURL != "" {
|
||||
bodyJSON.Set("avatar_url", tmpl(d.settings.AvatarURL))
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord Avatar URL", "error", tmplErr.Error(), "fallback", d.AvatarURL)
|
||||
bodyJSON.Set("avatar_url", d.AvatarURL)
|
||||
d.log.Warn("failed to template Discord Avatar URL", "error", tmplErr.Error(), "fallback", d.settings.AvatarURL)
|
||||
bodyJSON.Set("avatar_url", d.settings.AvatarURL)
|
||||
tmplErr = nil
|
||||
}
|
||||
}
|
||||
@@ -136,7 +125,13 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
}
|
||||
|
||||
linkEmbed := simplejson.New()
|
||||
linkEmbed.Set("title", tmpl(DefaultMessageTitleEmbed))
|
||||
|
||||
linkEmbed.Set("title", tmpl(d.settings.Title))
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord notification title", "error", tmplErr.Error())
|
||||
// Reset tmplErr for templating other fields.
|
||||
tmplErr = nil
|
||||
}
|
||||
linkEmbed.Set("footer", footer)
|
||||
linkEmbed.Set("type", "rich")
|
||||
|
||||
@@ -168,10 +163,10 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
tmplErr = nil
|
||||
}
|
||||
|
||||
u := tmpl(d.WebhookURL)
|
||||
u := tmpl(d.settings.WebhookURL)
|
||||
if tmplErr != nil {
|
||||
d.log.Warn("failed to template Discord URL", "error", tmplErr.Error(), "fallback", d.WebhookURL)
|
||||
u = d.WebhookURL
|
||||
d.log.Warn("failed to template Discord URL", "error", tmplErr.Error(), "fallback", d.settings.WebhookURL)
|
||||
u = d.settings.WebhookURL
|
||||
}
|
||||
|
||||
body, err := json.Marshal(bodyJSON)
|
||||
@@ -179,7 +174,7 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
||||
return false, err
|
||||
}
|
||||
|
||||
cmd, err := d.buildRequest(ctx, u, body, attachments)
|
||||
cmd, err := d.buildRequest(u, body, attachments)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -241,7 +236,7 @@ func (d DiscordNotifier) constructAttachments(ctx context.Context, as []*types.A
|
||||
return attachments
|
||||
}
|
||||
|
||||
func (d DiscordNotifier) buildRequest(ctx context.Context, url string, body []byte, attachments []discordAttachment) (*models.SendWebhookSync, error) {
|
||||
func (d DiscordNotifier) buildRequest(url string, body []byte, attachments []discordAttachment) (*models.SendWebhookSync, error) {
|
||||
cmd := &models.SendWebhookSync{
|
||||
Url: url,
|
||||
HttpMethod: "POST",
|
||||
|
||||
@@ -57,6 +57,33 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Default config with one alert and custom title",
|
||||
settings: `{"url": "http://localhost", "title": "Alerts firing: {{ len .Alerts.Firing }}"}`,
|
||||
alerts: []*types.Alert{
|
||||
{
|
||||
Alert: model.Alert{
|
||||
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
|
||||
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expMsg: map[string]interface{}{
|
||||
"content": "**Firing**\n\nValue: [no value]\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSilence: http://localhost/alerting/silence/new?alertmanager=grafana&matcher=alertname%3Dalert1&matcher=lbl1%3Dval1\nDashboard: http://localhost/d/abcd\nPanel: http://localhost/d/abcd?viewPanel=efgh\n",
|
||||
"embeds": []interface{}{map[string]interface{}{
|
||||
"color": 1.4037554e+07,
|
||||
"footer": map[string]interface{}{
|
||||
"icon_url": "https://grafana.com/assets/img/fav32.png",
|
||||
"text": "Grafana v" + setting.BuildVersion,
|
||||
},
|
||||
"title": "Alerts firing: 1",
|
||||
"url": "http://localhost/alerting/list",
|
||||
"type": "rich",
|
||||
}},
|
||||
"username": "Grafana",
|
||||
},
|
||||
expMsgError: nil,
|
||||
},
|
||||
{
|
||||
name: "Missing field in template",
|
||||
settings: `{
|
||||
@@ -262,25 +289,30 @@ func TestDiscordNotifier(t *testing.T) {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
settingsJson, err := simplejson.NewJson([]byte(c.settings))
|
||||
require.NoError(t, err)
|
||||
webhookSender := mockNotificationService()
|
||||
imageStore := &UnavailableImageStore{}
|
||||
|
||||
m := &NotificationChannelConfig{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJson,
|
||||
fc := FactoryConfig{
|
||||
Config: &NotificationChannelConfig{
|
||||
Name: "discord_testing",
|
||||
Type: "discord",
|
||||
Settings: settingsJson,
|
||||
},
|
||||
ImageStore: imageStore,
|
||||
// TODO: allow changing the associated values for different tests.
|
||||
NotificationService: webhookSender,
|
||||
Template: tmpl,
|
||||
}
|
||||
|
||||
webhookSender := mockNotificationService()
|
||||
cfg, err := NewDiscordConfig(m)
|
||||
dn, err := newDiscordNotifier(fc)
|
||||
if c.expInitError != "" {
|
||||
require.Equal(t, c.expInitError, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
imageStore := &UnavailableImageStore{}
|
||||
|
||||
ctx := notify.WithGroupKey(context.Background(), "alertname")
|
||||
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
|
||||
dn := NewDiscordNotifier(cfg, webhookSender, imageStore, tmpl)
|
||||
ok, err := dn.Notify(ctx, c.alerts...)
|
||||
if c.expMsgError != nil {
|
||||
require.False(t, ok)
|
||||
|
||||
@@ -852,6 +852,14 @@ func GetAvailableNotifiers() []*NotifierPlugin {
|
||||
Heading: "Discord settings",
|
||||
Description: "Sends notifications to Discord",
|
||||
Options: []NotifierOption{
|
||||
{
|
||||
Label: "Title",
|
||||
Description: "Templated title of the message",
|
||||
Element: ElementTypeInput,
|
||||
InputType: InputTypeText,
|
||||
Placeholder: channels.DefaultMessageTitleEmbed,
|
||||
PropertyName: "title",
|
||||
},
|
||||
{
|
||||
Label: "Message Content",
|
||||
Description: "Mention a group using @ or a user using <@ID> when notifying in a channel",
|
||||
|
||||
Reference in New Issue
Block a user