[GH-15906][MM-22844] Redesign message notification emails. (#17184)

* Redesign message notification emails.

* Fix tests and linter.

* Fix tests

* Fix tests

* gofmt.

* Fix date separator

* Update html files

* Remove date for message notification email.

* Modify subtitle for mentions and direct and group messages.

* Fix lint error

* Fix DM subtitle.

* Fixing translations

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Jesús Espino <jespinog@gmail.com>
Этот коммит содержится в:
Jyoti Patel
2021-05-10 11:50:44 -04:00
коммит произвёл GitHub
родитель bb35a29ed9
Коммит d97daaa1de
22 изменённых файлов: 1265 добавлений и 666 удалений

Просмотреть файл

@@ -4,9 +4,11 @@
package app
import (
"bytes"
"context"
"fmt"
"html/template"
"io"
"net/http"
"strconv"
"sync"
@@ -201,33 +203,61 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
translateFunc := i18n.GetUserTranslations(user.Locale)
displayNameFormat := *es.srv.Config().TeamSettings.TeammateNameDisplay
siteURL := *es.srv.Config().ServiceSettings.SiteURL
var contents string
for _, notification := range notifications {
sender, err := es.srv.Store.User().Get(context.Background(), notification.post.UserId)
if err != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
continue
postsData := make([]*postData, 0 /* len */, len(notifications) /* cap */)
embeddedFiles := make(map[string]io.Reader)
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
if license := es.srv.License(); license != nil && *license.Features.EmailNotificationContents {
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType
}
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
for i, notification := range notifications {
sender, errSender := es.srv.Store.User().Get(context.Background(), notification.post.UserId)
if errSender != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
}
channel, errCh := es.srv.Store.Channel().Get(notification.post.ChannelId, true)
if errCh != nil {
mlog.Warn("Unable to find channel of post for batched email notification")
}
senderProfileImage, _, errProfileImage := es.srv.GetProfileImage(sender)
if errProfileImage != nil {
mlog.Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(errProfileImage))
}
senderPhoto := fmt.Sprintf("user-avatar-%d.png", i)
if senderProfileImage != nil {
embeddedFiles[senderPhoto] = bytes.NewReader(senderProfileImage)
}
tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone()
t := translateFunc("api.email_batching.send_batched_email_notification.time", map[string]interface{}{
"Hour": tm.Hour(),
"Minute": fmt.Sprintf("%02d", tm.Minute()),
"Month": translateFunc(tm.Month().String()),
"Day": tm.Day(),
"Year": tm.Year(),
"Timezone": timezone,
})
MessageURL := siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
postsData = append(postsData, &postData{
SenderPhoto: senderPhoto,
SenderName: sender.GetDisplayName(displayNameFormat),
Time: t,
ChannelName: channel.DisplayName,
Message: template.HTML(es.srv.GetMessageForNotification(notification.post, translateFunc)),
MessageURL: MessageURL,
})
}
channel, errCh := es.srv.Store.Channel().Get(notification.post.ChannelId, true)
if errCh != nil {
mlog.Warn("Unable to find channel of post for batched email notification")
continue
}
emailNotificationContentsType := model.EMAIL_NOTIFICATION_CONTENTS_FULL
if license := es.srv.License(); license != nil && *license.Features.EmailNotificationContents {
emailNotificationContentsType = *es.srv.Config().EmailSettings.EmailNotificationContentsType
}
postContent, err := es.renderBatchedPost(notification, channel, sender, *es.srv.Config().ServiceSettings.SiteURL, displayNameFormat, translateFunc, user.Locale, emailNotificationContentsType)
if err != nil {
mlog.Warn("Unable to render post for batched email notification template", mlog.Err(err))
continue
}
contents += postContent
}
tm := time.Unix(notifications[0].post.CreateAt/1000, 0)
@@ -239,59 +269,31 @@ func (es *EmailService) sendBatchedEmailNotification(userID string, notification
"Day": tm.Day(),
})
data := es.newEmailTemplateData(user.Locale)
data.Props["SiteURL"] = *es.srv.Config().ServiceSettings.SiteURL
data.Props["Posts"] = template.HTML(contents)
data.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
body, err2 := es.srv.TemplatesContainer().RenderToString("post_batched_body", data)
if err2 != nil {
mlog.Warn("Unable build the batched email notification template", mlog.Err(err2))
return
firstSender, err := es.srv.Store.User().Get(context.Background(), notifications[0].post.UserId)
if err != nil {
mlog.Warn("Unable to find sender of post for batched email notification")
}
if nErr := es.sendNotificationMail(user.Email, subject, body); nErr != nil {
data := es.newEmailTemplateData(user.Locale)
data.Props["SiteURL"] = siteURL
data.Props["Title"] = translateFunc("api.email_batching.send_batched_email_notification.title", len(notifications)-1, map[string]interface{}{
"SenderName": firstSender.GetDisplayName(displayNameFormat),
})
data.Props["SubTitle"] = translateFunc("api.email_batching.send_batched_email_notification.subTitle")
data.Props["Button"] = translateFunc("api.email_batching.send_batched_email_notification.button")
data.Props["ButtonURL"] = siteURL
data.Props["Posts"] = postsData
data.Props["MessageButton"] = translateFunc("api.email_batching.send_batched_email_notification.messageButton")
data.Props["NotificationFooterTitle"] = translateFunc("app.notification.footer.title")
data.Props["NotificationFooterInfoLogin"] = translateFunc("app.notification.footer.infoLogin")
data.Props["NotificationFooterInfo"] = translateFunc("app.notification.footer.info")
renderedPage, renderErr := es.srv.TemplatesContainer().RenderToString("messages_notification", data)
if renderErr != nil {
mlog.Error("Unable to render email", mlog.Err(renderErr))
}
if nErr := es.sendNotificationMail(user.Email, subject, renderedPage); nErr != nil {
mlog.Warn("Unable to send batched email notification", mlog.String("email", user.Email), mlog.Err(nErr))
}
}
func (es *EmailService) renderBatchedPost(notification *batchedNotification, channel *model.Channel, sender *model.User, siteURL string, displayNameFormat string, translateFunc i18n.TranslateFunc, userLocale string, emailNotificationContentsType string) (string, error) {
// don't include message contents if email notification contents type is set to generic
var templateName = "post_batched_post_generic"
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
templateName = "post_batched_post_full"
}
data := es.newEmailTemplateData(userLocale)
data.Props["Button"] = translateFunc("api.email_batching.render_batched_post.go_to_post")
data.Props["PostMessage"] = es.srv.GetMessageForNotification(notification.post, translateFunc)
data.Props["PostLink"] = siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
data.Props["SenderName"] = sender.GetDisplayName(displayNameFormat)
tm := time.Unix(notification.post.CreateAt/1000, 0)
timezone, _ := tm.Zone()
data.Props["Date"] = translateFunc("api.email_batching.render_batched_post.date", map[string]interface{}{
"Year": tm.Year(),
"Month": translateFunc(tm.Month().String()),
"Day": tm.Day(),
"Hour": tm.Hour(),
"Minute": fmt.Sprintf("%02d", tm.Minute()),
"Timezone": timezone,
})
if channel.Type == model.CHANNEL_DIRECT {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.direct_message")
} else if channel.Type == model.CHANNEL_GROUP {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.group_message")
} else {
// don't include channel name if email notification contents type is set to generic
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["ChannelName"] = channel.DisplayName
} else {
data.Props["ChannelName"] = translateFunc("api.email_batching.render_batched_post.notification")
}
}
return es.srv.TemplatesContainer().RenderToString(templateName, data)
}

