Alerting: Add custom title to DingDing contact point (#57498)

* Add custom title to DingDing contact point

* Update pkg/services/ngalert/notifier/channels_config/available_channels.go

Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>

* Update pkg/services/ngalert/notifier/channels/dingding.go

Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>

* Add error checking before URL templating

* Remove comment

* Use simplejson

Co-authored-by: Santiago <santiagohernandez.1997@gmail.com>
This commit is contained in:
Alex Moreno
2022-10-27 16:16:36 +02:00
committed by GitHub
co-authored by Santiago
parent fb62660df7
commit 1ab0af1eb2
3 changed files with 127 additions and 89 deletions
@@ -17,109 +17,83 @@ import (
const defaultDingdingMsgType = "link"
type DingDingConfig struct {
*NotificationChannelConfig
MsgType string
Message string
URL string
type dingDingSettings struct {
URL string
MessageType string
Title string
Message string
}
func NewDingDingConfig(config *NotificationChannelConfig) (*DingDingConfig, error) {
url := config.Settings.Get("url").MustString()
if url == "" {
func buildDingDingSettings(fc FactoryConfig) (*dingDingSettings, error) {
URL := fc.Config.Settings.Get("url").MustString()
if URL == "" {
return nil, errors.New("could not find url property in settings")
}
return &DingDingConfig{
NotificationChannelConfig: config,
MsgType: config.Settings.Get("msgType").MustString(defaultDingdingMsgType),
Message: config.Settings.Get("message").MustString(DefaultMessageEmbed),
URL: config.Settings.Get("url").MustString(),
return &dingDingSettings{
URL: URL,
MessageType: fc.Config.Settings.Get("msgType").MustString(defaultDingdingMsgType),
Title: fc.Config.Settings.Get("title").MustString(DefaultMessageTitleEmbed),
Message: fc.Config.Settings.Get("message").MustString(DefaultMessageEmbed),
}, nil
}
func DingDingFactory(fc FactoryConfig) (NotificationChannel, error) {
cfg, err := NewDingDingConfig(fc.Config)
n, err := newDingDingNotifier(fc)
if err != nil {
return nil, receiverInitError{
Reason: err.Error(),
Cfg: *fc.Config,
}
}
return NewDingDingNotifier(cfg, fc.NotificationService, fc.Template), nil
return n, nil
}
// NewDingDingNotifier is the constructor for the Dingding notifier
func NewDingDingNotifier(config *DingDingConfig, ns notifications.WebhookSender, t *template.Template) *DingDingNotifier {
// newDingDingNotifier is the constructor for the Dingding notifier
func newDingDingNotifier(fc FactoryConfig) (*DingDingNotifier, error) {
settings, err := buildDingDingSettings(fc)
if err != nil {
return nil, err
}
return &DingDingNotifier{
Base: NewBase(&models.AlertNotification{
Uid: config.UID,
Name: config.Name,
Type: config.Type,
DisableResolveMessage: config.DisableResolveMessage,
Settings: config.Settings,
Uid: fc.Config.UID,
Name: fc.Config.Name,
Type: fc.Config.Type,
DisableResolveMessage: fc.Config.DisableResolveMessage,
Settings: fc.Config.Settings,
}),
MsgType: config.MsgType,
Message: config.Message,
URL: config.URL,
log: log.New("alerting.notifier.dingding"),
tmpl: t,
ns: ns,
}
log: log.New("alerting.notifier.dingding"),
ns: fc.NotificationService,
tmpl: fc.Template,
settings: *settings,
}, nil
}
// DingDingNotifier is responsible for sending alert notifications to ding ding.
type DingDingNotifier struct {
*Base
MsgType string
URL string
Message string
tmpl *template.Template
ns notifications.WebhookSender
log log.Logger
log log.Logger
ns notifications.WebhookSender
tmpl *template.Template
settings dingDingSettings
}
// Notify sends the alert notification to dingding.
func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
dd.log.Info("sending dingding")
ruleURL := joinUrlPath(dd.tmpl.ExternalURL.String(), "/alerting/list", dd.log)
q := url.Values{
"pc_slide": {"false"},
"url": {ruleURL},
}
// Use special link to auto open the message url outside of Dingding
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
messageURL := "dingtalk://dingtalkclient/page/link?" + q.Encode()
msgUrl := buildDingDingURL(dd)
var tmplErr error
tmpl, _ := TmplText(ctx, dd.tmpl, as, dd.log, &tmplErr)
message := tmpl(dd.Message)
title := tmpl(DefaultMessageTitleEmbed)
message := tmpl(dd.settings.Message)
title := tmpl(dd.settings.Title)
var bodyMsg map[string]interface{}
if tmpl(dd.MsgType) == "actionCard" {
bodyMsg = map[string]interface{}{
"msgtype": "actionCard",
"actionCard": map[string]string{
"text": message,
"title": title,
"singleTitle": "More",
"singleURL": messageURL,
},
}
} else {
link := map[string]string{
"text": message,
"title": title,
"messageUrl": messageURL,
}
bodyMsg = map[string]interface{}{
"msgtype": "link",
"link": link,
}
msgType := tmpl(dd.settings.MessageType)
b, err := buildBody(msgUrl, msgType, title, message)
if err != nil {
return false, err
}
if tmplErr != nil {
@@ -127,21 +101,13 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
tmplErr = nil
}
u := tmpl(dd.URL)
u := tmpl(dd.settings.URL)
if tmplErr != nil {
dd.log.Warn("failed to template DingDing URL", "error", tmplErr.Error(), "fallback", dd.URL)
u = dd.URL
dd.log.Warn("failed to template DingDing URL", "error", tmplErr.Error(), "fallback", dd.settings.URL)
u = dd.settings.URL
}
body, err := json.Marshal(bodyMsg)
if err != nil {
return false, err
}
cmd := &models.SendWebhookSync{
Url: u,
Body: string(body),
}
cmd := &models.SendWebhookSync{Url: u, Body: b}
if err := dd.ns.SendWebhookSync(ctx, cmd); err != nil {
return false, fmt.Errorf("send notification to dingding: %w", err)
@@ -153,3 +119,43 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
func (dd *DingDingNotifier) SendResolved() bool {
return !dd.GetDisableResolveMessage()
}
func buildDingDingURL(dd *DingDingNotifier) string {
q := url.Values{
"pc_slide": {"false"},
"url": {joinUrlPath(dd.tmpl.ExternalURL.String(), "/alerting/list", dd.log)},
}
// Use special link to auto open the message url outside Dingding
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
return "dingtalk://dingtalkclient/page/link?" + q.Encode()
}
func buildBody(msgUrl string, msgType string, title string, msg string) (string, error) {
var bodyMsg map[string]interface{}
if msgType == "actionCard" {
bodyMsg = map[string]interface{}{
"msgtype": "actionCard",
"actionCard": map[string]string{
"text": msg,
"title": title,
"singleTitle": "More",
"singleURL": msgUrl,
},
}
} else {
bodyMsg = map[string]interface{}{
"msgtype": "link",
"link": map[string]string{
"text": msg,
"title": title,
"messageUrl": msgUrl,
},
}
}
body, err := json.Marshal(bodyMsg)
if err != nil {
return "", err
}
return string(body), nil
}
@@ -79,6 +79,26 @@ func TestDingdingNotifier(t *testing.T) {
"msgtype": "actionCard",
},
expMsgError: nil,
}, {
name: "Default config with one alert and custom title and description",
settings: `{"url": "http://localhost", "title": "Alerts firing: {{ len .Alerts.Firing }}", "message": "customMessage"}}`,
alerts: []*types.Alert{
{
Alert: model.Alert{
Labels: model.LabelSet{"alertname": "alert1", "lbl1": "val1"},
Annotations: model.LabelSet{"ann1": "annv1", "__dashboardUid__": "abcd", "__panelId__": "efgh", "__values__": "{\"A\": 1234}", "__value_string__": "1234"},
},
},
},
expMsg: map[string]interface{}{
"msgtype": "link",
"link": map[string]interface{}{
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2F%2Flocalhost%2Falerting%2Flist",
"text": "customMessage",
"title": "Alerts firing: 1",
},
},
expMsgError: nil,
}, {
name: "Missing field in template",
settings: `{
@@ -149,14 +169,18 @@ func TestDingdingNotifier(t *testing.T) {
settingsJSON, err := simplejson.NewJson([]byte(c.settings))
require.NoError(t, err)
m := &NotificationChannelConfig{
Name: "dingding_testing",
Type: "dingding",
Settings: settingsJSON,
}
webhookSender := mockNotificationService()
cfg, err := NewDingDingConfig(m)
fc := FactoryConfig{
Config: &NotificationChannelConfig{
Name: "dingding_testing",
Type: "dingding",
Settings: settingsJSON,
},
// TODO: allow changing the associated values for different tests.
NotificationService: webhookSender,
Template: tmpl,
}
pn, err := newDingDingNotifier(fc)
if c.expInitError != "" {
require.Equal(t, c.expInitError, err.Error())
return
@@ -165,7 +189,6 @@ func TestDingdingNotifier(t *testing.T) {
ctx := notify.WithGroupKey(context.Background(), "alertname")
ctx = notify.WithGroupLabels(ctx, model.LabelSet{"alertname": ""})
pn := NewDingDingNotifier(cfg, webhookSender, tmpl)
ok, err := pn.Notify(ctx, c.alerts...)
if c.expMsgError != nil {
require.False(t, ok)
@@ -132,9 +132,18 @@ func GetAvailableNotifiers() []*NotifierPlugin {
},
},
},
{ // New in 9.3.
Label: "Title",
Element: ElementTypeInput,
InputType: InputTypeText,
Description: "Templated title of the message",
Placeholder: channels.DefaultMessageTitleEmbed,
PropertyName: "title",
},
{ // New in 8.0.
Label: "Message",
Element: ElementTypeTextArea,
Description: "Custom DingDing message. You can use template variables.",
Placeholder: channels.DefaultMessageEmbed,
PropertyName: "message",
},