MM-18463 Added update_badge notification when marking a channel as unread (#12499)

* MM-18463 Added update_badge notification when marking a channel as unread

* Address feedback
Этот коммит содержится в:
Harrison Healey
2019-10-07 09:58:39 -04:00
коммит произвёл GitHub
родитель 3fa26bd0ee
Коммит 66da2bab2b
9 изменённых файлов: 92 добавлений и 78 удалений

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

@@ -1806,12 +1806,16 @@ func (a *App) MarkChannelAsUnreadFromPost(postID string, userID string) (*model.
if err != nil { if err != nil {
return channel, updateErr return channel, updateErr
} }
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_UNREAD, channel.TeamId, channel.ChannelId, channel.UserId, nil) message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_POST_UNREAD, channel.TeamId, channel.ChannelId, channel.UserId, nil)
message.Add("msg_count", channel.MsgCount) message.Add("msg_count", channel.MsgCount)
message.Add("mention_count", channel.MentionCount) message.Add("mention_count", channel.MentionCount)
message.Add("last_viewed_at", channel.LastViewedAt) message.Add("last_viewed_at", channel.LastViewedAt)
message.Add("post_id", postID) message.Add("post_id", postID)
a.Publish(message) a.Publish(message)
a.UpdateMobileAppBadge(userID)
return channel, nil return channel, nil
} }

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

