MM-56629 Render Markdown in batched email notifications (#26217)
* [MM-56629] rendered markdown in batched email (#26116) * [MM-56629] added unit test for GenerateHyperlinkForChannels (#26116) * [MM-56629] refactored code to remove duplicates, updated test (#26116) * [MM-56629] updated landing url and cleared check-style (#26116) * [MM-56629] added app-layesrs and mocks (#26116) * [MM-56629] updated unit tests (#26116) * [MM-56629] reverted changes of removing unused context parameter (#26116) * [MM-56629] removed unused method generateHyperlinkForChannels (#26116) * [MM-56629] fixed check-style error (#26116) * [MM-56629] refactored (#26116) --------- Co-authored-by: Sazzad Hossain <sazzad.hossain@marginedge.com> Co-authored-by: Harrison Healey <harrisonmhealey@gmail.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ab029105fd
Коммит
bc887441d0
@@ -332,12 +332,13 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []*
|
||||
channelDisplayName = truncateUserNames(channel.DisplayName, 11)
|
||||
}
|
||||
|
||||
postMessage := es.GetMessageForNotification(notification.post, notification.teamName, siteURL, translateFunc)
|
||||
postsData = append(postsData, &postData{
|
||||
SenderPhoto: senderPhoto,
|
||||
SenderName: truncateUserNames(sender.GetDisplayName(displayNameFormat), 22),
|
||||
Time: t,
|
||||
ChannelName: channelDisplayName,
|
||||
Message: template.HTML(es.GetMessageForNotification(notification.post, translateFunc)),
|
||||
Message: template.HTML(postMessage),
|
||||
MessageURL: MessageURL,
|
||||
ShowChannelIcon: showChannelIcon,
|
||||
OtherChannelMembersCount: otherChannelMembersCount,
|
||||
|
||||
@@ -13,6 +13,8 @@ import (
|
||||
|
||||
model "github.com/mattermost/mattermost/server/public/model"
|
||||
|
||||
store "github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
|
||||
templates "github.com/mattermost/mattermost/server/v8/platform/shared/templates"
|
||||
|
||||
throttled "github.com/throttled/throttled"
|
||||
@@ -65,13 +67,37 @@ func (_m *ServiceInterface) CreateVerifyEmailToken(userID string, newEmail strin
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMessageForNotification provides a mock function with given fields: post, translateFunc
|
||||
func (_m *ServiceInterface) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string {
|
||||
ret := _m.Called(post, translateFunc)
|
||||
// GenerateHyperlinkForChannels provides a mock function with given fields: postMessage, teamName, teamURL
|
||||
func (_m *ServiceInterface) GenerateHyperlinkForChannels(postMessage string, teamName string, teamURL string) (string, error) {
|
||||
ret := _m.Called(postMessage, teamName, teamURL)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(*model.Post, i18n.TranslateFunc) string); ok {
|
||||
r0 = rf(post, translateFunc)
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) (string, error)); ok {
|
||||
return rf(postMessage, teamName, teamURL)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(string, string, string) string); ok {
|
||||
r0 = rf(postMessage, teamName, teamURL)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
|
||||
r1 = rf(postMessage, teamName, teamURL)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GetMessageForNotification provides a mock function with given fields: post, teamName, siteUrl, translateFunc
|
||||
func (_m *ServiceInterface) GetMessageForNotification(post *model.Post, teamName string, siteUrl string, translateFunc i18n.TranslateFunc) string {
|
||||
ret := _m.Called(post, teamName, siteUrl, translateFunc)
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func(*model.Post, string, string, i18n.TranslateFunc) string); ok {
|
||||
r0 = rf(post, teamName, siteUrl, translateFunc)
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
@@ -608,6 +634,11 @@ func (_m *ServiceInterface) SendWelcomeEmail(userID string, _a1 string, verified
|
||||
return r0
|
||||
}
|
||||
|
||||
// SetStore provides a mock function with given fields: st
|
||||
func (_m *ServiceInterface) SetStore(st store.Store) {
|
||||
_m.Called(st)
|
||||
}
|
||||
|
||||
// Stop provides a mock function with given fields:
|
||||
func (_m *ServiceInterface) Stop() {
|
||||
_m.Called()
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"net/url"
|
||||
@@ -28,9 +29,9 @@ type EmailMessageAttachment struct {
|
||||
FieldRows []FieldRow
|
||||
}
|
||||
|
||||
func (es *Service) GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string {
|
||||
func (es *Service) GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string {
|
||||
if strings.TrimSpace(post.Message) != "" || len(post.FileIds) == 0 {
|
||||
return post.Message
|
||||
return es.prepareNotificationMessageForEmail(post.Message, teamName, siteUrl)
|
||||
}
|
||||
|
||||
// extract the filenames from their paths and determine what type of files are attached
|
||||
@@ -134,3 +135,49 @@ func prepareTextForEmail(text, siteURL string) template.HTML {
|
||||
|
||||
return template.HTML(markdownText)
|
||||
}
|
||||
|
||||
func (es *Service) prepareNotificationMessageForEmail(postMessage, teamName, siteURL string) string {
|
||||
postMessage = html.EscapeString(postMessage)
|
||||
mdPostMessage, mdErr := utils.MarkdownToHTML(postMessage, siteURL)
|
||||
if mdErr != nil {
|
||||
mlog.Warn("Encountered error while converting markdown to HTML", mlog.Err(mdErr))
|
||||
mdPostMessage = postMessage
|
||||
}
|
||||
|
||||
landingURL := siteURL + "/landing#/" + teamName
|
||||
normalizedPostMessage, err := es.GenerateHyperlinkForChannels(mdPostMessage, teamName, landingURL)
|
||||
if err != nil {
|
||||
mlog.Warn("Encountered error while generating hyperlink for channels", mlog.String("team_name", teamName), mlog.Err(err))
|
||||
normalizedPostMessage = mdPostMessage
|
||||
}
|
||||
return normalizedPostMessage
|
||||
}
|
||||
|
||||
func (es *Service) GenerateHyperlinkForChannels(postMessage, teamName, landingURL string) (string, error) {
|
||||
channelNames := model.ChannelMentions(postMessage)
|
||||
if len(channelNames) == 0 {
|
||||
return postMessage, nil
|
||||
}
|
||||
|
||||
team, err := es.Store().Team().GetByName(teamName)
|
||||
if err != nil {
|
||||
mlog.Error("Team not found with the name", mlog.String("team_name", teamName), mlog.Err(err))
|
||||
return postMessage, nil
|
||||
}
|
||||
|
||||
channels, err := es.store.Channel().GetByNames(team.Id, channelNames, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
visited := make(map[string]bool)
|
||||
for _, ch := range channels {
|
||||
if !visited[ch.Id] && ch.Type == model.ChannelTypeOpen {
|
||||
channelURL := landingURL + "/channels/" + ch.Name
|
||||
channelHyperLink := fmt.Sprintf("<a href='%s'>%s</a>", channelURL, "~"+ch.Name)
|
||||
postMessage = strings.Replace(postMessage, "~"+ch.Name, channelHyperLink, -1)
|
||||
visited[ch.Id] = true
|
||||
}
|
||||
}
|
||||
return postMessage, nil
|
||||
}
|
||||
|
||||
@@ -162,14 +162,24 @@ type ServiceInterface interface {
|
||||
SendNoCardPaymentFailedEmail(email string, locale string, siteURL string) error
|
||||
SendRemoveExpiredLicenseEmail(ctaText, ctaLink, email, locale, siteURL string) error
|
||||
AddNotificationEmailToBatch(user *model.User, post *model.Post, team *model.Team) *model.AppError
|
||||
GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string
|
||||
GetMessageForNotification(post *model.Post, teamName, siteUrl string, translateFunc i18n.TranslateFunc) string
|
||||
GenerateHyperlinkForChannels(postMessage, teamName, teamURL string) (string, error)
|
||||
InitEmailBatching()
|
||||
SendChangeUsernameEmail(newUsername, email, locale, siteURL string) error
|
||||
CreateVerifyEmailToken(userID string, newEmail string) (*model.Token, error)
|
||||
SendIPFiltersChangedEmail(email string, userWhoChangedFilter *model.User, siteURL, portalURL, locale string, isWorkspaceOwner bool) error
|
||||
SetStore(st store.Store)
|
||||
Stop()
|
||||
}
|
||||
|
||||
func (es *Service) Store() store.Store {
|
||||
return es.store
|
||||
}
|
||||
|
||||
func (es *Service) SetStore(st store.Store) {
|
||||
es.store = st
|
||||
}
|
||||
|
||||
func (es *Service) GetPerDayEmailRateLimiter() *throttled.GCRARateLimiter {
|
||||
return es.perDayEmailRateLimiter
|
||||
}
|
||||
|
||||
Ссылка в новой задаче
Block a user