Set batched notification email timestamps to user TZ (#26121)

* refactor: migrate getFormattedPostTime to utils

Move app.getFormattedPostTime to utils and export along with its struct

* Set batch notification post times to user TZ

* default useMilitaryTime to false in batched email

If there is an error reading the user's preference for useMilitaryTime,
default to false, as that should be the default value if the user never
sets it.

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
bewing 2024-02-29 05:42:34 -06:00 committed by GitHub
parent c9fc6297f3
commit 96e5e17d13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 67 additions and 63 deletions

View File

@ -17,6 +17,7 @@ import (
"github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/i18n"
"github.com/mattermost/mattermost/server/public/shared/mlog" "github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/v8/channels/utils"
) )
const ( const (
@ -278,6 +279,13 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []*
} }
} }
var useMilitaryTime bool
if data, err := es.store.Preference().Get(user.Id, model.PreferenceCategoryDisplaySettings, model.PreferenceNameUseMilitaryTime); err != nil {
useMilitaryTime = false
} else {
useMilitaryTime = data.Value == "true"
}
if emailNotificationContentsType == model.EmailNotificationContentsFull { if emailNotificationContentsType == model.EmailNotificationContentsFull {
for i, notification := range notifications { for i, notification := range notifications {
sender, errSender := es.userService.GetUser(notification.post.UserId) sender, errSender := es.userService.GetUser(notification.post.UserId)
@ -300,17 +308,9 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []*
embeddedFiles[senderPhoto] = bytes.NewReader(senderProfileImage) embeddedFiles[senderPhoto] = bytes.NewReader(senderProfileImage)
} }
tm := time.Unix(notification.post.CreateAt/1000, 0) formattedTime := utils.GetFormattedPostTime(user, notification.post, useMilitaryTime, translateFunc)
timezone, _ := tm.Zone()
t := translateFunc("api.email_batching.send_batched_email_notification.time", map[string]any{ t := translateFunc("api.email_batching.send_batched_email_notification.time", formattedTime)
"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 MessageURL := siteURL + "/" + notification.teamName + "/pl/" + notification.post.Id
@ -346,13 +346,13 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []*
} }
} }
tm := time.Unix(notifications[0].post.CreateAt/1000, 0) formattedTime := utils.GetFormattedPostTime(user, notifications[0].post, useMilitaryTime, translateFunc)
subject := translateFunc("api.email_batching.send_batched_email_notification.subject", len(notifications), map[string]any{ subject := translateFunc("api.email_batching.send_batched_email_notification.subject", len(notifications), map[string]any{
"SiteName": es.config().TeamSettings.SiteName, "SiteName": es.config().TeamSettings.SiteName,
"Year": tm.Year(), "Year": formattedTime.Year,
"Month": translateFunc(tm.Month().String()), "Month": formattedTime.Month,
"Day": tm.Day(), "Day": formattedTime.Day,
}) })
data := es.NewEmailTemplateData(user.Locale) data := es.NewEmailTemplateData(user.Locale)

View File

