This commit is contained in:
cursor[bot] 2026-05-25 14:59:43 +08:00 committed by GitHub
commit 7296f28e66
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 253 additions and 4 deletions

View file

@ -813,6 +813,35 @@ func (a *App) buildIdLoadedPushNotificationMessage(rctx request.CTX, channel *mo
return msg
}
func PriorityNotificationTitle(priority *model.PostPriority, rootID string, channelType model.ChannelType, channelName string, userLocale i18n.TranslateFunc) string {
if rootID != "" || priority == nil || priority.Priority == nil {
return ""
}
switch *priority.Priority {
case model.PostPriorityImportant:
switch channelType {
case model.ChannelTypeDirect:
return userLocale("api.push_notification.title.important_dm")
case model.ChannelTypeGroup:
return userLocale("api.push_notification.title.important_gm")
default:
return userLocale("api.push_notification.title.important_channel", map[string]any{"channelName": channelName})
}
case model.PostPriorityUrgent:
switch channelType {
case model.ChannelTypeDirect:
return userLocale("api.push_notification.title.urgent_dm")
case model.ChannelTypeGroup:
return userLocale("api.push_notification.title.urgent_gm")
default:
return userLocale("api.push_notification.title.urgent_channel", map[string]any{"channelName": channelName})
}
default:
return ""
}
}
func (a *App) buildFullPushNotificationMessage(rctx request.CTX, contentsConfig string, post *model.Post, user *model.User, channel *model.Channel, channelName string, senderName string,
explicitMention bool, channelWideMention bool, replyToThreadType string,
) *model.PushNotification {
@ -833,6 +862,9 @@ func (a *App) buildFullPushNotificationMessage(rctx request.CTX, contentsConfig
cfg := a.Config()
if contentsConfig != model.GenericNoChannelNotification || channel.Type == model.ChannelTypeDirect {
msg.ChannelName = channelName
if priorityTitle := PriorityNotificationTitle(post.GetPriority(), post.RootId, channel.Type, channelName, userLocale); priorityTitle != "" {
msg.ChannelName = priorityTitle
}
}
if a.IsCRTEnabledForUser(rctx, user.Id) {

View file

@ -1083,6 +1083,98 @@ func TestBuildPushNotificationMessageMentions(t *testing.T) {
}
}
func TestBuildFullPushNotificationMessagePriorityTitles(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t)
for name, tc := range map[string]struct {
priority string
channelType model.ChannelType
channelName string
rootID string
contentsConfig string
expectedTitle string
}{
"urgent channel root post": {
priority: model.PostPriorityUrgent,
channelType: model.ChannelTypeOpen,
channelName: "Town Square",
expectedTitle: "URGENT message in Town Square",
},
"urgent direct message root post": {
priority: model.PostPriorityUrgent,
channelType: model.ChannelTypeDirect,
channelName: "@sender",
expectedTitle: "URGENT Direct Message",
},
"urgent group message root post": {
priority: model.PostPriorityUrgent,
channelType: model.ChannelTypeGroup,
channelName: "sender, receiver",
expectedTitle: "URGENT Group Message",
},
"important channel root post": {
priority: model.PostPriorityImportant,
channelType: model.ChannelTypeOpen,
channelName: "Town Square",
expectedTitle: "IMPORTANT message in Town Square",
},
"important direct message root post": {
priority: model.PostPriorityImportant,
channelType: model.ChannelTypeDirect,
channelName: "@sender",
expectedTitle: "IMPORTANT Direct Message",
},
"important group message root post": {
priority: model.PostPriorityImportant,
channelType: model.ChannelTypeGroup,
channelName: "sender, receiver",
expectedTitle: "IMPORTANT Group Message",
},
"priority reply keeps channel title": {
priority: model.PostPriorityUrgent,
channelType: model.ChannelTypeOpen,
channelName: "Town Square",
rootID: model.NewId(),
expectedTitle: "Reply in Town Square",
},
"generic no channel keeps channel title hidden": {
priority: model.PostPriorityUrgent,
channelType: model.ChannelTypeOpen,
channelName: "Town Square",
contentsConfig: model.GenericNoChannelNotification,
expectedTitle: "",
},
} {
t.Run(name, func(t *testing.T) {
contentsConfig := tc.contentsConfig
if contentsConfig == "" {
contentsConfig = model.FullNotification
}
post := &model.Post{
Id: model.NewId(),
UserId: model.NewId(),
RootId: tc.rootID,
Message: "hello",
Metadata: &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewPointer(tc.priority),
},
},
}
channel := &model.Channel{
Id: model.NewId(),
Type: tc.channelType,
}
user := &model.User{Locale: "en"}
msg := th.App.buildFullPushNotificationMessage(th.Context, contentsConfig, post, user, channel, tc.channelName, "sender", false, false, "")
assert.Equal(t, tc.expectedTitle, msg.ChannelName)
})
}
}
func TestSendPushNotifications(t *testing.T) {
mainHelper.Parallel(t)
th := Setup(t).InitBasic(t)

View file

@ -326,6 +326,11 @@ func (a *App) sendPersistentNotifications(post *model.Post, channel *model.Chann
Sender: sender,
}
if post.GetPriority() == nil {
post = a.PreparePostForClient(request.EmptyContext(a.Log()), post, &model.PreparePostForClientOpts{IncludePriority: true})
notification.Post = post
}
if int64(len(mentionedUsersList)) > *a.Config().TeamSettings.MaxNotificationsPerChannel {
return errors.Errorf("mentioned users: %d are more than allowed users: %d", len(mentionedUsersList), *a.Config().TeamSettings.MaxNotificationsPerChannel)
}
@ -372,7 +377,6 @@ func (a *App) sendPersistentNotifications(post *model.Post, channel *model.Chann
}
if len(desktopUsers) != 0 {
post = a.PreparePostForClient(request.EmptyContext(a.Log()), post, &model.PreparePostForClientOpts{IncludePriority: true})
postJSON, jsonErr := post.ToJSON()
if jsonErr != nil {
return errors.Wrapf(jsonErr, "failed to encode post to JSON")

View file

@ -3330,6 +3330,30 @@
"id": "api.push_notification.title.collapsed_threads_dm",
"translation": "Reply in Direct Message"
},
{
"id": "api.push_notification.title.important_channel",
"translation": "IMPORTANT message in {{.channelName}}"
},
{
"id": "api.push_notification.title.important_dm",
"translation": "IMPORTANT Direct Message"
},
{
"id": "api.push_notification.title.important_gm",
"translation": "IMPORTANT Group Message"
},
{
"id": "api.push_notification.title.urgent_channel",
"translation": "URGENT message in {{.channelName}}"
},
{
"id": "api.push_notification.title.urgent_dm",
"translation": "URGENT Direct Message"
},
{
"id": "api.push_notification.title.urgent_gm",
"translation": "URGENT Group Message"
},
{
"id": "api.push_notifications.message.parse.app_error",
"translation": "An error occurred building the push notification message."

View file

@ -110,7 +110,8 @@ const (
PostPropsSharedChannelState = "shared_channel_state"
PostPropsSharedChannelWorkspaceName = "workspace_name"
PostPriorityUrgent = "urgent"
PostPriorityImportant = "important"
PostPriorityUrgent = "urgent"
DefaultExpirySeconds = 60 * 60 * 24 * 7 // 7 days
DefaultReadDurationSeconds = 10 * 60 // 10 minutes

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {PostPriority} from '@mattermost/types/posts';
import {MarkUnread} from 'mattermost-redux/constants/channels';
import testConfigureStore from 'tests/test_store';
@ -223,6 +225,59 @@ describe('notification_actions', () => {
});
});
test.each([
['urgent channel message', 'channel_id', Constants.OPEN_CHANNEL, PostPriority.URGENT, 'URGENT message in Utopia'],
['urgent direct message', 'channel_id', Constants.DM_CHANNEL, PostPriority.URGENT, 'URGENT Direct Message'],
['urgent group message', 'gm_channel', Constants.GM_CHANNEL, PostPriority.URGENT, 'URGENT Group Message'],
['important channel message', 'channel_id', Constants.OPEN_CHANNEL, PostPriority.IMPORTANT, 'IMPORTANT message in Utopia'],
['important direct message', 'channel_id', Constants.DM_CHANNEL, PostPriority.IMPORTANT, 'IMPORTANT Direct Message'],
['important group message', 'gm_channel', Constants.GM_CHANNEL, PostPriority.IMPORTANT, 'IMPORTANT Group Message'],
])('should notify user with priority title for %s', async (_name, channelId, channelType, priority, expectedTitle) => {
post = {
...post,
root_id: '',
channel_id: channelId,
metadata: {
priority: {
priority,
},
},
};
msgProps = {
...msgProps,
post: JSON.stringify(post),
channel_type: channelType,
};
baseState.entities.channels.channels[channelId].type = channelType;
const store = testConfigureStore(baseState);
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
expect(spy).toHaveBeenCalledWith(expect.objectContaining({
title: expectedTitle,
}));
});
});
test('should not use priority title for thread reply notifications', async () => {
post = {
...post,
metadata: {
priority: {
priority: PostPriority.URGENT,
},
},
};
const store = testConfigureStore(baseState);
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
expect(spy).toHaveBeenCalledWith(expect.objectContaining({
title: 'Utopia',
}));
});
});
test('should not notify user when tab and channel are active', async () => {
const store = testConfigureStore(baseState);
baseState.views.browser.focused = true;

View file

@ -6,6 +6,7 @@ import type {Channel, ChannelMembership} from '@mattermost/types/channels';
import type {ServerError} from '@mattermost/types/errors';
import {isMessageAttachmentArray} from '@mattermost/types/message_attachments';
import type {Post} from '@mattermost/types/posts';
import {PostPriority} from '@mattermost/types/posts';
import type {UserProfile} from '@mattermost/types/users';
import {logError} from 'mattermost-redux/actions/errors';
@ -134,7 +135,7 @@ export function sendDesktopNotification(post: Post, msgProps: NewPostMessageProp
return {data: skipNotificationReason};
}
const title = getNotificationTitle(channel, msgProps, isCrtReply);
const title = getNotificationTitle(channel, msgProps, isCrtReply, post);
const body = getNotificationBody(state, post, msgProps);
//Play a sound if explicitly set in settings
@ -174,7 +175,37 @@ export function sendDesktopNotification(post: Post, msgProps: NewPostMessageProp
};
}
const getNotificationTitle = (channel: Pick<Channel, 'type' | 'display_name'>, msgProps: NewPostMessageProps, isCrtReply: boolean) => {
const getPriorityNotificationTitle = (priority: PostPriority | '' | undefined, channelType: Channel['type'] | undefined, channelTitle: string) => {
if (priority === PostPriority.IMPORTANT) {
if (channelType === Constants.DM_CHANNEL) {
return Utils.localizeMessage({id: 'notification.priority.important.dm', defaultMessage: 'IMPORTANT Direct Message'});
} else if (channelType === Constants.GM_CHANNEL) {
return Utils.localizeMessage({id: 'notification.priority.important.gm', defaultMessage: 'IMPORTANT Group Message'});
}
return Utils.localizeMessage(
{id: 'notification.priority.important.channel', defaultMessage: 'IMPORTANT message in {channelName}'},
{channelName: channelTitle},
);
}
if (priority === PostPriority.URGENT) {
if (channelType === Constants.DM_CHANNEL) {
return Utils.localizeMessage({id: 'notification.priority.urgent.dm', defaultMessage: 'URGENT Direct Message'});
} else if (channelType === Constants.GM_CHANNEL) {
return Utils.localizeMessage({id: 'notification.priority.urgent.gm', defaultMessage: 'URGENT Group Message'});
}
return Utils.localizeMessage(
{id: 'notification.priority.urgent.channel', defaultMessage: 'URGENT message in {channelName}'},
{channelName: channelTitle},
);
}
return '';
};
const getNotificationTitle = (channel: Pick<Channel, 'type' | 'display_name'>, msgProps: NewPostMessageProps, isCrtReply: boolean, post: Post) => {
let title = Utils.localizeMessage({id: 'channel_loader.title', defaultMessage: 'Posted'});
if (channel.type === Constants.DM_CHANNEL) {
title = Utils.localizeMessage({id: 'notification.dm', defaultMessage: 'Direct Message'});
@ -194,6 +225,10 @@ const getNotificationTitle = (channel: Pick<Channel, 'type' | 'display_name'>, m
title = Utils.localizeMessage({id: 'notification.crt', defaultMessage: 'Reply in {title}'}, {title});
}
if (!isCrtReply && !post.root_id) {
title = getPriorityNotificationTitle(post.metadata?.priority?.priority, channel.type || msgProps.channel_type, title) || title;
}
return title;
};

View file

@ -5816,6 +5816,12 @@
"no_results.user_groups.title": "No groups yet",
"notification.crt": "Reply in {title}",
"notification.dm": "Direct Message",
"notification.priority.important.channel": "IMPORTANT message in {channelName}",
"notification.priority.important.dm": "IMPORTANT Direct Message",
"notification.priority.important.gm": "IMPORTANT Group Message",
"notification.priority.urgent.channel": "URGENT message in {channelName}",
"notification.priority.urgent.dm": "URGENT Direct Message",
"notification.priority.urgent.gm": "URGENT Group Message",
"notify_admin_to_upgrade_cta.notify-admin.already_notified": "Already notified!",
"notify_admin_to_upgrade_cta.notify-admin.failed": "Try again later!",
"notify_admin_to_upgrade_cta.notify-admin.notified": "Admin notified!",