diff --git a/app/notification.go b/app/notification.go index 1dc1fdbd95..c3b5fa99b9 100644 --- a/app/notification.go +++ b/app/notification.go @@ -164,42 +164,13 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod continue } - userAllowsEmails := profileMap[id].NotifyProps[model.EMAIL_NOTIFY_PROP] != "false" - if channelEmail, ok := channelMemberNotifyPropsMap[id][model.EMAIL_NOTIFY_PROP]; ok { - if channelEmail != model.CHANNEL_NOTIFY_DEFAULT { - userAllowsEmails = channelEmail != "false" - } - } - - // Remove the user as recipient when the user has muted the channel. - if channelMuted, ok := channelMemberNotifyPropsMap[id][model.MARK_UNREAD_NOTIFY_PROP]; ok { - if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { - mlog.Debug("Channel muted for user", mlog.String("user_id", id), mlog.String("channel_mute", channelMuted)) - userAllowsEmails = false - } - } - //If email verification is required and user email is not verified don't send email. if *a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified { mlog.Error("Skipped sending notification email, address not verified.", mlog.String("user_email", profileMap[id].Email), mlog.String("user_id", id)) continue } - var status *model.Status - var err *model.AppError - if status, err = a.GetStatus(id); err != nil { - status = &model.Status{ - UserId: id, - Status: model.STATUS_OFFLINE, - Manual: false, - LastActivityAt: 0, - ActiveChannel: "", - } - } - - autoResponderRelated := status.Status == model.STATUS_OUT_OF_OFFICE || post.Type == model.POST_AUTO_RESPONDER - - if userAllowsEmails && status.Status != model.STATUS_ONLINE && profileMap[id].DeleteAt == 0 && !autoResponderRelated { + if a.userAllowsEmail(profileMap[id], channelMemberNotifyPropsMap[id], post) { a.sendNotificationEmail(notification, profileMap[id], team) } } @@ -377,6 +348,40 @@ func (a *App) SendNotifications(post *model.Post, team *model.Team, channel *mod return mentionedUsersList, nil } +func (a *App) userAllowsEmail(user *model.User, channelMemberNotificationProps model.StringMap, post *model.Post) bool { + userAllowsEmails := user.NotifyProps[model.EMAIL_NOTIFY_PROP] != "false" + if channelEmail, ok := channelMemberNotificationProps[model.EMAIL_NOTIFY_PROP]; ok { + if channelEmail != model.CHANNEL_NOTIFY_DEFAULT { + userAllowsEmails = channelEmail != "false" + } + } + + // Remove the user as recipient when the user has muted the channel. + if channelMuted, ok := channelMemberNotificationProps[model.MARK_UNREAD_NOTIFY_PROP]; ok { + if channelMuted == model.CHANNEL_MARK_UNREAD_MENTION { + mlog.Debug("Channel muted for user", mlog.String("user_id", user.Id), mlog.String("channel_mute", channelMuted)) + userAllowsEmails = false + } + } + + var status *model.Status + var err *model.AppError + if status, err = a.GetStatus(user.Id); err != nil { + status = &model.Status{ + UserId: user.Id, + Status: model.STATUS_OFFLINE, + Manual: false, + LastActivityAt: 0, + ActiveChannel: "", + } + } + + autoResponderRelated := status.Status == model.STATUS_OUT_OF_OFFICE || post.Type == model.POST_AUTO_RESPONDER + emailNotificationsAllowedForStatus := status.Status != model.STATUS_ONLINE && status.Status != model.STATUS_DND + + return userAllowsEmails && emailNotificationsAllowedForStatus && user.DeleteAt == 0 && !autoResponderRelated +} + // sendOutOfChannelMentions sends an ephemeral post to the sender of a post if any of the given potential mentions // are outside of the post's channel. Returns whether or not an ephemeral post was sent. func (a *App) sendOutOfChannelMentions(sender *model.User, post *model.Post, channel *model.Channel, potentialMentions []string) (bool, error) { diff --git a/app/notification_test.go b/app/notification_test.go index 13ee5ca588..5b08bffd2e 100644 --- a/app/notification_test.go +++ b/app/notification_test.go @@ -1789,3 +1789,100 @@ func TestGetNotificationNameFormat(t *testing.T) { assert.Equal(t, model.SHOW_USERNAME, th.App.GetNotificationNameFormat(th.BasicUser)) }) } + +func TestUserAllowsEmail(t *testing.T) { + th := Setup(t).InitBasic() + defer th.TearDown() + + t.Run("should return true", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOffline(user.Id, true) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.True(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: "some-post-type"})) + }) + + t.Run("should return false in case the status is ONLINE", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOnline(user.Id, true) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: "some-post-type"})) + }) + + t.Run("should return false in case the EMAIL_NOTIFY_PROP is false", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOffline(user.Id, true) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: "false", + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: "some-post-type"})) + }) + + t.Run("should return false in case the MARK_UNREAD_NOTIFY_PROP is CHANNEL_MARK_UNREAD_MENTION", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOffline(user.Id, true) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_MENTION, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: "some-post-type"})) + }) + + t.Run("should return false in case the Post type is POST_AUTO_RESPONDER", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOffline(user.Id, true) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: model.POST_AUTO_RESPONDER})) + }) + + t.Run("should return false in case the status is STATUS_OUT_OF_OFFICE", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusOutOfOffice(user.Id) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: model.POST_AUTO_RESPONDER})) + }) + + t.Run("should return false in case the status is STATUS_ONLINE", func(t *testing.T) { + user := th.CreateUser() + + th.App.SetStatusDoNotDisturb(user.Id) + + channelMemberNotificationProps := model.StringMap{ + model.EMAIL_NOTIFY_PROP: model.CHANNEL_NOTIFY_DEFAULT, + model.MARK_UNREAD_NOTIFY_PROP: model.CHANNEL_MARK_UNREAD_ALL, + } + + assert.False(t, th.App.userAllowsEmail(user, channelMemberNotificationProps, &model.Post{Type: model.POST_AUTO_RESPONDER})) + }) + +}