Просмотреть файл

@@ -283,55 +283,3 @@ func TestCheckPendingNotificationsCantParseInterval(t *testing.T) {
require.Nil(t, job.pendingNotifications[th.BasicUser.Id], "should have sent queued post")
}
/*
* Ensures that post contents are not included in notification email when email notification content type is set to generic
*/
func TestRenderBatchedPostGeneric(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
var post = &model.Post{}
post.Message = "This is the message"
var notification = &batchedNotification{}
notification.post = post
var channel = &model.Channel{}
channel.DisplayName = "Some Test Channel"
var sender = &model.User{}
sender.Email = "sender@test.com"
translateFunc := func(translationID string, args ...interface{}) string {
// mock translateFunc just returns the translation id - this is good enough for our purposes
return translationID
}
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_GENERIC)
require.NoError(t, err)
require.NotContains(t, rendered, post.Message, "Rendered email should not contain post contents when email notification contents type is set to Generic.")
}
/*
* Ensures that post contents included in notification email when email notification content type is set to full
*/
func TestRenderBatchedPostFull(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
var post = &model.Post{}
post.Message = "This is the message"
var notification = &batchedNotification{}
notification.post = post
var channel = &model.Channel{}
channel.DisplayName = "Some Test Channel"
var sender = &model.User{}
sender.Email = "sender@test.com"
translateFunc := func(translationID string, args ...interface{}) string {
// mock translateFunc just returns the translation id - this is good enough for our purposes
return translationID
}
rendered, err := th.Server.EmailService.renderBatchedPost(notification, channel, sender, "http://localhost:8065", "", translateFunc, "en", model.EMAIL_NOTIFICATION_CONTENTS_FULL)
require.NoError(t, err)
require.Contains(t, rendered, post.Message, "Rendered email should contain post contents when email notification contents type is set to Full.")
}

