mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
SMTP: Update email templates to include populated <title> tag (#61430)
* add .TemplateData property to data in order to populate template <title> tags with the compiled subject value * update all templates * re-enable integration test and update implementation to check changes * chore: fmt * add HiddenSubject template func and update text templates * slight performance improvement, only execute subject template once * update template I missed --------- Co-authored-by: Gilles De Mey <gilles.de.mey@gmail.com>
This commit is contained in:
parent
a92c081a33
commit
8dab3bf36c
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject! Use the HTML comment below ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "{{ .InvitedBy }} has added you to the {{ .OrgName }} organization" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .InvitedBy }} has added you to the {{ .OrgName }} organization" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
</mj-head>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "[[.InvitedBy]] has added you to the [[.OrgName]] organization"]]
|
||||
[[HiddenSubject .Subject "[[.InvitedBy]] has added you to the [[.OrgName]] organization"]]
|
||||
|
||||
You have been added to [[.OrgName]]
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject below! ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "{{ .InvitedBy }} has invited you to join Grafana" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .InvitedBy }} has invited you to join Grafana" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
</mj-head>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "[[.InvitedBy]] has invited you to join Grafana"]]
|
||||
[[HiddenSubject .Subject "[[.InvitedBy]] has invited you to join Grafana"]]
|
||||
|
||||
You're invited to join [[.OrgName]]
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject below! ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "{{ .Title }}" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .Title }}" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
<!-- Summary of the email contents, this will go in the email preview -->
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "[[.Title]]"]]
|
||||
[[HiddenSubject .Subject "[[.Title]]"]]
|
||||
|
||||
[[.Title]]
|
||||
----------------
|
||||
|
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject below! ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "Reset your Grafana password - {{.Name}}" }}
|
||||
{{ Subject .Subject .TemplateData "Reset your Grafana password - {{.Name}}" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
</mj-head>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "Reset your Grafana password - [[.Name]]"]]
|
||||
[[HiddenSubject .Subject "Reset your Grafana password - [[.Name]]"]]
|
||||
|
||||
Hi [[.Name]],
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject below! ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "Welcome to Grafana, please complete your sign up!" }}
|
||||
{{ Subject .Subject .TemplateData "Welcome to Grafana, please complete your sign up!" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
</mj-head>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "Welcome to Grafana, please complete your sign up!"]]
|
||||
[[HiddenSubject .Subject "Welcome to Grafana, please complete your sign up!"]]
|
||||
|
||||
Complete the signup
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
<mj-head>
|
||||
<!-- ⬇ Don't forget to specifify an email subject below! ⬇ -->
|
||||
<mj-title>
|
||||
{{ Subject .Subject "Welcome to Grafana" }}
|
||||
{{ Subject .Subject .TemplateData "Welcome to Grafana" }}
|
||||
</mj-title>
|
||||
<mj-include path="./partials/layout/head.mjml" />
|
||||
</mj-head>
|
||||
|
@ -1,4 +1,4 @@
|
||||
[[Subject .Subject "Welcome to Grafana"]]
|
||||
[[HiddenSubject .Subject "Welcome to Grafana"]]
|
||||
|
||||
Hi [[.Name]],
|
||||
|
||||
|
@ -33,4 +33,9 @@ func setDefaultTemplateData(cfg *setting.Cfg, data map[string]interface{}, u *us
|
||||
if u != nil {
|
||||
data["Name"] = u.NameOrFallback()
|
||||
}
|
||||
dataCopy := map[string]interface{}{}
|
||||
for k, v := range data {
|
||||
dataCopy[k] = v
|
||||
}
|
||||
data["TemplateData"] = dataCopy
|
||||
}
|
||||
|
@ -82,26 +82,31 @@ func (ns *NotificationService) buildEmailMessage(cmd *SendEmailCommand) (*Messag
|
||||
|
||||
subject := cmd.Subject
|
||||
if cmd.Subject == "" {
|
||||
var subjectText interface{}
|
||||
subjectData := data["Subject"].(map[string]interface{})
|
||||
subjectText, hasSubject := subjectData["value"]
|
||||
subjectText, hasSubject := subjectData["executed_template"].(string)
|
||||
if hasSubject {
|
||||
// first check to see if the template has already been executed in a template func
|
||||
subject = subjectText
|
||||
} else {
|
||||
subjectTemplate, hasSubject := subjectData["value"]
|
||||
|
||||
if !hasSubject {
|
||||
return nil, fmt.Errorf("missing subject in template %s", cmd.Template)
|
||||
if !hasSubject {
|
||||
return nil, fmt.Errorf("missing subject in template %s", cmd.Template)
|
||||
}
|
||||
|
||||
subjectTmpl, err := template.New("subject").Parse(subjectTemplate.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subjectBuffer bytes.Buffer
|
||||
err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subject = subjectBuffer.String()
|
||||
}
|
||||
|
||||
subjectTmpl, err := template.New("subject").Parse(subjectText.(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var subjectBuffer bytes.Buffer
|
||||
err = subjectTmpl.ExecuteTemplate(&subjectBuffer, "subject", data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subject = subjectBuffer.String()
|
||||
}
|
||||
|
||||
addr := mail.Address{Name: ns.Cfg.Smtp.FromName, Address: ns.Cfg.Smtp.FromAddress}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -53,7 +54,8 @@ func ProvideService(bus bus.Bus, cfg *setting.Cfg, mailer Mailer, store TempUser
|
||||
|
||||
mailTemplates = template.New("name")
|
||||
mailTemplates.Funcs(template.FuncMap{
|
||||
"Subject": subjectTemplateFunc,
|
||||
"Subject": subjectTemplateFunc,
|
||||
"HiddenSubject": hiddenSubjectTemplateFunc,
|
||||
})
|
||||
mailTemplates.Funcs(sprig.FuncMap())
|
||||
|
||||
@ -143,11 +145,35 @@ func (ns *NotificationService) SendWebhookSync(ctx context.Context, cmd *SendWeb
|
||||
})
|
||||
}
|
||||
|
||||
func subjectTemplateFunc(obj map[string]interface{}, value string) string {
|
||||
// hiddenSubjectTemplateFunc sets the subject template (value) on the map represented by `.Subject.` (obj) so that it can be compiled and executed later.
|
||||
// It returns a blank string, so there will be no resulting value left in place of the template.
|
||||
func hiddenSubjectTemplateFunc(obj map[string]interface{}, value string) string {
|
||||
obj["value"] = value
|
||||
return ""
|
||||
}
|
||||
|
||||
// subjectTemplateFunc does the same thing has hiddenSubjectTemplateFunc, but in addition it executes and returns the subject template using the data represented in `.TemplateData` (data)
|
||||
// This results in the template being replaced by the subject string.
|
||||
func subjectTemplateFunc(obj map[string]interface{}, data map[string]interface{}, value string) string {
|
||||
obj["value"] = value
|
||||
|
||||
titleTmpl, err := template.New("title").Parse(value)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = titleTmpl.ExecuteTemplate(&buf, "title", data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
subj := buf.String()
|
||||
// Since we have already executed the template, save it to subject data so we don't have to do it again later on
|
||||
obj["executed_template"] = subj
|
||||
return subj
|
||||
}
|
||||
|
||||
func (ns *NotificationService) SendEmailCommandHandlerSync(ctx context.Context, cmd *SendEmailCommandSync) error {
|
||||
message, err := ns.buildEmailMessage(&SendEmailCommand{
|
||||
Data: cmd.Data,
|
||||
|
@ -12,19 +12,17 @@ import (
|
||||
|
||||
func TestEmailIntegrationTest(t *testing.T) {
|
||||
t.Run("Given the notifications service", func(t *testing.T) {
|
||||
t.Skip()
|
||||
|
||||
setting.StaticRootPath = "../../../public/"
|
||||
setting.BuildVersion = "4.0.0"
|
||||
|
||||
ns := &NotificationService{}
|
||||
ns.Bus = newBus(t)
|
||||
ns.Cfg = setting.NewCfg()
|
||||
ns.Cfg.Smtp.Enabled = true
|
||||
ns.Cfg.Smtp.TemplatesPatterns = []string{"emails/*.html", "emails/*.txt"}
|
||||
ns.Cfg.Smtp.FromAddress = "from@address.com"
|
||||
ns.Cfg.Smtp.FromName = "Grafana Admin"
|
||||
ns.Cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"}
|
||||
cfg := setting.NewCfg()
|
||||
cfg.Smtp.Enabled = true
|
||||
cfg.StaticRootPath = "../../../public/"
|
||||
cfg.Smtp.TemplatesPatterns = []string{"emails/*.html", "emails/*.txt"}
|
||||
cfg.Smtp.FromAddress = "from@address.com"
|
||||
cfg.Smtp.FromName = "Grafana Admin"
|
||||
cfg.Smtp.ContentTypes = []string{"text/html", "text/plain"}
|
||||
ns, err := ProvideService(newBus(t), cfg, NewFakeMailer(), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("When sending reset email password", func(t *testing.T) {
|
||||
cmd := &SendEmailCommand{
|
||||
@ -59,11 +57,19 @@ func TestEmailIntegrationTest(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
sentMsg := <-ns.mailQueue
|
||||
require.Equal(t, sentMsg.From, "Grafana Admin <from@address.com>")
|
||||
require.Equal(t, sentMsg.To[0], "asdf@asdf.com")
|
||||
err = os.WriteFile("../../../tmp/test_email.html", []byte(sentMsg.Body["text/html"]), 0777)
|
||||
require.Equal(t, "\"Grafana Admin\" <from@address.com>", sentMsg.From)
|
||||
require.Equal(t, "asdf@asdf.com", sentMsg.To[0])
|
||||
require.Equal(t, "[CRITICAL] Imaginary timeseries alert", sentMsg.Subject)
|
||||
require.Contains(t, sentMsg.Body["text/html"], "<title>[CRITICAL] Imaginary timeseries alert</title>")
|
||||
|
||||
path, err := os.MkdirTemp("../../..", "tmp")
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile("../../../tmp/test_email.txt", []byte(sentMsg.Body["text/plain"]), 0777)
|
||||
t.Cleanup(func() {
|
||||
_ = os.RemoveAll(path)
|
||||
})
|
||||
err = os.WriteFile(path+"/test_email.html", []byte(sentMsg.Body["text/html"]), 0777)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(path+"/test_email.txt", []byte(sentMsg.Body["text/plain"]), 0777)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
|
@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title>{{Subject .Subject .TemplateData "{{.Title}}"}}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
|
||||
@ -204,7 +205,6 @@ text-decoration: underline;
|
||||
</tr>
|
||||
<tr style="vertical-align: top; padding: 0;" align="left">
|
||||
<td class="mini-centered-text" style="color: #343b41; mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto; border-collapse: collapse !important; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; margin: 0; padding: 25px 35px; font: 400 16px/27px 'Helvetica Neue', Helvetica, Arial, sans-serif;" align="center" valign="top">
|
||||
{{Subject .Subject "{{.Title}}"}}
|
||||
|
||||
<table class="row" style="border-spacing: 0; border-collapse: collapse; vertical-align: top; text-align: left; width: 100%; position: relative; display: block; padding: 0px;">
|
||||
<tr style="vertical-align: top; padding: 0;" align="left">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "{{.Title}}"}}
|
||||
{{HiddenSubject .Subject "{{.Title}}"}}
|
||||
|
||||
{{.Title}}
|
||||
----------------
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "{{ .InvitedBy }} has added you to the {{ .OrgName }} organization" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .InvitedBy }} has added you to the {{ .OrgName }} organization" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "{{.InvitedBy}} has added you to the {{.OrgName}} organization"}}
|
||||
{{HiddenSubject .Subject "{{.InvitedBy}} has added you to the {{.OrgName}} organization"}}
|
||||
|
||||
You have been added to {{.OrgName}}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "{{ .InvitedBy }} has invited you to join Grafana" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .InvitedBy }} has invited you to join Grafana" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "{{.InvitedBy}} has invited you to join Grafana"}}
|
||||
{{HiddenSubject .Subject "{{.InvitedBy}} has invited you to join Grafana"}}
|
||||
|
||||
You're invited to join {{.OrgName}}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "{{ .Title }}" }}
|
||||
{{ Subject .Subject .TemplateData "{{ .Title }}" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
@ -238,8 +238,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;">
|
||||
<mj-raw>
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;"><mj-raw>
|
||||
{{ range $line := (splitList "\n" .Message) }}
|
||||
</mj-raw>
|
||||
{{ $line }}<br>
|
||||
@ -469,8 +468,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;">
|
||||
<mj-raw>
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;"><mj-raw>
|
||||
{{ range $line := (splitList "\n" .Annotations.description) }}
|
||||
</mj-raw>
|
||||
{{ $line }}<br>
|
||||
@ -532,8 +530,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;">
|
||||
<mj-raw>
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;"><mj-raw>
|
||||
{{ range $refID, $value := .Values }}
|
||||
</mj-raw>
|
||||
{{ $refID }}={{ $value }} <mj-raw>
|
||||
@ -978,8 +975,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;">
|
||||
<mj-raw>
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;"><mj-raw>
|
||||
{{ range $line := (splitList "\n" .Annotations.description) }}
|
||||
</mj-raw>
|
||||
{{ $line }}<br>
|
||||
@ -1041,8 +1037,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="left" style="font-size:0px;padding:10px 25px;word-break:break-word;">
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;">
|
||||
<mj-raw>
|
||||
<div style="font-family:Ubuntu, Helvetica, Arial, sans-serif;font-size:13px;line-height:1.5;text-align:left;color:#FFFFFF;"><mj-raw>
|
||||
{{ range $refID, $value := .Values }}
|
||||
</mj-raw>
|
||||
{{ $refID }}={{ $value }} <mj-raw>
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "{{.Title}}"}}
|
||||
{{HiddenSubject .Subject "{{.Title}}"}}
|
||||
|
||||
{{.Title}}
|
||||
----------------
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "Reset your Grafana password - {{.Name}}" }}
|
||||
{{ Subject .Subject .TemplateData "Reset your Grafana password - {{.Name}}" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "Reset your Grafana password - {{.Name}}"}}
|
||||
{{HiddenSubject .Subject "Reset your Grafana password - {{.Name}}"}}
|
||||
|
||||
Hi {{.Name}},
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "Welcome to Grafana, please complete your sign up!" }}
|
||||
{{ Subject .Subject .TemplateData "Welcome to Grafana, please complete your sign up!" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "Welcome to Grafana, please complete your sign up!"}}
|
||||
{{HiddenSubject .Subject "Welcome to Grafana, please complete your sign up!"}}
|
||||
|
||||
Complete the signup
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
<head>
|
||||
<title>
|
||||
{{ Subject .Subject "Welcome to Grafana" }}
|
||||
{{ Subject .Subject .TemplateData "Welcome to Grafana" }}
|
||||
</title>
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{Subject .Subject "Welcome to Grafana"}}
|
||||
{{HiddenSubject .Subject "Welcome to Grafana"}}
|
||||
|
||||
Hi {{.Name}},
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user