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:
co-authored by
Gilles De Mey
parent
a92c081a33
commit
8dab3bf36c
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user