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
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
312bf283a8
Коммит
31e6cdd0e6
@@ -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)
|
||||
|
||||
Ссылка в новой задаче
Block a user