Skip to content

Commit

Permalink
NGAlert: Add message field to email notification channel (grafana#34044)
Browse files Browse the repository at this point in the history
Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
  • Loading branch information
codesome authored May 17, 2021
1 parent 5a43bd4 commit d5ae55c
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 45 deletions.
2 changes: 1 addition & 1 deletion pkg/services/ngalert/notifier/alertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ func (am *Alertmanager) buildReceiverIntegrations(receiver *apimodels.PostableAp
)
switch r.Type {
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":
n, err = channels.NewPagerdutyNotifier(cfg, tmpl)
case "slack":
Expand Down
6 changes: 6 additions & 0 deletions pkg/services/ngalert/notifier/available_channels.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ func GetAvailableNotifiers() []*alerting.NotifierPlugin {
PropertyName: "addresses",
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",
},
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/ngalert/notifier/channels/dingding.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (dd *DingDingNotifier) Notify(ctx context.Context, as ...*types.Alert) (boo
tmpl := notify.TmplText(dd.tmpl, data, &tmplErr)

message := tmpl(dd.Message)
title := getTitleFromTemplateData(data)
title := tmpl(`{{ template "default.title" . }}`)

var bodyMsg map[string]interface{}
if dd.MsgType == "actionCard" {
Expand Down
4 changes: 2 additions & 2 deletions pkg/services/ngalert/notifier/channels/dingding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func TestDingdingNotifier(t *testing.T) {
"link": map[string]interface{}{
"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",
"title": "[firing:1] (val1)",
"title": "[FIRING:1] (val1)",
},
},
expInitError: nil,
Expand Down Expand Up @@ -79,7 +79,7 @@ func TestDingdingNotifier(t *testing.T) {
"singleTitle": "More",
"singleURL": "dingtalk://dingtalkclient/page/link?pc_slide=false&url=http%3A%2Flocalhost%2Falerting%2Flist",
"text": "2 alerts are firing, 0 are resolved",
"title": "[firing:2] ",
"title": "[FIRING:2] ",
},
"msgtype": "actionCard",
},
Expand Down
25 changes: 17 additions & 8 deletions pkg/services/ngalert/notifier/channels/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package channels

import (
"context"
"net/url"
"fmt"
"path"

gokit_log "github.com/go-kit/kit/log"
Expand All @@ -24,13 +24,14 @@ type EmailNotifier struct {
old_notifiers.NotifierBase
Addresses []string
SingleEmail bool
Message string
log log.Logger
externalUrl *url.URL
tmpl *template.Template
}

// NewEmailNotifier is the constructor function
// 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 {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
}
Expand All @@ -49,38 +50,46 @@ func NewEmailNotifier(model *models.AlertNotification, externalUrl *url.URL) (*E
NotifierBase: old_notifiers.NewNotifierBase(model),
Addresses: addresses,
SingleEmail: singleEmail,
Message: model.Settings.Get("message").MustString(),
log: log.New("alerting.notifier.email"),
externalUrl: externalUrl,
tmpl: t,
}, nil
}

// Notify sends the alert notification.
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.
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{
SendEmailCommand: models.SendEmailCommand{
Subject: title,
Data: map[string]interface{}{
"Title": title,
"Message": tmpl(en.Message),
"Status": data.Status,
"Alerts": data.Alerts,
"GroupLabels": data.GroupLabels,
"CommonLabels": data.CommonLabels,
"CommonAnnotations": data.CommonAnnotations,
"ExternalURL": data.ExternalURL,
"RuleUrl": path.Join(en.externalUrl.String(), "/alerting/list"),
"AlertPageUrl": path.Join(en.externalUrl.String(), "/alerting/list?alertState=firing&view=state"),
"RuleUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list"),
"AlertPageUrl": path.Join(en.tmpl.ExternalURL.String(), "/alerting/list?alertState=firing&view=state"),
},
To: en.Addresses,
SingleEmail: en.SingleEmail,
Template: "ng_alert_notification.html",
},
}

if tmplErr != nil {
return false, fmt.Errorf("failed to template email message: %w", tmplErr)
}

if err := bus.DispatchCtx(ctx, cmd); err != nil {
return false, err
}
Expand Down
24 changes: 15 additions & 9 deletions pkg/services/ngalert/notifier/channels/email_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@ import (
)

func TestEmailNotifier(t *testing.T) {
tmpl := templateForTests(t)

externalURL, err := url.Parse("http://localhost")
require.NoError(t, err)
tmpl.ExternalURL = externalURL

t.Run("empty settings should return error", func(t *testing.T) {
json := `{ }`
Expand All @@ -29,21 +32,23 @@ func TestEmailNotifier(t *testing.T) {
Settings: settingsJSON,
}

_, err := NewEmailNotifier(model, externalURL)
_, err := NewEmailNotifier(model, tmpl)
require.Error(t, err)
})

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))
require.NoError(t, err)