@@ -20,6 +20,7 @@ type NotificationType string
const NOTIFICATION_TYPE_CLEAR NotificationType = "clear" const NOTIFICATION_TYPE_CLEAR NotificationType = "clear"
const NOTIFICATION_TYPE_MESSAGE NotificationType = "message" const NOTIFICATION_TYPE_MESSAGE NotificationType = "message"
const NOTIFICATION_TYPE_UPDATE_BADGE NotificationType = "update_badge"
const PUSH_NOTIFICATION_HUB_WORKERS = 1000 const PUSH_NOTIFICATION_HUB_WORKERS = 1000
const PUSH_NOTIFICATIONS_HUB_BUFFER_PER_WORKER = 50 const PUSH_NOTIFICATIONS_HUB_BUFFER_PER_WORKER = 50
@@ -53,16 +54,23 @@ func (hub *PushNotificationsHub) GetGoChannelFromUserId(userId string) chan Push
func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, func (a *App) sendPushNotificationSync(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
explicitMention bool, channelWideMention bool, replyToThreadType string) *model.AppError { explicitMention bool, channelWideMention bool, replyToThreadType string) *model.AppError {
msg, err := a.BuildPushNotificationMessage(post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType)
sessions, err := a.getMobileAppSessions(user.Id)
if err != nil { if err != nil {
return err return err
} }
msg := a.BuildPushNotificationMessage(post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType) return a.sendPushNotificationToAllSessions(msg, user.Id, "")
}
func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, userId string, skipSessionId string) *model.AppError {
sessions, err := a.getMobileAppSessions(userId)
if err != nil {
return err
}
for _, session := range sessions { for _, session := range sessions {
if session.IsExpired() { // Don't send notifications to this session if it's expired or we want to skip it
if session.IsExpired() || (skipSessionId != "" && skipSessionId == session.Id) {
continue continue
} }
@@ -170,63 +178,22 @@ func (a *App) getPushNotificationMessage(postMessage string, explicitMention, ch
return senderName + userLocale("api.post.send_notifications_and_forget.push_general_message") return senderName + userLocale("api.post.send_notifications_and_forget.push_general_message")
} }
func (a *App) ClearPushNotificationSync(currentSessionId, userId, channelId string) { func (a *App) ClearPushNotificationSync(currentSessionId, userId, channelId string) *model.AppError {
sessions, err := a.getMobileAppSessions(userId) msg := &model.PushNotification{
if err != nil {
mlog.Error("error getting mobile app sessions", mlog.Err(err))
return
}
msg := model.PushNotification{
Type: model.PUSH_TYPE_CLEAR, Type: model.PUSH_TYPE_CLEAR,
Version: model.PUSH_MESSAGE_V2, Version: model.PUSH_MESSAGE_V2,
ChannelId: channelId, ChannelId: channelId,
ContentAvailable: 1, ContentAvailable: 1,
} }
if unreadCount, err := a.Srv.Store.User().GetUnreadCount(userId); err != nil { unreadCount, err := a.Srv.Store.User().GetUnreadCount(userId)
msg.Badge = 0 if err != nil {
mlog.Error("We could not get the unread message count for", mlog.String("user_id", userId), mlog.Err(err)) return err
} else {
msg.Badge = int(unreadCount)
} }
for _, session := range sessions { msg.Badge = int(unreadCount)
if currentSessionId != session.Id {
tmpMessage := model.PushNotificationFromJson(strings.NewReader(msg.ToJson()))
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
tmpMessage.AckId = model.NewId()
err := a.sendToPushProxy(*tmpMessage, session) return a.sendPushNotificationToAllSessions(msg, userId, currentSessionId)
if err != nil {
a.NotificationsLog.Error("Notification error",
mlog.String("ackId", tmpMessage.AckId),
mlog.String("type", tmpMessage.Type),
mlog.String("userId", session.UserId),
mlog.String("postId", tmpMessage.PostId),
mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", err.Error()),
)
continue
}
a.NotificationsLog.Info("Notification sent",
mlog.String("ackId", tmpMessage.AckId),
mlog.String("type", tmpMessage.Type),
mlog.String("userId", session.UserId),
mlog.String("postId", tmpMessage.PostId),
mlog.String("channelId", tmpMessage.ChannelId),
mlog.String("deviceId", tmpMessage.DeviceId),
mlog.String("status", model.PUSH_SEND_SUCCESS),
)
if a.Metrics != nil {
a.Metrics.IncrementPostSentPush()
}
}
}
} }
func (a *App) ClearPushNotification(currentSessionId, userId, channelId string) { func (a *App) ClearPushNotification(currentSessionId, userId, channelId string) {
@@ -239,6 +206,32 @@ func (a *App) ClearPushNotification(currentSessionId, userId, channelId string)
} }
} }
func (a *App) UpdateMobileAppBadgeSync(userId string) *model.AppError {
msg := &model.PushNotification{
Type: model.PUSH_TYPE_UPDATE_BADGE,
Version: model.PUSH_MESSAGE_V2,
Sound: "none",
ContentAvailable: 1,
}
unreadCount, err := a.Srv.Store.User().GetUnreadCount(userId)
if err != nil {
return err
}
msg.Badge = int(unreadCount)
return a.sendPushNotificationToAllSessions(msg, userId, "")
}
func (a *App) UpdateMobileAppBadge(userId string) {
channel := a.Srv.PushNotificationsHub.GetGoChannelFromUserId(userId)
channel <- PushNotification{
notificationType: NOTIFICATION_TYPE_UPDATE_BADGE,
userId: userId,
}
}
func (a *App) CreatePushNotificationsHub() { func (a *App) CreatePushNotificationsHub() {
hub := PushNotificationsHub{ hub := PushNotificationsHub{
Channels: []chan PushNotification{}, Channels: []chan PushNotification{},
@@ -251,11 +244,13 @@ func (a *App) CreatePushNotificationsHub() {
func (a *App) pushNotificationWorker(notifications chan PushNotification) { func (a *App) pushNotificationWorker(notifications chan PushNotification) {
for notification := range notifications { for notification := range notifications {
var err *model.AppError
switch notification.notificationType { switch notification.notificationType {
case NOTIFICATION_TYPE_CLEAR: case NOTIFICATION_TYPE_CLEAR:
a.ClearPushNotificationSync(notification.currentSessionId, notification.userId, notification.channelId) err = a.ClearPushNotificationSync(notification.currentSessionId, notification.userId, notification.channelId)
case NOTIFICATION_TYPE_MESSAGE: case NOTIFICATION_TYPE_MESSAGE:
a.sendPushNotificationSync( err = a.sendPushNotificationSync(
notification.post, notification.post,
notification.user, notification.user,
notification.channel, notification.channel,
@@ -265,9 +260,15 @@ func (a *App) pushNotificationWorker(notifications chan PushNotification) {
notification.channelWideMention, notification.channelWideMention,
notification.replyToThreadType, notification.replyToThreadType,
) )
case NOTIFICATION_TYPE_UPDATE_BADGE:
err = a.UpdateMobileAppBadgeSync(notification.userId)
default: default:
mlog.Error("Invalid notification type", mlog.String("notification_type", string(notification.notificationType))) mlog.Error("Invalid notification type", mlog.String("notification_type", string(notification.notificationType)))
} }
if err != nil {
mlog.Error("Unable to send push notification", mlog.String("notification_type", string(notification.notificationType)), mlog.Err(err))
}
} }
} }
@@ -429,9 +430,9 @@ func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *mo
} }
func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
explicitMention bool, channelWideMention bool, replyToThreadType string) model.PushNotification { explicitMention bool, channelWideMention bool, replyToThreadType string) (*model.PushNotification, *model.AppError) {
msg := model.PushNotification{ msg := &model.PushNotification{
Category: model.CATEGORY_CAN_REPLY, Category: model.CATEGORY_CAN_REPLY,
Version: model.PUSH_MESSAGE_V2, Version: model.PUSH_MESSAGE_V2,
Type: model.PUSH_TYPE_MESSAGE, Type: model.PUSH_TYPE_MESSAGE,
@@ -443,19 +444,19 @@ func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, c
} }
if user.NotifyProps["push"] == "all" { if user.NotifyProps["push"] == "all" {
if unreadCount, err := a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id); err != nil { unreadCount, err := a.Srv.Store.User().GetAnyUnreadPostCountForChannel(user.Id, channel.Id)
msg.Badge = 1 if err != nil {
mlog.Error("We could not get the unread message count for the user", mlog.String("user_id", user.Id), mlog.Err(err)) return nil, err
} else {
msg.Badge = int(unreadCount)
} }
msg.Badge = int(unreadCount)
} else { } else {
if unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id); err != nil { unreadCount, err := a.Srv.Store.User().GetUnreadCount(user.Id)
msg.Badge = 1 if err != nil {
mlog.Error("We could not get the unread message count for the user", mlog.String("user_id", user.Id), mlog.Err(err)) return nil, err
} else {
msg.Badge = int(unreadCount)
} }
msg.Badge = int(unreadCount)
} }
cfg := a.Config() cfg := a.Config()
@@ -483,5 +484,5 @@ func (a *App) BuildPushNotificationMessage(post *model.Post, user *model.User, c
msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale) msg.Message = a.getPushNotificationMessage(post.Message, explicitMention, channelWideMention, hasFiles, msg.SenderName, channelName, channel.Type, replyToThreadType, userLocale)
return msg return msg, nil
} }

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

