[MM-57066][MM-57329] Added metrics for all notification stopping points, consolidated categories between metrics and logging (#26799)

* [MM-57066] Add metric counters for notification events

* Some small changes

* Account for Metrics() sometimes being nil

* Fix test (again)

* Fix more tests

* A few changes from testing - added success counter

* Missed a mock

* Lint

* Add feature flag for notification monitoring
This commit is contained in:
Devin Binnie 2024-04-18 10:30:08 -04:00 committed by GitHub
parent 0ce5def8e2
commit 02e23a3275
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 572 additions and 241 deletions

View File

@ -641,13 +641,16 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
c.App.CountNotificationAck(model.NotificationTypePush)
err := c.App.SendAckToPushProxy(&ack) err := c.App.SendAckToPushProxy(&ack)
if ack.IsIdLoaded { if ack.IsIdLoaded {
if err != nil { if err != nil {
c.App.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonPushProxySendError)
c.App.NotificationsLog().Error("Notification ack not sent to push proxy", c.App.NotificationsLog().Error("Notification ack not sent to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonPushProxySendError),
mlog.String("ack_id", ack.Id), mlog.String("ack_id", ack.Id),
mlog.String("push_type", ack.NotificationType), mlog.String("push_type", ack.NotificationType),
mlog.String("post_id", ack.PostId), mlog.String("post_id", ack.PostId),
@ -656,6 +659,8 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
mlog.Int("received_at", ack.ClientReceivedAt), mlog.Int("received_at", ack.ClientReceivedAt),
mlog.Err(err), mlog.Err(err),
) )
} else {
c.App.CountNotificationReason(model.NotificationStatusSuccess, model.NotificationTypePush, model.NotificationReason(""))
} }
// Return post data only when PostId is passed. // Return post data only when PostId is passed.
if ack.PostId != "" && ack.NotificationType == model.PushTypeMessage { if ack.PostId != "" && ack.NotificationType == model.PushTypeMessage {
@ -687,6 +692,7 @@ func pushNotificationAck(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
c.App.CountNotificationReason(model.NotificationStatusSuccess, model.NotificationTypePush, model.NotificationReason(""))
ReturnStatusOK(w) ReturnStatusOK(w)
} }

View File

@ -513,6 +513,9 @@ type AppIface interface {
ConvertGroupMessageToChannel(c request.CTX, convertedByUserId string, gmConversionRequest *model.GroupMessageConversionRequestBody) (*model.Channel, *model.AppError) ConvertGroupMessageToChannel(c request.CTX, convertedByUserId string, gmConversionRequest *model.GroupMessageConversionRequestBody) (*model.Channel, *model.AppError)
CopyFileInfos(rctx request.CTX, userID string, fileIDs []string) ([]string, *model.AppError) CopyFileInfos(rctx request.CTX, userID string, fileIDs []string) ([]string, *model.AppError)
CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError) CopyWranglerPostlist(c request.CTX, wpl *model.WranglerPostList, targetChannel *model.Channel) (*model.Post, *model.AppError)
CountNotification(notificationType model.NotificationType)
CountNotificationAck(notificationType model.NotificationType)
CountNotificationReason(notificationStatus model.NotificationStatus, notificationType model.NotificationType, notificationReason model.NotificationReason)
CreateChannel(c request.CTX, channel *model.Channel, addMember bool) (*model.Channel, *model.AppError) CreateChannel(c request.CTX, channel *model.Channel, addMember bool) (*model.Channel, *model.AppError)
CreateChannelBookmark(c request.CTX, newBookmark *model.ChannelBookmark, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError) CreateChannelBookmark(c request.CTX, newBookmark *model.ChannelBookmark, connectionId string) (*model.ChannelBookmarkWithFileInfo, *model.AppError)
CreateChannelWithUser(c request.CTX, channel *model.Channel, userID string) (*model.Channel, *model.AppError) CreateChannelWithUser(c request.CTX, channel *model.Channel, userID string) (*model.Channel, *model.AppError)

View File

@ -24,10 +24,11 @@ func (a *App) NotifySessionsExpired() error {
// Get all mobile sessions that expired within the last hour. // Get all mobile sessions that expired within the last hour.
sessions, err := a.ch.srv.Store().Session().GetSessionsExpired(OneHourMillis, true, true) sessions, err := a.ch.srv.Store().Session().GetSessionsExpired(OneHourMillis, true, true)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Cannot get sessions expired", a.NotificationsLog().Error("Cannot get sessions expired",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(err), mlog.Err(err),
) )
return model.NewAppError("NotifySessionsExpired", "app.session.analytics_session_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) return model.NewAppError("NotifySessionsExpired", "app.session.analytics_session_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
@ -46,10 +47,11 @@ func (a *App) NotifySessionsExpired() error {
errPush := a.sendToPushProxy(tmpMessage, session) errPush := a.sendToPushProxy(tmpMessage, session)
if errPush != nil { if errPush != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonPushProxySendError)
a.NotificationsLog().Error("Failed to send to push proxy", a.NotificationsLog().Error("Failed to send to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonPushProxyError), mlog.String("reason", model.NotificationReasonPushProxySendError),
mlog.String("ack_id", tmpMessage.AckId), mlog.String("ack_id", tmpMessage.AckId),
mlog.String("push_type", tmpMessage.Type), mlog.String("push_type", tmpMessage.Type),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),
@ -60,7 +62,7 @@ func (a *App) NotifySessionsExpired() error {
} }
a.NotificationsLog().Trace("Notification sent to push proxy", a.NotificationsLog().Trace("Notification sent to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("ack_id", tmpMessage.AckId), mlog.String("ack_id", tmpMessage.AckId),
mlog.String("push_type", tmpMessage.Type), mlog.String("push_type", tmpMessage.Type),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),

View File

@ -24,17 +24,17 @@ import (
func (a *App) canSendPushNotifications() bool { func (a *App) canSendPushNotifications() bool {
if !*a.Config().EmailSettings.SendPushNotifications { if !*a.Config().EmailSettings.SendPushNotifications {
a.NotificationsLog().Debug("Push notifications are disabled - server config", a.NotificationsLog().Debug("Push notifications are disabled - server config",
mlog.String("status", model.StatusBlocked), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonServerConfig), mlog.String("reason", "push_disabled"),
) )
return false return false
} }
pushServer := *a.Config().EmailSettings.PushNotificationServer pushServer := *a.Config().EmailSettings.PushNotificationServer
if license := a.Srv().License(); pushServer == model.MHPNS && (license == nil || !*license.Features.MHPNS) { if license := a.Srv().License(); pushServer == model.MHPNS && (license == nil || !*license.Features.MHPNS) {
a.NotificationsLog().Info("Push notifications are disabled - license missing", a.NotificationsLog().Warn("Push notifications are disabled - license missing",
mlog.String("status", model.StatusBlocked), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonServerConfig), mlog.String("reason", "push_disabled_license"),
) )
mlog.Warn("Push notifications have been disabled. Update your license or go to System Console > Environment > Push Notification Server to use a different server") mlog.Warn("Push notifications have been disabled. Update your license or go to System Console > Environment > Push Notification Server to use a different server")
return false return false
@ -97,11 +97,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
pResult := <-pchan pResult := <-pchan
if pResult.NErr != nil { if pResult.NErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Error fetching profiles", a.NotificationsLog().Error("Error fetching profiles",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(pResult.NErr), mlog.Err(pResult.NErr),
) )
return nil, pResult.NErr return nil, pResult.NErr
@ -110,11 +111,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
cmnResult := <-cmnchan cmnResult := <-cmnchan
if cmnResult.NErr != nil { if cmnResult.NErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Error fetching notify props", a.NotificationsLog().Error("Error fetching notify props",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(cmnResult.NErr), mlog.Err(cmnResult.NErr),
) )
return nil, cmnResult.NErr return nil, cmnResult.NErr
@ -125,11 +127,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if tchan != nil { if tchan != nil {
tResult := <-tchan tResult := <-tchan
if tResult.NErr != nil { if tResult.NErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Error fetching thread followers", a.NotificationsLog().Error("Error fetching thread followers",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(tResult.NErr), mlog.Err(tResult.NErr),
) )
return nil, tResult.NErr return nil, tResult.NErr
@ -143,11 +146,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if gchan != nil { if gchan != nil {
gResult := <-gchan gResult := <-gchan
if gResult.NErr != nil { if gResult.NErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Error fetching group mentions", a.NotificationsLog().Error("Error fetching group mentions",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(gResult.NErr), mlog.Err(gResult.NErr),
) )
return nil, gResult.NErr return nil, gResult.NErr
@ -169,11 +173,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
group := groups[groupID] group := groups[groupID]
anyUsersMentionedByGroup, err := a.insertGroupMentions(sender.Id, group, channel, profileMap, mentions) anyUsersMentionedByGroup, err := a.insertGroupMentions(sender.Id, group, channel, profileMap, mentions)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Failed to populate group mentions", a.NotificationsLog().Error("Failed to populate group mentions",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(err), mlog.Err(err),
) )
return nil, err return nil, err
@ -190,8 +195,8 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
a.NotificationsLog().Warn("Failed to send warning for out of channel mentions", a.NotificationsLog().Warn("Failed to send warning for out of channel mentions",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", "failed_to_send_out_of_channel"),
mlog.Err(err), mlog.Err(err),
) )
c.Logger().Error("Failed to send warning for out of channel mentions", mlog.String("user_id", sender.Id), mlog.String("post_id", post.Id), mlog.Err(err)) c.Logger().Error("Failed to send warning for out of channel mentions", mlog.String("user_id", sender.Id), mlog.String("post_id", post.Id), mlog.Err(err))
@ -368,7 +373,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if *a.Config().EmailSettings.SendEmailNotifications { if *a.Config().EmailSettings.SendEmailNotifications {
a.NotificationsLog().Trace("Begin sending email notifications", a.NotificationsLog().Trace("Begin sending email notifications",
mlog.String("type", model.TypeEmail), mlog.String("type", model.NotificationTypeEmail),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
@ -377,11 +382,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
for _, id := range emailRecipients { for _, id := range emailRecipients {
if profileMap[id] == nil { if profileMap[id] == nil {
a.NotificationsLog().Warn("Missing profile", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeEmail, model.NotificationReasonMissingProfile)
mlog.String("type", model.TypeEmail), a.NotificationsLog().Error("Missing profile",
mlog.String("type", model.NotificationTypeEmail),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonMissingProfile), mlog.String("reason", model.NotificationReasonMissingProfile),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -390,11 +396,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
//If email verification is required and user email is not verified don't send email. //If email verification is required and user email is not verified don't send email.
if *a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified { if *a.Config().EmailSettings.RequireEmailVerification && !profileMap[id].EmailVerified {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypeEmail, model.NotificationReasonEmailNotVerified)
a.NotificationsLog().Debug("Email not verified", a.NotificationsLog().Debug("Email not verified",
mlog.String("type", model.TypeEmail), mlog.String("type", model.NotificationTypeEmail),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserConfig), mlog.String("reason", model.NotificationReasonEmailNotVerified),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -408,11 +415,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(err)) a.Log().Warn("Unable to get the sender user profile image.", mlog.String("user_id", sender.Id), mlog.Err(err))
} }
if err := a.sendNotificationEmail(c, notification, profileMap[id], team, senderProfileImage); err != nil { if err := a.sendNotificationEmail(c, notification, profileMap[id], team, senderProfileImage); err != nil {
a.NotificationsLog().Warn("Error sending email notification", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeEmail, model.NotificationReasonEmailSendError)
mlog.String("type", model.TypeEmail), a.NotificationsLog().Error("Error sending email notification",
mlog.String("type", model.NotificationTypeEmail),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonEmailSendError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
mlog.Err(err), mlog.Err(err),
@ -421,10 +429,10 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
} }
} else { } else {
a.NotificationsLog().Debug("Email disallowed by user", a.NotificationsLog().Debug("Email disallowed by user",
mlog.String("type", model.TypeEmail), mlog.String("type", model.NotificationTypeEmail),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserConfig), mlog.String("reason", "email_disallowed_by_user"),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -432,7 +440,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
} }
a.NotificationsLog().Trace("Finished sending email notifications", a.NotificationsLog().Trace("Finished sending email notifications",
mlog.String("type", model.TypeEmail), mlog.String("type", model.NotificationTypeEmail),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
@ -440,11 +448,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
// Check for channel-wide mentions in channels that have too many members for those to work // Check for channel-wide mentions in channels that have too many members for those to work
if int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel { if int64(len(profileMap)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypeAll, model.NotificationReasonTooManyUsersInChannel)
a.NotificationsLog().Debug("Too many users to notify - will send ephemeral message", a.NotificationsLog().Debug("Too many users to notify - will send ephemeral message",
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonServerConfig), mlog.String("reason", model.NotificationReasonTooManyUsersInChannel),
) )
T := i18n.GetUserTranslations(sender.Locale) T := i18n.GetUserTranslations(sender.Locale)
@ -488,18 +497,19 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if a.canSendPushNotifications() { if a.canSendPushNotifications() {
a.NotificationsLog().Trace("Begin sending push notifications", a.NotificationsLog().Trace("Begin sending push notifications",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
for _, id := range mentionedUsersList { for _, id := range mentionedUsersList {
if profileMap[id] == nil { if profileMap[id] == nil {
a.NotificationsLog().Warn("Missing profile", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonMissingProfile)
mlog.String("type", model.TypePush), a.NotificationsLog().Error("Missing profile",
mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonMissingProfile), mlog.String("reason", model.NotificationReasonMissingProfile),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -508,9 +518,9 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if notificationsForCRT.Push.Contains(id) { if notificationsForCRT.Push.Contains(id) {
a.NotificationsLog().Trace("Skipped direct push notification - will send as CRT notification", a.NotificationsLog().Trace("Skipped direct push notification - will send as CRT notification",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
) )
continue continue
@ -546,11 +556,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
for _, id := range allActivityPushUserIds { for _, id := range allActivityPushUserIds {
if profileMap[id] == nil { if profileMap[id] == nil {
a.NotificationsLog().Warn("Missing profile", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonMissingProfile)
mlog.String("type", model.TypePush), a.NotificationsLog().Error("Missing profile",
mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonMissingProfile), mlog.String("reason", model.NotificationReasonMissingProfile),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -559,9 +570,9 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if notificationsForCRT.Push.Contains(id) { if notificationsForCRT.Push.Contains(id) {
a.NotificationsLog().Trace("Skipped direct push notification - will send as CRT notification", a.NotificationsLog().Trace("Skipped direct push notification - will send as CRT notification",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
) )
continue continue
@ -589,11 +600,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
for _, id := range notificationsForCRT.Push { for _, id := range notificationsForCRT.Push {
if profileMap[id] == nil { if profileMap[id] == nil {
a.NotificationsLog().Warn("Missing profile", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonMissingProfile)
mlog.String("type", model.TypePush), a.NotificationsLog().Error("Missing profile",
mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonMissingProfile), mlog.String("reason", model.NotificationReasonMissingProfile),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
) )
@ -615,11 +627,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
model.CommentsNotifyCRT, model.CommentsNotifyCRT,
) )
} else { } else {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypePush, statusReason)
a.NotificationsLog().Debug("Notification not sent - status", a.NotificationsLog().Debug("Notification not sent - status",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserConfig), mlog.String("reason", statusReason),
mlog.String("status_reason", statusReason), mlog.String("status_reason", statusReason),
mlog.String("sender_id", post.UserId), mlog.String("sender_id", post.UserId),
mlog.String("receiver_id", id), mlog.String("receiver_id", id),
@ -629,14 +642,14 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
} }
a.NotificationsLog().Trace("Finished sending push notifications", a.NotificationsLog().Trace("Finished sending push notifications",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
} }
a.NotificationsLog().Trace("Begin sending websocket notifications", a.NotificationsLog().Trace("Begin sending websocket notifications",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
@ -690,11 +703,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
published, err := a.publishWebsocketEventForPermalinkPost(c, post, message) published, err := a.publishWebsocketEventForPermalinkPost(c, post, message)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Couldn't send websocket notification for permalink post", a.NotificationsLog().Error("Couldn't send websocket notification for permalink post",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.Err(err), mlog.Err(err),
) )
@ -704,11 +718,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
removePermalinkMetadataFromPost(post) removePermalinkMetadataFromPost(post)
postJSON, jsonErr := post.ToJSON() postJSON, jsonErr := post.ToJSON()
if jsonErr != nil { if jsonErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonParseError)
a.NotificationsLog().Error("JSON parse error", a.NotificationsLog().Error("JSON parse error",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonParseError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.Err(err), mlog.Err(err),
) )
@ -725,11 +740,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
// A user following a thread but had left the channel won't get a notification // A user following a thread but had left the channel won't get a notification
// https://mattermost.atlassian.net/browse/MM-36769 // https://mattermost.atlassian.net/browse/MM-36769
if profileMap[uid] == nil { if profileMap[uid] == nil {
a.NotificationsLog().Warn("Missing profile", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonMissingProfile)
mlog.String("type", model.TypeWebsocket), a.NotificationsLog().Error("Missing profile",
mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonMissingProfile), mlog.String("reason", model.NotificationReasonMissingProfile),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
) )
@ -741,11 +757,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
if threadMembership == nil { if threadMembership == nil {
tm, err := a.Srv().Store().Thread().GetMembershipForUser(uid, post.RootId) tm, err := a.Srv().Store().Thread().GetMembershipForUser(uid, post.RootId)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Missing thread membership", a.NotificationsLog().Error("Missing thread membership",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
mlog.Err(err), mlog.Err(err),
@ -753,11 +770,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
return nil, errors.Wrapf(err, "Missing thread membership for participant in notifications. user_id=%q thread_id=%q", uid, post.RootId) return nil, errors.Wrapf(err, "Missing thread membership for participant in notifications. user_id=%q thread_id=%q", uid, post.RootId)
} }
if tm == nil { if tm == nil {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypeWebsocket, model.NotificationReasonMissingThreadMembership)
a.NotificationsLog().Warn("Missing thread membership", a.NotificationsLog().Warn("Missing thread membership",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonMissingThreadMembership),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
) )
@ -767,11 +785,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
} }
userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.IsPostPriorityEnabled()) userThread, err := a.Srv().Store().Thread().GetThreadForUser(threadMembership, true, a.IsPostPriorityEnabled())
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Missing thread", a.NotificationsLog().Error("Missing thread",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
mlog.Err(err), mlog.Err(err),
@ -800,11 +819,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
// should set unread mentions, and unread replies to 0 // should set unread mentions, and unread replies to 0
_, err = a.Srv().Store().Thread().MaintainMembership(uid, post.RootId, opts) _, err = a.Srv().Store().Thread().MaintainMembership(uid, post.RootId, opts)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Failed to update thread membership", a.NotificationsLog().Error("Failed to update thread membership",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
mlog.Err(err), mlog.Err(err),
@ -819,11 +839,12 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
sanitizedPost, err := a.SanitizePostMetadataForUser(c, userThread.Post, uid) sanitizedPost, err := a.SanitizePostMetadataForUser(c, userThread.Post, uid)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonParseError)
a.NotificationsLog().Error("Failed to sanitize metadata", a.NotificationsLog().Error("Failed to sanitize metadata",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonParseError),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("receiver_id", uid), mlog.String("receiver_id", uid),
mlog.Err(err), mlog.Err(err),
@ -847,7 +868,7 @@ func (a *App) SendNotifications(c request.CTX, post *model.Post, team *model.Tea
} }
a.NotificationsLog().Trace("Finish sending websocket notifications", a.NotificationsLog().Trace("Finish sending websocket notifications",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("sender_id", sender.Id), mlog.String("sender_id", sender.Id),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
) )
@ -1709,3 +1730,50 @@ func ShouldAckWebsocketNotification(channelType model.ChannelType, userNotificat
return false return false
} }
func (a *App) CountNotification(notificationType model.NotificationType) {
if a.Metrics() == nil {
return
}
if !a.Config().FeatureFlags.NotificationMonitoring {
return
}
a.Metrics().IncrementNotificationCounter(notificationType)
}
func (a *App) CountNotificationAck(notificationType model.NotificationType) {
if a.Metrics() == nil {
return
}
if !a.Config().FeatureFlags.NotificationMonitoring {
return
}
a.Metrics().IncrementNotificationAckCounter(notificationType)
}
func (a *App) CountNotificationReason(
notificationStatus model.NotificationStatus,
notificationType model.NotificationType,
notificationReason model.NotificationReason,
) {
if a.Metrics() == nil {
return
}
if !a.Config().FeatureFlags.NotificationMonitoring {
return
}
switch notificationStatus {
case model.NotificationStatusSuccess:
a.Metrics().IncrementNotificationSuccessCounter(notificationType)
case model.NotificationStatusError:
a.Metrics().IncrementNotificationErrorCounter(notificationType, notificationReason)
case model.NotificationStatusNotSent:
a.Metrics().IncrementNotificationNotSentCounter(notificationType, notificationReason)
}
}

View File

@ -24,22 +24,12 @@ import (
) )
type notificationType string type notificationType string
type notifyPropsReason string
type statusReason string
const ( const (
notificationTypeClear notificationType = "clear" notificationTypeClear notificationType = "clear"
notificationTypeMessage notificationType = "message" notificationTypeMessage notificationType = "message"
notificationTypeUpdateBadge notificationType = "update_badge" notificationTypeUpdateBadge notificationType = "update_badge"
notificationTypeDummy notificationType = "dummy" notificationTypeDummy notificationType = "dummy"
NotifyPropsReasonChannelMuted notifyPropsReason = "channel_muted"
NotifyPropsReasonSystemMessage notifyPropsReason = "system_message"
NotifyPropsReasonSetToNone notifyPropsReason = "notify_props_set_to_note"
NotifyPropsReasonSetToMention notifyPropsReason = "notify_props_set_to_mention_and_was_not_mentioned"
StatusReasonDNDOrOOO statusReason = "status_is_dnd_or_ooo"
StatusReasonIsActive statusReason = "user_is_active_on_channel"
) )
type PushNotificationsHub struct { type PushNotificationsHub struct {
@ -107,10 +97,12 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
if rejectionReason != "" { if rejectionReason != "" {
// Notifications rejected by a plugin should not be considered errors // Notifications rejected by a plugin should not be considered errors
a.NotificationsLog().Info("Notification rejected by plugin", // This is likely normal operation so no need for metrics here
mlog.String("type", model.TypePush), a.NotificationsLog().Debug("Notification rejected by plugin",
mlog.String("status", model.StatusNotSent), mlog.String("type", model.NotificationTypePush),
mlog.String("reason", rejectionReason), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.NotificationReasonRejectedByPlugin),
mlog.String("rejection_reason", rejectionReason),
mlog.String("user_id", userID), mlog.String("user_id", userID),
) )
return nil return nil
@ -118,10 +110,11 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
sessions, err := a.getMobileAppSessions(userID) sessions, err := a.getMobileAppSessions(userID)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Failed to send mobile app sessions", a.NotificationsLog().Error("Failed to send mobile app sessions",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("user_id", userID), mlog.String("user_id", userID),
mlog.Err(err), mlog.Err(err),
) )
@ -129,10 +122,11 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
} }
if msg == nil { if msg == nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonParseError)
a.NotificationsLog().Error("Failed to parse push notification", a.NotificationsLog().Error("Failed to parse push notification",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonParseError),
mlog.String("user_id", userID), mlog.String("user_id", userID),
) )
return model.NewAppError( return model.NewAppError(
@ -147,10 +141,11 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
for _, session := range sessions { for _, session := range sessions {
// Don't send notifications to this session if it's expired or we want to skip it // Don't send notifications to this session if it's expired or we want to skip it
if session.IsExpired() || (skipSessionId != "" && skipSessionId == session.Id) { if session.IsExpired() || (skipSessionId != "" && skipSessionId == session.Id) {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypePush, model.NotificationReasonSessionExpired)
a.NotificationsLog().Debug("Session expired or skipped", a.NotificationsLog().Debug("Session expired or skipped",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserStatus), mlog.String("reason", model.NotificationReasonSessionExpired),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),
mlog.String("session_id", session.Id), mlog.String("session_id", session.Id),
) )
@ -164,10 +159,11 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
err := a.sendToPushProxy(tmpMessage, session) err := a.sendToPushProxy(tmpMessage, session)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonPushProxySendError)
a.NotificationsLog().Error("Failed to send to push proxy", a.NotificationsLog().Error("Failed to send to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonPushProxyError), mlog.String("reason", model.NotificationReasonPushProxySendError),
mlog.String("ack_id", tmpMessage.AckId), mlog.String("ack_id", tmpMessage.AckId),
mlog.String("push_type", tmpMessage.Type), mlog.String("push_type", tmpMessage.Type),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),
@ -178,7 +174,7 @@ func (a *App) sendPushNotificationToAllSessions(msg *model.PushNotification, use
} }
a.NotificationsLog().Trace("Notification sent to push proxy", a.NotificationsLog().Trace("Notification sent to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("ack_id", tmpMessage.AckId), mlog.String("ack_id", tmpMessage.AckId),
mlog.String("push_type", tmpMessage.Type), mlog.String("push_type", tmpMessage.Type),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),
@ -477,7 +473,7 @@ func (a *App) sendToPushProxy(msg *model.PushNotification, session *model.Sessio
msg.ServerId = a.TelemetryId() msg.ServerId = a.TelemetryId()
a.NotificationsLog().Trace("Notification will be sent", a.NotificationsLog().Trace("Notification will be sent",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("ack_id", msg.AckId), mlog.String("ack_id", msg.AckId),
mlog.String("push_type", msg.Type), mlog.String("push_type", msg.Type),
mlog.String("user_id", session.UserId), mlog.String("user_id", session.UserId),
@ -507,7 +503,7 @@ func (a *App) SendAckToPushProxy(ack *model.PushNotificationAck) error {
} }
a.NotificationsLog().Trace("Notification successfully received", a.NotificationsLog().Trace("Notification successfully received",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("ack_id", ack.Id), mlog.String("ack_id", ack.Id),
mlog.String("push_type", ack.NotificationType), mlog.String("push_type", ack.NotificationType),
mlog.String("post_id", ack.PostId), mlog.String("post_id", ack.PostId),
@ -553,12 +549,12 @@ func (a *App) getMobileAppSessions(userID string) ([]*model.Session, *model.AppE
func (a *App) ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post, isGM bool) bool { func (a *App) ShouldSendPushNotification(user *model.User, channelNotifyProps model.StringMap, wasMentioned bool, status *model.Status, post *model.Post, isGM bool) bool {
if notifyPropsAllowedReason := DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned, isGM); notifyPropsAllowedReason != "" { if notifyPropsAllowedReason := DoesNotifyPropsAllowPushNotification(user, channelNotifyProps, post, wasMentioned, isGM); notifyPropsAllowedReason != "" {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypePush, notifyPropsAllowedReason)
a.NotificationsLog().Debug("Notification not sent - notify props", a.NotificationsLog().Debug("Notification not sent - notify props",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserConfig), mlog.String("reason", notifyPropsAllowedReason),
mlog.String("notify_props_reason", notifyPropsAllowedReason),
mlog.String("sender_id", post.UserId), mlog.String("sender_id", post.UserId),
mlog.String("receiver_id", user.Id), mlog.String("receiver_id", user.Id),
) )
@ -566,12 +562,12 @@ func (a *App) ShouldSendPushNotification(user *model.User, channelNotifyProps mo
} }
if statusAllowedReason := DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId); statusAllowedReason != "" { if statusAllowedReason := DoesStatusAllowPushNotification(user.NotifyProps, status, post.ChannelId); statusAllowedReason != "" {
a.CountNotificationReason(model.NotificationStatusNotSent, model.NotificationTypePush, statusAllowedReason)
a.NotificationsLog().Debug("Notification not sent - status", a.NotificationsLog().Debug("Notification not sent - status",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusNotSent), mlog.String("status", model.NotificationStatusNotSent),
mlog.String("reason", model.ReasonUserConfig), mlog.String("reason", statusAllowedReason),
mlog.String("status_reason", statusAllowedReason),
mlog.String("sender_id", post.UserId), mlog.String("sender_id", post.UserId),
mlog.String("receiver_id", user.Id), mlog.String("receiver_id", user.Id),
mlog.String("receiver_status", status.Status), mlog.String("receiver_status", status.Status),
@ -582,7 +578,7 @@ func (a *App) ShouldSendPushNotification(user *model.User, channelNotifyProps mo
return true return true
} }
func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned, isGM bool) notifyPropsReason { func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps model.StringMap, post *model.Post, wasMentioned, isGM bool) model.NotificationReason {
userNotifyProps := user.NotifyProps userNotifyProps := user.NotifyProps
userNotify := userNotifyProps[model.PushNotifyProp] userNotify := userNotifyProps[model.PushNotifyProp]
channelNotify, ok := channelNotifyProps[model.PushNotifyProp] channelNotify, ok := channelNotifyProps[model.PushNotifyProp]
@ -600,19 +596,19 @@ func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
// If the channel is muted do not send push notifications // If the channel is muted do not send push notifications
if channelNotifyProps[model.MarkUnreadNotifyProp] == model.ChannelMarkUnreadMention { if channelNotifyProps[model.MarkUnreadNotifyProp] == model.ChannelMarkUnreadMention {
return NotifyPropsReasonChannelMuted return model.NotificationReasonChannelMuted
} }
if post.IsSystemMessage() { if post.IsSystemMessage() {
return NotifyPropsReasonSystemMessage return model.NotificationReasonSystemMessage
} }
if notify == model.ChannelNotifyNone { if notify == model.ChannelNotifyNone {
return NotifyPropsReasonSetToNone return model.NotificationReasonLevelSetToNone
} }
if notify == model.ChannelNotifyMention && !wasMentioned { if notify == model.ChannelNotifyMention && !wasMentioned {
return NotifyPropsReasonSetToMention return model.NotificationReasonNotMentioned
} }
if (notify == model.ChannelNotifyAll) && if (notify == model.ChannelNotifyAll) &&
@ -623,10 +619,10 @@ func DoesNotifyPropsAllowPushNotification(user *model.User, channelNotifyProps m
return "" return ""
} }
func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *model.Status, channelID string) statusReason { func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *model.Status, channelID string) model.NotificationReason {
// If User status is DND or OOO return false right away // If User status is DND or OOO return false right away
if status.Status == model.StatusDnd || status.Status == model.StatusOutOfOffice { if status.Status == model.StatusDnd || status.Status == model.StatusOutOfOffice {
return StatusReasonDNDOrOOO return model.NotificationReasonUserStatus
} }
pushStatus, ok := userNotifyProps[model.PushStatusNotifyProp] pushStatus, ok := userNotifyProps[model.PushStatusNotifyProp]
@ -642,7 +638,7 @@ func DoesStatusAllowPushNotification(userNotifyProps model.StringMap, status *mo
return "" return ""
} }
return StatusReasonIsActive return model.NotificationReasonUserIsActive
} }
func (a *App) BuildPushNotificationMessage(c request.CTX, contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string, func (a *App) BuildPushNotificationMessage(c request.CTX, contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
@ -689,11 +685,12 @@ func (a *App) SendTestPushNotification(deviceID string) string {
pushResponse, err := a.rawSendToPushProxy(msg) pushResponse, err := a.rawSendToPushProxy(msg)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonPushProxySendError)
a.NotificationsLog().Error("Failed to send test notification to push proxy", a.NotificationsLog().Error("Failed to send test notification to push proxy",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("push_type", msg.Type), mlog.String("push_type", msg.Type),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonPushProxyError), mlog.String("reason", model.NotificationReasonPushProxySendError),
mlog.String("device_id", msg.DeviceId), mlog.String("device_id", msg.DeviceId),
mlog.Err(err), mlog.Err(err),
) )
@ -704,11 +701,12 @@ func (a *App) SendTestPushNotification(deviceID string) string {
case model.PushStatusRemove: case model.PushStatusRemove:
return "false" return "false"
case model.PushStatusFail: case model.PushStatusFail:
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypePush, model.NotificationReasonPushProxyError)
a.NotificationsLog().Error("Push proxy failed to send test notification", a.NotificationsLog().Error("Push proxy failed to send test notification",
mlog.String("type", model.TypePush), mlog.String("type", model.NotificationTypePush),
mlog.String("push_type", msg.Type), mlog.String("push_type", msg.Type),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonPushProxyError), mlog.String("reason", model.NotificationReasonPushProxyError),
mlog.String("device_id", msg.DeviceId), mlog.String("device_id", msg.DeviceId),
mlog.Err(errors.New(pushResponse[model.PushStatusErrorMsg])), mlog.Err(errors.New(pushResponse[model.PushStatusErrorMsg])),
) )

View File

@ -34,7 +34,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost bool withSystemPost bool
wasMentioned bool wasMentioned bool
isMuted bool isMuted bool
expected notifyPropsReason expected model.NotificationReason
isGM bool isGM bool
}{ }{
{ {
@ -44,7 +44,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: true, withSystemPost: true,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSystemMessage, expected: model.NotificationReasonSystemMessage,
isGM: false, isGM: false,
}, },
{ {
@ -54,7 +54,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: true, withSystemPost: true,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSystemMessage, expected: model.NotificationReasonSystemMessage,
isGM: false, isGM: false,
}, },
{ {
@ -84,7 +84,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: false, isGM: false,
}, },
{ {
@ -104,7 +104,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -114,7 +114,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -144,7 +144,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: false, isGM: false,
}, },
{ {
@ -164,7 +164,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -174,7 +174,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -244,7 +244,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: false, isGM: false,
}, },
{ {
@ -264,7 +264,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: false, isGM: false,
}, },
{ {
@ -284,7 +284,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: false, isGM: false,
}, },
{ {
@ -304,7 +304,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -314,7 +314,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -324,7 +324,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -334,7 +334,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -344,7 +344,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -354,7 +354,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: true, wasMentioned: true,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: false, isGM: false,
}, },
{ {
@ -364,7 +364,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: true, isMuted: true,
expected: NotifyPropsReasonChannelMuted, expected: model.NotificationReasonChannelMuted,
isGM: false, isGM: false,
}, },
{ {
@ -374,7 +374,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToNone, expected: model.NotificationReasonLevelSetToNone,
isGM: true, isGM: true,
}, },
{ {
@ -404,7 +404,7 @@ func TestDoesNotifyPropsAllowPushNotification(t *testing.T) {
withSystemPost: false, withSystemPost: false,
wasMentioned: false, wasMentioned: false,
isMuted: false, isMuted: false,
expected: NotifyPropsReasonSetToMention, expected: model.NotificationReasonNotMentioned,
isGM: true, isGM: true,
}, },
} }
@ -447,7 +447,7 @@ func TestDoesStatusAllowPushNotification(t *testing.T) {
userNotifySetting string userNotifySetting string
status *model.Status status *model.Status
channelID string channelID string
expected statusReason expected model.NotificationReason
}{ }{
{ {
name: "WHEN props is ONLINE and user is offline with channel", name: "WHEN props is ONLINE and user is offline with channel",
@ -489,21 +489,21 @@ func TestDoesStatusAllowPushNotification(t *testing.T) {
userNotifySetting: model.StatusOnline, userNotifySetting: model.StatusOnline,
status: online, status: online,
channelID: "", channelID: "",
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is ONLINE and user is dnd with channel", name: "WHEN props is ONLINE and user is dnd with channel",
userNotifySetting: model.StatusOnline, userNotifySetting: model.StatusOnline,
status: dnd, status: dnd,
channelID: channelID, channelID: channelID,
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
{ {
name: "WHEN props is ONLINE and user is dnd without channel", name: "WHEN props is ONLINE and user is dnd without channel",
userNotifySetting: model.StatusOnline, userNotifySetting: model.StatusOnline,
status: dnd, status: dnd,
channelID: "", channelID: "",
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
{ {
name: "WHEN props is AWAY and user is offline with channel", name: "WHEN props is AWAY and user is offline with channel",
@ -538,28 +538,28 @@ func TestDoesStatusAllowPushNotification(t *testing.T) {
userNotifySetting: model.StatusAway, userNotifySetting: model.StatusAway,
status: online, status: online,
channelID: channelID, channelID: channelID,
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is AWAY and user is online without channel", name: "WHEN props is AWAY and user is online without channel",
userNotifySetting: model.StatusAway, userNotifySetting: model.StatusAway,
status: online, status: online,
channelID: "", channelID: "",
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is AWAY and user is dnd with channel", name: "WHEN props is AWAY and user is dnd with channel",
userNotifySetting: model.StatusAway, userNotifySetting: model.StatusAway,
status: dnd, status: dnd,
channelID: channelID, channelID: channelID,
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
{ {
name: "WHEN props is AWAY and user is dnd without channel", name: "WHEN props is AWAY and user is dnd without channel",
userNotifySetting: model.StatusAway, userNotifySetting: model.StatusAway,
status: dnd, status: dnd,
channelID: "", channelID: "",
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
{ {
name: "WHEN props is OFFLINE and user is offline with channel", name: "WHEN props is OFFLINE and user is offline with channel",
@ -580,42 +580,42 @@ func TestDoesStatusAllowPushNotification(t *testing.T) {
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: away, status: away,
channelID: channelID, channelID: channelID,
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is OFFLINE and user is away without channel", name: "WHEN props is OFFLINE and user is away without channel",
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: away, status: away,
channelID: "", channelID: "",
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is OFFLINE and user is online with channel", name: "WHEN props is OFFLINE and user is online with channel",
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: online, status: online,
channelID: channelID, channelID: channelID,
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is OFFLINE and user is online without channel", name: "WHEN props is OFFLINE and user is online without channel",
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: online, status: online,
channelID: "", channelID: "",
expected: StatusReasonIsActive, expected: model.NotificationReasonUserIsActive,
}, },
{ {
name: "WHEN props is OFFLINE and user is dnd with channel", name: "WHEN props is OFFLINE and user is dnd with channel",
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: dnd, status: dnd,
channelID: channelID, channelID: channelID,
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
{ {
name: "WHEN props is OFFLINE and user is dnd without channel", name: "WHEN props is OFFLINE and user is dnd without channel",
userNotifySetting: model.StatusOffline, userNotifySetting: model.StatusOffline,
status: dnd, status: dnd,
channelID: "", channelID: "",
expected: StatusReasonDNDOrOOO, expected: model.NotificationReasonUserStatus,
}, },
} }

View File

@ -1967,6 +1967,51 @@ func (a *OpenTracingAppLayer) CopyWranglerPostlist(c request.CTX, wpl *model.Wra
return resultVar0, resultVar1 return resultVar0, resultVar1
} }
func (a *OpenTracingAppLayer) CountNotification(notificationType model.NotificationType) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CountNotification")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
a.app.CountNotification(notificationType)
}
func (a *OpenTracingAppLayer) CountNotificationAck(notificationType model.NotificationType) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CountNotificationAck")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
a.app.CountNotificationAck(notificationType)
}
func (a *OpenTracingAppLayer) CountNotificationReason(notificationStatus model.NotificationStatus, notificationType model.NotificationType, notificationReason model.NotificationReason) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CountNotificationReason")
a.ctx = newCtx
a.app.Srv().Store().SetContext(newCtx)
defer func() {
a.app.Srv().Store().SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
a.app.CountNotificationReason(notificationStatus, notificationType, notificationReason)
}
func (a *OpenTracingAppLayer) CreateBot(rctx request.CTX, bot *model.Bot) (*model.Bot, *model.AppError) { func (a *OpenTracingAppLayer) CreateBot(rctx request.CTX, bot *model.Bot) (*model.Bot, *model.AppError) {
origCtx := a.ctx origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateBot") span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.CreateBot")

View File

@ -357,11 +357,12 @@ func (a *App) CreatePost(c request.CTX, post *model.Post, channel *model.Channel
if rpost.RootId != "" { if rpost.RootId != "" {
if appErr := a.ResolvePersistentNotification(c, parentPostList.Posts[post.RootId], rpost.UserId); appErr != nil { if appErr := a.ResolvePersistentNotification(c, parentPostList.Posts[post.RootId], rpost.UserId); appErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonResolvePersistentNotificationError)
a.NotificationsLog().Error("Error resolving persistent notification", a.NotificationsLog().Error("Error resolving persistent notification",
mlog.String("sender_id", rpost.UserId), mlog.String("sender_id", rpost.UserId),
mlog.String("post_id", post.RootId), mlog.String("post_id", post.RootId),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonResolvePersistentNotificationError),
mlog.Err(appErr), mlog.Err(appErr),
) )
return nil, appErr return nil, appErr
@ -487,10 +488,11 @@ func (a *App) handlePostEvents(c request.CTX, post *model.Post, user *model.User
if channel.TeamId != "" { if channel.TeamId != "" {
t, err := a.Srv().Store().Team().Get(channel.TeamId) t, err := a.Srv().Store().Team().Get(channel.TeamId)
if err != nil { if err != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Missing team", a.NotificationsLog().Error("Missing team",
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.Err(err), mlog.Err(err),
) )
return err return err
@ -781,11 +783,12 @@ func (a *App) publishWebsocketEventForPermalinkPost(c request.CTX, post *model.P
} }
if !model.IsValidId(previewedPostID) { if !model.IsValidId(previewedPostID) {
a.NotificationsLog().Warn("Invalid post prop id for permalink post", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonParseError)
mlog.String("type", model.TypeWebsocket), a.NotificationsLog().Error("Invalid post prop id for permalink post",
mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonParseError),
mlog.String("prop_value", previewedPostID), mlog.String("prop_value", previewedPostID),
) )
c.Logger().Warn("invalid post prop value", mlog.String("prop_key", model.PostPropsPreviewedPost), mlog.String("prop_value", previewedPostID)) c.Logger().Warn("invalid post prop value", mlog.String("prop_key", model.PostPropsPreviewedPost), mlog.String("prop_value", previewedPostID))
@ -795,12 +798,14 @@ func (a *App) publishWebsocketEventForPermalinkPost(c request.CTX, post *model.P
previewedPost, err := a.GetSinglePost(previewedPostID, false) previewedPost, err := a.GetSinglePost(previewedPostID, false)
if err != nil { if err != nil {
if err.StatusCode == http.StatusNotFound { if err.StatusCode == http.StatusNotFound {
a.NotificationsLog().Warn("permalink post not found", a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
mlog.String("type", model.TypeWebsocket), a.NotificationsLog().Error("permalink post not found",
mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id), mlog.String("post_id", post.Id),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonServerError), mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("referenced_post_id", previewedPostID), mlog.String("referenced_post_id", previewedPostID),
mlog.Err(err),
) )
c.Logger().Warn("permalinked post not found", mlog.String("referenced_post_id", previewedPostID)) c.Logger().Warn("permalinked post not found", mlog.String("referenced_post_id", previewedPostID))
return false, nil return false, nil
@ -810,12 +815,29 @@ func (a *App) publishWebsocketEventForPermalinkPost(c request.CTX, post *model.P
userIDs, nErr := a.Srv().Store().Channel().GetAllChannelMemberIdsByChannelId(post.ChannelId) userIDs, nErr := a.Srv().Store().Channel().GetAllChannelMemberIdsByChannelId(post.ChannelId)
if nErr != nil { if nErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Cannot get channel members",
mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id),
mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("referenced_post_id", previewedPostID),
mlog.Err(nErr),
)
return false, model.NewAppError("publishWebsocketEventForPermalinkPost", "app.channel.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err) return false, model.NewAppError("publishWebsocketEventForPermalinkPost", "app.channel.get_members.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
} }
permalinkPreviewedChannel, err := a.GetChannel(c, previewedPost.ChannelId) permalinkPreviewedChannel, err := a.GetChannel(c, previewedPost.ChannelId)
if err != nil { if err != nil {
if err.StatusCode == http.StatusNotFound { if err.StatusCode == http.StatusNotFound {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonFetchError)
a.NotificationsLog().Error("Cannot get channel",
mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("post_id", post.Id),
mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.NotificationReasonFetchError),
mlog.String("referenced_post_id", previewedPostID),
)
c.Logger().Warn("channel containing permalinked post not found", mlog.String("referenced_channel_id", previewedPost.ChannelId)) c.Logger().Warn("channel containing permalinked post not found", mlog.String("referenced_channel_id", previewedPost.ChannelId))
return false, nil return false, nil
} }

View File

@ -42,11 +42,12 @@ func (a *App) SaveAcknowledgementForPost(c request.CTX, postID, userID string) (
} }
if appErr := a.ResolvePersistentNotification(c, post, userID); appErr != nil { if appErr := a.ResolvePersistentNotification(c, post, userID); appErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeWebsocket, model.NotificationReasonResolvePersistentNotificationError)
a.NotificationsLog().Error("Error resolving persistent notification", a.NotificationsLog().Error("Error resolving persistent notification",
mlog.String("sender_id", userID), mlog.String("sender_id", userID),
mlog.String("post_id", post.RootId), mlog.String("post_id", post.RootId),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonResolvePersistentNotificationError),
mlog.Err(appErr), mlog.Err(appErr),
) )
return nil, appErr return nil, appErr

View File

@ -67,11 +67,12 @@ func (a *App) SaveReactionForPost(c request.CTX, reaction *model.Reaction) (*mod
if post.RootId == "" { if post.RootId == "" {
if appErr := a.ResolvePersistentNotification(c, post, reaction.UserId); appErr != nil { if appErr := a.ResolvePersistentNotification(c, post, reaction.UserId); appErr != nil {
a.CountNotificationReason(model.NotificationStatusError, model.NotificationTypeAll, model.NotificationReasonResolvePersistentNotificationError)
a.NotificationsLog().Error("Error resolving persistent notification", a.NotificationsLog().Error("Error resolving persistent notification",
mlog.String("sender_id", reaction.UserId), mlog.String("sender_id", reaction.UserId),
mlog.String("post_id", post.RootId), mlog.String("post_id", post.RootId),
mlog.String("status", model.StatusServerError), mlog.String("status", model.NotificationStatusError),
mlog.String("reason", model.ReasonFetchError), mlog.String("reason", model.NotificationReasonResolvePersistentNotificationError),
mlog.Err(appErr), mlog.Err(appErr),
) )
return nil, appErr return nil, appErr

View File

@ -86,6 +86,7 @@ func (h *postedAckBroadcastHook) Process(msg *platform.HookedWebSocketEvent, web
// This works since we currently do have an order for broadcast hooks, but this probably should be reworked going forward // This works since we currently do have an order for broadcast hooks, but this probably should be reworked going forward
if msg.Get("followers") != nil || msg.Get("mentions") != nil { if msg.Get("followers") != nil || msg.Get("mentions") != nil {
msg.Add("should_ack", true) msg.Add("should_ack", true)
incrementWebsocketCounter(webConn)
return nil return nil
} }
@ -107,6 +108,7 @@ func (h *postedAckBroadcastHook) Process(msg *platform.HookedWebSocketEvent, web
// Always ACK direct channels // Always ACK direct channels
if channelType == model.ChannelTypeDirect { if channelType == model.ChannelTypeDirect {
msg.Add("should_ack", true) msg.Add("should_ack", true)
incrementWebsocketCounter(webConn)
return nil return nil
} }
@ -117,11 +119,24 @@ func (h *postedAckBroadcastHook) Process(msg *platform.HookedWebSocketEvent, web
if len(users) > 0 && slices.Contains(users, webConn.UserId) { if len(users) > 0 && slices.Contains(users, webConn.UserId) {
msg.Add("should_ack", true) msg.Add("should_ack", true)
incrementWebsocketCounter(webConn)
} }
return nil return nil
} }
func incrementWebsocketCounter(wc *platform.WebConn) {
if wc.Platform.Metrics() == nil {
return
}
if !wc.Platform.Config().FeatureFlags.NotificationMonitoring {
return
}
wc.Platform.Metrics().IncrementNotificationCounter(model.NotificationTypeWebsocket)
}
// getTypedArg returns a correctly typed hook argument with the given key, reinterpreting the type using JSON encoding // getTypedArg returns a correctly typed hook argument with the given key, reinterpreting the type using JSON encoding
// if necessary. This is needed because broadcast hook args are JSON encoded in a multi-server environment, and any // if necessary. This is needed because broadcast hook args are JSON encoded in a multi-server environment, and any
// type information is lost because those types aren't known at decode time. // type information is lost because those types aren't known at decode time.

View File

@ -87,7 +87,8 @@ func TestPostedAckHook_Process(t *testing.T) {
hook := &postedAckBroadcastHook{} hook := &postedAckBroadcastHook{}
userID := model.NewId() userID := model.NewId()
webConn := &platform.WebConn{ webConn := &platform.WebConn{
UserId: userID, UserId: userID,
Platform: &platform.PlatformService{},
} }
t.Run("should ack if user is in the list of users to notify", func(t *testing.T) { t.Run("should ack if user is in the list of users to notify", func(t *testing.T) {

View File

@ -26,14 +26,38 @@ func ping(req *model.WebSocketRequest) (map[string]any, *model.AppError) {
func (api *API) websocketNotificationAck(req *model.WebSocketRequest) (map[string]any, *model.AppError) { func (api *API) websocketNotificationAck(req *model.WebSocketRequest) (map[string]any, *model.AppError) {
// Log the ACKs if necessary // Log the ACKs if necessary
api.App.NotificationsLog().Debug("Websocket notification acknowledgment", api.App.NotificationsLog().Debug("Websocket notification acknowledgment",
mlog.String("type", model.TypeWebsocket), mlog.String("type", model.NotificationTypeWebsocket),
mlog.String("user_id", req.Session.UserId), mlog.String("user_id", req.Session.UserId),
mlog.Any("user_agent", req.Data["user_agent"]), mlog.Any("user_agent", req.Data["user_agent"]),
mlog.Any("post_id", req.Data["post_id"]), mlog.Any("post_id", req.Data["post_id"]),
mlog.Any("result", req.Data["result"]), mlog.Any("status", req.Data["status"]),
mlog.Any("reason", req.Data["reason"]), mlog.Any("reason", req.Data["reason"]),
mlog.Any("data", req.Data["data"]), mlog.Any("data", req.Data["data"]),
) )
// Count metrics for websocket acks
api.App.CountNotificationAck(model.NotificationTypeWebsocket)
status := req.Data["status"]
reason := req.Data["reason"]
if status == nil {
return nil, nil
}
notificationStatus := model.NotificationStatus(status.(string))
if reason == nil && notificationStatus != model.NotificationStatusSuccess {
return nil, nil
}
var notificationReason model.NotificationReason
if reason != nil {
notificationReason = model.NotificationReason(reason.(string))
}
api.App.CountNotificationReason(
notificationStatus,
model.NotificationTypeWebsocket,
notificationReason,
)
return nil, nil return nil, nil
} }

View File

@ -95,4 +95,10 @@ type MetricsInterface interface {
SetReplicaLagAbsolute(node string, value float64) SetReplicaLagAbsolute(node string, value float64)
SetReplicaLagTime(node string, value float64) SetReplicaLagTime(node string, value float64)
IncrementNotificationCounter(notificationType model.NotificationType)
IncrementNotificationAckCounter(notificationType model.NotificationType)
IncrementNotificationSuccessCounter(notificationType model.NotificationType)
IncrementNotificationErrorCounter(notificationType model.NotificationType, errorReason model.NotificationReason)
IncrementNotificationNotSentCounter(notificationType model.NotificationType, notSentReason model.NotificationReason)
} }

View File

@ -163,6 +163,31 @@ func (_m *MetricsInterface) IncrementMemCacheMissCounterSession() {
_m.Called() _m.Called()
} }
// IncrementNotificationAckCounter provides a mock function with given fields: notificationType
func (_m *MetricsInterface) IncrementNotificationAckCounter(notificationType model.NotificationType) {
_m.Called(notificationType)
}
// IncrementNotificationCounter provides a mock function with given fields: notificationType
func (_m *MetricsInterface) IncrementNotificationCounter(notificationType model.NotificationType) {
_m.Called(notificationType)
}
// IncrementNotificationErrorCounter provides a mock function with given fields: notificationType, errorReason
func (_m *MetricsInterface) IncrementNotificationErrorCounter(notificationType model.NotificationType, errorReason model.NotificationReason) {
_m.Called(notificationType, errorReason)
}
// IncrementNotificationNotSentCounter provides a mock function with given fields: notificationType, notSentReason
func (_m *MetricsInterface) IncrementNotificationNotSentCounter(notificationType model.NotificationType, notSentReason model.NotificationReason) {
_m.Called(notificationType, notSentReason)
}
// IncrementNotificationSuccessCounter provides a mock function with given fields: notificationType
func (_m *MetricsInterface) IncrementNotificationSuccessCounter(notificationType model.NotificationType) {
_m.Called(notificationType)
}
// IncrementPostBroadcast provides a mock function with given fields: // IncrementPostBroadcast provides a mock function with given fields:
func (_m *MetricsInterface) IncrementPostBroadcast() { func (_m *MetricsInterface) IncrementPostBroadcast() {
_m.Called() _m.Called()

View File

@ -41,6 +41,7 @@ const (
MetricsSubsystemSharedChannels = "shared_channels" MetricsSubsystemSharedChannels = "shared_channels"
MetricsSubsystemSystem = "system" MetricsSubsystemSystem = "system"
MetricsSubsystemJobs = "jobs" MetricsSubsystemJobs = "jobs"
MetricsSubsystemNotifications = "notifications"
MetricsCloudInstallationLabel = "installationId" MetricsCloudInstallationLabel = "installationId"
MetricsCloudDatabaseClusterLabel = "databaseClusterName" MetricsCloudDatabaseClusterLabel = "databaseClusterName"
MetricsCloudInstallationGroupLabel = "installationGroupId" MetricsCloudInstallationGroupLabel = "installationGroupId"
@ -201,6 +202,12 @@ type MetricsInterfaceImpl struct {
ServerStartTime prometheus.Gauge ServerStartTime prometheus.Gauge
JobsActive *prometheus.GaugeVec JobsActive *prometheus.GaugeVec
NotificationTotalCounters *prometheus.CounterVec
NotificationAckCounters *prometheus.CounterVec
NotificationSuccessCounters *prometheus.CounterVec
NotificationErrorCounters *prometheus.CounterVec
NotificationNotSentCounters *prometheus.CounterVec
} }
func init() { func init() {
@ -1041,6 +1048,67 @@ func New(ps *platform.PlatformService, driver, dataSource string) *MetricsInterf
[]string{"type"}, []string{"type"},
) )
m.Registry.MustRegister(m.JobsActive) m.Registry.MustRegister(m.JobsActive)
m.NotificationTotalCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemNotifications,
Name: "total",
Help: "Total number of notification events",
ConstLabels: additionalLabels,
},
[]string{"type"},
)
m.Registry.MustRegister(m.NotificationTotalCounters)
m.NotificationAckCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemNotifications,
Name: "total_ack",
Help: "Total number of notification events acknowledged",
ConstLabels: additionalLabels,
},
[]string{"type"},
)
m.Registry.MustRegister(m.NotificationAckCounters)
m.NotificationSuccessCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemNotifications,
Name: "success",
Help: "Total number of successfully sent notifications",
ConstLabels: additionalLabels,
},
[]string{"type"},
)
m.Registry.MustRegister(m.NotificationSuccessCounters)
m.NotificationErrorCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemNotifications,
Name: "error",
Help: "Total number of errors that stop the notification flow",
ConstLabels: additionalLabels,
},
[]string{"type", "reason"},
)
m.Registry.MustRegister(m.NotificationErrorCounters)
m.NotificationNotSentCounters = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: MetricsNamespace,
Subsystem: MetricsSubsystemNotifications,
Name: "not_sent",
Help: "Total number of notifications the system deliberately did not send",
ConstLabels: additionalLabels,
},
[]string{"type", "reason"},
)
m.Registry.MustRegister(m.NotificationNotSentCounters)
return m return m
} }
@ -1467,6 +1535,26 @@ func (mi *MetricsInterfaceImpl) SetReplicaLagTime(node string, value float64) {
mi.DbReplicaLagGaugeTime.With(prometheus.Labels{"node": node}).Set(value) mi.DbReplicaLagGaugeTime.With(prometheus.Labels{"node": node}).Set(value)
} }
func (mi *MetricsInterfaceImpl) IncrementNotificationCounter(notificationType model.NotificationType) {
mi.NotificationTotalCounters.With(prometheus.Labels{"type": string(notificationType)}).Inc()
}
func (mi *MetricsInterfaceImpl) IncrementNotificationAckCounter(notificationType model.NotificationType) {
mi.NotificationAckCounters.With(prometheus.Labels{"type": string(notificationType)}).Inc()
}
func (mi *MetricsInterfaceImpl) IncrementNotificationSuccessCounter(notificationType model.NotificationType) {
mi.NotificationSuccessCounters.With(prometheus.Labels{"type": string(notificationType)}).Inc()
}
func (mi *MetricsInterfaceImpl) IncrementNotificationErrorCounter(notificationType model.NotificationType, errorReason model.NotificationReason) {
mi.NotificationErrorCounters.With(prometheus.Labels{"type": string(notificationType), "reason": string(errorReason)}).Inc()
}
func (mi *MetricsInterfaceImpl) IncrementNotificationNotSentCounter(notificationType model.NotificationType, notSentReason model.NotificationReason) {
mi.NotificationNotSentCounters.With(prometheus.Labels{"type": string(notificationType), "reason": string(notSentReason)}).Inc()
}
func (mi *MetricsInterfaceImpl) IncrementHTTPWebSockets(originClient string) { func (mi *MetricsInterfaceImpl) IncrementHTTPWebSockets(originClient string) {
mi.HTTPWebsocketsGauge.With(prometheus.Labels{"origin_client": originClient}).Inc() mi.HTTPWebsocketsGauge.With(prometheus.Labels{"origin_client": originClient}).Inc()
} }

View File

@ -55,6 +55,8 @@ type FeatureFlags struct {
ChannelBookmarks bool ChannelBookmarks bool
WebSocketEventScope bool WebSocketEventScope bool
NotificationMonitoring bool
} }
func (f *FeatureFlags) SetDefaults() { func (f *FeatureFlags) SetDefaults() {
@ -77,6 +79,7 @@ func (f *FeatureFlags) SetDefaults() {
f.CloudDedicatedExportUI = false f.CloudDedicatedExportUI = false
f.ChannelBookmarks = false f.ChannelBookmarks = false
f.WebSocketEventScope = false f.WebSocketEventScope = false
f.NotificationMonitoring = true
} }
// ToMap returns the feature flags as a map[string]string // ToMap returns the feature flags as a map[string]string

View File

@ -3,20 +3,36 @@
package model package model
type NotificationStatus string
type NotificationType string
type NotificationReason string
const ( const (
StatusBlocked = "blocked" NotificationStatusSuccess NotificationStatus = "success"
StatusServerError = "server_error" NotificationStatusError NotificationStatus = "error"
StatusNotSent = "not_sent" NotificationStatusNotSent NotificationStatus = "not_sent"
TypeEmail = "email" NotificationTypeAll NotificationType = "all"
TypeWebsocket = "websocket" NotificationTypeEmail NotificationType = "email"
TypePush = "push" NotificationTypeWebsocket NotificationType = "websocket"
NotificationTypePush NotificationType = "push"
ReasonServerConfig = "server_config" NotificationReasonFetchError NotificationReason = "fetch_error"
ReasonUserConfig = "user_config" NotificationReasonParseError NotificationReason = "json_parse_error"
ReasonUserStatus = "user_status" NotificationReasonPushProxyError NotificationReason = "push_proxy_error"
ReasonFetchError = "error_fetching" NotificationReasonPushProxySendError NotificationReason = "push_proxy_send_error"
ReasonServerError = "server_error" NotificationReasonRejectedByPlugin NotificationReason = "rejected_by_plugin"
ReasonMissingProfile = "missing_profile" NotificationReasonSessionExpired NotificationReason = "session_expired"
ReasonPushProxyError = "push_proxy_error" NotificationReasonChannelMuted NotificationReason = "channel_muted"
NotificationReasonSystemMessage NotificationReason = "system_message"
NotificationReasonLevelSetToNone NotificationReason = "notify_level_none"
NotificationReasonNotMentioned NotificationReason = "not_mentioned"
NotificationReasonUserStatus NotificationReason = "user_status"
NotificationReasonUserIsActive NotificationReason = "user_is_active"
NotificationReasonMissingProfile NotificationReason = "missing_profile"
NotificationReasonEmailNotVerified NotificationReason = "email_not_verified"
NotificationReasonEmailSendError NotificationReason = "email_send_error"
NotificationReasonTooManyUsersInChannel NotificationReason = "too_many_users_in_channel"
NotificationReasonResolvePersistentNotificationError NotificationReason = "resolve_persistent_notification_error"
NotificationReasonMissingThreadMembership NotificationReason = "missing_thread_membership"
) )

View File

@ -13,7 +13,7 @@
"@mattermost/client": "*", "@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12", "@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.39", "@mattermost/compass-icons": "0.1.39",
"@mattermost/desktop-api": "5.8.0-4", "@mattermost/desktop-api": "5.8.0-5",
"@mattermost/types": "*", "@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127", "@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16", "@mui/material": "5.11.16",

View File

@ -80,11 +80,11 @@ export function completePostReceive(post: Post, websocketMessageProps: NewPostMe
dispatch(setThreadRead(post)); dispatch(setThreadRead(post));
} }
const {result, reason, data} = await dispatch(sendDesktopNotification(post, websocketMessageProps)); const {status, reason, data} = await dispatch(sendDesktopNotification(post, websocketMessageProps));
// Only ACK for posts that require it // Only ACK for posts that require it
if (websocketMessageProps.should_ack) { if (websocketMessageProps.should_ack) {
WebSocketClient.acknowledgePostedNotification(post.id, result, reason, data); WebSocketClient.acknowledgePostedNotification(post.id, status, reason, data);
} }
return {data: true}; return {data: true};

View File

@ -57,11 +57,11 @@ export function sendDesktopNotification(post, msgProps) {
const currentUserId = getCurrentUserId(state); const currentUserId = getCurrentUserId(state);
if ((currentUserId === post.user_id && post.props.from_webhook !== 'true')) { if ((currentUserId === post.user_id && post.props.from_webhook !== 'true')) {
return {result: 'not_sent', reason: 'own_post'}; return {status: 'not_sent', reason: 'own_post'};
} }
if (isSystemMessage(post) && !isUserAddedInChannel(post, currentUserId)) { if (isSystemMessage(post) && !isUserAddedInChannel(post, currentUserId)) {
return {result: 'not_sent', reason: 'system_message'}; return {status: 'not_sent', reason: 'system_message'};
} }
let userFromPost = getUser(state, post.user_id); let userFromPost = getUser(state, post.user_id);
@ -92,15 +92,15 @@ export function sendDesktopNotification(post, msgProps) {
const isCrtReply = isCollapsedThreadsEnabled(state) && post.root_id !== ''; const isCrtReply = isCollapsedThreadsEnabled(state) && post.root_id !== '';
if (!member) { if (!member) {
return {result: 'not_sent', reason: 'no_member'}; return {status: 'error', reason: 'no_member'};
} }
if (isChannelMuted(member)) { if (isChannelMuted(member)) {
return {result: 'not_sent', reason: 'channel_muted'}; return {status: 'not_sent', reason: 'channel_muted'};
} }
if (userStatus === UserStatuses.DND || userStatus === UserStatuses.OUT_OF_OFFICE) { if (userStatus === UserStatuses.DND || userStatus === UserStatuses.OUT_OF_OFFICE) {
return {result: 'not_sent', reason: 'user_status', data: userStatus}; return {status: 'not_sent', reason: 'user_status', data: userStatus};
} }
const channelNotifyProp = member?.notify_props?.desktop || NotificationLevels.DEFAULT; const channelNotifyProp = member?.notify_props?.desktop || NotificationLevels.DEFAULT;
@ -115,7 +115,7 @@ export function sendDesktopNotification(post, msgProps) {
} }
if (notifyLevel === NotificationLevels.NONE) { if (notifyLevel === NotificationLevels.NONE) {
return {result: 'not_sent', reason: 'notify_level', data: notifyLevel}; return {status: 'not_sent', reason: 'notify_level_none'};
} else if (channel?.type === 'G' && notifyLevel === NotificationLevels.MENTION) { } else if (channel?.type === 'G' && notifyLevel === NotificationLevels.MENTION) {
// Compose the whole text in the message, including interactive messages. // Compose the whole text in the message, including interactive messages.
let text = post.message; let text = post.message;
@ -189,13 +189,13 @@ export function sendDesktopNotification(post, msgProps) {
} }
if (!isExplicitlyMentioned) { if (!isExplicitlyMentioned) {
return {result: 'not_sent', reason: 'not_explicitly_mentioned', data: mentionableText}; return {status: 'not_sent', reason: 'not_explicitly_mentioned', data: mentionableText};
} }
} else if (notifyLevel === NotificationLevels.MENTION && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) { } else if (notifyLevel === NotificationLevels.MENTION && mentions.indexOf(user.id) === -1 && msgProps.channel_type !== Constants.DM_CHANNEL) {
return {result: 'not_sent', reason: 'not_mentioned'}; return {status: 'not_sent', reason: 'not_mentioned'};
} else if (isCrtReply && notifyLevel === NotificationLevels.ALL && followers.indexOf(currentUserId) === -1) { } else if (isCrtReply && notifyLevel === NotificationLevels.ALL && followers.indexOf(currentUserId) === -1) {
// if user is not following the thread don't notify // if user is not following the thread don't notify
return {result: 'not_sent', reason: 'not_following_thread'}; return {status: 'not_sent', reason: 'not_following_thread'};
} }
const config = getConfig(state); const config = getConfig(state);
@ -273,18 +273,18 @@ export function sendDesktopNotification(post, msgProps) {
const channelId = channel ? channel.id : null; const channelId = channel ? channel.id : null;
let notify = false; let notify = false;
let notifyResult = {result: 'not_sent', reason: 'unknown'}; let notifyResult = {status: 'not_sent', reason: 'unknown'};
if (state.views.browser.focused) { if (state.views.browser.focused) {
notifyResult = {result: 'not_sent', reason: 'window_is_focused'}; notifyResult = {status: 'not_sent', reason: 'window_is_focused'};
if (isCrtReply) { if (isCrtReply) {
notify = !isThreadOpen(state, post.root_id); notify = !isThreadOpen(state, post.root_id);
if (!notify) { if (!notify) {
notifyResult = {result: 'not_sent', reason: 'thread_is_open', data: post.root_id}; notifyResult = {status: 'not_sent', reason: 'thread_is_open', data: post.root_id};
} }
} else { } else {
notify = activeChannel && activeChannel.id !== channelId; notify = activeChannel && activeChannel.id !== channelId;
if (!notify) { if (!notify) {
notifyResult = {result: 'not_sent', reason: 'channel_is_open', data: activeChannel?.id}; notifyResult = {status: 'not_sent', reason: 'channel_is_open', data: activeChannel?.id};
} }
} }
} else { } else {
@ -305,7 +305,7 @@ export function sendDesktopNotification(post, msgProps) {
const hookResult = await dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args)); const hookResult = await dispatch(runDesktopNotificationHooks(post, msgProps, channel, teamId, args));
if (hookResult.error) { if (hookResult.error) {
dispatch(logError(hookResult.error)); dispatch(logError(hookResult.error));
return {result: 'error', reason: 'desktop_notification_hook', data: String(hookResult.error)}; return {status: 'error', reason: 'desktop_notification_hook', data: String(hookResult.error)};
} }
let silent = false; let silent = false;
@ -323,7 +323,7 @@ export function sendDesktopNotification(post, msgProps) {
} }
if (args.notify && !notify) { if (args.notify && !notify) {
notifyResult = {result: 'not_sent', reason: 'desktop_notification_hook', data: String(hookResult)}; notifyResult = {status: 'not_sent', reason: 'desktop_notification_hook', data: String(hookResult)};
} }
return notifyResult; return notifyResult;
@ -349,6 +349,6 @@ export const notifyMe = (title, body, channel, teamId, silent, soundName, url) =
}); });
} catch (error) { } catch (error) {
dispatch(logError(error)); dispatch(logError(error));
return {result: 'error', reason: 'notification_api', data: String(error)}; return {status: 'error', reason: 'notification_api', data: String(error)};
} }
}; };

View File

@ -167,7 +167,7 @@ class DesktopAppAPI {
) => { ) => {
if (window.desktopAPI?.sendNotification) { if (window.desktopAPI?.sendNotification) {
const result = await window.desktopAPI.sendNotification(title, body, channelId, teamId, url, silent, soundName); const result = await window.desktopAPI.sendNotification(title, body, channelId, teamId, url, silent, soundName);
return result ?? {result: 'unsupported', reason: 'desktop_app_unsupported'}; return result ?? {status: 'unsupported', reason: 'desktop_app_unsupported'};
} }
// get the desktop app to trigger the notification // get the desktop app to trigger the notification
@ -186,7 +186,7 @@ class DesktopAppAPI {
}, },
window.location.origin, window.location.origin,
); );
return {result: 'unsupported', reason: 'desktop_app_unsupported'}; return {status: 'unsupported', reason: 'desktop_app_unsupported'};
}; };
doBrowserHistoryPush = (path: string) => { doBrowserHistoryPush = (path: string) => {

View File

@ -53,7 +53,7 @@ export async function showNotification(
if (Notification.permission !== 'granted' && requestedNotificationPermission) { if (Notification.permission !== 'granted' && requestedNotificationPermission) {
// User didn't allow notifications // User didn't allow notifications
return {result: 'not_sent', reason: 'notifications_permission_previously_denied', data: Notification.permission, callback: () => {}}; return {status: 'not_sent', reason: 'notifications_permission_previously_denied', data: Notification.permission, callback: () => {}};
} }
requestedNotificationPermission = true; requestedNotificationPermission = true;
@ -68,7 +68,7 @@ export async function showNotification(
if (permission !== 'granted') { if (permission !== 'granted') {
// User has denied notification for the site // User has denied notification for the site
return {result: 'not_sent', reason: 'notifications_permission_denied', data: permission, callback: () => {}}; return {status: 'not_sent', reason: 'notifications_permission_denied', data: permission, callback: () => {}};
} }
const notification = new Notification(title, { const notification = new Notification(title, {
@ -95,7 +95,7 @@ export async function showNotification(
} }
return { return {
result: 'success', status: 'success',
callback: () => { callback: () => {
notification.close(); notification.close();
}, },

View File

@ -62,7 +62,7 @@
"@mattermost/client": "*", "@mattermost/client": "*",
"@mattermost/compass-components": "^0.2.12", "@mattermost/compass-components": "^0.2.12",
"@mattermost/compass-icons": "0.1.39", "@mattermost/compass-icons": "0.1.39",
"@mattermost/desktop-api": "5.8.0-4", "@mattermost/desktop-api": "5.8.0-5",
"@mattermost/types": "*", "@mattermost/types": "*",
"@mui/base": "5.0.0-alpha.127", "@mui/base": "5.0.0-alpha.127",
"@mui/material": "5.11.16", "@mui/material": "5.11.16",
@ -4290,9 +4290,9 @@
"link": true "link": true
}, },
"node_modules/@mattermost/desktop-api": { "node_modules/@mattermost/desktop-api": {
"version": "5.8.0-4", "version": "5.8.0-5",
"resolved": "https://registry.npmjs.org/@mattermost/desktop-api/-/desktop-api-5.8.0-4.tgz", "resolved": "https://registry.npmjs.org/@mattermost/desktop-api/-/desktop-api-5.8.0-5.tgz",
"integrity": "sha512-oEbh3ByDgM432LrjO9JsRnIwqBIAkF6bJaQkHjYeQAYwkI4/s4CSQ4kZcfMiO9hE0MjfF1VeXnGTBv9wyWsbAw==", "integrity": "sha512-YPtFRnduVFOXyK25GedJA+PkAKmFpLDKqfxvV/IIS+SMypv6BD17LJ8AkdBsguFFa6ZWLlZU40M5grKYBbjOuA==",
"peerDependencies": { "peerDependencies": {
"typescript": "^4.3.0 || ^5.0.0" "typescript": "^4.3.0 || ^5.0.0"
}, },

View File

@ -412,11 +412,11 @@ export default class WebSocketClient {
this.sendMessage('user_update_active_status', data, callback); this.sendMessage('user_update_active_status', data, callback);
} }
acknowledgePostedNotification(postId: string, result: 'error' | 'not_sent' | 'unsupported' | 'success', reason?: string, postedData?: string) { acknowledgePostedNotification(postId: string, status: string, reason?: string, postedData?: string) {
const data = { const data = {
post_id: postId, post_id: postId,
user_agent: window.navigator.userAgent, user_agent: window.navigator.userAgent,
result, status,
reason, reason,
data: postedData, data: postedData,
}; };

View File

@ -221,3 +221,10 @@ export type PostInfo = {
team_display_name: string; team_display_name: string;
has_joined_team: boolean; has_joined_team: boolean;
} }
export type NotificationStatus = 'error' | 'not_sent' | 'unsupported' | 'success';
export type NotificationResult = {
status: NotificationStatus;
reason?: string;
data?: string;
}