mirror of
https://github.com/grafana/grafana.git
synced 2026-09-05 04:40:13 -05:00
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:
@@ -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",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user