mirror of
https://github.com/grafana/grafana.git
synced 2025-01-19 13:03:32 -06:00
NGAlert: Add message field to email notification channel (#34044)
Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
This commit is contained in:
parent
5a43bd4581
commit
d5ae55c5dd
@ -407,7 +407,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
|
|||||||
)
|
)
|
||||||
switch r.Type {
|
switch r.Type {
|
||||||
case "email":
|
case "email":
|
||||||
n, err = channels.NewEmailNotifier(cfg, tmpl.ExternalURL) // Email notifier already has a default template.
|
n, err = channels.NewEmailNotifier(cfg, tmpl) // Email notifier already has a default template.
|
||||||
case "pagerduty":
|
case "pagerduty":
|
||||||
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
|
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
|
||||||
case "slack":
|
case "slack":
|
||||||
|
@ -60,6 +60,12 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
|
|||||||
PropertyName: "addresses",
|
PropertyName: "addresses",
|
||||||
Required: true,
|
Required: true,
|
||||||
},
|
},
|
||||||
|
{ // New in 8.0.
|
||||||
|
Label: "Message",
|
||||||
|
Description: "Optional message to include with the email. You can use template variables",
|
||||||
|
Element: alerting.ElementTypeTextArea,
|
||||||
|
PropertyName: "message",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -72,7 +72,7 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
|
|||||||
tmpl := notify.TmplText(dd.tmpl, data, &tmplErr)
|
tmpl := notify.TmplText(dd.tmpl, data, &tmplErr)
|
||||||
|
|
||||||
message := tmpl(dd.Message)
|
message := tmpl(dd.Message)
|
||||||
title := getTitleFromTemplateData(data)
|
title := tmpl(`{{ template "default.title" . }}`)
|
||||||
|
|
||||||
var bodyMsg map[string]interface{}
|
var bodyMsg map[string]interface{}
|
||||||
if dd.MsgType == "actionCard" {
|
if dd.MsgType == "actionCard" {
|
||||||
|
@ -49,7 +49,7 @@ func TestDingdingNotifier(t *testing.T) {
|
|||||||
"link": map[string]interface{}{
|
"link": map[string]interface{}{
|
||||||
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
"messageUrl": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
||||||
"text": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
"text": "\n**Firing**\nLabels:\n - alertname = alert1\n - lbl1 = val1\nAnnotations:\n - ann1 = annv1\nSource: \n\n\n\n\n",
|
||||||
"title": "[firing:1] (val1)",
|
"title": "[FIRING:1] (val1)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expInitError: nil,
|
expInitError: nil,
|
||||||
@ -79,7 +79,7 @@ func TestDingdingNotifier(t *testing.T) {
|
|||||||
"singleTitle": "More",
|
"singleTitle": "More",
|
||||||
"singleURL": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
"singleURL": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
|
||||||
"text": "2 alerts are firing, 0 are resolved",
|
"text": "2 alerts are firing, 0 are resolved",
|
||||||
"title": "[firing:2] ",
|
"title": "[FIRING:2] ",
|
||||||
},
|
},
|
||||||
"msgtype": "actionCard",
|
"msgtype": "actionCard",
|
||||||
},
|
},
|
||||||
|
@ -2,7 +2,7 @@ package channels
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
gokit_log "github.com/go-kit/kit/log"
|
gokit_log "github.com/go-kit/kit/log"
|
||||||
@ -24,13 +24,14 @@ type EmailNotifier struct {
|
|||||||
old_notifiers.NotifierBase
|
old_notifiers.NotifierBase
|
||||||
Addresses []string
|
Addresses []string
|
||||||
SingleEmail bool
|
SingleEmail bool
|
||||||
|
Message string
|
||||||
log log.Logger
|
log log.Logger
|
||||||
externalUrl *url.URL
|
tmpl *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEmailNotifier is the constructor function
|
// NewEmailNotifier is the constructor function
|
||||||
// for the EmailNotifier.
|
// for the EmailNotifier.
|
||||||
func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL) (*EmailNotifier, error) {
|
func NewEmailNotifier(model *models.AlertNotification, t *template.Template) (*EmailNotifier, error) {
|
||||||
if model.Settings == nil {
|
if model.Settings == nil {
|
||||||
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
|
||||||
}
|
}
|
||||||
@ -49,31 +50,35 @@ func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL) (*E
|
|||||||
NotifierBase: old_notifiers.NewNotifierBase(model),
|
NotifierBase: old_notifiers.NewNotifierBase(model),
|
||||||
Addresses: addresses,
|
Addresses: addresses,
|
||||||
SingleEmail: singleEmail,
|
SingleEmail: singleEmail,
|
||||||
|
Message: model.Settings.Get("message").MustString(),
|
||||||
log: log.New("alerting.notifier.email"),
|
log: log.New("alerting.notifier.email"),
|
||||||
externalUrl: externalUrl,
|
tmpl: t,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify sends the alert notification.
|
// Notify sends the alert notification.
|
||||||
func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||||
// We only need ExternalURL from this template object. This hack should go away with https://github.com/prometheus/alertmanager/pull/2508.
|
// We only need ExternalURL from this template object. This hack should go away with https://github.com/prometheus/alertmanager/pull/2508.
|
||||||
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.externalUrl}, as, gokit_log.NewNopLogger())
|
data := notify.GetTemplateData(ctx, &template.Template{ExternalURL: en.tmpl.ExternalURL}, as, gokit_log.NewNopLogger())
|
||||||
|
var tmplErr error
|
||||||
|
tmpl := notify.TmplText(en.tmpl, data, &tmplErr)
|
||||||
|
|
||||||
title := getTitleFromTemplateData(data)
|
title := tmpl(`{{ template "default.title" . }}`)
|
||||||
|
|
||||||
cmd := &models.SendEmailCommandSync{
|
cmd := &models.SendEmailCommandSync{
|
||||||
SendEmailCommand: models.SendEmailCommand{
|
SendEmailCommand: models.SendEmailCommand{
|
||||||
Subject: title,
|
Subject: title,
|
||||||
Data: map[string]interface{}{
|
Data: map[string]interface{}{
|
||||||
"Title": title,
|
"Title": title,
|
||||||
|
"Message": tmpl(en.Message),
|
||||||
"Status": data.Status,
|
"Status": data.Status,
|
||||||
"Alerts": data.Alerts,
|
"Alerts": data.Alerts,
|
||||||
"GroupLabels": data.GroupLabels,
|
"GroupLabels": data.GroupLabels,
|
||||||
"CommonLabels": data.CommonLabels,
|
"CommonLabels": data.CommonLabels,
|
||||||
"CommonAnnotations": data.CommonAnnotations,
|
"CommonAnnotations": data.CommonAnnotations,
|
||||||
"ExternalURL": data.ExternalURL,
|
"ExternalURL": data.ExternalURL,
|
||||||
"RuleUrl": path.Join(en.externalUrl.String(), "/alerting/list"),
|
"RuleUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list"),
|
||||||
"AlertPageUrl": path.Join(en.externalUrl.String(), "/alerting/list?alertState=firing&view=state"),
|
"AlertPageUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list?alertState=firing&view=state"),
|
||||||
},
|
},
|
||||||
To: en.Addresses,
|
To: en.Addresses,
|
||||||
SingleEmail: en.SingleEmail,
|
SingleEmail: en.SingleEmail,
|
||||||
@ -81,6 +86,10 @@ func (en *EmailNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tmplErr != nil {
|
||||||
|
return false, fmt.Errorf("failed to template email message: %w", tmplErr)
|
||||||
|
}
|
||||||
|
|
||||||
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
if err := bus.DispatchCtx(ctx, cmd); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestEmailNotifier(t *testing.T) {
|
func TestEmailNotifier(t *testing.T) {
|
||||||
|
tmpl := templateForTests(t)
|
||||||
|
|
||||||
externalURL, err := url.Parse("http://localhost")
|
externalURL, err := url.Parse("http://localhost")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
tmpl.ExternalURL = externalURL
|
||||||
|
|
||||||
t.Run("empty settings should return error", func(t *testing.T) {
|
t.Run("empty settings should return error", func(t *testing.T) {
|
||||||
json := `{ }`
|
json := `{ }`
|
||||||
@ -29,21 +32,23 @@ func TestEmailNotifier(t *testing.T) {
|
|||||||
Settings: settingsJSON,
|
Settings: settingsJSON,
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := NewEmailNotifier(model, externalURL)
|
_, err := NewEmailNotifier(model, tmpl)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("with the correct settings it should not fail and produce the expected command", func(t *testing.T) {
|
t.Run("with the correct settings it should not fail and produce the expected command", func(t *testing.T) {
|
||||||
json := `{"addresses": "someops@example.com;somedev@example.com"}`
|
json := `{
|
||||||
|
"addresses": "someops@example.com;somedev@example.com",
|
||||||
|
"message": "{{ template \"default.title\" . }}"
|
||||||
|
}`
|
||||||
settingsJSON, err := simplejson.NewJson([]byte(json))
|
settingsJSON, err := simplejson.NewJson([]byte(json))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
emailNotifier, err := NewEmailNotifier(&models.AlertNotification{
|
emailNotifier, err := NewEmailNotifier(&models.AlertNotification{
|
||||||
Name: "ops",
|
Name: "ops",
|
||||||
Type: "email",
|
Type: "email",
|
||||||
|
|
||||||
Settings: settingsJSON,
|
Settings: settingsJSON,
|
||||||
}, externalURL)
|
}, tmpl)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -72,13 +77,14 @@ func TestEmailNotifier(t *testing.T) {
|
|||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
|
|
||||||
require.Equal(t, map[string]interface{}{
|
require.Equal(t, map[string]interface{}{
|
||||||
"subject": "[firing:1] (AlwaysFiring warning)",
|
"subject": "[FIRING:1] (AlwaysFiring warning)",
|
||||||
"to": []string{"someops@example.com", "somedev@example.com"},
|
"to": []string{"someops@example.com", "somedev@example.com"},
|
||||||
"single_email": false,
|
"single_email": false,
|
||||||
"template": "ng_alert_notification.html",
|
"template": "ng_alert_notification.html",
|
||||||
"data": map[string]interface{}{
|
"data": map[string]interface{}{
|
||||||
"Title": "[firing:1] (AlwaysFiring warning)",
|
"Title": "[FIRING:1] (AlwaysFiring warning)",
|
||||||
"Status": "firing",
|
"Message": "[FIRING:1] (AlwaysFiring warning)",
|
||||||
|
"Status": "firing",
|
||||||
"Alerts": template.Alerts{
|
"Alerts": template.Alerts{
|
||||||
template.Alert{
|
template.Alert{
|
||||||
Status: "firing",
|
Status: "firing",
|
||||||
|
@ -141,7 +141,7 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
|
|||||||
HRef: pn.tmpl.ExternalURL.String(),
|
HRef: pn.tmpl.ExternalURL.String(),
|
||||||
Text: "External URL",
|
Text: "External URL",
|
||||||
}},
|
}},
|
||||||
Description: getTitleFromTemplateData(data), // TODO: this can be configurable template.
|
Description: tmpl(`{{ template "default.title" . }}`), // TODO: this can be configurable template.
|
||||||
Payload: &pagerDutyPayload{
|
Payload: &pagerDutyPayload{
|
||||||
Component: tmpl(pn.Component),
|
Component: tmpl(pn.Component),
|
||||||
Summary: tmpl(pn.Summary),
|
Summary: tmpl(pn.Summary),
|
||||||
|
@ -51,7 +51,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
|||||||
expMsg: &pagerDutyMessage{
|
expMsg: &pagerDutyMessage{
|
||||||
RoutingKey: "abcdefgh0123456789",
|
RoutingKey: "abcdefgh0123456789",
|
||||||
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||||
Description: "[firing:1] (val1)",
|
Description: "[FIRING:1] (val1)",
|
||||||
EventAction: "trigger",
|
EventAction: "trigger",
|
||||||
Payload: &pagerDutyPayload{
|
Payload: &pagerDutyPayload{
|
||||||
Summary: "[FIRING:1] (val1)",
|
Summary: "[FIRING:1] (val1)",
|
||||||
@ -98,7 +98,7 @@ func TestPagerdutyNotifier(t *testing.T) {
|
|||||||
expMsg: &pagerDutyMessage{
|
expMsg: &pagerDutyMessage{
|
||||||
RoutingKey: "abcdefgh0123456789",
|
RoutingKey: "abcdefgh0123456789",
|
||||||
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
|
||||||
Description: "[firing:2] ",
|
Description: "[FIRING:2] ",
|
||||||
EventAction: "trigger",
|
EventAction: "trigger",
|
||||||
Payload: &pagerDutyPayload{
|
Payload: &pagerDutyPayload{
|
||||||
Summary: "[FIRING:2] ",
|
Summary: "[FIRING:2] ",
|
||||||
|
@ -54,7 +54,7 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
|
|||||||
var tmplErr error
|
var tmplErr error
|
||||||
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)
|
||||||
|
|
||||||
title := getTitleFromTemplateData(data)
|
title := tmpl(`{{ template "default.title" . }}`)
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"@type": "MessageCard",
|
"@type": "MessageCard",
|
||||||
"@context": "http://schema.org/extensions",
|
"@context": "http://schema.org/extensions",
|
||||||
|
@ -47,8 +47,8 @@ func TestTeamsNotifier(t *testing.T) {
|
|||||||
expMsg: map[string]interface{}{
|
expMsg: map[string]interface{}{
|
||||||
"@type": "MessageCard",
|
"@type": "MessageCard",
|
||||||
"@context": "http://schema.org/extensions",
|
"@context": "http://schema.org/extensions",
|
||||||
"summary": "[firing:1] (val1)",
|
"summary": "[FIRING:1] (val1)",
|
||||||
"title": "[firing:1] (val1)",
|
"title": "[FIRING:1] (val1)",
|
||||||
"themeColor": "#D63232",
|
"themeColor": "#D63232",
|
||||||
"sections": []map[string]interface{}{
|
"sections": []map[string]interface{}{
|
||||||
{
|
{
|
||||||
@ -89,8 +89,8 @@ func TestTeamsNotifier(t *testing.T) {
|
|||||||
expMsg: map[string]interface{}{
|
expMsg: map[string]interface{}{
|
||||||
"@type": "MessageCard",
|
"@type": "MessageCard",
|
||||||
"@context": "http://schema.org/extensions",
|
"@context": "http://schema.org/extensions",
|
||||||
"summary": "[firing:2] ",
|
"summary": "[FIRING:2] ",
|
||||||
"title": "[firing:2] ",
|
"title": "[FIRING:2] ",
|
||||||
"themeColor": "#D63232",
|
"themeColor": "#D63232",
|
||||||
"sections": []map[string]interface{}{
|
"sections": []map[string]interface{}{
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
package channels
|
package channels
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/prometheus/alertmanager/template"
|
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,15 +16,3 @@ func getAlertStatusColor(status model.AlertStatus) string {
|
|||||||
}
|
}
|
||||||
return ColorAlertResolved
|
return ColorAlertResolved
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTitleFromTemplateData(data *template.Data) string {
|
|
||||||
title := "[" + data.Status
|
|
||||||
if data.Status == string(model.AlertFiring) {
|
|
||||||
title += fmt.Sprintf(":%d", len(data.Alerts.Firing()))
|
|
||||||
}
|
|
||||||
title += "] " + strings.Join(data.GroupLabels.SortedPairs().Values(), " ") + " "
|
|
||||||
if len(data.CommonLabels) > len(data.GroupLabels) {
|
|
||||||
title += "(" + strings.Join(data.CommonLabels.Remove(data.GroupLabels.Names()).Values(), " ") + ")"
|
|
||||||
}
|
|
||||||
return title
|
|
||||||
}
|
|
||||||
|
@ -146,6 +146,22 @@ var expAvailableChannelJsonOutput = `
|
|||||||
"required": true,
|
"required": true,
|
||||||
"validationRule": "",
|
"validationRule": "",
|
||||||
"secure": false
|
"secure": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"element": "textarea",
|
||||||
|
"inputType": "",
|
||||||
|
"label": "Message",
|
||||||
|
"description": "Optional message to include with the email. You can use template variables",
|
||||||
|
"placeholder": "",
|
||||||
|
"propertyName": "message",
|
||||||
|
"selectOptions": null,
|
||||||
|
"showWhen": {
|
||||||
|
"field": "",
|
||||||
|
"is": ""
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"validationRule": "",
|
||||||
|
"secure": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user