emailNotifier, err := NewEmailNotifier(&models.AlertNotification{
Name: "ops",
Type: "email",

Name: "ops",
Type: "email",
Settings: settingsJSON,
}, externalURL)
}, tmpl)

require.NoError(t, err)

Expand Down Expand Up @@ -72,13 +77,14 @@ func TestEmailNotifier(t *testing.T) {
require.True(t, ok)

require.Equal(t, map[string]interface{}{
"subject": "[firing:1] (AlwaysFiring warning)",
"subject": "[FIRING:1] (AlwaysFiring warning)",
"to": []string{"someops@example.com", "somedev@example.com"},
"single_email": false,
"template": "ng_alert_notification.html",
"data": map[string]interface{}{
"Title": "[firing:1] (AlwaysFiring warning)",
"Status": "firing",
"Title": "[FIRING:1] (AlwaysFiring warning)",
"Message": "[FIRING:1] (AlwaysFiring warning)",
"Status": "firing",
"Alerts": template.Alerts{
template.Alert{
Status: "firing",
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/ngalert/notifier/channels/pagerduty.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (pn *PagerdutyNotifier) buildPagerdutyMessage(ctx context.Context, alerts m
HRef: pn.tmpl.ExternalURL.String(),
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{
Component: tmpl(pn.Component),
Summary: tmpl(pn.Summary),
Expand Down
4 changes: 2 additions & 2 deletions pkg/services/ngalert/notifier/channels/pagerduty_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestPagerdutyNotifier(t *testing.T) {
expMsg: &pagerDutyMessage{
RoutingKey: "abcdefgh0123456789",
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
Description: "[firing:1] (val1)",
Description: "[FIRING:1] (val1)",
EventAction: "trigger",
Payload: &pagerDutyPayload{
Summary: "[FIRING:1] (val1)",
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestPagerdutyNotifier(t *testing.T) {
expMsg: &pagerDutyMessage{
RoutingKey: "abcdefgh0123456789",
DedupKey: "6e3538104c14b583da237e9693b76debbc17f0f8058ef20492e5853096cf8733",
Description: "[firing:2] ",
Description: "[FIRING:2] ",
EventAction: "trigger",
Payload: &pagerDutyPayload{
Summary: "[FIRING:2] ",
Expand Down
2 changes: 1 addition & 1 deletion pkg/services/ngalert/notifier/channels/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (tn *TeamsNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
var tmplErr error
tmpl := notify.TmplText(tn.tmpl, data, &tmplErr)

title := getTitleFromTemplateData(data)
title := tmpl(`{{ template "default.title" . }}`)
body := map[string]interface{}{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
Expand Down
8 changes: 4 additions & 4 deletions pkg/services/ngalert/notifier/channels/teams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ func TestTeamsNotifier(t *testing.T) {
expMsg: map[string]interface{}{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"summary": "[firing:1] (val1)",
"title": "[firing:1] (val1)",
"summary": "[FIRING:1] (val1)",
"title": "[FIRING:1] (val1)",
"themeColor": "#D63232",
"sections": []map[string]interface{}{
{
Expand Down Expand Up @@ -89,8 +89,8 @@ func TestTeamsNotifier(t *testing.T) {
expMsg: map[string]interface{}{
"@type": "MessageCard",
"@context": "http://schema.org/extensions",
"summary": "[firing:2] ",
"title": "[firing:2] ",
"summary": "[FIRING:2] ",
"title": "[FIRING:2] ",
"themeColor": "#D63232",
"sections": []map[string]interface{}{
{
Expand Down
16 changes: 0 additions & 16 deletions pkg/services/ngalert/notifier/channels/utils.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package channels

import (
"fmt"
"strings"

"github.com/prometheus/alertmanager/template"
"github.com/prometheus/common/model"
)

Expand All @@ -20,15 +16,3 @@ func getAlertStatusColor(status model.AlertStatus) string {
}
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
}
16 changes: 16 additions & 0 deletions pkg/tests/api/alerting/api_available_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,22 @@ var expAvailableChannelJsonOutput = `
"required": true,
"validationRule": "",
"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
}
]
},
Expand Down

0 comments on commit d5ae55c

Please sign in to comment.