mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 18:03:43 +08:00
FEATURE: Add ability to watch chat threads (#28639)
This change introduces a new thread notification level allowing users to get notified when someone replies to the thread. Users who watch a thread will get a green notification on the chat icon and a user notification (blue). User notifications are consolidated based on thread id to prevent cluttering the original users notification area. --------- Co-authored-by: Régis Hanol <regis@hanol.fr>
This commit is contained in:
parent
cef1dcfc7d
commit
997fbc9757
|
@ -164,6 +164,7 @@ class Notification < ActiveRecord::Base
|
|||
new_features: 37,
|
||||
admin_problems: 38,
|
||||
linked_consolidated: 39,
|
||||
chat_watched_thread: 40,
|
||||
following: 800, # Used by https://github.com/discourse/discourse-follow
|
||||
following_created_topic: 801, # Used by https://github.com/discourse/discourse-follow
|
||||
following_replied: 802, # Used by https://github.com/discourse/discourse-follow
|
||||
|
|
|
@ -15,7 +15,6 @@ module Jobs
|
|||
@is_direct_message_channel = @chat_channel.direct_message_channel?
|
||||
|
||||
always_notification_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
|
||||
|
||||
members =
|
||||
::Chat::UserChatChannelMembership
|
||||
.includes(user: :groups)
|
||||
|
@ -25,9 +24,10 @@ module Jobs
|
|||
.where(chat_channel_id: @chat_channel.id)
|
||||
.where(following: true)
|
||||
.where(
|
||||
"desktop_notification_level = ? OR mobile_notification_level = ?",
|
||||
always_notification_level,
|
||||
always_notification_level,
|
||||
"desktop_notification_level = :always OR mobile_notification_level = :always OR users.id IN (SELECT user_id FROM user_chat_thread_memberships WHERE thread_id = :thread_id AND notification_level = :watching)",
|
||||
always: always_notification_level,
|
||||
thread_id: @chat_message.thread_id,
|
||||
watching: ::Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
.merge(User.not_suspended)
|
||||
|
||||
|
@ -83,6 +83,17 @@ module Jobs
|
|||
channel_id: @chat_channel.id,
|
||||
}
|
||||
|
||||
if @chat_message.in_thread? && !membership.muted?
|
||||
thread_membership =
|
||||
::Chat::UserChatThreadMembership.find_by(
|
||||
user_id: user.id,
|
||||
thread_id: @chat_message.thread_id,
|
||||
notification_level: "watching",
|
||||
)
|
||||
|
||||
thread_membership && create_watched_thread_notification(thread_membership)
|
||||
end
|
||||
|
||||
if membership.desktop_notifications_always? && !membership.muted?
|
||||
send_notification =
|
||||
DiscoursePluginRegistry.push_notification_filters.all? do |filter|
|
||||
|
@ -101,6 +112,27 @@ module Jobs
|
|||
::PostAlerter.push_notification(user, payload)
|
||||
end
|
||||
end
|
||||
|
||||
def create_watched_thread_notification(thread_membership)
|
||||
thread = @chat_message.thread
|
||||
description = thread.title.presence || thread.original_message.message
|
||||
|
||||
data = {
|
||||
username: @creator.username,
|
||||
chat_message_id: @chat_message.id,
|
||||
chat_channel_id: @chat_channel.id,
|
||||
chat_thread_id: @chat_message.thread_id,
|
||||
last_read_message_id: thread_membership&.last_read_message_id,
|
||||
description: description,
|
||||
user_ids: [@chat_message.user_id],
|
||||
}
|
||||
|
||||
Notification.consolidate_or_create!(
|
||||
notification_type: ::Notification.types[:chat_watched_thread],
|
||||
user_id: thread_membership.user_id,
|
||||
data: data.to_json,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,10 @@ module Chat
|
|||
before_create { self.last_message_id = self.original_message_id }
|
||||
|
||||
def add(user, notification_level: Chat::NotificationLevels.all[:tracking])
|
||||
Chat::UserChatThreadMembership.find_or_create_by!(
|
||||
membership = Chat::UserChatThreadMembership.find_by(user: user, thread: self)
|
||||
return membership if membership
|
||||
|
||||
Chat::UserChatThreadMembership.create!(
|
||||
user: user,
|
||||
thread: self,
|
||||
notification_level: notification_level,
|
||||
|
|
|
@ -8,11 +8,15 @@ module Chat
|
|||
attr_accessor :channel_tracking, :thread_tracking
|
||||
|
||||
class TrackingStateInfo
|
||||
attr_accessor :unread_count, :mention_count, :last_reply_created_at
|
||||
attr_accessor :unread_count,
|
||||
:mention_count,
|
||||
:watched_threads_unread_count,
|
||||
:last_reply_created_at
|
||||
|
||||
def initialize(info)
|
||||
@unread_count = info.present? ? info[:unread_count] : 0
|
||||
@mention_count = info.present? ? info[:mention_count] : 0
|
||||
@watched_threads_unread_count = info.present? ? info[:watched_threads_unread_count] : 0
|
||||
@last_reply_created_at = info.present? ? info[:last_reply_created_at] : nil
|
||||
end
|
||||
|
||||
|
@ -24,6 +28,7 @@ module Chat
|
|||
{
|
||||
unread_count: unread_count,
|
||||
mention_count: mention_count,
|
||||
watched_threads_unread_count: watched_threads_unread_count,
|
||||
last_reply_created_at: last_reply_created_at,
|
||||
}
|
||||
end
|
||||
|
|
|
@ -43,11 +43,28 @@ module Chat
|
|||
WHERE NOT read
|
||||
AND user_chat_channel_memberships.chat_channel_id = memberships.chat_channel_id
|
||||
AND notifications.user_id = :user_id
|
||||
AND notifications.notification_type = :notification_type
|
||||
AND notifications.notification_type = :notification_type_mention
|
||||
AND (data::json->>'chat_message_id')::bigint > COALESCE(user_chat_channel_memberships.last_read_message_id, 0)
|
||||
AND (data::json->>'chat_channel_id')::bigint = memberships.chat_channel_id
|
||||
AND (chat_messages.thread_id IS NULL OR chat_messages.id = chat_threads.original_message_id)
|
||||
) AS mention_count,
|
||||
(
|
||||
SELECT COUNT(*) AS watched_threads_unread_count
|
||||
FROM chat_messages
|
||||
INNER JOIN chat_channels ON chat_channels.id = chat_messages.chat_channel_id
|
||||
INNER JOIN chat_threads ON chat_threads.id = chat_messages.thread_id AND chat_threads.channel_id = chat_messages.chat_channel_id
|
||||
INNER JOIN user_chat_thread_memberships ON user_chat_thread_memberships.thread_id = chat_threads.id
|
||||
WHERE chat_messages.chat_channel_id = memberships.chat_channel_id
|
||||
AND chat_messages.thread_id = user_chat_thread_memberships.thread_id
|
||||
AND chat_messages.user_id != :user_id
|
||||
AND chat_messages.deleted_at IS NULL
|
||||
AND chat_messages.thread_id IS NOT NULL
|
||||
AND chat_messages.id != chat_threads.original_message_id
|
||||
AND chat_messages.id > COALESCE(user_chat_thread_memberships.last_read_message_id, 0)
|
||||
AND user_chat_thread_memberships.user_id = :user_id
|
||||
AND user_chat_thread_memberships.notification_level = :watching_level
|
||||
AND (chat_channels.threading_enabled OR chat_threads.force = true)
|
||||
) AS watched_threads_unread_count,
|
||||
memberships.chat_channel_id AS channel_id
|
||||
FROM user_chat_channel_memberships AS memberships
|
||||
WHERE memberships.user_id = :user_id AND memberships.chat_channel_id IN (:channel_ids)
|
||||
|
@ -59,12 +76,12 @@ module Chat
|
|||
SELECT * FROM (
|
||||
#{sql}
|
||||
) AS channel_tracking
|
||||
WHERE (unread_count > 0 OR mention_count > 0)
|
||||
WHERE (unread_count > 0 OR mention_count > 0 OR watched_threads_unread_count > 0)
|
||||
SQL
|
||||
|
||||
sql += <<~SQL if include_missing_memberships && include_read
|
||||
UNION ALL
|
||||
SELECT 0 AS unread_count, 0 AS mention_count, chat_channels.id AS channel_id
|
||||
SELECT 0 AS unread_count, 0 AS mention_count, 0 AS watched_threads_unread_count, chat_channels.id AS channel_id
|
||||
FROM chat_channels
|
||||
LEFT JOIN user_chat_channel_memberships ON user_chat_channel_memberships.chat_channel_id = chat_channels.id
|
||||
AND user_chat_channel_memberships.user_id = :user_id
|
||||
|
@ -77,7 +94,8 @@ module Chat
|
|||
sql,
|
||||
channel_ids: channel_ids,
|
||||
user_id: user_id,
|
||||
notification_type: Notification.types[:chat_mention],
|
||||
notification_type_mention: ::Notification.types[:chat_mention],
|
||||
watching_level: ::Chat::UserChatThreadMembership.notification_levels[:watching],
|
||||
limit: MAX_CHANNELS,
|
||||
)
|
||||
end
|
||||
|
|
|
@ -54,12 +54,33 @@ module Chat
|
|||
AND chat_messages.thread_id IS NOT NULL
|
||||
AND chat_messages.id != chat_threads.original_message_id
|
||||
AND (chat_channels.threading_enabled OR chat_threads.force = true)
|
||||
AND user_chat_thread_memberships.notification_level NOT IN (:quiet_notification_levels)
|
||||
AND user_chat_thread_memberships.notification_level = :tracking_level
|
||||
AND original_message.deleted_at IS NULL
|
||||
AND user_chat_channel_memberships.muted = false
|
||||
AND user_chat_channel_memberships.user_id = :user_id
|
||||
) AS unread_count,
|
||||
0 AS mention_count,
|
||||
0 as mention_count,
|
||||
(
|
||||
SELECT COUNT(*) AS watched_threads_unread_count
|
||||
FROM chat_messages
|
||||
INNER JOIN chat_channels ON chat_channels.id = chat_messages.chat_channel_id
|
||||
INNER JOIN chat_threads ON chat_threads.id = chat_messages.thread_id AND chat_threads.channel_id = chat_messages.chat_channel_id
|
||||
INNER JOIN user_chat_thread_memberships ON user_chat_thread_memberships.thread_id = chat_threads.id
|
||||
INNER JOIN user_chat_channel_memberships ON user_chat_channel_memberships.chat_channel_id = chat_messages.chat_channel_id
|
||||
INNER JOIN chat_messages AS original_message ON original_message.id = chat_threads.original_message_id
|
||||
WHERE chat_messages.thread_id = memberships.thread_id
|
||||
AND chat_messages.user_id != :user_id
|
||||
AND user_chat_thread_memberships.user_id = :user_id
|
||||
AND chat_messages.id > COALESCE(user_chat_thread_memberships.last_read_message_id, 0)
|
||||
AND chat_messages.deleted_at IS NULL
|
||||
AND chat_messages.thread_id IS NOT NULL
|
||||
AND chat_messages.id != chat_threads.original_message_id
|
||||
AND (chat_channels.threading_enabled OR chat_threads.force = true)
|
||||
AND user_chat_thread_memberships.notification_level = :watching_level
|
||||
AND original_message.deleted_at IS NULL
|
||||
AND user_chat_channel_memberships.user_id = :user_id
|
||||
AND NOT user_chat_channel_memberships.muted
|
||||
) AS watched_threads_unread_count,
|
||||
chat_threads.channel_id,
|
||||
memberships.thread_id
|
||||
FROM user_chat_thread_memberships AS memberships
|
||||
|
@ -75,12 +96,12 @@ module Chat
|
|||
SELECT * FROM (
|
||||
#{sql}
|
||||
) AS thread_tracking
|
||||
WHERE (unread_count > 0 OR mention_count > 0)
|
||||
WHERE (unread_count > 0 OR mention_count > 0 OR watched_threads_unread_count > 0)
|
||||
SQL
|
||||
|
||||
sql += <<~SQL if include_missing_memberships && include_read
|
||||
UNION ALL
|
||||
SELECT 0 AS unread_count, 0 AS mention_count, chat_threads.channel_id, chat_threads.id AS thread_id
|
||||
SELECT 0 AS unread_count, 0 AS mention_count, 0 AS watched_threads_unread_count, chat_threads.channel_id, chat_threads.id AS thread_id
|
||||
FROM chat_channels
|
||||
INNER JOIN chat_threads ON chat_threads.channel_id = chat_channels.id
|
||||
LEFT JOIN user_chat_thread_memberships ON user_chat_thread_memberships.thread_id = chat_threads.id
|
||||
|
@ -99,10 +120,8 @@ module Chat
|
|||
user_id: user_id,
|
||||
notification_type: ::Notification.types[:chat_mention],
|
||||
limit: MAX_THREADS,
|
||||
quiet_notification_levels: [
|
||||
::Chat::UserChatThreadMembership.notification_levels[:muted],
|
||||
::Chat::UserChatThreadMembership.notification_levels[:normal],
|
||||
],
|
||||
tracking_level: ::Chat::UserChatThreadMembership.notification_levels[:tracking],
|
||||
watching_level: ::Chat::UserChatThreadMembership.notification_levels[:watching],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -52,7 +52,14 @@ module Chat
|
|||
include_read: include_read,
|
||||
)
|
||||
.map do |ct|
|
||||
[ct.channel_id, { mention_count: ct.mention_count, unread_count: ct.unread_count }]
|
||||
[
|
||||
ct.channel_id,
|
||||
{
|
||||
mention_count: ct.mention_count,
|
||||
unread_count: ct.unread_count,
|
||||
watched_threads_unread_count: ct.watched_threads_unread_count,
|
||||
},
|
||||
]
|
||||
end
|
||||
.to_h
|
||||
end
|
||||
|
@ -85,6 +92,7 @@ module Chat
|
|||
channel_id: tt.channel_id,
|
||||
mention_count: tt.mention_count,
|
||||
unread_count: tt.unread_count,
|
||||
watched_threads_unread_count: tt.watched_threads_unread_count,
|
||||
}
|
||||
|
||||
if include_last_reply_details
|
||||
|
|
|
@ -103,6 +103,7 @@ module Chat
|
|||
"user_chat_thread_memberships.notification_level IN (?)",
|
||||
[
|
||||
::Chat::UserChatThreadMembership.notification_levels[:normal],
|
||||
::Chat::UserChatThreadMembership.notification_levels[:watching],
|
||||
::Chat::UserChatThreadMembership.notification_levels[:tracking],
|
||||
],
|
||||
)
|
||||
|
|
|
@ -22,6 +22,9 @@ export default class ChatChannelUnreadIndicator extends Component {
|
|||
if (this.#hasChannelMentions()) {
|
||||
return this.args.channel.tracking.mentionCount;
|
||||
}
|
||||
if (this.#hasWatchedThreads()) {
|
||||
return this.args.channel.tracking.watchedThreadsUnreadCount;
|
||||
}
|
||||
return this.args.channel.tracking.unreadCount;
|
||||
}
|
||||
|
||||
|
@ -30,7 +33,9 @@ export default class ChatChannelUnreadIndicator extends Component {
|
|||
return this.#hasChannelMentions();
|
||||
}
|
||||
return (
|
||||
this.args.channel.isDirectMessageChannel || this.#hasChannelMentions()
|
||||
this.args.channel.isDirectMessageChannel ||
|
||||
this.#hasChannelMentions() ||
|
||||
this.#hasWatchedThreads()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -38,6 +43,10 @@ export default class ChatChannelUnreadIndicator extends Component {
|
|||
return this.args.channel.tracking.mentionCount > 0;
|
||||
}
|
||||
|
||||
#hasWatchedThreads() {
|
||||
return this.args.channel.tracking.watchedThreadsUnreadCount > 0;
|
||||
}
|
||||
|
||||
#onlyMentions() {
|
||||
return hasChatIndicator(this.currentUser).ONLY_MENTIONS;
|
||||
}
|
||||
|
|
|
@ -680,6 +680,8 @@ export default class ChatChannel extends Component {
|
|||
|
||||
thread.tracking.unreadCount = threadTracking[thread.id].unread_count;
|
||||
thread.tracking.mentionCount = threadTracking[thread.id].mention_count;
|
||||
thread.tracking.watchedThreadsUnreadCount =
|
||||
threadTracking[thread.id].watched_threads_unread_count;
|
||||
}
|
||||
|
||||
#flushIgnoreNextScroll() {
|
||||
|
|
|
@ -84,6 +84,30 @@ export default class ChatThreadList extends Component {
|
|||
thread.originalMessage?.id !== thread.lastMessageId
|
||||
)
|
||||
.sort((threadA, threadB) => {
|
||||
// if both threads have watched unread count, then show latest first
|
||||
if (
|
||||
threadA.tracking.watchedThreadsUnreadCount &&
|
||||
threadB.tracking.watchedThreadsUnreadCount
|
||||
) {
|
||||
if (
|
||||
threadA.preview.lastReplyCreatedAt >
|
||||
threadB.preview.lastReplyCreatedAt
|
||||
) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// sort threads by watched unread count
|
||||
if (threadA.tracking.watchedThreadsUnreadCount) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (threadB.tracking.watchedThreadsUnreadCount) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// If both are unread we just want to sort by last reply date + time descending.
|
||||
if (threadA.tracking.unreadCount && threadB.tracking.unreadCount) {
|
||||
if (
|
||||
|
|
|
@ -28,6 +28,8 @@ export default class FooterUnreadIndicator extends Component {
|
|||
return this.chatTrackingStateManager.publicChannelMentionCount;
|
||||
} else if (this.badgeType === DMS_TAB) {
|
||||
return this.chatTrackingStateManager.directMessageUnreadCount;
|
||||
} else if (this.badgeType === THREADS_TAB) {
|
||||
return this.chatTrackingStateManager.watchedThreadsUnreadCount;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@ export default class Channel extends Component {
|
|||
return (
|
||||
this.args.item.model.isDirectMessageChannel ||
|
||||
(this.args.item.model.isCategoryChannel &&
|
||||
this.args.item.model.tracking.mentionCount > 0)
|
||||
this.args.item.model.tracking.mentionCount > 0) ||
|
||||
(this.args.item.model.isCategoryChannel &&
|
||||
this.args.item.model.tracking.watchedThreadsUnreadCount > 0)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ export default class ChatThreadListItem extends Component {
|
|||
class={{concatClass
|
||||
"chat-thread-list-item"
|
||||
(if (gt @thread.tracking.unreadCount 0) "-is-unread")
|
||||
(if (gt @thread.tracking.watchedThreadsUnreadCount 0) "-is-urgent")
|
||||
}}
|
||||
data-thread-id={{@thread.id}}
|
||||
...attributes
|
||||
|
|
|
@ -1,21 +1,32 @@
|
|||
import Component from "@glimmer/component";
|
||||
|
||||
const MAX_UNREAD_COUNT = 99;
|
||||
|
||||
export default class ChatThreadUnreadIndicator extends Component {
|
||||
get unreadCount() {
|
||||
return this.args.thread.tracking.unreadCount;
|
||||
}
|
||||
|
||||
get urgentCount() {
|
||||
return this.args.thread.tracking.watchedThreadsUnreadCount;
|
||||
}
|
||||
|
||||
get showUnreadIndicator() {
|
||||
return this.unreadCount > 0;
|
||||
return this.unreadCount > 0 || this.urgentCount > 0;
|
||||
}
|
||||
|
||||
get unreadCountLabel() {
|
||||
return this.unreadCount > 99 ? "99+" : this.unreadCount;
|
||||
const count = this.urgentCount > 0 ? this.urgentCount : this.unreadCount;
|
||||
return count > MAX_UNREAD_COUNT ? `${MAX_UNREAD_COUNT}+` : count;
|
||||
}
|
||||
|
||||
get isUrgent() {
|
||||
return this.urgentCount > 0 ? "-urgent" : "";
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.showUnreadIndicator}}
|
||||
<span class="chat-thread-list-item-unread-indicator">
|
||||
<span class="chat-thread-list-item-unread-indicator {{this.isUrgent}}">
|
||||
<span class="chat-thread-list-item-unread-indicator__number">
|
||||
{{this.unreadCountLabel}}
|
||||
</span>
|
||||
|
|
|
@ -46,6 +46,8 @@ export default class UserThreads extends Component {
|
|||
if (tracking) {
|
||||
thread.tracking.mentionCount = tracking.mention_count;
|
||||
thread.tracking.unreadCount = tracking.unread_count;
|
||||
thread.tracking.watchedThreadsUnreadCount =
|
||||
tracking.watched_threads_unread_count;
|
||||
}
|
||||
|
||||
this.trackChannel(thread.channel);
|
||||
|
|
|
@ -103,6 +103,43 @@ export default {
|
|||
};
|
||||
}
|
||||
);
|
||||
|
||||
api.registerNotificationTypeRenderer(
|
||||
"chat_watched_thread",
|
||||
(NotificationItemBase) => {
|
||||
return class extends NotificationItemBase {
|
||||
icon = "discourse-threads";
|
||||
linkTitle = I18n.t("notifications.titles.chat_watched_thread");
|
||||
description = this.notification.data.description;
|
||||
|
||||
get label() {
|
||||
const data = this.notification.data;
|
||||
|
||||
if (data.user_ids.length > 2) {
|
||||
return I18n.t("notifications.chat_watched_thread_label", {
|
||||
username: formatUsername(data.username2),
|
||||
count: data.user_ids.length - 1,
|
||||
});
|
||||
} else if (data.user_ids.length === 2) {
|
||||
return I18n.t("notifications.chat_watched_thread_label", {
|
||||
username: formatUsername(data.username2),
|
||||
username2: formatUsername(data.username),
|
||||
count: 1,
|
||||
});
|
||||
} else {
|
||||
return formatUsername(data.username);
|
||||
}
|
||||
}
|
||||
|
||||
get linkHref() {
|
||||
const data = this.notification.data;
|
||||
return getURL(
|
||||
`/chat/c/-/${data.chat_channel_id}/t/${data.chat_thread_id}/${data.chat_message_id}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (api.registerUserMenuTab) {
|
||||
|
@ -123,7 +160,8 @@ export default {
|
|||
get count() {
|
||||
return (
|
||||
this.getUnreadCountForType("chat_mention") +
|
||||
this.getUnreadCountForType("chat_invitation")
|
||||
this.getUnreadCountForType("chat_invitation") +
|
||||
this.getUnreadCountForType("chat_watched_thread")
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -133,6 +171,7 @@ export default {
|
|||
"chat_mention",
|
||||
"chat_message",
|
||||
"chat_quoted",
|
||||
"chat_watched_thread",
|
||||
];
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
} from "discourse/lib/notification-levels";
|
||||
|
||||
export const threadNotificationButtonLevels = [
|
||||
NotificationLevels.WATCHING,
|
||||
NotificationLevels.TRACKING,
|
||||
NotificationLevels.REGULAR,
|
||||
].map(buttonDetails);
|
||||
|
|
|
@ -114,6 +114,12 @@ export default class ChatChannel {
|
|||
return Array.from(this.threadsManager.unreadThreadOverview.values()).length;
|
||||
}
|
||||
|
||||
get watchedThreadsUnreadCount() {
|
||||
return this.threadsManager.threads.reduce((unreadCount, thread) => {
|
||||
return unreadCount + thread.tracking.watchedThreadsUnreadCount;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
updateLastViewedAt() {
|
||||
this.currentUserMembership.lastViewedAt = new Date();
|
||||
}
|
||||
|
|
|
@ -7,16 +7,19 @@ export default class ChatTrackingState {
|
|||
|
||||
@tracked _unreadCount;
|
||||
@tracked _mentionCount;
|
||||
@tracked _watchedThreadsUnreadCount;
|
||||
|
||||
constructor(owner, params = {}) {
|
||||
setOwner(this, owner);
|
||||
this._unreadCount = params.unreadCount ?? 0;
|
||||
this._mentionCount = params.mentionCount ?? 0;
|
||||
this._watchedThreadsUnreadCount = params.watchedThreadsUnreadCount ?? 0;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._unreadCount = 0;
|
||||
this._mentionCount = 0;
|
||||
this._watchedThreadsUnreadCount = 0;
|
||||
}
|
||||
|
||||
get unreadCount() {
|
||||
|
@ -42,4 +45,16 @@ export default class ChatTrackingState {
|
|||
this.chatTrackingStateManager.triggerNotificationsChanged();
|
||||
}
|
||||
}
|
||||
|
||||
get watchedThreadsUnreadCount() {
|
||||
return this._watchedThreadsUnreadCount;
|
||||
}
|
||||
|
||||
set watchedThreadsUnreadCount(value) {
|
||||
const valueChanged = this._watchedThreadsUnreadCount !== value;
|
||||
if (valueChanged) {
|
||||
this._watchedThreadsUnreadCount = value;
|
||||
this.chatTrackingStateManager.triggerNotificationsChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import Service, { service } from "@ember/service";
|
||||
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
|
||||
|
@ -267,7 +268,17 @@ export default class ChatSubscriptionsManager extends Service {
|
|||
busData.thread_id,
|
||||
busData.message.created_at
|
||||
);
|
||||
thread.tracking.unreadCount++;
|
||||
|
||||
if (
|
||||
thread.currentUserMembership.notificationLevel ===
|
||||
NotificationLevels.WATCHING
|
||||
) {
|
||||
thread.tracking.watchedThreadsUnreadCount++;
|
||||
channel.tracking.watchedThreadsUnreadCount++;
|
||||
} else {
|
||||
thread.tracking.unreadCount++;
|
||||
}
|
||||
|
||||
this._updateActiveLastViewedAt(channel);
|
||||
}
|
||||
}
|
||||
|
@ -339,6 +350,8 @@ export default class ChatSubscriptionsManager extends Service {
|
|||
|
||||
channel.tracking.unreadCount = busData.unread_count;
|
||||
channel.tracking.mentionCount = busData.mention_count;
|
||||
channel.tracking.watchedThreadsUnreadCount =
|
||||
busData.watched_threads_unread_count;
|
||||
|
||||
if (
|
||||
busData.hasOwnProperty("unread_thread_overview") &&
|
||||
|
@ -366,6 +379,8 @@ export default class ChatSubscriptionsManager extends Service {
|
|||
busData.thread_tracking.unread_count;
|
||||
thread.tracking.mentionCount =
|
||||
busData.thread_tracking.mention_count;
|
||||
thread.tracking.watchedThreadsUnreadCount =
|
||||
busData.thread_tracking.watched_threads_unread_count;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -70,7 +70,11 @@ export default class ChatTrackingStateManager extends Service {
|
|||
}
|
||||
|
||||
get allChannelUrgentCount() {
|
||||
return this.publicChannelMentionCount + this.directMessageUnreadCount;
|
||||
return (
|
||||
this.publicChannelMentionCount +
|
||||
this.directMessageUnreadCount +
|
||||
this.watchedThreadsUnreadCount
|
||||
);
|
||||
}
|
||||
|
||||
get hasUnreadThreads() {
|
||||
|
@ -79,6 +83,12 @@ export default class ChatTrackingStateManager extends Service {
|
|||
);
|
||||
}
|
||||
|
||||
get watchedThreadsUnreadCount() {
|
||||
return this.#publicChannels.reduce((unreadCount, channel) => {
|
||||
return unreadCount + channel.tracking.watchedThreadsUnreadCount;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
super.willDestroy(...arguments);
|
||||
cancel(this._onTriggerNotificationDebounceHandler);
|
||||
|
@ -108,6 +118,8 @@ export default class ChatTrackingStateManager extends Service {
|
|||
}
|
||||
model.tracking.unreadCount = state.unread_count;
|
||||
model.tracking.mentionCount = state.mention_count;
|
||||
model.tracking.watchedThreadsUnreadCount =
|
||||
state.watched_threads_unread_count;
|
||||
}
|
||||
|
||||
get #publicChannels() {
|
||||
|
|
|
@ -144,6 +144,8 @@ export default class Chat extends Service {
|
|||
const state = channelsView.tracking.channel_tracking[channel.id];
|
||||
channel.tracking.unreadCount = state.unread_count;
|
||||
channel.tracking.mentionCount = state.mention_count;
|
||||
channel.tracking.watchedThreadsUnreadCount =
|
||||
state.watched_threads_unread_count;
|
||||
|
||||
channel.currentUserMembership =
|
||||
channelObject.current_user_membership;
|
||||
|
|
|
@ -132,7 +132,7 @@ en:
|
|||
header_indicator_preference:
|
||||
title: "Show activity indicator in header"
|
||||
all_new: "All New Messages"
|
||||
dm_and_mentions: "Direct Messages and Mentions"
|
||||
dm_and_mentions: "Direct Messages, Mentions and Watched Threads"
|
||||
only_mentions: "Only Mentions"
|
||||
never: "Never"
|
||||
separate_sidebar_mode:
|
||||
|
@ -634,10 +634,13 @@ en:
|
|||
notifications:
|
||||
regular:
|
||||
title: "Normal"
|
||||
description: "You will be notified if someone mentions your @name in this thread."
|
||||
description: "Get notified when someone mentions you in this thread."
|
||||
tracking:
|
||||
title: "Tracking"
|
||||
description: "A count of new replies for this thread will be shown in the thread list and the channel. You will be notified if someone mentions your @name in this thread."
|
||||
description: "Get notified when someone mentions you in this thread and see a count of new replies in the thread list."
|
||||
watching:
|
||||
title: "Watching"
|
||||
description: "Get notified about all replies in this thread and see a count of new replies in the thread list."
|
||||
participants_other_count:
|
||||
one: "+%{count}"
|
||||
other: "+%{count}"
|
||||
|
@ -664,6 +667,9 @@ en:
|
|||
chat_invitation: "invited you to join a chat channel"
|
||||
chat_invitation_html: "<span>%{username}</span> <span>invited you to join a chat channel</span>"
|
||||
chat_quoted: "<span>%{username}</span> %{description}"
|
||||
chat_watched_thread_label:
|
||||
one: "%{username} and %{username2}"
|
||||
other: "%{username} and %{count} others"
|
||||
|
||||
popup:
|
||||
chat_mention:
|
||||
|
@ -685,6 +691,7 @@ en:
|
|||
chat_mention: "Chat mention"
|
||||
chat_invitation: "Chat invitation"
|
||||
chat_quoted: "Chat quoted"
|
||||
chat_watched_thread: "Chat watched thread"
|
||||
action_codes:
|
||||
chat:
|
||||
enabled: '%{who} enabled <button class="btn-link open-chat">chat</button> %{when}'
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Chat
|
||||
module NotificationConsolidationExtension
|
||||
CONSOLIDATION_THRESHOLD = 1
|
||||
|
||||
def self.watched_thread_message_plan
|
||||
Notifications::ConsolidateNotifications.new(
|
||||
from: Notification.types[:chat_watched_thread],
|
||||
to: Notification.types[:chat_watched_thread],
|
||||
threshold: CONSOLIDATION_THRESHOLD,
|
||||
unconsolidated_query_blk:
|
||||
Proc.new do |notifications, data|
|
||||
notifications.where("data::json ->> 'consolidated' IS NULL").where(
|
||||
"data::json ->> 'chat_thread_id' = ?",
|
||||
data[:chat_thread_id].to_s,
|
||||
)
|
||||
end,
|
||||
consolidated_query_blk:
|
||||
Proc.new do |notifications, data|
|
||||
notifications.where("(data::json ->> 'consolidated')::bool").where(
|
||||
"data::json ->> 'chat_thread_id' = ?",
|
||||
data[:chat_thread_id].to_s,
|
||||
)
|
||||
end,
|
||||
).set_mutations(
|
||||
set_data_blk:
|
||||
lambda do |notification|
|
||||
data = notification.data_hash
|
||||
|
||||
last_watched_thread_notification =
|
||||
Notification
|
||||
.where(user_id: notification.user_id)
|
||||
.order("notifications.id DESC")
|
||||
.where("data::json ->> 'chat_thread_id' = ?", data[:chat_thread_id].to_s)
|
||||
.where(notification_type: Notification.types[:chat_watched_thread])
|
||||
.first
|
||||
|
||||
return data if !last_watched_thread_notification
|
||||
|
||||
consolidated_data = last_watched_thread_notification.data_hash
|
||||
|
||||
if data[:last_read_message_id].to_i <= consolidated_data[:chat_message_id].to_i
|
||||
data[:chat_message_id] = consolidated_data[:chat_message_id]
|
||||
end
|
||||
|
||||
if !consolidated_data[:username2] && data[:username] != consolidated_data[:username]
|
||||
data.merge(
|
||||
username2: consolidated_data[:username],
|
||||
user_ids: consolidated_data[:user_ids].concat(data[:user_ids]),
|
||||
)
|
||||
else
|
||||
data.merge(
|
||||
username: consolidated_data[:username],
|
||||
username2: consolidated_data[:username2],
|
||||
user_ids: (consolidated_data[:user_ids].concat(data[:user_ids])).uniq,
|
||||
)
|
||||
end
|
||||
end,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -68,6 +68,7 @@ after_initialize do
|
|||
|
||||
Guardian.prepend Chat::GuardianExtensions
|
||||
UserNotifications.prepend Chat::UserNotificationsExtension
|
||||
Notifications::ConsolidationPlan.prepend Chat::NotificationConsolidationExtension
|
||||
UserOption.prepend Chat::UserOptionExtension
|
||||
Category.prepend Chat::CategoryExtension
|
||||
Reviewable.prepend Chat::ReviewableExtension
|
||||
|
@ -533,6 +534,10 @@ after_initialize do
|
|||
Proc.new { |user| Jobs.enqueue(Jobs::Chat::DeleteUserMessages, user_id: user.id) },
|
||||
)
|
||||
|
||||
register_notification_consolidation_plan(
|
||||
Chat::NotificationConsolidationExtension.watched_thread_message_plan,
|
||||
)
|
||||
|
||||
register_bookmarkable(Chat::MessageBookmarkable)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,16 +12,21 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
||||
end
|
||||
|
||||
def run_job
|
||||
def run_job(message)
|
||||
described_class.new.execute(chat_message_id: message.id, except_user_ids: except_user_ids)
|
||||
end
|
||||
|
||||
def notification_messages_for(user)
|
||||
def notification_messages_for(user, chat_message: message)
|
||||
MessageBus
|
||||
.track_publish { run_job }
|
||||
.track_publish { run_job(chat_message) }
|
||||
.filter { |m| m.channel == "/chat/notification-alert/#{user.id}" }
|
||||
end
|
||||
|
||||
def track_core_notification(user:, message:, type: ::Notification.types[:chat_watched_thread])
|
||||
described_class.new.execute(chat_message_id: message.id)
|
||||
Notification.where(user: user, notification_type: type).last
|
||||
end
|
||||
|
||||
context "for a category channel" do
|
||||
fab!(:channel) { Fabricate(:category_channel) }
|
||||
fab!(:membership1) do
|
||||
|
@ -62,6 +67,77 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||
)
|
||||
end
|
||||
|
||||
context "with watched threads" do
|
||||
fab!(:chat_message) { Fabricate(:chat_message, chat_channel: channel, user: user1) }
|
||||
fab!(:thread) { Fabricate(:chat_thread, channel: channel, original_message: chat_message) }
|
||||
fab!(:thread_message) do
|
||||
Fabricate(:chat_message, chat_channel: channel, thread: thread, user: user2)
|
||||
end
|
||||
|
||||
before { channel.update!(threading_enabled: true) }
|
||||
|
||||
context "with channel notification_level is always" do
|
||||
before do
|
||||
always = Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
|
||||
membership1.update!(desktop_notification_level: always, mobile_notification_level: always)
|
||||
end
|
||||
|
||||
it "creates a core notification when watching the thread" do
|
||||
thread.membership_for(user1).update!(
|
||||
notification_level: Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
|
||||
notification = track_core_notification(user: user1, message: thread_message)
|
||||
|
||||
expect(notification).to be_present
|
||||
expect(notification.notification_type).to eq(Notification.types[:chat_watched_thread])
|
||||
end
|
||||
|
||||
it "does not create a core notification when not watching the thread" do
|
||||
notification = track_core_notification(user: user1, message: thread_message)
|
||||
|
||||
expect(notification).to be_nil
|
||||
end
|
||||
|
||||
it "does not create a core notification when the channel is muted" do
|
||||
thread.membership_for(user1).update!(
|
||||
notification_level: Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
membership1.update!(muted: true)
|
||||
notification = track_core_notification(user: user1, message: thread_message)
|
||||
|
||||
expect(notification).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context "without channel notifications" do
|
||||
before do
|
||||
thread.membership_for(user1).update!(
|
||||
notification_level: Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
end
|
||||
|
||||
it "creates a core notification for watched threads" do
|
||||
expect { run_job(thread_message) }.to change { Notification.count }
|
||||
end
|
||||
|
||||
it "does not create a core notification if channel is muted" do
|
||||
membership1.update!(muted: true)
|
||||
expect { run_job(thread_message) }.not_to change { Notification.count }
|
||||
end
|
||||
|
||||
it "does not create a desktop notification" do
|
||||
messages = notification_messages_for(user1)
|
||||
expect(messages).to be_empty
|
||||
end
|
||||
|
||||
it "does not create a mobile notification" do
|
||||
PostAlerter.expects(:push_notification).never
|
||||
run_job(thread_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with chat_notification_translation_args plugin_modifier" do
|
||||
let(:modifier_block) do
|
||||
Proc.new do |args|
|
||||
|
|
|
@ -26,7 +26,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
before { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||
|
||||
it "returns a correct unread count" do
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 1, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
channel_id: channel_1.id,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "when the membership has been muted" do
|
||||
|
@ -38,7 +45,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
end
|
||||
|
||||
it "returns a zeroed unread count" do
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 0, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -47,13 +61,27 @@ describe Chat::ChannelUnreadsQuery do
|
|||
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: thread_om) }
|
||||
|
||||
it "does include the original message in the unread count" do
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 2, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not include other thread messages in the unread count" do
|
||||
Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
||||
Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 2, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,8 +98,18 @@ describe Chat::ChannelUnreadsQuery do
|
|||
it "returns accurate counts" do
|
||||
expect(query).to match_array(
|
||||
[
|
||||
{ mention_count: 0, unread_count: 1, channel_id: channel_1.id },
|
||||
{ mention_count: 0, unread_count: 2, channel_id: channel_2.id },
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
@ -86,7 +124,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
|
||||
it "does not return counts for the channels" do
|
||||
expect(query).to match_array(
|
||||
[{ mention_count: 0, unread_count: 1, channel_id: channel_1.id }],
|
||||
[
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -96,8 +141,18 @@ describe Chat::ChannelUnreadsQuery do
|
|||
it "does return zeroed counts for the channels" do
|
||||
expect(query).to match_array(
|
||||
[
|
||||
{ mention_count: 0, unread_count: 1, channel_id: channel_1.id },
|
||||
{ mention_count: 0, unread_count: 0, channel_id: channel_2.id },
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
@ -107,7 +162,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
|
||||
it "does not return counts for the channels" do
|
||||
expect(query).to match_array(
|
||||
[{ mention_count: 0, unread_count: 1, channel_id: channel_1.id }],
|
||||
[
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -137,7 +199,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
message = Fabricate(:chat_message, chat_channel: channel_1)
|
||||
create_mention(message, channel_1)
|
||||
|
||||
expect(query.first).to eq({ mention_count: 1, unread_count: 1, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 1,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "for unread mentions in a thread" do
|
||||
|
@ -146,7 +215,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
|
||||
it "does include the original message in the mention count" do
|
||||
create_mention(thread_om, channel_1)
|
||||
expect(query.first).to eq({ mention_count: 1, unread_count: 1, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 1,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not include other thread messages in the mention count" do
|
||||
|
@ -154,7 +230,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
thread_message_2 = Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
||||
create_mention(thread_message_1, channel_1)
|
||||
create_mention(thread_message_2, channel_1)
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 1, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -173,8 +256,93 @@ describe Chat::ChannelUnreadsQuery do
|
|||
|
||||
expect(query).to match_array(
|
||||
[
|
||||
{ mention_count: 1, unread_count: 1, channel_id: channel_1.id },
|
||||
{ mention_count: 1, unread_count: 2, channel_id: channel_2.id },
|
||||
{
|
||||
mention_count: 1,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
{
|
||||
mention_count: 1,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with watched threads" do
|
||||
fab!(:message) { Fabricate(:chat_message, chat_channel: channel_1, user: current_user) }
|
||||
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: message) }
|
||||
fab!(:thread_reply) { Fabricate(:chat_message, chat_channel: channel_1, thread: thread) }
|
||||
|
||||
before do
|
||||
channel_1.update(threading_enabled: true)
|
||||
channel_1.membership_for(current_user).mark_read!(message.id)
|
||||
thread.membership_for(current_user).update!(
|
||||
notification_level: ::Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
end
|
||||
|
||||
it "returns correct watched thread unread count" do
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 1,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "returns unread and watched thread unread counts" do
|
||||
Fabricate(:chat_message, chat_channel: channel_1)
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 1,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "for multiple channels" do
|
||||
fab!(:channel_2) { Fabricate(:category_channel) }
|
||||
fab!(:message_2) { Fabricate(:chat_message, chat_channel: channel_2, user: current_user) }
|
||||
fab!(:thread_2) { Fabricate(:chat_thread, channel: channel_2, original_message: message_2) }
|
||||
let(:channel_ids) { [channel_1.id, channel_2.id] }
|
||||
|
||||
before do
|
||||
channel_2.add(current_user)
|
||||
channel_2.update(threading_enabled: true)
|
||||
channel_2.membership_for(current_user).mark_read!(message_2.id)
|
||||
thread_2.membership_for(current_user).update!(
|
||||
notification_level: ::Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
|
||||
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_2)
|
||||
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_2)
|
||||
end
|
||||
|
||||
it "returns accurate counts" do
|
||||
expect(query).to match_array(
|
||||
[
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 1,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 2,
|
||||
channel_id: channel_2.id,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
@ -183,7 +351,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||
|
||||
context "with nothing unread" do
|
||||
it "returns a correct state" do
|
||||
expect(query.first).to eq({ mention_count: 0, unread_count: 0, channel_id: channel_1.id })
|
||||
expect(query.first).to eq(
|
||||
{
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
context "when include_read is false" do
|
||||
|
|
|
@ -47,10 +47,34 @@ describe Chat::ThreadUnreadsQuery do
|
|||
it "gets a count of all the thread unreads across the channels" do
|
||||
expect(query.map(&:to_h)).to match_array(
|
||||
[
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 1 },
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_2.id, unread_count: 0 },
|
||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_3.id, unread_count: 1 },
|
||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_4.id, unread_count: 1 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_2.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_4.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
@ -58,7 +82,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||
it "does not count deleted messages" do
|
||||
message_1.trash!
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -66,17 +96,35 @@ describe Chat::ThreadUnreadsQuery do
|
|||
channel_1.membership_for(current_user).update!(muted: true)
|
||||
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not messages in threads where threading_enabled is false on the channel" do
|
||||
channel_1.update!(threading_enabled: false)
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_2.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_2.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_2.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -86,7 +134,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||
.find_by(user: current_user)
|
||||
.update!(last_read_message_id: message_1.id)
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -94,14 +148,26 @@ describe Chat::ThreadUnreadsQuery do
|
|||
thread_1.original_message.destroy
|
||||
thread_1.update!(original_message: message_1)
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "does not count the thread as unread if the original message is deleted" do
|
||||
thread_1.original_message.destroy
|
||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -116,6 +182,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||
mention_count: 0,
|
||||
thread_id: thread_2.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -129,8 +196,20 @@ describe Chat::ThreadUnreadsQuery do
|
|||
it "gets a count of all the thread unreads for the specified threads" do
|
||||
expect(query.map(&:to_h)).to match_array(
|
||||
[
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 1 },
|
||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_3.id, unread_count: 1 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
@ -145,7 +224,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||
|
||||
it "gets a zeroed out count for the thread" do
|
||||
expect(query.map(&:to_h)).to include(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -160,7 +245,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||
|
||||
it "gets a zeroed out count for the thread" do
|
||||
expect(query.map(&:to_h)).to include(
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 0 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -176,6 +267,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -192,12 +284,14 @@ describe Chat::ThreadUnreadsQuery do
|
|||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -214,6 +308,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
|
@ -230,11 +325,113 @@ describe Chat::ThreadUnreadsQuery do
|
|||
it "gets a count of all the thread unreads across the channels filtered by thread id" do
|
||||
expect(query.map(&:to_h)).to match_array(
|
||||
[
|
||||
{ channel_id: channel_1.id, mention_count: 0, thread_id: thread_1.id, unread_count: 1 },
|
||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_3.id, unread_count: 1 },
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_1.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
mention_count: 0,
|
||||
thread_id: thread_3.id,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with watched threads" do
|
||||
let(:channel_ids) { [channel_1.id] }
|
||||
|
||||
before do
|
||||
[thread_1, thread_3].each do |thread|
|
||||
thread.membership_for(current_user).update!(
|
||||
notification_level: Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
end
|
||||
|
||||
3.times { Fabricate(:chat_message, chat_channel: channel_1, thread: thread_1) }
|
||||
2.times { Fabricate(:chat_message, chat_channel: channel_1, thread: thread_2) }
|
||||
end
|
||||
|
||||
it "returns correct count for channel" do
|
||||
expect(query.map(&:to_h)).to match_array(
|
||||
[
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
thread_id: thread_1.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 3,
|
||||
},
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
thread_id: thread_2.id,
|
||||
mention_count: 0,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
it "returns correct count across multiple channels" do
|
||||
channel_ids.push(channel_2.id)
|
||||
Fabricate(:chat_message, chat_channel: channel_2, thread: thread_3)
|
||||
|
||||
expect(query.map(&:to_h)).to match_array(
|
||||
[
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
thread_id: thread_1.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 3,
|
||||
},
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
thread_id: thread_2.id,
|
||||
mention_count: 0,
|
||||
unread_count: 2,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
thread_id: thread_3.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 1,
|
||||
},
|
||||
{
|
||||
channel_id: channel_2.id,
|
||||
thread_id: thread_4.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
context "when include_read is false" do
|
||||
let(:include_read) { false }
|
||||
|
||||
it "does not get threads with no unread messages" do
|
||||
expect(query.map(&:to_h)).to include(
|
||||
{
|
||||
channel_id: channel_1.id,
|
||||
thread_id: thread_1.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 3,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -62,10 +62,12 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||
channel_1.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
channel_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -113,10 +115,12 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||
channel_1.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
channel_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
@ -125,11 +129,13 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||
thread_1.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
},
|
||||
thread_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
},
|
||||
},
|
||||
|
@ -152,12 +158,14 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||
thread_1.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
last_reply_created_at: thread_1.reload.last_message.created_at,
|
||||
},
|
||||
thread_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
||||
},
|
||||
|
@ -172,12 +180,14 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||
thread_1.id => {
|
||||
unread_count: 0,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_1.id,
|
||||
last_reply_created_at: nil,
|
||||
},
|
||||
thread_2.id => {
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
channel_id: channel_2.id,
|
||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
||||
},
|
||||
|
|
|
@ -180,7 +180,14 @@ RSpec.describe Chat::ListChannelMessages do
|
|||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
||||
|
||||
expect(result.tracking.thread_tracking).to eq(
|
||||
{ thread_1.id => { channel_id: channel.id, mention_count: 0, unread_count: 0 } },
|
||||
{
|
||||
thread_1.id => {
|
||||
channel_id: channel.id,
|
||||
mention_count: 0,
|
||||
unread_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -193,7 +200,14 @@ RSpec.describe Chat::ListChannelMessages do
|
|||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
||||
|
||||
expect(result.tracking.thread_tracking).to eq(
|
||||
{ thread_1.id => { channel_id: channel.id, mention_count: 0, unread_count: 1 } },
|
||||
{
|
||||
thread_1.id => {
|
||||
channel_id: channel.id,
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -214,7 +228,14 @@ RSpec.describe Chat::ListChannelMessages do
|
|||
|
||||
expect(result.tracking.channel_tracking).to eq({})
|
||||
expect(result.tracking.thread_tracking).to eq(
|
||||
{ thread_1.id => { channel_id: channel.id, mention_count: 0, unread_count: 1 } },
|
||||
{
|
||||
thread_1.id => {
|
||||
channel_id: channel.id,
|
||||
mention_count: 0,
|
||||
unread_count: 1,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,7 @@ RSpec.describe Chat::ListUserChannels do
|
|||
expect(result.structured[:public_channels]).to eq([channel_1])
|
||||
expect(result.structured[:direct_message_channels]).to eq([])
|
||||
expect(result.structured[:tracking].channel_tracking[channel_1.id]).to eq(
|
||||
{ mention_count: 0, unread_count: 0 },
|
||||
{ mention_count: 0, unread_count: 0, watched_threads_unread_count: 0 },
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -143,6 +143,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||
"membership_id" => membership_1.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
"watched_threads_unread_count" => 0,
|
||||
},
|
||||
channel_2.id.to_s => {
|
||||
"last_read_message_id" => message_4.id,
|
||||
|
@ -150,6 +151,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||
"membership_id" => membership_2.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
"watched_threads_unread_count" => 0,
|
||||
},
|
||||
channel_3.id.to_s => {
|
||||
"last_read_message_id" => message_6.id,
|
||||
|
@ -157,6 +159,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||
"membership_id" => membership_3.id,
|
||||
"mention_count" => 0,
|
||||
"unread_count" => 0,
|
||||
"watched_threads_unread_count" => 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
|
|
@ -109,7 +109,12 @@ describe Chat::Publisher do
|
|||
{ thread.id.to_s => thread.reload.last_message.created_at.iso8601(3) },
|
||||
)
|
||||
expect(data["thread_tracking"]).to eq(
|
||||
{ "unread_count" => 1, "mention_count" => 0, "last_reply_created_at" => nil },
|
||||
{
|
||||
"unread_count" => 1,
|
||||
"mention_count" => 0,
|
||||
"watched_threads_unread_count" => 0,
|
||||
"last_reply_created_at" => nil,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -119,7 +124,12 @@ describe Chat::Publisher do
|
|||
expect(data["thread_id"]).to eq(thread.id)
|
||||
expect(data["unread_thread_overview"]).to eq({})
|
||||
expect(data["thread_tracking"]).to eq(
|
||||
{ "unread_count" => 0, "mention_count" => 0, "last_reply_created_at" => nil },
|
||||
{
|
||||
"unread_count" => 0,
|
||||
"mention_count" => 0,
|
||||
"watched_threads_unread_count" => 0,
|
||||
"last_reply_created_at" => nil,
|
||||
},
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -44,6 +44,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_1.id => {
|
||||
unread_count: 4, # 2 messages + 2 thread original messages
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -55,11 +56,13 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_id: channel_1.id,
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
thread_2.id => {
|
||||
channel_id: channel_1.id,
|
||||
unread_count: 2,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -74,6 +77,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_1.id => {
|
||||
unread_count: 4, # 2 messages + 2 thread original messages
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -89,6 +93,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_1.id => {
|
||||
unread_count: 4, # 2 messages + 2 thread original messages
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -100,6 +105,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_id: channel_1.id,
|
||||
unread_count: 2,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -117,10 +123,12 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_1.id => {
|
||||
unread_count: 4, # 2 messages + 2 thread original messages
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
channel_2.id => {
|
||||
unread_count: 0,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
@ -132,21 +140,25 @@ RSpec.describe ::Chat::TrackingState do
|
|||
channel_id: channel_1.id,
|
||||
unread_count: 1,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
thread_2.id => {
|
||||
channel_id: channel_1.id,
|
||||
unread_count: 2,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
thread_3.id => {
|
||||
channel_id: channel_2.id,
|
||||
unread_count: 0,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
thread_4.id => {
|
||||
channel_id: channel_2.id,
|
||||
unread_count: 0,
|
||||
mention_count: 0,
|
||||
watched_threads_unread_count: 0,
|
||||
},
|
||||
)
|
||||
end
|
||||
|
|
|
@ -153,6 +153,17 @@ RSpec.describe "Mobile Chat footer", type: :system, mobile: true do
|
|||
|
||||
expect(page).to have_no_css("#c-footer-threads .c-unread-indicator")
|
||||
end
|
||||
|
||||
it "is urgent for watched thread messages" do
|
||||
thread.membership_for(current_user).update!(
|
||||
notification_level: ::Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
|
||||
expect(page).to have_css("#c-footer-threads .c-unread-indicator.-urgent")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,20 +45,23 @@ module PageObjects
|
|||
".chat-thread-list-item__last-reply-timestamp .relative-date[data-time='#{(last_reply.created_at.iso8601.to_time.to_f * 1000).to_i}']"
|
||||
end
|
||||
|
||||
def has_unread_item?(id, count: nil)
|
||||
def has_unread_item?(id, count: nil, urgent: false)
|
||||
selector_class = urgent ? ".-is-urgent" : ".-is-unread"
|
||||
|
||||
if count.nil?
|
||||
component.has_css?(item_by_id_selector(id) + ".-is-unread")
|
||||
component.has_css?(item_by_id_selector(id) + selector_class)
|
||||
else
|
||||
component.has_css?(
|
||||
item_by_id_selector(id) +
|
||||
".-is-unread .chat-thread-list-item-unread-indicator__number",
|
||||
item_by_id_selector(id) + selector_class +
|
||||
" .chat-thread-list-item-unread-indicator__number",
|
||||
text: count.to_s,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def has_no_unread_item?(id)
|
||||
component.has_no_css?(item_by_id_selector(id) + ".-is-unread")
|
||||
def has_no_unread_item?(id, urgent: false)
|
||||
selector_class = urgent ? ".-is-urgent" : ".-is-unread"
|
||||
component.has_no_css?(item_by_id_selector(id) + selector_class)
|
||||
end
|
||||
|
||||
def item_by_id_selector(id)
|
||||
|
|
|
@ -43,6 +43,19 @@ describe "Thread tracking state | drawer", type: :system do
|
|||
expect(thread_list_page).to have_unread_item(thread.id)
|
||||
end
|
||||
|
||||
it "shows an urgent indicator on the watched thread in the list" do
|
||||
thread.membership_for(current_user).update!(
|
||||
notification_level: ::Chat::NotificationLevels.all[:watching],
|
||||
)
|
||||
|
||||
visit("/")
|
||||
chat_page.open_from_header
|
||||
drawer_page.open_channel(channel)
|
||||
drawer_page.open_thread_list
|
||||
expect(drawer_page).to have_open_thread_list
|
||||
expect(thread_list_page).to have_unread_item(thread.id, urgent: true)
|
||||
end
|
||||
|
||||
it "marks the thread as read and removes both indicators when the user opens it" do
|
||||
skip("Flaky on CI") if ENV["CI"]
|
||||
|
||||
|
|
|
@ -74,6 +74,18 @@ describe "Thread tracking state | full page", type: :system do
|
|||
expect(thread_list_page).to have_no_unread_item(thread.id)
|
||||
end
|
||||
|
||||
it "shows and urgent for the header of the list when a new watched unread arrives" do
|
||||
thread.membership_for(current_user).update!(last_read_message_id: message_2.id)
|
||||
thread.membership_for(current_user).update!(notification_level: :watching)
|
||||
|
||||
chat_page.visit_channel(channel)
|
||||
channel_page.open_thread_list
|
||||
|
||||
expect(thread_list_page).to have_no_unread_item(thread.id, urgent: true)
|
||||
Fabricate(:chat_message, thread: thread, use_service: true)
|
||||
expect(thread_list_page).to have_unread_item(thread.id, urgent: true)
|
||||
end
|
||||
|
||||
it "allows the user to change their tracking level for an existing thread" do
|
||||
chat_page.visit_thread(thread)
|
||||
thread_page.notification_level = :normal
|
||||
|
|
|
@ -119,6 +119,9 @@
|
|||
"chat_quoted": {
|
||||
"type": "integer"
|
||||
},
|
||||
"chat_watched_thread": {
|
||||
"type": "integer"
|
||||
},
|
||||
"assigned": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue
Block a user