Просмотреть файл

@@ -128,15 +128,7 @@ func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.
}
func (a *App) ReadFile(path string) ([]byte, *model.AppError) {
backend, err := a.FileBackend()
if err != nil {
return nil, err
}
result, nErr := backend.ReadFile(path)
if nErr != nil {
return nil, model.NewAppError("ReadFile", "api.file.read_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
return a.srv.ReadFile(path)
}
// Caller must close the first return value
@@ -202,16 +194,7 @@ func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
}
func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
backend, err := a.FileBackend()
if err != nil {
return 0, err
}
result, nErr := backend.WriteFile(fr, path)
if nErr != nil {
return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
return a.srv.WriteFile(fr, path)
}
func (a *App) AppendFile(fr io.Reader, path string) (int64, *model.AppError) {

Просмотреть файл

@@ -254,8 +254,11 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod
}
if a.userAllowsEmail(profileMap[id], channelMemberNotifyPropsMap[id], post) {
err := a.sendNotificationEmail(notification, profileMap[id], team)
senderProfileImage, _, err := a.GetProfileImage(sender)
if err != nil {
a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(err))
}
if err := a.sendNotificationEmail(notification, profileMap[id], team, senderProfileImage); err != nil {
mlog.Warn("Unable to send notification email.", mlog.Err(err))
}
}

Просмотреть файл

