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:
Alex Moreno
2022-10-27 16:17:18 +02:00
committed by GitHub
co-authored by Santiago
parent 1ab0af1eb2
commit 3d437117ad
3 changed files with 103 additions and 68 deletions
@@ -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",