@@ -10,6 +10,7 @@ import (
"github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/model"
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestDoesNotifyPropsAllowPushNotification(t *testing.T) { func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
@@ -942,7 +943,8 @@ func TestBuildPushNotificationMessage(t *testing.T) {
} { } {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
receiver.NotifyProps["push"] = tc.pushNotifyProps receiver.NotifyProps["push"] = tc.pushNotifyProps
msg := th.App.BuildPushNotificationMessage(post, receiver, channel, channel.Name, sender.Username, tc.explicitMention, tc.channelWideMention, tc.replyToThreadType) msg, err := th.App.BuildPushNotificationMessage(post, receiver, channel, channel.Name, sender.Username, tc.explicitMention, tc.channelWideMention, tc.replyToThreadType)
require.Nil(t, err)
assert.Equal(t, tc.expectedBadge, msg.Badge) assert.Equal(t, tc.expectedBadge, msg.Badge)
}) })
} }

2
go.mod
Просмотреть файл

@@ -53,6 +53,8 @@ require (
github.com/miekg/dns v1.1.19 // indirect github.com/miekg/dns v1.1.19 // indirect
github.com/minio/minio-go/v6 v6.0.38 github.com/minio/minio-go/v6 v6.0.38
github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/muesli/smartcrop v0.3.0 // indirect github.com/muesli/smartcrop v0.3.0 // indirect
github.com/olekukonko/tablewriter v0.0.1 // indirect github.com/olekukonko/tablewriter v0.0.1 // indirect
github.com/onsi/ginkgo v1.8.0 // indirect github.com/onsi/ginkgo v1.8.0 // indirect

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

@@ -15,9 +15,12 @@ const (
PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn" PUSH_NOTIFY_APPLE_REACT_NATIVE = "apple_rn"
PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn" PUSH_NOTIFY_ANDROID_REACT_NATIVE = "android_rn"
PUSH_TYPE_MESSAGE = "message" PUSH_TYPE_MESSAGE = "message"
PUSH_TYPE_CLEAR = "clear" PUSH_TYPE_CLEAR = "clear"
PUSH_MESSAGE_V2 = "v2" PUSH_TYPE_UPDATE_BADGE = "update_badge"
PUSH_MESSAGE_V2 = "v2"
PUSH_SOUND_NONE = "none"
// The category is set to handle a set of interactive Actions // The category is set to handle a set of interactive Actions
// with the push notifications // with the push notifications

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

@@ -1129,7 +1129,7 @@ func (us SqlUserStore) AnalyticsActiveCount(timePeriod int64, options model.User
return v, nil return v, nil
} }
func (us SqlUserStore) GetUnreadCount(userId string) (int64, error) { func (us SqlUserStore) GetUnreadCount(userId string) (int64, *model.AppError) {
query := ` query := `
SELECT SUM(CASE WHEN c.Type = 'D' THEN (c.TotalMsgCount - cm.MsgCount) ELSE cm.MentionCount END) SELECT SUM(CASE WHEN c.Type = 'D' THEN (c.TotalMsgCount - cm.MsgCount) ELSE cm.MentionCount END)
FROM Channels c FROM Channels c

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

@@ -277,7 +277,7 @@ type UserStore interface {
GetSystemAdminProfiles() (map[string]*model.User, *model.AppError) GetSystemAdminProfiles() (map[string]*model.User, *model.AppError)
PermanentDelete(userId string) *model.AppError PermanentDelete(userId string) *model.AppError
AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, *model.AppError) AnalyticsActiveCount(time int64, options model.UserCountOptions) (int64, *model.AppError)
GetUnreadCount(userId string) (int64, error) GetUnreadCount(userId string) (int64, *model.AppError)
GetUnreadCountForChannel(userId string, channelId string) (int64, *model.AppError) GetUnreadCountForChannel(userId string, channelId string) (int64, *model.AppError)
GetAnyUnreadPostCountForChannel(userId string, channelId string) (int64, *model.AppError) GetAnyUnreadPostCountForChannel(userId string, channelId string) (int64, *model.AppError)
GetRecentlyActiveUsersForTeam(teamId string, offset, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) GetRecentlyActiveUsersForTeam(teamId string, offset, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError)

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

@@ -810,7 +810,7 @@ func (_m *UserStore) GetTeamGroupUsers(teamID string) ([]*model.User, *model.App
} }
// GetUnreadCount provides a mock function with given fields: userId // GetUnreadCount provides a mock function with given fields: userId
func (_m *UserStore) GetUnreadCount(userId string) (int64, error) { func (_m *UserStore) GetUnreadCount(userId string) (int64, *model.AppError) {
ret := _m.Called(userId) ret := _m.Called(userId)
var r0 int64 var r0 int64
@@ -820,11 +820,13 @@ func (_m *UserStore) GetUnreadCount(userId string) (int64, error) {
r0 = ret.Get(0).(int64) r0 = ret.Get(0).(int64)
} }
var r1 error var r1 *model.AppError
if rf, ok := ret.Get(1).(func(string) error); ok { if rf, ok := ret.Get(1).(func(string) *model.AppError); ok {
r1 = rf(userId) r1 = rf(userId)
} else { } else {
r1 = ret.Error(1) if ret.Get(1) != nil {
r1 = ret.Get(1).(*model.AppError)
}
} }
return r0, r1 return r0, r1

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

@@ -6504,7 +6504,7 @@ func (s *TimerLayerUserStore) GetTeamGroupUsers(teamID string) ([]*model.User, *
return resultVar0, resultVar1 return resultVar0, resultVar1
} }
func (s *TimerLayerUserStore) GetUnreadCount(userId string) (int64, error) { func (s *TimerLayerUserStore) GetUnreadCount(userId string) (int64, *model.AppError) {
start := timemodule.Now() start := timemodule.Now()
resultVar0, resultVar1 := s.UserStore.GetUnreadCount(userId) resultVar0, resultVar1 := s.UserStore.GetUnreadCount(userId)