@@ -4,9 +4,11 @@
package app
import (
"bytes"
"fmt"
"html"
"html/template"
"io"
"net/url"
"path/filepath"
"strings"
@@ -18,7 +20,7 @@ import (
"github.com/pkg/errors"
)
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team) error {
func (a *App) sendNotificationEmail(notification *PostNotification, user *model.User, team *model.Team, senderProfileImage []byte) error {
channel := notification.Channel
post := notification.Post
@@ -95,14 +97,24 @@ func (a *App) sendNotificationEmail(notification *PostNotification, user *model.
subjectText = getNotificationEmailSubject(user, post, translateFunc, *a.Config().TeamSettings.SiteName, team.DisplayName, useMilitaryTime)
}
senderPhoto := ""
embeddedFiles := make(map[string]io.Reader)
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL && senderProfileImage != nil {
senderPhoto = "user-avatar.png"
embeddedFiles = map[string]io.Reader{
senderPhoto: bytes.NewReader(senderProfileImage),
}
}
landingURL := a.GetSiteURL() + "/landing#/" + team.Name
var bodyText, err = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc)
var bodyText, err = a.getNotificationEmailBody(user, post, channel, channelName, senderName, team.Name, landingURL, emailNotificationContentsType, useMilitaryTime, translateFunc, senderPhoto)
if err != nil {
return errors.Wrap(err, "unable to render the email notification template")
}
a.Srv().Go(func() {
if nErr := a.Srv().EmailService.sendNotificationMail(user.Email, html.UnescapeString(subjectText), bodyText); nErr != nil {
if nErr := a.Srv().EmailService.sendMailWithEmbeddedFiles(user.Email, html.UnescapeString(subjectText), bodyText, embeddedFiles); nErr != nil {
mlog.Error("Error while sending the email", mlog.String("user_email", user.Email), mlog.Err(nErr))
}
})
@@ -162,15 +174,33 @@ func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post,
return translateFunc("app.notification.subject.group_message.generic", subjectParameters)
}
type postData struct {
SenderName string
ChannelName string
Message template.HTML
MessageURL string
SenderPhoto string
PostPhoto string
Time string
}
/**
* Computes the email body for notification messages
*/
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc) (string, error) {
// only include message contents in notification email if email notification contents type is set to full
var templateName = "post_body_generic"
data := a.Srv().EmailService.newEmailTemplateData(recipient.Locale)
func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post, channel *model.Channel, channelName string, senderName string, teamName string, landingURL string, emailNotificationContentsType string, useMilitaryTime bool, translateFunc i18n.TranslateFunc, senderPhoto string) (string, error) {
pData := postData{
SenderName: senderName,
SenderPhoto: senderPhoto,
}
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
messageTime := map[string]interface{}{
"Hour": t.Hour,
"Minute": t.Minute,
"TimeZone": t.TimeZone,
}
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
templateName = "post_body_full"
postMessage := a.GetMessageForNotification(post, translateFunc)
postMessage = html.EscapeString(postMessage)
normalizedPostMessage, err := a.generateHyperlinkForChannels(postMessage, teamName, landingURL)
@@ -178,72 +208,47 @@ func (a *App) getNotificationEmailBody(recipient *model.User, post *model.Post,
mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
normalizedPostMessage = postMessage
}
data.Props["PostMessage"] = template.HTML(normalizedPostMessage)
pData.Message = template.HTML(normalizedPostMessage)
pData.Time = translateFunc("app.notification.body.dm.time", messageTime)
}
data := a.Srv().EmailService.newEmailTemplateData(recipient.Locale)
data.Props["SiteURL"] = a.GetSiteURL()
if teamName != "select_team" {
data.Props["TeamLink"] = landingURL + "/pl/" + post.Id
data.Props["ButtonURL"] = landingURL + "/pl/" + post.Id
} else {
data.Props["TeamLink"] = landingURL
}
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
info := map[string]interface{}{
"Hour": t.Hour,
"Minute": t.Minute,
"TimeZone": t.TimeZone,
"Month": t.Month,
"Day": t.Day,
}
if channel.Type == model.CHANNEL_DIRECT {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.full")
data.Props["Info1"] = ""
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.direct.full", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.direct.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.direct.generic", info)
}
} else if channel.Type == model.CHANNEL_GROUP {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.group_message.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.group_message.full2", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.group_message.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.group_message.generic", info)
}
} else {
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.full")
data.Props["Info1"] = translateFunc("app.notification.body.text.notification.full",
map[string]interface{}{
"ChannelName": channelName,
})
info["SenderName"] = senderName
data.Props["Info2"] = translateFunc("app.notification.body.text.notification.full2", info)
} else {
data.Props["BodyText"] = translateFunc("app.notification.body.intro.notification.generic", map[string]interface{}{
"SenderName": senderName,
})
data.Props["Info"] = translateFunc("app.notification.body.text.notification.generic", info)
}
data.Props["ButtonURL"] = landingURL
}
data.Props["SenderName"] = senderName
data.Props["Button"] = translateFunc("api.templates.post_body.button")
data.Props["NotificationFooterTitle"] = translateFunc("app.notification.footer.title")
data.Props["NotificationFooterInfoLogin"] = translateFunc("app.notification.footer.infoLogin")
data.Props["NotificationFooterInfo"] = translateFunc("app.notification.footer.info")
return a.Srv().TemplatesContainer().RenderToString(templateName, data)
if channel.Type == model.CHANNEL_DIRECT {
// Direct Messages
data.Props["Title"] = translateFunc("app.notification.body.dm.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.dm.subTitle", map[string]interface{}{"SenderName": senderName})
} else if channel.Type == model.CHANNEL_GROUP {
// Group Messages
data.Props["Title"] = translateFunc("app.notification.body.group.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.group.subTitle", map[string]interface{}{"SenderName": senderName})
} else {
// mentions
data.Props["Title"] = translateFunc("app.notification.body.mention.title", map[string]interface{}{"SenderName": senderName})
data.Props["SubTitle"] = translateFunc("app.notification.body.mention.subTitle", map[string]interface{}{"SenderName": senderName, "ChannelName": channelName})
pData.ChannelName = channelName
}
// only include posts in notification email if email notification contents type is set to full
if emailNotificationContentsType == model.EMAIL_NOTIFICATION_CONTENTS_FULL {
data.Props["Posts"] = []postData{pData}
} else {
data.Props["Posts"] = []postData{}
}
return a.Srv().TemplatesContainer().RenderToString("messages_notification", data)
}
type formattedPostTime struct {

Просмотреть файл

@@ -8,7 +8,6 @@ import (
"fmt"
"html/template"
"regexp"
"strings"
"testing"
"time"
@@ -91,11 +90,9 @@ func TestGetNotificationEmailBodyFullNotificationPublicChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got %s", body))
require.Contains(t, body, "Channel: "+channel.DisplayName, "Expected email text 'Channel: %s'. Got %s", channel.DisplayName, body)
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -124,11 +121,9 @@ func TestGetNotificationEmailBodyFullNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message.", fmt.Sprintf("Expected email text 'You have a new Group Message. Got "+body))
require.Contains(t, body, "Channel: ChannelName", fmt.Sprintf("Expected email text 'Channel: ChannelName'. Got %s", body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -157,11 +152,9 @@ func TestGetNotificationEmailBodyFullNotificationPrivateChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification.", fmt.Sprintf("Expected email text 'You have a new notification. Got "+body))
require.Contains(t, body, "Channel: "+channel.DisplayName, fmt.Sprintf("Expected email text 'Channel: "+channel.DisplayName+"'. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -190,10 +183,9 @@ func TestGetNotificationEmailBodyFullNotificationDirectChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message.", fmt.Sprintf("Expected email text 'You have a new Direct Message. Got "+body))
require.Contains(t, body, senderName+" - ", fmt.Sprintf("Expected email text '%s - '. Got %s", senderName, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, post.Message, fmt.Sprintf("Expected email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -226,11 +218,11 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeWithTimezone(t *testi
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc, "user-avatar.png")
require.NoError(t, err)
r, _ := regexp.Compile("E([S|D]+)T")
zone := r.FindString(body)
require.Contains(t, body, "sender - 9:43 AM "+zone+", April 25", fmt.Sprintf("Expected email text 'sender - 9:43 AM %s, April 25'. Got %s", zone, body))
require.Contains(t, body, "9:43 AM "+zone, fmt.Sprintf("Expected email text '9:43 AM %s'. Got %s", zone, body))
}
func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing.T) {
@@ -276,10 +268,10 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing
tmp, err := template.New("foo").Parse(`{{.}}`)
require.NoError(t, err)
var text bytes.Buffer
err = tmp.Execute(&text, fmt.Sprintf("sender - %s:%s %s, %s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone, formattedTime.Month, formattedTime.Day))
err = tmp.Execute(&text, fmt.Sprintf("%s:%s %s", formattedTime.Hour, formattedTime.Minute, formattedTime.TimeZone))
require.NoError(t, err)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
postTimeLine := text.String()
require.Contains(t, body, postTimeLine, fmt.Sprintf("Expected email text '%s'. Got %s", postTimeLine, body))
@@ -313,10 +305,9 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime12Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, false, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "sender - 2:30 PM", fmt.Sprintf("Expected email text 'sender - 2:30 PM'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
require.Contains(t, body, "2:30 PM", fmt.Sprintf("Expected email text '2:30 PM'. Got %s", body))
}
func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T) {
@@ -347,10 +338,9 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "sender - 14:30", fmt.Sprintf("Expected email text 'sender - 14:30'. Got %s", body))
require.Contains(t, body, "April 25", fmt.Sprintf("Expected email text 'April 25'. Got %s", body))
require.Contains(t, body, "14:30", fmt.Sprintf("Expected email text '14:30'. Got %s", body))
}
// from here
@@ -378,11 +368,9 @@ func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "Channel: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -410,11 +398,9 @@ func TestGetNotificationEmailBodyGenericNotificationGroupChannel(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Group Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Group Message from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -442,11 +428,9 @@ func TestGetNotificationEmailBodyGenericNotificationPrivateChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new notification from "+senderName, fmt.Sprintf("Expected email text 'You have a new notification from %s'. Got %s", senderName, body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "mentioned you in a message", fmt.Sprintf("Expected email text 'mentioned you in a message. Got %s", body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -474,11 +458,9 @@ func TestGetNotificationEmailBodyGenericNotificationDirectChannel(t *testing.T)
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "You have a new Direct Message from "+senderName, fmt.Sprintf("Expected email text 'You have a new Direct Message from "+senderName+"'. Got "+body))
require.False(t, strings.Contains(body, "CHANNEL: "+channel.DisplayName), fmt.Sprintf("Did not expect email text 'CHANNEL: %s'. Got %s", channel.DisplayName, body))
require.False(t, strings.Contains(body, post.Message), fmt.Sprintf("Did not expect email text '%s'. Got %s", post.Message, body))
require.Contains(t, body, "sent you a new message", fmt.Sprintf("Expected email text 'sent you a new message. Got "+body))
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -510,7 +492,7 @@ func TestGetNotificationEmailEscapingChars(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
channelName, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
assert.NotContains(t, body, message)
@@ -554,7 +536,7 @@ func TestGetNotificationEmailBodyPublicChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
@@ -620,7 +602,7 @@ func TestGetNotificationEmailBodyMultiPublicChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
channelURL2 := teamURL + "/channels/" + ch2.Name
@@ -669,7 +651,7 @@ func TestGetNotificationEmailBodyPrivateChannelMention(t *testing.T) {
body, err := th.App.getNotificationEmailBody(recipient, post, ch,
ch.Name, senderName, teamName, teamURL,
emailNotificationContentsType, true, translateFunc)
emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
channelURL := teamURL + "/channels/" + ch.Name
mention := "~" + ch.Name
@@ -813,7 +795,7 @@ func TestLandingLink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, teamURL, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}
@@ -843,7 +825,7 @@ func TestLandingLinkPermalink(t *testing.T) {
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc)
body, err := th.App.getNotificationEmailBody(recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, teamURL+"/pl/"+post.Id, fmt.Sprintf("Expected email text '%s'. Got %s", teamURL, body))
}

Просмотреть файл

@@ -4,12 +4,14 @@
package app
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"hash/maphash"
"html/template"
"io"
"io/ioutil"
"net"
"net/http"
@@ -2114,6 +2116,76 @@ func (a *App) generateSupportPacketYaml() (*model.FileData, string) {
return nil, warning
}
func (s *Server) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if *s.Config().FileSettings.DriverName == "" {
img, appErr := s.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
return img, false, nil
}
path := "users/" + user.Id + "/profile.png"
data, err := s.ReadFile(path)
if err != nil {
img, appErr := s.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
if user.LastPictureUpdate == 0 {
if _, err := s.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err
}
}
return img, true, nil
}
return data, false, nil
}
func (s *Server) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
var img []byte
var appErr *model.AppError
if user.IsBot {
img = model.BotDefaultImage
appErr = nil
} else {
img, appErr = CreateProfileImage(user.Username, user.Id, *s.Config().FileSettings.InitialFont)
}
if appErr != nil {
return nil, appErr
}
return img, nil
}
func (s *Server) ReadFile(path string) ([]byte, *model.AppError) {
backend, err := s.FileBackend()
if err != nil {
return nil, err
}
result, nErr := backend.ReadFile(path)
if nErr != nil {
return nil, model.NewAppError("ReadFile", "api.file.read_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
}
func (s *Server) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
backend, err := s.FileBackend()
if err != nil {
return 0, err
}
result, nErr := backend.WriteFile(fr, path)
if nErr != nil {
return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
}
return result, nil
}
func runDNDStatusExpireJob(a *App) {
if a.IsLeader() {
a.srv.dndnTaskMut.Lock()

Просмотреть файл

@@ -875,48 +875,11 @@ func getFont(initialFont string) (*truetype.Font, error) {
}
func (a *App) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) {
if *a.Config().FileSettings.DriverName == "" {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
return img, false, nil
}
path := "users/" + user.Id + "/profile.png"
data, err := a.ReadFile(path)
if err != nil {
img, appErr := a.GetDefaultProfileImage(user)
if appErr != nil {
return nil, false, appErr
}
if user.LastPictureUpdate == 0 {
if _, err := a.WriteFile(bytes.NewReader(img), path); err != nil {
return nil, false, err
}
}
return img, true, nil
}
return data, false, nil
return a.srv.GetProfileImage(user)
}
func (a *App) GetDefaultProfileImage(user *model.User) ([]byte, *model.AppError) {
var img []byte
var appErr *model.AppError
if user.IsBot {
img = model.BotDefaultImage
appErr = nil
} else {
img, appErr = CreateProfileImage(user.Username, user.Id, *a.Config().FileSettings.InitialFont)
}
if appErr != nil {
return nil, appErr
}
return img, nil
return a.srv.GetDefaultProfileImage(user)
}
func (a *App) SetDefaultProfileImage(user *model.User) *model.AppError {