Slack attachment in email notifications (#20946)

* First POC

* Added author data and markdown support

* WIP

* styled fields

* WIP

* WIP

* IMplementation done

* Updated template

* fixed mjml compilation error

* removed card.mjml

* Added test

* Updated a styling

* Updated a styling

* CI

* Updated generated templates

* Explicitelly specified font for message attachments

* Lint fix

* Removed leftover existances of slack attachment
Этот коммит содержится в:
Harshil Sharma
2022-10-20 11:05:22 +05:30
коммит произвёл GitHub
родитель 312bf283a8
Коммит 31e6cdd0e6
12 изменённых файлов: 729 добавлений и 45 удалений

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

@@ -201,6 +201,18 @@ func truncateUserNames(name string, i int) string {
return name
}
type FieldRow struct {
Cells []*model.SlackAttachmentField
}
type EmailMessageAttachment struct {
model.SlackAttachment
Pretext template.HTML
Text template.HTML
FieldRows []FieldRow
}
type postData struct {
SenderName string
ChannelName string
@@ -211,6 +223,7 @@ type postData struct {
Time string
ShowChannelIcon bool
OtherChannelMembersCount int
MessageAttachments []*EmailMessageAttachment
}
/**
@@ -245,6 +258,7 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
}
pData.Message = template.HTML(normalizedPostMessage)
pData.Time = translateFunc("app.notification.body.dm.time", messageTime)
pData.MessageAttachments = a.processMessageAttachments(post)
}
data := a.Srv().EmailService.NewEmailTemplateData(recipient.Locale)
@@ -306,6 +320,81 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
return a.Srv().TemplatesContainer().RenderToString("messages_notification", data)
}
func (a *App) processMessageAttachments(post *model.Post) []*EmailMessageAttachment {
emailMessageAttachments := []*EmailMessageAttachment{}
for _, messageAttachment := range post.Attachments() {
emailMessageAttachment := &EmailMessageAttachment{
SlackAttachment: *messageAttachment,
Pretext: a.prepareTextForEmail(messageAttachment.Pretext),
Text: a.prepareTextForEmail(messageAttachment.Text),
}
stripedTitle, err := utils.StripMarkdown(emailMessageAttachment.Title)
if err != nil {
mlog.Warn("Failed parse to markdown from messageatatchment title", mlog.String("post_id", post.Id), mlog.Err(err))
stripedTitle = ""
}
emailMessageAttachment.Title = stripedTitle
shortFieldRow := FieldRow{}
for i := range messageAttachment.Fields {
// Create a new instance to avoid altering the original pointer reference
// We update field value to parse markdown.
// If we do that on the original pointer, the rendered text in mattermost
// becomes invalid as its no longer a markdown string, but rather an HTML string.
field := &model.SlackAttachmentField{
Title: messageAttachment.Fields[i].Title,
Value: messageAttachment.Fields[i].Value,
Short: messageAttachment.Fields[i].Short,
}
if stringValue, ok := field.Value.(string); ok {
field.Value = a.prepareTextForEmail(stringValue)
}
if !field.Short {
if len(shortFieldRow.Cells) > 0 {
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
shortFieldRow = FieldRow{}
}
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, FieldRow{[]*model.SlackAttachmentField{field}})
} else {
shortFieldRow.Cells = append(shortFieldRow.Cells, field)
if len(shortFieldRow.Cells) == 2 {
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
shortFieldRow = FieldRow{}
}
}
}
// collect any leftover short fields
if len(shortFieldRow.Cells) > 0 {
emailMessageAttachment.FieldRows = append(emailMessageAttachment.FieldRows, shortFieldRow)
shortFieldRow = FieldRow{}
}
emailMessageAttachments = append(emailMessageAttachments, emailMessageAttachment)
}
return emailMessageAttachments
}
func (a *App) prepareTextForEmail(text string) template.HTML {
escapedText := html.EscapeString(text)
markdownText, err := utils.MarkdownToHTML(escapedText)
if err != nil {
mlog.Warn("Encountered error while converting markdown to HTML", mlog.Err(err))
return template.HTML(text)
}
return template.HTML(markdownText)
}
type formattedPostTime struct {
Time time.Time
Year string

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

@@ -339,6 +339,103 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTime24Hour(t *testing.T)
require.Contains(t, body, "14:30", fmt.Sprintf("Expected email text '14:30'. Got %s", body))
}
func TestGetNotificationEmailBodyFullNotificationWithSlackAttachments(t *testing.T) {
th := SetupWithStoreMock(t)
defer th.TearDown()
recipient := &model.User{}
post := &model.Post{
Message: "This is the message",
}
messageAttachments := []*model.SlackAttachment{
{
Color: "#FF0000",
Pretext: "message attachment 1 pretext",
AuthorName: "author name",
AuthorLink: "https://example.com/slack_attachment_1/author_link",
AuthorIcon: "https://example.com/slack_attachment_1/author_icon",
Title: "message attachment 1 title",
TitleLink: "https://example.com/slack_attachment_1/title_link",
Text: "message attachment 1 text",
ImageURL: "https://example.com/slack_attachment_1/image",
ThumbURL: "https://example.com/slack_attachment_1/thumb",
Fields: []*model.SlackAttachmentField{
{
Short: true,
Title: "message attachment 1 field 1 title",
Value: "message attachment 1 field 1 value",
},
{
Short: false,
Title: "message attachment 1 field 2 title",
Value: "message attachment 1 field 2 value",
},
{
Short: true,
Title: "message attachment 1 field 3 title",
Value: "message attachment 1 field 3 value",
},
{
Short: true,
Title: "message attachment 1 field 4 title",
Value: "message attachment 1 field 4 value",
},
},
},
{
Color: "#FF0000",
Pretext: "message attachment 2 pretext",
AuthorName: "author name 2",
Text: "message attachment 2 text",
},
}
model.ParseSlackAttachment(post, messageAttachments)
channel := &model.Channel{
DisplayName: "ChannelName",
Type: model.ChannelTypeOpen,
}
channelName := "ChannelName"
senderName := "sender"
teamName := "testteam"
teamURL := "http://localhost:8065/testteam"
emailNotificationContentsType := model.EmailNotificationContentsFull
translateFunc := i18n.GetUserTranslations("en")
storeMock := th.App.Srv().Store().(*mocks.Store)
teamStoreMock := mocks.TeamStore{}
teamStoreMock.On("GetByName", "testteam").Return(&model.Team{Name: "testteam"}, nil)
storeMock.On("Team").Return(&teamStoreMock)
body, err := th.App.getNotificationEmailBody(th.Context, recipient, post, channel, channelName, senderName, teamName, teamURL, emailNotificationContentsType, true, translateFunc, "user-avatar.png")
require.NoError(t, err)
require.Contains(t, body, "#FF0000")
require.Contains(t, body, "message attachment 1 pretext")
require.Contains(t, body, "author name")
require.Contains(t, body, "https://example.com/slack_attachment_1/author_link")
require.Contains(t, body, "https://example.com/slack_attachment_1/author_icon")
require.Contains(t, body, "message attachment 1 title")
require.Contains(t, body, "https://example.com/slack_attachment_1/title_link")
require.Contains(t, body, "message attachment 1 text")
require.Contains(t, body, "https://example.com/slack_attachment_1/image")
require.Contains(t, body, "https://example.com/slack_attachment_1/thumb")
require.Contains(t, body, "message attachment 1 field 1 title")
require.Contains(t, body, "message attachment 1 field 1 value")
require.Contains(t, body, "message attachment 1 field 2 title")
require.Contains(t, body, "message attachment 1 field 2 value")
require.Contains(t, body, "message attachment 1 field 3 title")
require.Contains(t, body, "message attachment 1 field 3 value")
require.Contains(t, body, "message attachment 1 field 4 title")
require.Contains(t, body, "message attachment 1 field 4 value")
require.Contains(t, body, "https://example.com/slack_attachment_1/thumb")
require.Contains(t, body, "message attachment 2 pretext")
require.Contains(t, body, "author name 2")
require.Contains(t, body, "message attachment 2 text")
}
// from here
func TestGetNotificationEmailBodyGenericNotificationPublicChannel(t *testing.T) {
th := SetupWithStoreMock(t)