mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06: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,
|
new_features: 37,
|
||||||
admin_problems: 38,
|
admin_problems: 38,
|
||||||
linked_consolidated: 39,
|
linked_consolidated: 39,
|
||||||
|
chat_watched_thread: 40,
|
||||||
following: 800, # Used by https://github.com/discourse/discourse-follow
|
following: 800, # Used by https://github.com/discourse/discourse-follow
|
||||||
following_created_topic: 801, # 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
|
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?
|
@is_direct_message_channel = @chat_channel.direct_message_channel?
|
||||||
|
|
||||||
always_notification_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
|
always_notification_level = ::Chat::UserChatChannelMembership::NOTIFICATION_LEVELS[:always]
|
||||||
|
|
||||||
members =
|
members =
|
||||||
::Chat::UserChatChannelMembership
|
::Chat::UserChatChannelMembership
|
||||||
.includes(user: :groups)
|
.includes(user: :groups)
|
||||||
@ -25,9 +24,10 @@ module Jobs
|
|||||||
.where(chat_channel_id: @chat_channel.id)
|
.where(chat_channel_id: @chat_channel.id)
|
||||||
.where(following: true)
|
.where(following: true)
|
||||||
.where(
|
.where(
|
||||||
"desktop_notification_level = ? OR mobile_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_notification_level,
|
always: always_notification_level,
|
||||||
always_notification_level,
|
thread_id: @chat_message.thread_id,
|
||||||
|
watching: ::Chat::NotificationLevels.all[:watching],
|
||||||
)
|
)
|
||||||
.merge(User.not_suspended)
|
.merge(User.not_suspended)
|
||||||
|
|
||||||
@ -83,6 +83,17 @@ module Jobs
|
|||||||
channel_id: @chat_channel.id,
|
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?
|
if membership.desktop_notifications_always? && !membership.muted?
|
||||||
send_notification =
|
send_notification =
|
||||||
DiscoursePluginRegistry.push_notification_filters.all? do |filter|
|
DiscoursePluginRegistry.push_notification_filters.all? do |filter|
|
||||||
@ -101,6 +112,27 @@ module Jobs
|
|||||||
::PostAlerter.push_notification(user, payload)
|
::PostAlerter.push_notification(user, payload)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -43,7 +43,10 @@ module Chat
|
|||||||
before_create { self.last_message_id = self.original_message_id }
|
before_create { self.last_message_id = self.original_message_id }
|
||||||
|
|
||||||
def add(user, notification_level: Chat::NotificationLevels.all[:tracking])
|
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,
|
user: user,
|
||||||
thread: self,
|
thread: self,
|
||||||
notification_level: notification_level,
|
notification_level: notification_level,
|
||||||
|
@ -8,11 +8,15 @@ module Chat
|
|||||||
attr_accessor :channel_tracking, :thread_tracking
|
attr_accessor :channel_tracking, :thread_tracking
|
||||||
|
|
||||||
class TrackingStateInfo
|
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)
|
def initialize(info)
|
||||||
@unread_count = info.present? ? info[:unread_count] : 0
|
@unread_count = info.present? ? info[:unread_count] : 0
|
||||||
@mention_count = info.present? ? info[:mention_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
|
@last_reply_created_at = info.present? ? info[:last_reply_created_at] : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -24,6 +28,7 @@ module Chat
|
|||||||
{
|
{
|
||||||
unread_count: unread_count,
|
unread_count: unread_count,
|
||||||
mention_count: mention_count,
|
mention_count: mention_count,
|
||||||
|
watched_threads_unread_count: watched_threads_unread_count,
|
||||||
last_reply_created_at: last_reply_created_at,
|
last_reply_created_at: last_reply_created_at,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
@ -43,11 +43,28 @@ module Chat
|
|||||||
WHERE NOT read
|
WHERE NOT read
|
||||||
AND user_chat_channel_memberships.chat_channel_id = memberships.chat_channel_id
|
AND user_chat_channel_memberships.chat_channel_id = memberships.chat_channel_id
|
||||||
AND notifications.user_id = :user_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_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 (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)
|
AND (chat_messages.thread_id IS NULL OR chat_messages.id = chat_threads.original_message_id)
|
||||||
) AS mention_count,
|
) 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
|
memberships.chat_channel_id AS channel_id
|
||||||
FROM user_chat_channel_memberships AS memberships
|
FROM user_chat_channel_memberships AS memberships
|
||||||
WHERE memberships.user_id = :user_id AND memberships.chat_channel_id IN (:channel_ids)
|
WHERE memberships.user_id = :user_id AND memberships.chat_channel_id IN (:channel_ids)
|
||||||
@ -59,12 +76,12 @@ module Chat
|
|||||||
SELECT * FROM (
|
SELECT * FROM (
|
||||||
#{sql}
|
#{sql}
|
||||||
) AS channel_tracking
|
) 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 += <<~SQL if include_missing_memberships && include_read
|
sql += <<~SQL if include_missing_memberships && include_read
|
||||||
UNION ALL
|
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
|
FROM chat_channels
|
||||||
LEFT JOIN user_chat_channel_memberships ON user_chat_channel_memberships.chat_channel_id = chat_channels.id
|
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
|
AND user_chat_channel_memberships.user_id = :user_id
|
||||||
@ -77,7 +94,8 @@ module Chat
|
|||||||
sql,
|
sql,
|
||||||
channel_ids: channel_ids,
|
channel_ids: channel_ids,
|
||||||
user_id: user_id,
|
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,
|
limit: MAX_CHANNELS,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -54,12 +54,33 @@ module Chat
|
|||||||
AND chat_messages.thread_id IS NOT NULL
|
AND chat_messages.thread_id IS NOT NULL
|
||||||
AND chat_messages.id != chat_threads.original_message_id
|
AND chat_messages.id != chat_threads.original_message_id
|
||||||
AND (chat_channels.threading_enabled OR chat_threads.force = true)
|
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 original_message.deleted_at IS NULL
|
||||||
AND user_chat_channel_memberships.muted = false
|
AND user_chat_channel_memberships.muted = false
|
||||||
AND user_chat_channel_memberships.user_id = :user_id
|
AND user_chat_channel_memberships.user_id = :user_id
|
||||||
) AS unread_count,
|
) 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,
|
chat_threads.channel_id,
|
||||||
memberships.thread_id
|
memberships.thread_id
|
||||||
FROM user_chat_thread_memberships AS memberships
|
FROM user_chat_thread_memberships AS memberships
|
||||||
@ -75,12 +96,12 @@ module Chat
|
|||||||
SELECT * FROM (
|
SELECT * FROM (
|
||||||
#{sql}
|
#{sql}
|
||||||
) AS thread_tracking
|
) 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 += <<~SQL if include_missing_memberships && include_read
|
sql += <<~SQL if include_missing_memberships && include_read
|
||||||
UNION ALL
|
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
|
FROM chat_channels
|
||||||
INNER JOIN chat_threads ON chat_threads.channel_id = chat_channels.id
|
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
|
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,
|
user_id: user_id,
|
||||||
notification_type: ::Notification.types[:chat_mention],
|
notification_type: ::Notification.types[:chat_mention],
|
||||||
limit: MAX_THREADS,
|
limit: MAX_THREADS,
|
||||||
quiet_notification_levels: [
|
tracking_level: ::Chat::UserChatThreadMembership.notification_levels[:tracking],
|
||||||
::Chat::UserChatThreadMembership.notification_levels[:muted],
|
watching_level: ::Chat::UserChatThreadMembership.notification_levels[:watching],
|
||||||
::Chat::UserChatThreadMembership.notification_levels[:normal],
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -52,7 +52,14 @@ module Chat
|
|||||||
include_read: include_read,
|
include_read: include_read,
|
||||||
)
|
)
|
||||||
.map do |ct|
|
.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
|
end
|
||||||
.to_h
|
.to_h
|
||||||
end
|
end
|
||||||
@ -85,6 +92,7 @@ module Chat
|
|||||||
channel_id: tt.channel_id,
|
channel_id: tt.channel_id,
|
||||||
mention_count: tt.mention_count,
|
mention_count: tt.mention_count,
|
||||||
unread_count: tt.unread_count,
|
unread_count: tt.unread_count,
|
||||||
|
watched_threads_unread_count: tt.watched_threads_unread_count,
|
||||||
}
|
}
|
||||||
|
|
||||||
if include_last_reply_details
|
if include_last_reply_details
|
||||||
|
@ -103,6 +103,7 @@ module Chat
|
|||||||
"user_chat_thread_memberships.notification_level IN (?)",
|
"user_chat_thread_memberships.notification_level IN (?)",
|
||||||
[
|
[
|
||||||
::Chat::UserChatThreadMembership.notification_levels[:normal],
|
::Chat::UserChatThreadMembership.notification_levels[:normal],
|
||||||
|
::Chat::UserChatThreadMembership.notification_levels[:watching],
|
||||||
::Chat::UserChatThreadMembership.notification_levels[:tracking],
|
::Chat::UserChatThreadMembership.notification_levels[:tracking],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -22,6 +22,9 @@ export default class ChatChannelUnreadIndicator extends Component {
|
|||||||
if (this.#hasChannelMentions()) {
|
if (this.#hasChannelMentions()) {
|
||||||
return this.args.channel.tracking.mentionCount;
|
return this.args.channel.tracking.mentionCount;
|
||||||
}
|
}
|
||||||
|
if (this.#hasWatchedThreads()) {
|
||||||
|
return this.args.channel.tracking.watchedThreadsUnreadCount;
|
||||||
|
}
|
||||||
return this.args.channel.tracking.unreadCount;
|
return this.args.channel.tracking.unreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +33,9 @@ export default class ChatChannelUnreadIndicator extends Component {
|
|||||||
return this.#hasChannelMentions();
|
return this.#hasChannelMentions();
|
||||||
}
|
}
|
||||||
return (
|
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;
|
return this.args.channel.tracking.mentionCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#hasWatchedThreads() {
|
||||||
|
return this.args.channel.tracking.watchedThreadsUnreadCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
#onlyMentions() {
|
#onlyMentions() {
|
||||||
return hasChatIndicator(this.currentUser).ONLY_MENTIONS;
|
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.unreadCount = threadTracking[thread.id].unread_count;
|
||||||
thread.tracking.mentionCount = threadTracking[thread.id].mention_count;
|
thread.tracking.mentionCount = threadTracking[thread.id].mention_count;
|
||||||
|
thread.tracking.watchedThreadsUnreadCount =
|
||||||
|
threadTracking[thread.id].watched_threads_unread_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
#flushIgnoreNextScroll() {
|
#flushIgnoreNextScroll() {
|
||||||
|
@ -84,6 +84,30 @@ export default class ChatThreadList extends Component {
|
|||||||
thread.originalMessage?.id !== thread.lastMessageId
|
thread.originalMessage?.id !== thread.lastMessageId
|
||||||
)
|
)
|
||||||
.sort((threadA, threadB) => {
|
.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 both are unread we just want to sort by last reply date + time descending.
|
||||||
if (threadA.tracking.unreadCount && threadB.tracking.unreadCount) {
|
if (threadA.tracking.unreadCount && threadB.tracking.unreadCount) {
|
||||||
if (
|
if (
|
||||||
|
@ -28,6 +28,8 @@ export default class FooterUnreadIndicator extends Component {
|
|||||||
return this.chatTrackingStateManager.publicChannelMentionCount;
|
return this.chatTrackingStateManager.publicChannelMentionCount;
|
||||||
} else if (this.badgeType === DMS_TAB) {
|
} else if (this.badgeType === DMS_TAB) {
|
||||||
return this.chatTrackingStateManager.directMessageUnreadCount;
|
return this.chatTrackingStateManager.directMessageUnreadCount;
|
||||||
|
} else if (this.badgeType === THREADS_TAB) {
|
||||||
|
return this.chatTrackingStateManager.watchedThreadsUnreadCount;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,9 @@ export default class Channel extends Component {
|
|||||||
return (
|
return (
|
||||||
this.args.item.model.isDirectMessageChannel ||
|
this.args.item.model.isDirectMessageChannel ||
|
||||||
(this.args.item.model.isCategoryChannel &&
|
(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
|
class={{concatClass
|
||||||
"chat-thread-list-item"
|
"chat-thread-list-item"
|
||||||
(if (gt @thread.tracking.unreadCount 0) "-is-unread")
|
(if (gt @thread.tracking.unreadCount 0) "-is-unread")
|
||||||
|
(if (gt @thread.tracking.watchedThreadsUnreadCount 0) "-is-urgent")
|
||||||
}}
|
}}
|
||||||
data-thread-id={{@thread.id}}
|
data-thread-id={{@thread.id}}
|
||||||
...attributes
|
...attributes
|
||||||
|
@ -1,21 +1,32 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
|
||||||
|
const MAX_UNREAD_COUNT = 99;
|
||||||
|
|
||||||
export default class ChatThreadUnreadIndicator extends Component {
|
export default class ChatThreadUnreadIndicator extends Component {
|
||||||
get unreadCount() {
|
get unreadCount() {
|
||||||
return this.args.thread.tracking.unreadCount;
|
return this.args.thread.tracking.unreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get urgentCount() {
|
||||||
|
return this.args.thread.tracking.watchedThreadsUnreadCount;
|
||||||
|
}
|
||||||
|
|
||||||
get showUnreadIndicator() {
|
get showUnreadIndicator() {
|
||||||
return this.unreadCount > 0;
|
return this.unreadCount > 0 || this.urgentCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unreadCountLabel() {
|
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>
|
<template>
|
||||||
{{#if this.showUnreadIndicator}}
|
{{#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">
|
<span class="chat-thread-list-item-unread-indicator__number">
|
||||||
{{this.unreadCountLabel}}
|
{{this.unreadCountLabel}}
|
||||||
</span>
|
</span>
|
||||||
|
@ -46,6 +46,8 @@ export default class UserThreads extends Component {
|
|||||||
if (tracking) {
|
if (tracking) {
|
||||||
thread.tracking.mentionCount = tracking.mention_count;
|
thread.tracking.mentionCount = tracking.mention_count;
|
||||||
thread.tracking.unreadCount = tracking.unread_count;
|
thread.tracking.unreadCount = tracking.unread_count;
|
||||||
|
thread.tracking.watchedThreadsUnreadCount =
|
||||||
|
tracking.watched_threads_unread_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.trackChannel(thread.channel);
|
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) {
|
if (api.registerUserMenuTab) {
|
||||||
@ -123,7 +160,8 @@ export default {
|
|||||||
get count() {
|
get count() {
|
||||||
return (
|
return (
|
||||||
this.getUnreadCountForType("chat_mention") +
|
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_mention",
|
||||||
"chat_message",
|
"chat_message",
|
||||||
"chat_quoted",
|
"chat_quoted",
|
||||||
|
"chat_watched_thread",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
} from "discourse/lib/notification-levels";
|
} from "discourse/lib/notification-levels";
|
||||||
|
|
||||||
export const threadNotificationButtonLevels = [
|
export const threadNotificationButtonLevels = [
|
||||||
|
NotificationLevels.WATCHING,
|
||||||
NotificationLevels.TRACKING,
|
NotificationLevels.TRACKING,
|
||||||
NotificationLevels.REGULAR,
|
NotificationLevels.REGULAR,
|
||||||
].map(buttonDetails);
|
].map(buttonDetails);
|
||||||
|
@ -114,6 +114,12 @@ export default class ChatChannel {
|
|||||||
return Array.from(this.threadsManager.unreadThreadOverview.values()).length;
|
return Array.from(this.threadsManager.unreadThreadOverview.values()).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get watchedThreadsUnreadCount() {
|
||||||
|
return this.threadsManager.threads.reduce((unreadCount, thread) => {
|
||||||
|
return unreadCount + thread.tracking.watchedThreadsUnreadCount;
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
updateLastViewedAt() {
|
updateLastViewedAt() {
|
||||||
this.currentUserMembership.lastViewedAt = new Date();
|
this.currentUserMembership.lastViewedAt = new Date();
|
||||||
}
|
}
|
||||||
|
@ -7,16 +7,19 @@ export default class ChatTrackingState {
|
|||||||
|
|
||||||
@tracked _unreadCount;
|
@tracked _unreadCount;
|
||||||
@tracked _mentionCount;
|
@tracked _mentionCount;
|
||||||
|
@tracked _watchedThreadsUnreadCount;
|
||||||
|
|
||||||
constructor(owner, params = {}) {
|
constructor(owner, params = {}) {
|
||||||
setOwner(this, owner);
|
setOwner(this, owner);
|
||||||
this._unreadCount = params.unreadCount ?? 0;
|
this._unreadCount = params.unreadCount ?? 0;
|
||||||
this._mentionCount = params.mentionCount ?? 0;
|
this._mentionCount = params.mentionCount ?? 0;
|
||||||
|
this._watchedThreadsUnreadCount = params.watchedThreadsUnreadCount ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this._unreadCount = 0;
|
this._unreadCount = 0;
|
||||||
this._mentionCount = 0;
|
this._mentionCount = 0;
|
||||||
|
this._watchedThreadsUnreadCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get unreadCount() {
|
get unreadCount() {
|
||||||
@ -42,4 +45,16 @@ export default class ChatTrackingState {
|
|||||||
this.chatTrackingStateManager.triggerNotificationsChanged();
|
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 Service, { service } from "@ember/service";
|
||||||
|
import { NotificationLevels } from "discourse/lib/notification-levels";
|
||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
import { CHANNEL_STATUSES } from "discourse/plugins/chat/discourse/models/chat-channel";
|
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.thread_id,
|
||||||
busData.message.created_at
|
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);
|
this._updateActiveLastViewedAt(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -339,6 +350,8 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
|
|
||||||
channel.tracking.unreadCount = busData.unread_count;
|
channel.tracking.unreadCount = busData.unread_count;
|
||||||
channel.tracking.mentionCount = busData.mention_count;
|
channel.tracking.mentionCount = busData.mention_count;
|
||||||
|
channel.tracking.watchedThreadsUnreadCount =
|
||||||
|
busData.watched_threads_unread_count;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
busData.hasOwnProperty("unread_thread_overview") &&
|
busData.hasOwnProperty("unread_thread_overview") &&
|
||||||
@ -366,6 +379,8 @@ export default class ChatSubscriptionsManager extends Service {
|
|||||||
busData.thread_tracking.unread_count;
|
busData.thread_tracking.unread_count;
|
||||||
thread.tracking.mentionCount =
|
thread.tracking.mentionCount =
|
||||||
busData.thread_tracking.mention_count;
|
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() {
|
get allChannelUrgentCount() {
|
||||||
return this.publicChannelMentionCount + this.directMessageUnreadCount;
|
return (
|
||||||
|
this.publicChannelMentionCount +
|
||||||
|
this.directMessageUnreadCount +
|
||||||
|
this.watchedThreadsUnreadCount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get hasUnreadThreads() {
|
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() {
|
willDestroy() {
|
||||||
super.willDestroy(...arguments);
|
super.willDestroy(...arguments);
|
||||||
cancel(this._onTriggerNotificationDebounceHandler);
|
cancel(this._onTriggerNotificationDebounceHandler);
|
||||||
@ -108,6 +118,8 @@ export default class ChatTrackingStateManager extends Service {
|
|||||||
}
|
}
|
||||||
model.tracking.unreadCount = state.unread_count;
|
model.tracking.unreadCount = state.unread_count;
|
||||||
model.tracking.mentionCount = state.mention_count;
|
model.tracking.mentionCount = state.mention_count;
|
||||||
|
model.tracking.watchedThreadsUnreadCount =
|
||||||
|
state.watched_threads_unread_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
get #publicChannels() {
|
get #publicChannels() {
|
||||||
|
@ -144,6 +144,8 @@ export default class Chat extends Service {
|
|||||||
const state = channelsView.tracking.channel_tracking[channel.id];
|
const state = channelsView.tracking.channel_tracking[channel.id];
|
||||||
channel.tracking.unreadCount = state.unread_count;
|
channel.tracking.unreadCount = state.unread_count;
|
||||||
channel.tracking.mentionCount = state.mention_count;
|
channel.tracking.mentionCount = state.mention_count;
|
||||||
|
channel.tracking.watchedThreadsUnreadCount =
|
||||||
|
state.watched_threads_unread_count;
|
||||||
|
|
||||||
channel.currentUserMembership =
|
channel.currentUserMembership =
|
||||||
channelObject.current_user_membership;
|
channelObject.current_user_membership;
|
||||||
|
@ -132,7 +132,7 @@ en:
|
|||||||
header_indicator_preference:
|
header_indicator_preference:
|
||||||
title: "Show activity indicator in header"
|
title: "Show activity indicator in header"
|
||||||
all_new: "All New Messages"
|
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"
|
only_mentions: "Only Mentions"
|
||||||
never: "Never"
|
never: "Never"
|
||||||
separate_sidebar_mode:
|
separate_sidebar_mode:
|
||||||
@ -634,10 +634,13 @@ en:
|
|||||||
notifications:
|
notifications:
|
||||||
regular:
|
regular:
|
||||||
title: "Normal"
|
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:
|
tracking:
|
||||||
title: "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:
|
participants_other_count:
|
||||||
one: "+%{count}"
|
one: "+%{count}"
|
||||||
other: "+%{count}"
|
other: "+%{count}"
|
||||||
@ -664,6 +667,9 @@ en:
|
|||||||
chat_invitation: "invited you to join a chat channel"
|
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_invitation_html: "<span>%{username}</span> <span>invited you to join a chat channel</span>"
|
||||||
chat_quoted: "<span>%{username}</span> %{description}"
|
chat_quoted: "<span>%{username}</span> %{description}"
|
||||||
|
chat_watched_thread_label:
|
||||||
|
one: "%{username} and %{username2}"
|
||||||
|
other: "%{username} and %{count} others"
|
||||||
|
|
||||||
popup:
|
popup:
|
||||||
chat_mention:
|
chat_mention:
|
||||||
@ -685,6 +691,7 @@ en:
|
|||||||
chat_mention: "Chat mention"
|
chat_mention: "Chat mention"
|
||||||
chat_invitation: "Chat invitation"
|
chat_invitation: "Chat invitation"
|
||||||
chat_quoted: "Chat quoted"
|
chat_quoted: "Chat quoted"
|
||||||
|
chat_watched_thread: "Chat watched thread"
|
||||||
action_codes:
|
action_codes:
|
||||||
chat:
|
chat:
|
||||||
enabled: '%{who} enabled <button class="btn-link open-chat">chat</button> %{when}'
|
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
|
Guardian.prepend Chat::GuardianExtensions
|
||||||
UserNotifications.prepend Chat::UserNotificationsExtension
|
UserNotifications.prepend Chat::UserNotificationsExtension
|
||||||
|
Notifications::ConsolidationPlan.prepend Chat::NotificationConsolidationExtension
|
||||||
UserOption.prepend Chat::UserOptionExtension
|
UserOption.prepend Chat::UserOptionExtension
|
||||||
Category.prepend Chat::CategoryExtension
|
Category.prepend Chat::CategoryExtension
|
||||||
Reviewable.prepend Chat::ReviewableExtension
|
Reviewable.prepend Chat::ReviewableExtension
|
||||||
@ -533,6 +534,10 @@ after_initialize do
|
|||||||
Proc.new { |user| Jobs.enqueue(Jobs::Chat::DeleteUserMessages, user_id: user.id) },
|
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)
|
register_bookmarkable(Chat::MessageBookmarkable)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -12,16 +12,21 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||||||
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_job
|
def run_job(message)
|
||||||
described_class.new.execute(chat_message_id: message.id, except_user_ids: except_user_ids)
|
described_class.new.execute(chat_message_id: message.id, except_user_ids: except_user_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def notification_messages_for(user)
|
def notification_messages_for(user, chat_message: message)
|
||||||
MessageBus
|
MessageBus
|
||||||
.track_publish { run_job }
|
.track_publish { run_job(chat_message) }
|
||||||
.filter { |m| m.channel == "/chat/notification-alert/#{user.id}" }
|
.filter { |m| m.channel == "/chat/notification-alert/#{user.id}" }
|
||||||
end
|
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
|
context "for a category channel" do
|
||||||
fab!(:channel) { Fabricate(:category_channel) }
|
fab!(:channel) { Fabricate(:category_channel) }
|
||||||
fab!(:membership1) do
|
fab!(:membership1) do
|
||||||
@ -62,6 +67,77 @@ RSpec.describe Jobs::Chat::NotifyWatching do
|
|||||||
)
|
)
|
||||||
end
|
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
|
context "with chat_notification_translation_args plugin_modifier" do
|
||||||
let(:modifier_block) do
|
let(:modifier_block) do
|
||||||
Proc.new do |args|
|
Proc.new do |args|
|
||||||
|
@ -26,7 +26,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
before { Fabricate(:chat_message, chat_channel: channel_1) }
|
before { Fabricate(:chat_message, chat_channel: channel_1) }
|
||||||
|
|
||||||
it "returns a correct unread count" do
|
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
|
end
|
||||||
|
|
||||||
context "when the membership has been muted" do
|
context "when the membership has been muted" do
|
||||||
@ -38,7 +45,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "returns a zeroed unread count" do
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -47,13 +61,27 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: thread_om) }
|
fab!(:thread) { Fabricate(:chat_thread, channel: channel_1, original_message: thread_om) }
|
||||||
|
|
||||||
it "does include the original message in the unread count" do
|
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
|
end
|
||||||
|
|
||||||
it "does not include other thread messages in the unread count" do
|
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)
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -70,8 +98,18 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
it "returns accurate counts" do
|
it "returns accurate counts" do
|
||||||
expect(query).to match_array(
|
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
|
end
|
||||||
@ -86,7 +124,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
|
|
||||||
it "does not return counts for the channels" do
|
it "does not return counts for the channels" do
|
||||||
expect(query).to match_array(
|
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
|
||||||
|
|
||||||
@ -96,8 +141,18 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
it "does return zeroed counts for the channels" do
|
it "does return zeroed counts for the channels" do
|
||||||
expect(query).to match_array(
|
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
|
end
|
||||||
@ -107,7 +162,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
|
|
||||||
it "does not return counts for the channels" do
|
it "does not return counts for the channels" do
|
||||||
expect(query).to match_array(
|
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
|
||||||
end
|
end
|
||||||
@ -137,7 +199,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
message = Fabricate(:chat_message, chat_channel: channel_1)
|
message = Fabricate(:chat_message, chat_channel: channel_1)
|
||||||
create_mention(message, 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
|
end
|
||||||
|
|
||||||
context "for unread mentions in a thread" do
|
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
|
it "does include the original message in the mention count" do
|
||||||
create_mention(thread_om, channel_1)
|
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
|
end
|
||||||
|
|
||||||
it "does not include other thread messages in the mention count" do
|
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)
|
thread_message_2 = Fabricate(:chat_message, chat_channel: channel_1, thread: thread)
|
||||||
create_mention(thread_message_1, channel_1)
|
create_mention(thread_message_1, channel_1)
|
||||||
create_mention(thread_message_2, 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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -173,8 +256,93 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
|
|
||||||
expect(query).to match_array(
|
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
|
end
|
||||||
@ -183,7 +351,14 @@ describe Chat::ChannelUnreadsQuery do
|
|||||||
|
|
||||||
context "with nothing unread" do
|
context "with nothing unread" do
|
||||||
it "returns a correct state" 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
|
end
|
||||||
|
|
||||||
context "when include_read is false" do
|
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
|
it "gets a count of all the thread unreads across the channels" do
|
||||||
expect(query.map(&:to_h)).to match_array(
|
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_1.id,
|
||||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_3.id, unread_count: 1 },
|
mention_count: 0,
|
||||||
{ channel_id: channel_2.id, mention_count: 0, thread_id: thread_4.id, unread_count: 1 },
|
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
|
end
|
||||||
@ -58,7 +82,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
it "does not count deleted messages" do
|
it "does not count deleted messages" do
|
||||||
message_1.trash!
|
message_1.trash!
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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
|
end
|
||||||
|
|
||||||
@ -66,17 +96,35 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
channel_1.membership_for(current_user).update!(muted: true)
|
channel_1.membership_for(current_user).update!(muted: true)
|
||||||
|
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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
|
end
|
||||||
|
|
||||||
it "does not messages in threads where threading_enabled is false on the channel" do
|
it "does not messages in threads where threading_enabled is false on the channel" do
|
||||||
channel_1.update!(threading_enabled: false)
|
channel_1.update!(threading_enabled: false)
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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(
|
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
|
end
|
||||||
|
|
||||||
@ -86,7 +134,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
.find_by(user: current_user)
|
.find_by(user: current_user)
|
||||||
.update!(last_read_message_id: message_1.id)
|
.update!(last_read_message_id: message_1.id)
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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
|
end
|
||||||
|
|
||||||
@ -94,14 +148,26 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
thread_1.original_message.destroy
|
thread_1.original_message.destroy
|
||||||
thread_1.update!(original_message: message_1)
|
thread_1.update!(original_message: message_1)
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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
|
end
|
||||||
|
|
||||||
it "does not count the thread as unread if the original message is deleted" do
|
it "does not count the thread as unread if the original message is deleted" do
|
||||||
thread_1.original_message.destroy
|
thread_1.original_message.destroy
|
||||||
expect(query.map(&:to_h).find { |tracking| tracking[:thread_id] == thread_1.id }).to eq(
|
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
|
end
|
||||||
|
|
||||||
@ -116,6 +182,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
thread_id: thread_2.id,
|
thread_id: thread_2.id,
|
||||||
unread_count: 0,
|
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
|
it "gets a count of all the thread unreads for the specified threads" do
|
||||||
expect(query.map(&:to_h)).to match_array(
|
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
|
||||||
@ -145,7 +224,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
|
|
||||||
it "gets a zeroed out count for the thread" do
|
it "gets a zeroed out count for the thread" do
|
||||||
expect(query.map(&:to_h)).to include(
|
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
|
||||||
end
|
end
|
||||||
@ -160,7 +245,13 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
|
|
||||||
it "gets a zeroed out count for the thread" do
|
it "gets a zeroed out count for the thread" do
|
||||||
expect(query.map(&:to_h)).to include(
|
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
|
||||||
end
|
end
|
||||||
@ -176,6 +267,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
thread_id: thread_3.id,
|
thread_id: thread_3.id,
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -192,12 +284,14 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
thread_id: thread_1.id,
|
thread_id: thread_1.id,
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
thread_id: thread_3.id,
|
thread_id: thread_3.id,
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -214,6 +308,7 @@ describe Chat::ThreadUnreadsQuery do
|
|||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
thread_id: thread_3.id,
|
thread_id: thread_3.id,
|
||||||
unread_count: 1,
|
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
|
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(
|
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
|
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
|
end
|
||||||
|
@ -62,10 +62,12 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
channel_2.id => {
|
channel_2.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -113,10 +115,12 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
channel_2.id => {
|
channel_2.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -125,11 +129,13 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||||||
thread_1.id => {
|
thread_1.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
},
|
},
|
||||||
thread_2.id => {
|
thread_2.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -152,12 +158,14 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||||||
thread_1.id => {
|
thread_1.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
last_reply_created_at: thread_1.reload.last_message.created_at,
|
last_reply_created_at: thread_1.reload.last_message.created_at,
|
||||||
},
|
},
|
||||||
thread_2.id => {
|
thread_2.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
last_reply_created_at: thread_2.reload.last_message.created_at,
|
||||||
},
|
},
|
||||||
@ -172,12 +180,14 @@ RSpec.describe Chat::TrackingStateReportQuery do
|
|||||||
thread_1.id => {
|
thread_1.id => {
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
last_reply_created_at: nil,
|
last_reply_created_at: nil,
|
||||||
},
|
},
|
||||||
thread_2.id => {
|
thread_2.id => {
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
last_reply_created_at: thread_2.reload.last_message.created_at,
|
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)
|
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
||||||
|
|
||||||
expect(result.tracking.thread_tracking).to eq(
|
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
|
end
|
||||||
|
|
||||||
@ -193,7 +200,14 @@ RSpec.describe Chat::ListChannelMessages do
|
|||||||
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
Fabricate(:chat_message, chat_channel: channel, thread: thread_1)
|
||||||
|
|
||||||
expect(result.tracking.thread_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
|
||||||
end
|
end
|
||||||
@ -214,7 +228,14 @@ RSpec.describe Chat::ListChannelMessages do
|
|||||||
|
|
||||||
expect(result.tracking.channel_tracking).to eq({})
|
expect(result.tracking.channel_tracking).to eq({})
|
||||||
expect(result.tracking.thread_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
|
||||||
end
|
end
|
||||||
|
@ -20,7 +20,7 @@ RSpec.describe Chat::ListUserChannels do
|
|||||||
expect(result.structured[:public_channels]).to eq([channel_1])
|
expect(result.structured[:public_channels]).to eq([channel_1])
|
||||||
expect(result.structured[:direct_message_channels]).to eq([])
|
expect(result.structured[:direct_message_channels]).to eq([])
|
||||||
expect(result.structured[:tracking].channel_tracking[channel_1.id]).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
|
end
|
||||||
|
|
||||||
|
@ -143,6 +143,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||||||
"membership_id" => membership_1.id,
|
"membership_id" => membership_1.id,
|
||||||
"mention_count" => 0,
|
"mention_count" => 0,
|
||||||
"unread_count" => 0,
|
"unread_count" => 0,
|
||||||
|
"watched_threads_unread_count" => 0,
|
||||||
},
|
},
|
||||||
channel_2.id.to_s => {
|
channel_2.id.to_s => {
|
||||||
"last_read_message_id" => message_4.id,
|
"last_read_message_id" => message_4.id,
|
||||||
@ -150,6 +151,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||||||
"membership_id" => membership_2.id,
|
"membership_id" => membership_2.id,
|
||||||
"mention_count" => 0,
|
"mention_count" => 0,
|
||||||
"unread_count" => 0,
|
"unread_count" => 0,
|
||||||
|
"watched_threads_unread_count" => 0,
|
||||||
},
|
},
|
||||||
channel_3.id.to_s => {
|
channel_3.id.to_s => {
|
||||||
"last_read_message_id" => message_6.id,
|
"last_read_message_id" => message_6.id,
|
||||||
@ -157,6 +159,7 @@ RSpec.describe Chat::MarkAllUserChannelsRead do
|
|||||||
"membership_id" => membership_3.id,
|
"membership_id" => membership_3.id,
|
||||||
"mention_count" => 0,
|
"mention_count" => 0,
|
||||||
"unread_count" => 0,
|
"unread_count" => 0,
|
||||||
|
"watched_threads_unread_count" => 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -109,7 +109,12 @@ describe Chat::Publisher do
|
|||||||
{ thread.id.to_s => thread.reload.last_message.created_at.iso8601(3) },
|
{ thread.id.to_s => thread.reload.last_message.created_at.iso8601(3) },
|
||||||
)
|
)
|
||||||
expect(data["thread_tracking"]).to eq(
|
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
|
||||||
end
|
end
|
||||||
@ -119,7 +124,12 @@ describe Chat::Publisher do
|
|||||||
expect(data["thread_id"]).to eq(thread.id)
|
expect(data["thread_id"]).to eq(thread.id)
|
||||||
expect(data["unread_thread_overview"]).to eq({})
|
expect(data["unread_thread_overview"]).to eq({})
|
||||||
expect(data["thread_tracking"]).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
|
||||||
end
|
end
|
||||||
|
@ -44,6 +44,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 4, # 2 messages + 2 thread original messages
|
unread_count: 4, # 2 messages + 2 thread original messages
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -55,11 +56,13 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
thread_2.id => {
|
thread_2.id => {
|
||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
unread_count: 2,
|
unread_count: 2,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -74,6 +77,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 4, # 2 messages + 2 thread original messages
|
unread_count: 4, # 2 messages + 2 thread original messages
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -89,6 +93,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 4, # 2 messages + 2 thread original messages
|
unread_count: 4, # 2 messages + 2 thread original messages
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -100,6 +105,7 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
unread_count: 2,
|
unread_count: 2,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -117,10 +123,12 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_1.id => {
|
channel_1.id => {
|
||||||
unread_count: 4, # 2 messages + 2 thread original messages
|
unread_count: 4, # 2 messages + 2 thread original messages
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
channel_2.id => {
|
channel_2.id => {
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
@ -132,21 +140,25 @@ RSpec.describe ::Chat::TrackingState do
|
|||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
unread_count: 1,
|
unread_count: 1,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
thread_2.id => {
|
thread_2.id => {
|
||||||
channel_id: channel_1.id,
|
channel_id: channel_1.id,
|
||||||
unread_count: 2,
|
unread_count: 2,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
thread_3.id => {
|
thread_3.id => {
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
thread_4.id => {
|
thread_4.id => {
|
||||||
channel_id: channel_2.id,
|
channel_id: channel_2.id,
|
||||||
unread_count: 0,
|
unread_count: 0,
|
||||||
mention_count: 0,
|
mention_count: 0,
|
||||||
|
watched_threads_unread_count: 0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
end
|
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")
|
expect(page).to have_no_css("#c-footer-threads .c-unread-indicator")
|
||||||
end
|
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
|
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}']"
|
".chat-thread-list-item__last-reply-timestamp .relative-date[data-time='#{(last_reply.created_at.iso8601.to_time.to_f * 1000).to_i}']"
|
||||||
end
|
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?
|
if count.nil?
|
||||||
component.has_css?(item_by_id_selector(id) + ".-is-unread")
|
component.has_css?(item_by_id_selector(id) + selector_class)
|
||||||
else
|
else
|
||||||
component.has_css?(
|
component.has_css?(
|
||||||
item_by_id_selector(id) +
|
item_by_id_selector(id) + selector_class +
|
||||||
".-is-unread .chat-thread-list-item-unread-indicator__number",
|
" .chat-thread-list-item-unread-indicator__number",
|
||||||
text: count.to_s,
|
text: count.to_s,
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_no_unread_item?(id)
|
def has_no_unread_item?(id, urgent: false)
|
||||||
component.has_no_css?(item_by_id_selector(id) + ".-is-unread")
|
selector_class = urgent ? ".-is-urgent" : ".-is-unread"
|
||||||
|
component.has_no_css?(item_by_id_selector(id) + selector_class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def item_by_id_selector(id)
|
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)
|
expect(thread_list_page).to have_unread_item(thread.id)
|
||||||
end
|
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
|
it "marks the thread as read and removes both indicators when the user opens it" do
|
||||||
skip("Flaky on CI") if ENV["CI"]
|
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)
|
expect(thread_list_page).to have_no_unread_item(thread.id)
|
||||||
end
|
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
|
it "allows the user to change their tracking level for an existing thread" do
|
||||||
chat_page.visit_thread(thread)
|
chat_page.visit_thread(thread)
|
||||||
thread_page.notification_level = :normal
|
thread_page.notification_level = :normal
|
||||||
|
@ -119,6 +119,9 @@
|
|||||||
"chat_quoted": {
|
"chat_quoted": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"chat_watched_thread": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"assigned": {
|
"assigned": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user