From 96e5e17d13989ff9346c8f28ce895ed896c5bdad Mon Sep 17 00:00:00 2001 From: bewing Date: Thu, 29 Feb 2024 05:42:34 -0600 Subject: [PATCH] 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 --- server/channels/app/email/email_batching.go | 28 +++++----- server/channels/app/notification_email.go | 52 ++----------------- .../channels/app/notification_email_test.go | 3 +- server/channels/utils/time.go | 47 +++++++++++++++++ 4 files changed, 67 insertions(+), 63 deletions(-) diff --git a/server/channels/app/email/email_batching.go b/server/channels/app/email/email_batching.go index 5a0585b2ef..3d9fac3cc7 100644 --- a/server/channels/app/email/email_batching.go +++ b/server/channels/app/email/email_batching.go @@ -17,6 +17,7 @@ import ( "github.com/mattermost/mattermost/server/public/model" "github.com/mattermost/mattermost/server/public/shared/i18n" "github.com/mattermost/mattermost/server/public/shared/mlog" + "github.com/mattermost/mattermost/server/v8/channels/utils" ) 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 { for i, notification := range notifications { sender, errSender := es.userService.GetUser(notification.post.UserId) @@ -300,17 +308,9 @@ func (es *Service) sendBatchedEmailNotification(userID string, notifications []* embeddedFiles[senderPhoto] = bytes.NewReader(senderProfileImage) } - tm := time.Unix(notification.post.CreateAt/1000, 0) - timezone, _ := tm.Zone() + formattedTime := utils.GetFormattedPostTime(user, notification.post, useMilitaryTime, translateFunc) - t := translateFunc("api.email_batching.send_batched_email_notification.time", map[string]any{ - "Hour": tm.Hour(), - "Minute": fmt.Sprintf("%02d", tm.Minute()), - "Month": translateFunc(tm.Month().String()), - "Day": tm.Day(), - "Year": tm.Year(), - "TimeZone": timezone, - }) + t := translateFunc("api.email_batching.send_batched_email_notification.time", formattedTime) 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{ "SiteName": es.config().TeamSettings.SiteName, - "Year": tm.Year(), - "Month": translateFunc(tm.Month().String()), - "Day": tm.Day(), + "Year": formattedTime.Year, + "Month": formattedTime.Month, + "Day": formattedTime.Day, }) data := es.NewEmailTemplateData(user.Locale) diff --git a/server/channels/app/notification_email.go b/server/channels/app/notification_email.go index afd67ab2bd..a7ad3e3cf7 100644 --- a/server/channels/app/notification_email.go +++ b/server/channels/app/notification_email.go @@ -9,9 +9,7 @@ import ( "html" "html/template" "io" - "strconv" "strings" - "time" "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 */ 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{ "SiteName": siteName, "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 */ 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{ "SiteName": siteName, "TeamName": teamName, @@ -178,7 +176,7 @@ func getNotificationEmailSubject(user *model.User, post *model.Post, translateFu * 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 { - t := getFormattedPostTime(user, post, useMilitaryTime, translateFunc) + t := utils.GetFormattedPostTime(user, post, useMilitaryTime, translateFunc) var subjectParameters = map[string]any{ "SiteName": siteName, "Month": t.Month, @@ -226,7 +224,7 @@ func (a *App) getNotificationEmailBody(c request.CTX, recipient *model.User, pos SenderPhoto: senderPhoto, } - t := getFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) + t := utils.GetFormattedPostTime(recipient, post, useMilitaryTime, translateFunc) messageTime := map[string]any{ "Hour": t.Hour, "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) } -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) { team, err := a.GetTeamByName(teamName) if err != nil { diff --git a/server/channels/app/notification_email_test.go b/server/channels/app/notification_email_test.go index ce6f376e7b..ea983d0c3d 100644 --- a/server/channels/app/notification_email_test.go +++ b/server/channels/app/notification_email_test.go @@ -18,6 +18,7 @@ import ( "github.com/mattermost/mattermost/server/public/shared/i18n" "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/utils" ) func TestGetDirectMessageNotificationEmailSubject(t *testing.T) { @@ -255,7 +256,7 @@ func TestGetNotificationEmailBodyFullNotificationLocaleTimeNoTimezone(t *testing tm := time.Unix(post.CreateAt/1000, 0) zone, _ := tm.Zone() - formattedTime := formattedPostTime{ + formattedTime := utils.FormattedPostTime{ Hour: fmt.Sprintf("%02d", tm.Hour()), Minute: fmt.Sprintf("%02d", tm.Minute()), TimeZone: zone, diff --git a/server/channels/utils/time.go b/server/channels/utils/time.go index 7843fe4977..1768b7fe81 100644 --- a/server/channels/utils/time.go +++ b/server/channels/utils/time.go @@ -4,7 +4,12 @@ package utils import ( + "fmt" + "strconv" "time" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/shared/i18n" ) func MillisFromTime(t time.Time) int64 { @@ -28,3 +33,45 @@ func EndOfDay(t time.Time) time.Time { func Yesterday() time.Time { 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, + } +}