diff --git a/pkg/services/ngalert/notifier/alertmanager.go b/pkg/services/ngalert/notifier/alertmanager.go index 8710630236e..3392bb8bb5b 100644 --- a/pkg/services/ngalert/notifier/alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager.go @@ -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": diff --git a/pkg/services/ngalert/notifier/available_channels.go b/pkg/services/ngalert/notifier/available_channels.go index 0b9fae741a7..341d6a9f881 100644 --- a/pkg/services/ngalert/notifier/available_channels.go +++ b/pkg/services/ngalert/notifier/available_channels.go @@ -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", + }, }, }, { diff --git a/pkg/services/ngalert/notifier/channels/dingding.go b/pkg/services/ngalert/notifier/channels/dingding.go index 73a4134d4e9..0938720a497 100644 --- a/pkg/services/ngalert/notifier/channels/dingding.go +++ b/pkg/services/ngalert/notifier/channels/dingding.go @@ -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" { diff --git a/pkg/services/ngalert/notifier/channels/dingding_test.go b/pkg/services/ngalert/notifier/channels/dingding_test.go index 2c4c169261f..4258675a07c 100644 --- a/pkg/services/ngalert/notifier/channels/dingding_test.go +++ b/pkg/services/ngalert/notifier/channels/dingding_test.go @@ -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, @@ -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", }, diff --git a/pkg/services/ngalert/notifier/channels/email.go b/pkg/services/ngalert/notifier/channels/email.go index 7d4eab11487..ae18b2ec1ee 100644 --- a/pkg/services/ngalert/notifier/channels/email.go +++ b/pkg/services/ngalert/notifier/channels/email.go @@ -2,7 +2,7 @@ package channels import ( "context" - "net/url" + "fmt" "path" gokit_log "github.com/go-kit/kit/log" @@ -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"} } @@ -49,31 +50,35 @@ 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, @@ -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 { return false, err } diff --git a/pkg/services/ngalert/notifier/channels/email_test.go b/pkg/services/ngalert/notifier/channels/email_test.go index 0847a450cea..6001660c8ac 100644 --- a/pkg/services/ngalert/notifier/channels/email_test.go +++ b/pkg/services/ngalert/notifier/channels/email_test.go @@ -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 := `{ }` @@ -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) @@ -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", diff --git a/pkg/services/ngalert/notifier/channels/pagerduty.go b/pkg/services/ngalert/notifier/channels/pagerduty.go index a22566f2173..f72fb338d1b 100644 --- a/pkg/services/ngalert/notifier/channels/pagerduty.go +++ b/pkg/services/ngalert/notifier/channels/pagerduty.go @@ -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), diff --git a/pkg/services/ngalert/notifier/channels/pagerduty_test.go b/pkg/services/ngalert/notifier/channels/pagerduty_test.go index 49f821e8f5c..85e4fee71ae 100644 --- a/pkg/services/ngalert/notifier/channels/pagerduty_test.go +++ b/pkg/services/ngalert/notifier/channels/pagerduty_test.go @@ -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)", @@ -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] ", diff --git a/pkg/services/ngalert/notifier/channels/teams.go b/pkg/services/ngalert/notifier/channels/teams.go index c9bfe4894ce..f291c88859b 100644 --- a/pkg/services/ngalert/notifier/channels/teams.go +++ b/pkg/services/ngalert/notifier/channels/teams.go @@ -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", diff --git a/pkg/services/ngalert/notifier/channels/teams_test.go b/pkg/services/ngalert/notifier/channels/teams_test.go index 83fa93d18ad..9192bdf62cc 100644 --- a/pkg/services/ngalert/notifier/channels/teams_test.go +++ b/pkg/services/ngalert/notifier/channels/teams_test.go @@ -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{}{ { @@ -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{}{ { diff --git a/pkg/services/ngalert/notifier/channels/utils.go b/pkg/services/ngalert/notifier/channels/utils.go index 906acb50065..8162552a66c 100644 --- a/pkg/services/ngalert/notifier/channels/utils.go +++ b/pkg/services/ngalert/notifier/channels/utils.go @@ -1,10 +1,6 @@ package channels import ( - "fmt" - "strings" - - "github.com/prometheus/alertmanager/template" "github.com/prometheus/common/model" ) @@ -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 -} diff --git a/pkg/tests/api/alerting/api_available_channel_test.go b/pkg/tests/api/alerting/api_available_channel_test.go index bef8289893d..5ca9bd6fa3b 100644 --- a/pkg/tests/api/alerting/api_available_channel_test.go +++ b/pkg/tests/api/alerting/api_available_channel_test.go @@ -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 } ] },