@ -9,9 +9,7 @@ import (
"html" "html"
"html/template" "html/template"
"io" "io"
"strconv"
"strings" "strings"
"time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -148,7 +146,7 @@ func (a *App) sendNotificationEmail(c request.CTX, notification *PostNotificatio
* Computes the subject line for direct notification email messages * Computes the subject line for direct notification email messages
*/ */
func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string { func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, senderName string, useMilitaryTime bool) string {
t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) t := utils.GetFormattedPostTime(user, post, useMilitaryTime, translateFunc)
var subjectParameters = map[string]any{ var subjectParameters = map[string]any{
"SiteName": siteName, "SiteName": siteName,
"SenderDisplayName": senderName, "SenderDisplayName": senderName,
@ -163,7 +161,7 @@ func getDirectMessageNotificationEmailSubject(user *model.User, post *model.Post
* Computes the subject line for group, public, and private email messages * Computes the subject line for group, public, and private email messages
*/ */
func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, teamName string, useMilitaryTime bool) string { func getNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, teamName string, useMilitaryTime bool) string {
t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) t := utils.GetFormattedPostTime(user, post, useMilitaryTime, translateFunc)
var subjectParameters = map[string]any{ var subjectParameters = map[string]any{
"SiteName": siteName, "SiteName": siteName,
"TeamName": teamName, "TeamName": teamName,
@ -178,7 +176,7 @@ func getNotificationEmailSubject(user *model.User, post *model.Post, translateFu
* Computes the subject line for group email messages * Computes the subject line for group email messages
*/ */
func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, channelName string, emailNotificationContentsType string, useMilitaryTime bool) string { func getGroupMessageNotificationEmailSubject(user *model.User, post *model.Post, translateFunc i18n.TranslateFunc, siteName string, channelName string, emailNotificationContentsType string, useMilitaryTime bool) string {
t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) t := utils.GetFormattedPostTime(user, post, useMilitaryTime, translateFunc)
var subjectParameters = map[string]any{ var subjectParameters = map[string]any{
"SiteName": siteName, "SiteName": siteName,
"Month": t.Month, "Month": t.Month,
@ -226,7 +224,7 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
SenderPhoto: senderPhoto, SenderPhoto: senderPhoto,
} }
t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) t := utils.GetFormattedPostTime(recipient, post, useMilitaryTime, translateFunc)
messageTime := map[string]any{ messageTime := map[string]any{
"Hour": t.Hour, "Hour": t.Hour,
"Minute": t.Minute, "Minute": t.Minute,
@ -311,48 +309,6 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos
return a.Srv().TemplatesContainer().RenderToString("messages_notification", data) return a.Srv().TemplatesContainer().RenderToString("messages_notification", data)
} }
type formattedPostTime struct {
Time time.Time
Year string
Month string
Day string
Hour string
Minute string
TimeZone string
}
func getFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) formattedPostTime {
preferredTimezone := user.GetPreferredTimezone()
postTime := time.Unix(post.CreateAt/1000, 0)
zone, _ := postTime.Zone()
localTime := postTime
if preferredTimezone != "" {
loc, _ := time.LoadLocation(preferredTimezone)
if loc != nil {
localTime = postTime.In(loc)
zone, _ = localTime.Zone()
}
}
hour := localTime.Format("15")
period := ""
if !useMilitaryTime {
hour = localTime.Format("3")
period = " " + localTime.Format("PM")
}
return formattedPostTime{
Time: localTime,
Year: strconv.Itoa(localTime.Year()),
Month: translateFunc(localTime.Month().String()),
Day: strconv.Itoa(localTime.Day()),
Hour: hour,
Minute: fmt.Sprintf("%02d"+period, localTime.Minute()),
TimeZone: zone,
}
}
func (a *App) generateHyperlinkForChannels(c request.CTX, postMessage, teamName, teamURL string) (string, *model.AppError) { func (a *App) generateHyperlinkForChannels(c request.CTX, postMessage, teamName, teamURL string) (string, *model.AppError) {
team, err := a.GetTeamByName(teamName) team, err := a.GetTeamByName(teamName)
if err != nil { if err != nil {

View File

@ -18,6 +18,7 @@ import (
"github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/i18n"
"github.com/mattermost/mattermost/server/public/shared/timezones" "github.com/mattermost/mattermost/server/public/shared/timezones"
"github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks" "github.com/mattermost/mattermost/server/v8/channels/store/storetest/mocks"
"github.com/mattermost/mattermost/server/v8/channels/utils"
) )
func TestGetDirectMessageNotificationEmailSubject(t *testing.T) { func TestGetDirectMessageNotificationEmailSubject(t *testing.T) {
@ -255,7 +256,7 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing
tm := time.Unix(post.CreateAt/1000, 0) tm := time.Unix(post.CreateAt/1000, 0)
zone, _ := tm.Zone() zone, _ := tm.Zone()
formattedTime := formattedPostTime{ formattedTime := utils.FormattedPostTime{
Hour: fmt.Sprintf("%02d", tm.Hour()), Hour: fmt.Sprintf("%02d", tm.Hour()),
Minute: fmt.Sprintf("%02d", tm.Minute()), Minute: fmt.Sprintf("%02d", tm.Minute()),
TimeZone: zone, TimeZone: zone,

View File

@ -4,7 +4,12 @@
package utils package utils
import ( import (
"fmt"
"strconv"
"time" "time"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/i18n"
) )
func MillisFromTime(t time.Time) int64 { func MillisFromTime(t time.Time) int64 {
@ -28,3 +33,45 @@ func EndOfDay(t time.Time) time.Time {
func Yesterday() time.Time { func Yesterday() time.Time {
return time.Now().AddDate(0, 0, -1) return time.Now().AddDate(0, 0, -1)
} }
type FormattedPostTime struct {
Time time.Time
Year string
Month string
Day string
Hour string
Minute string
TimeZone string
}
func GetFormattedPostTime(user *model.User, post *model.Post, useMilitaryTime bool, translateFunc i18n.TranslateFunc) FormattedPostTime {
preferredTimezone := user.GetPreferredTimezone()
postTime := time.Unix(post.CreateAt/1000, 0)
zone, _ := postTime.Zone()
localTime := postTime
if preferredTimezone != "" {
loc, _ := time.LoadLocation(preferredTimezone)
if loc != nil {
localTime = postTime.In(loc)
zone, _ = localTime.Zone()
}
}
hour := localTime.Format("15")
period := ""
if !useMilitaryTime {
hour = localTime.Format("3")
period = " " + localTime.Format("PM")
}
return FormattedPostTime{
Time: localTime,
Year: strconv.Itoa(localTime.Year()),
Month: translateFunc(localTime.Month().String()),
Day: strconv.Itoa(localTime.Day()),
Hour: hour,
Minute: fmt.Sprintf("%02d"+period, localTime.Minute()),
TimeZone: zone,
}
}