FIX: unread notification counts
This commit is contained in:
parent
f4108702c8
commit
9bba8ef0a0
|
@ -51,35 +51,37 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList {
|
|||
}
|
||||
|
||||
async fetchItems() {
|
||||
const data = await ajax(
|
||||
`/u/${this.currentUser.username}/user-menu-bookmarks`
|
||||
);
|
||||
const content = [];
|
||||
|
||||
const { currentUser, siteSettings, site } = this;
|
||||
const { username } = currentUser;
|
||||
|
||||
const data = await ajax(`/u/${username}/user-menu-bookmarks`);
|
||||
|
||||
const notifications = data.notifications.map((n) => Notification.create(n));
|
||||
await Notification.applyTransformations(notifications);
|
||||
notifications.forEach((notification) => {
|
||||
content.push(
|
||||
new UserMenuNotificationItem({
|
||||
notification,
|
||||
currentUser: this.currentUser,
|
||||
siteSettings: this.siteSettings,
|
||||
site: this.site,
|
||||
currentUser,
|
||||
siteSettings,
|
||||
site,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
const bookmarks = data.bookmarks.map((b) => Bookmark.create(b));
|
||||
await Bookmark.applyTransformations(bookmarks);
|
||||
content.push(
|
||||
...bookmarks.map((bookmark) => {
|
||||
return new UserMenuBookmarkItem({
|
||||
bookmarks.forEach((bookmark) => {
|
||||
content.push(
|
||||
new UserMenuBookmarkItem({
|
||||
bookmark,
|
||||
siteSettings: this.siteSettings,
|
||||
site: this.site,
|
||||
});
|
||||
})
|
||||
);
|
||||
siteSettings,
|
||||
site,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
|
|
@ -390,8 +390,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
if notifications.present?
|
||||
notification_ids = notifications.split(",").map(&:to_i)
|
||||
Notification.read(current_user, notification_ids)
|
||||
Notification.read!(current_user, ids: notifications.split(",").map(&:to_i))
|
||||
current_user.reload
|
||||
current_user.publish_notifications_state
|
||||
cookie_args = {}
|
||||
|
|
|
@ -9,10 +9,8 @@ class NotificationsController < ApplicationController
|
|||
|
||||
def index
|
||||
user =
|
||||
if params[:username] && !params[:recent]
|
||||
user_record = User.find_by(username: params[:username].to_s)
|
||||
raise Discourse::NotFound if !user_record
|
||||
user_record
|
||||
if params[:username].present? && params[:recent].blank?
|
||||
User.find_by_username(params[:username].to_s) || (raise Discourse::NotFound)
|
||||
else
|
||||
current_user
|
||||
end
|
||||
|
@ -29,37 +27,31 @@ class NotificationsController < ApplicationController
|
|||
if params[:recent].present?
|
||||
limit = fetch_limit_from_params(default: 15, max: INDEX_LIMIT)
|
||||
|
||||
include_reviewables = false
|
||||
|
||||
notifications =
|
||||
Notification.prioritized_list(current_user, count: limit, types: notification_types)
|
||||
# notification_types is blank for the "all notifications" user menu tab
|
||||
include_reviewables = notification_types.blank? && guardian.can_see_review_queue?
|
||||
|
||||
if notifications.present? && !(params.has_key?(:silent) || @readonly_mode)
|
||||
if current_user.bump_last_seen_notification!
|
||||
current_user.reload
|
||||
current_user.publish_notifications_state
|
||||
end
|
||||
end
|
||||
|
||||
if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode &&
|
||||
include_reviewables
|
||||
current_user_id = current_user.id
|
||||
Scheduler::Defer.later "bump last seen reviewable for user" do
|
||||
# we lookup current_user again in the background thread to avoid
|
||||
# concurrency issues where the user object returned by the
|
||||
# current_user controller method is changed by the time the deferred
|
||||
# block is executed
|
||||
User.find_by(id: current_user_id)&.bump_last_seen_reviewable!
|
||||
end
|
||||
end
|
||||
|
||||
notifications =
|
||||
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
||||
|
||||
notifications =
|
||||
Notification.populate_acting_user(notifications) if SiteSetting.show_user_menu_avatars
|
||||
|
||||
include_reviewables = notification_types.blank? && guardian.can_see_review_queue?
|
||||
bump_notification = notifications.present?
|
||||
bump_reviewable = include_reviewables && params[:bump_last_seen_reviewable]
|
||||
|
||||
if !params.has_key?(:silent) && !@readonly_mode
|
||||
if bump_notification || bump_reviewable
|
||||
current_user_id = current_user.id
|
||||
Scheduler::Defer.later "bump last seen notification/reviewable for user" do
|
||||
if user = User.find_by(id: current_user_id)
|
||||
user.bump_last_seen_notification! if bump_notification
|
||||
user.bump_last_seen_reviewable! if bump_reviewable
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
json = {
|
||||
notifications: serialize_data(notifications, NotificationSerializer),
|
||||
seen_notification_id: current_user.seen_notification_id,
|
||||
|
@ -77,19 +69,20 @@ class NotificationsController < ApplicationController
|
|||
limit = fetch_limit_from_params(default: INDEX_LIMIT, max: INDEX_LIMIT)
|
||||
offset = params[:offset].to_i
|
||||
|
||||
notifications =
|
||||
Notification.where(user_id: user.id).visible.includes(:topic).order(created_at: :desc)
|
||||
|
||||
notifications = notifications.where(read: true) if params[:filter] == "read"
|
||||
|
||||
notifications = notifications.where(read: false) if params[:filter] == "unread"
|
||||
notifications = user.notifications.visible.includes(:topic).order(created_at: :desc)
|
||||
notifications = notifications.read if params[:filter] == "read"
|
||||
notifications = notifications.unread if params[:filter] == "unread"
|
||||
|
||||
total_rows = notifications.dup.count
|
||||
|
||||
notifications = notifications.offset(offset).limit(limit)
|
||||
|
||||
notifications =
|
||||
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
||||
|
||||
notifications =
|
||||
Notification.populate_acting_user(notifications) if SiteSetting.show_user_menu_avatars
|
||||
|
||||
render_json_dump(
|
||||
notifications: serialize_data(notifications, NotificationSerializer),
|
||||
total_rows_notifications: total_rows,
|
||||
|
@ -106,27 +99,20 @@ class NotificationsController < ApplicationController
|
|||
end
|
||||
|
||||
def mark_read
|
||||
if params[:id]
|
||||
Notification.read(current_user, [params[:id].to_i])
|
||||
if id = params[:id]
|
||||
Notification.read!(current_user, id:)
|
||||
else
|
||||
if types = params[:dismiss_types]&.split(",").presence
|
||||
invalid = []
|
||||
types.map! do |type|
|
||||
type_id = Notification.types[type.to_sym]
|
||||
invalid << type if !type_id
|
||||
type_id
|
||||
end
|
||||
if invalid.size > 0
|
||||
raise Discourse::InvalidParameters.new("invalid notification types: #{invalid.inspect}")
|
||||
Notification.types[type.to_sym] ||
|
||||
(raise Discourse::InvalidParameters.new("invalid notification type: #{type}"))
|
||||
end
|
||||
end
|
||||
|
||||
Notification.read_types(current_user, types)
|
||||
current_user.bump_last_seen_notification!
|
||||
Notification.read!(current_user, types:)
|
||||
end
|
||||
|
||||
current_user.reload
|
||||
current_user.publish_notifications_state
|
||||
current_user.bump_last_seen_notification!
|
||||
|
||||
render json: success_json
|
||||
end
|
||||
|
|
|
@ -1879,55 +1879,40 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
USER_MENU_LIST_LIMIT = 20
|
||||
|
||||
def user_menu_bookmarks
|
||||
if !current_user.username_equals_to?(params[:username])
|
||||
raise Discourse::InvalidAccess.new("username doesn't match current_user's username")
|
||||
end
|
||||
|
||||
reminder_notifications =
|
||||
BookmarkQuery.new(user: current_user).unread_notifications(limit: USER_MENU_LIST_LIMIT)
|
||||
if reminder_notifications.size < USER_MENU_LIST_LIMIT
|
||||
exclude_bookmark_ids =
|
||||
reminder_notifications.filter_map { |notification| notification.data_hash[:bookmark_id] }
|
||||
reminders =
|
||||
current_user
|
||||
.notifications
|
||||
.for_user_menu(USER_MENU_LIST_LIMIT)
|
||||
.unread
|
||||
.where(notification_type: Notification.types[:bookmark_reminder])
|
||||
|
||||
bookmark_list =
|
||||
UserBookmarkList.new(
|
||||
user: current_user,
|
||||
guardian: guardian,
|
||||
per_page: USER_MENU_LIST_LIMIT - reminder_notifications.size,
|
||||
)
|
||||
reminders =
|
||||
Notification.filter_inaccessible_topic_notifications(current_user.guardian, reminders)
|
||||
|
||||
bookmark_list.load do |query|
|
||||
if exclude_bookmark_ids.present?
|
||||
query.where("bookmarks.id NOT IN (?)", exclude_bookmark_ids)
|
||||
end
|
||||
end
|
||||
end
|
||||
reminders = Notification.populate_acting_user(reminders) if SiteSetting.show_user_menu_avatars
|
||||
|
||||
if reminder_notifications.present?
|
||||
if SiteSetting.show_user_menu_avatars
|
||||
Notification.populate_acting_user(reminder_notifications)
|
||||
end
|
||||
serialized_notifications =
|
||||
ActiveModel::ArraySerializer.new(
|
||||
reminder_notifications,
|
||||
each_serializer: NotificationSerializer,
|
||||
scope: guardian,
|
||||
)
|
||||
end
|
||||
bookmark_list = []
|
||||
|
||||
if bookmark_list
|
||||
if reminders.count < USER_MENU_LIST_LIMIT
|
||||
reminded_bookmark_ids = reminders.filter_map { _1.data_hash[:bookmark_id] }
|
||||
per_page = USER_MENU_LIST_LIMIT - reminders.count
|
||||
bookmark_list = UserBookmarkList.new(user: current_user, guardian:, per_page:)
|
||||
bookmark_list.bookmark_serializer_opts = { link_to_first_unread_post: true }
|
||||
serialized_bookmarks =
|
||||
serialize_data(bookmark_list, UserBookmarkListSerializer, scope: guardian, root: false)[
|
||||
:bookmarks
|
||||
]
|
||||
bookmark_list.load do |query|
|
||||
query.where.not(id: reminded_bookmark_ids) if reminded_bookmark_ids.present?
|
||||
end
|
||||
end
|
||||
|
||||
render json: {
|
||||
notifications: serialized_notifications || [],
|
||||
bookmarks: serialized_bookmarks || [],
|
||||
}
|
||||
notifications = serialize_data(reminders, NotificationSerializer)
|
||||
bookmarks = serialize_data(bookmark_list, UserBookmarkListSerializer, root: false)[:bookmarks]
|
||||
|
||||
render json: { notifications:, bookmarks: }
|
||||
end
|
||||
|
||||
def user_menu_messages
|
||||
|
@ -1941,8 +1926,9 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
unread_notifications =
|
||||
Notification
|
||||
.for_user_menu(current_user.id, limit: USER_MENU_LIST_LIMIT)
|
||||
current_user
|
||||
.notifications
|
||||
.for_user_menu(USER_MENU_LIST_LIMIT)
|
||||
.unread
|
||||
.where(
|
||||
notification_type: [
|
||||
|
@ -1962,18 +1948,14 @@ class UsersController < ApplicationController
|
|||
.list_private_messages_direct_and_groups(
|
||||
current_user,
|
||||
groups_messages_notification_level: :watching,
|
||||
) do |query|
|
||||
if exclude_topic_ids.present?
|
||||
query.where("topics.id NOT IN (?)", exclude_topic_ids)
|
||||
else
|
||||
query
|
||||
end
|
||||
end
|
||||
) { |query| query.where.not(id: exclude_topic_ids) if exclude_topic_ids.present? }
|
||||
|
||||
read_notifications =
|
||||
Notification
|
||||
.for_user_menu(current_user.id, limit: limit)
|
||||
.where(read: true, notification_type: Notification.types[:group_message_summary])
|
||||
current_user
|
||||
.notifications
|
||||
.for_user_menu(limit)
|
||||
.read
|
||||
.where(notification_type: Notification.types[:group_message_summary])
|
||||
.to_a
|
||||
end
|
||||
|
||||
|
|
|
@ -309,7 +309,11 @@ class UserNotifications < ActionMailer::Base
|
|||
end
|
||||
|
||||
if @counts.size < 3
|
||||
value = user.unread_notifications_of_type(Notification.types[:liked], since: @since)
|
||||
value =
|
||||
user.unread_notifications_count(
|
||||
notification_type: Notification.types[:liked],
|
||||
since: @since,
|
||||
)
|
||||
if value > 0
|
||||
@counts << {
|
||||
id: "likes_received",
|
||||
|
|
|
@ -14,49 +14,26 @@ class Notification < ActiveRecord::Base
|
|||
validates_presence_of :data
|
||||
validates_presence_of :notification_type
|
||||
|
||||
scope :unread, lambda { where(read: false) }
|
||||
scope :recent,
|
||||
lambda { |n = nil|
|
||||
n ||= 10
|
||||
order("notifications.created_at desc").limit(n)
|
||||
}
|
||||
scope :read, -> { where(read: true) }
|
||||
scope :unread, -> { where(read: false) }
|
||||
scope :visible,
|
||||
lambda {
|
||||
-> do
|
||||
joins("LEFT JOIN topics ON notifications.topic_id = topics.id").where(
|
||||
"topics.id IS NULL OR topics.deleted_at IS NULL",
|
||||
"notifications.topic_id IS NULL OR (topics.id IS NOT NULL AND topics.deleted_at IS NULL)",
|
||||
)
|
||||
}
|
||||
scope :unread_type, ->(user, type, limit = 30) { unread_types(user, [type], limit) }
|
||||
scope :unread_types,
|
||||
->(user, types, limit = 30) do
|
||||
where(user_id: user.id, read: false, notification_type: types)
|
||||
.visible
|
||||
.includes(:topic)
|
||||
.limit(limit)
|
||||
end
|
||||
scope :prioritized,
|
||||
->(deprioritized_types = []) do
|
||||
scope = order("notifications.high_priority AND NOT notifications.read DESC")
|
||||
low_pri =
|
||||
"AND notifications.notification_type NOT IN (#{deprioritized_types.join(",")})" if deprioritized_types.present?
|
||||
|
||||
if deprioritized_types.present?
|
||||
scope =
|
||||
scope.order(
|
||||
DB.sql_fragment(
|
||||
"NOT notifications.read AND notifications.notification_type NOT IN (?) DESC",
|
||||
deprioritized_types,
|
||||
),
|
||||
)
|
||||
else
|
||||
scope = scope.order("NOT notifications.read DESC")
|
||||
end
|
||||
|
||||
scope.order("notifications.created_at DESC")
|
||||
end
|
||||
|
||||
scope :for_user_menu,
|
||||
->(user_id, limit: 30) do
|
||||
where(user_id: user_id).visible.prioritized.includes(:topic).limit(limit)
|
||||
order <<~SQL
|
||||
NOT notifications.read AND notifications.high_priority DESC,
|
||||
NOT notifications.read #{low_pri || ""} DESC,
|
||||
notifications.created_at DESC
|
||||
SQL
|
||||
end
|
||||
scope :for_user_menu, ->(limit) { visible.prioritized.includes(:topic).limit(limit) }
|
||||
|
||||
attr_accessor :skip_send_email
|
||||
|
||||
|
@ -68,8 +45,7 @@ class Notification < ActiveRecord::Base
|
|||
before_create do
|
||||
# if we have manually set the notification to high_priority on create then
|
||||
# make sure that is respected
|
||||
self.high_priority =
|
||||
self.high_priority || Notification.high_priority_types.include?(self.notification_type)
|
||||
self.high_priority ||= Notification.high_priority_types.include?(self.notification_type)
|
||||
end
|
||||
|
||||
def self.consolidate_or_create!(notification_params)
|
||||
|
@ -172,71 +148,31 @@ class Notification < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.normal_priority_types
|
||||
@normal_priority_types ||= types.reject { |_k, v| high_priority_types.include?(v) }.values
|
||||
@normal_priority_types ||= types.values - high_priority_types
|
||||
end
|
||||
|
||||
def self.mark_posts_read(user, topic_id, post_numbers)
|
||||
Notification.where(
|
||||
user_id: user.id,
|
||||
topic_id: topic_id,
|
||||
post_number: post_numbers,
|
||||
read: false,
|
||||
).update_all(read: true)
|
||||
end
|
||||
def self.read!(user, id: nil, ids: nil, types: nil, topic_id: nil, post_numbers: nil)
|
||||
query = user.notifications.unread
|
||||
query = query.where(id: id) if id.present?
|
||||
query = query.where(id: ids) if ids.present?
|
||||
query = query.where(notification_type: types) if types.present?
|
||||
|
||||
def self.read(user, notification_ids)
|
||||
Notification.where(id: notification_ids, user_id: user.id, read: false).update_all(read: true)
|
||||
end
|
||||
if topic_id.present? && post_numbers.present?
|
||||
query = query.where(topic_id: topic_id, post_number: post_numbers)
|
||||
end
|
||||
|
||||
def self.read_types(user, types = nil)
|
||||
query = Notification.where(user_id: user.id, read: false)
|
||||
query = query.where(notification_type: types) if types
|
||||
query.update_all(read: true)
|
||||
end
|
||||
|
||||
def self.interesting_after(min_date)
|
||||
result =
|
||||
where("created_at > ?", min_date)
|
||||
.includes(:topic)
|
||||
.visible
|
||||
.unread
|
||||
.limit(20)
|
||||
.order(
|
||||
"CASE WHEN notification_type = #{Notification.types[:replied]} THEN 1
|
||||
WHEN notification_type = #{Notification.types[:mentioned]} THEN 2
|
||||
ELSE 3
|
||||
END, created_at DESC",
|
||||
)
|
||||
.to_a
|
||||
|
||||
# Remove any duplicates by type and topic
|
||||
if result.present?
|
||||
seen = {}
|
||||
to_remove = Set.new
|
||||
|
||||
result.each do |r|
|
||||
seen[r.notification_type] ||= Set.new
|
||||
if seen[r.notification_type].include?(r.topic_id)
|
||||
to_remove << r.id
|
||||
else
|
||||
seen[r.notification_type] << r.topic_id
|
||||
end
|
||||
end
|
||||
result.reject! { |r| to_remove.include?(r.id) }
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
# Clean up any notifications the user can no longer see. For example, if a topic was previously
|
||||
# public then turns private.
|
||||
# Clean up any notifications the user can no longer see.
|
||||
# For example, if a topic was previously public then turns private.
|
||||
def self.remove_for(user_id, topic_id)
|
||||
Notification.where(user_id: user_id, topic_id: topic_id).delete_all
|
||||
end
|
||||
|
||||
def self.filter_inaccessible_topic_notifications(guardian, notifications)
|
||||
topic_ids = notifications.map { |n| n.topic_id }.compact.uniq
|
||||
accessible_topic_ids = guardian.can_see_topic_ids(topic_ids: topic_ids)
|
||||
topic_ids = notifications.map(&:topic_id).compact.uniq
|
||||
accessible_topic_ids = guardian.can_see_topic_ids(topic_ids:)
|
||||
notifications.select { |n| n.topic_id.blank? || accessible_topic_ids.include?(n.topic_id) }
|
||||
end
|
||||
|
||||
|
@ -266,114 +202,43 @@ class Notification < ActiveRecord::Base
|
|||
# `Notification.prioritized_list` to deprioritize like typed notifications. Also See
|
||||
# `db/migrate/20240306063428_add_indexes_to_notifications.rb`.
|
||||
def self.like_types
|
||||
[
|
||||
@@like_types ||= [
|
||||
Notification.types[:liked],
|
||||
Notification.types[:liked_consolidated],
|
||||
Notification.types[:reaction],
|
||||
]
|
||||
end
|
||||
|
||||
def self.prioritized_list(user, count: 30, types: [])
|
||||
return [] if !user&.user_option
|
||||
def self.never = UserOption.like_notification_frequency_type[:never]
|
||||
|
||||
notifications =
|
||||
user
|
||||
.notifications
|
||||
.includes(:topic)
|
||||
.visible
|
||||
.prioritized(types.present? ? [] : like_types)
|
||||
.limit(count)
|
||||
def self.prioritized_list(user, count: 30, types: [])
|
||||
return Notification.none unless user&.user_option
|
||||
|
||||
notifications = user.notifications.includes(:topic).visible.limit(count)
|
||||
|
||||
if types.present?
|
||||
notifications = notifications.where(notification_type: types)
|
||||
elsif user.user_option.like_notification_frequency ==
|
||||
UserOption.like_notification_frequency_type[:never]
|
||||
like_types.each do |notification_type|
|
||||
notifications = notifications.where("notification_type <> ?", notification_type)
|
||||
end
|
||||
end
|
||||
notifications.to_a
|
||||
end
|
||||
|
||||
def self.recent_report(user, count = nil, types = [])
|
||||
return unless user && user.user_option
|
||||
|
||||
count ||= 10
|
||||
notifications = user.notifications.visible.recent(count).includes(:topic)
|
||||
|
||||
notifications = notifications.where(notification_type: types) if types.present?
|
||||
if user.user_option.like_notification_frequency ==
|
||||
UserOption.like_notification_frequency_type[:never]
|
||||
[
|
||||
Notification.types[:liked],
|
||||
Notification.types[:liked_consolidated],
|
||||
].each do |notification_type|
|
||||
notifications = notifications.where("notification_type <> ?", notification_type)
|
||||
end
|
||||
end
|
||||
|
||||
notifications = notifications.to_a
|
||||
|
||||
if notifications.present?
|
||||
builder = DB.build(<<~SQL)
|
||||
SELECT n.id FROM notifications n
|
||||
/*where*/
|
||||
ORDER BY n.id ASC
|
||||
/*limit*/
|
||||
SQL
|
||||
|
||||
builder.where(<<~SQL, user_id: user.id)
|
||||
n.high_priority = TRUE AND
|
||||
n.user_id = :user_id AND
|
||||
NOT read
|
||||
SQL
|
||||
builder.where("notification_type IN (:types)", types: types) if types.present?
|
||||
builder.limit(count.to_i)
|
||||
|
||||
ids = builder.query_single
|
||||
|
||||
if ids.length > 0
|
||||
notifications +=
|
||||
user
|
||||
.notifications
|
||||
.order("notifications.created_at DESC")
|
||||
.where(id: ids)
|
||||
.joins(:topic)
|
||||
.limit(count)
|
||||
end
|
||||
|
||||
notifications
|
||||
.uniq(&:id)
|
||||
.sort do |x, y|
|
||||
if x.unread_high_priority? && !y.unread_high_priority?
|
||||
-1
|
||||
elsif y.unread_high_priority? && !x.unread_high_priority?
|
||||
1
|
||||
else
|
||||
y.created_at <=> x.created_at
|
||||
end
|
||||
end
|
||||
.take(count)
|
||||
notifications = notifications.prioritized.where(notification_type: types)
|
||||
else
|
||||
[]
|
||||
notifications = notifications.prioritized(like_types)
|
||||
if user.user_option.like_notification_frequency == never
|
||||
notifications = notifications.where.not(notification_type: like_types)
|
||||
end
|
||||
end
|
||||
|
||||
notifications
|
||||
end
|
||||
|
||||
USERNAME_FIELDS ||= %i[username display_username mentioned_by_username invited_by_username]
|
||||
|
||||
def self.populate_acting_user(notifications)
|
||||
usernames =
|
||||
notifications.map do |notification|
|
||||
notification.acting_username =
|
||||
(
|
||||
notification.data_hash[:username] || notification.data_hash[:display_username] ||
|
||||
notification.data_hash[:mentioned_by_username] ||
|
||||
notification.data_hash[:invited_by_username]
|
||||
)&.downcase
|
||||
notifications.filter_map do |n|
|
||||
n.acting_username = n.data_hash.values_at(*USERNAME_FIELDS).compact.first&.downcase
|
||||
end
|
||||
|
||||
users = User.where(username_lower: usernames.uniq).index_by(&:username_lower)
|
||||
notifications.each do |notification|
|
||||
notification.acting_user = users[notification.acting_username]
|
||||
end
|
||||
|
||||
notifications.each { |n| n.acting_user = users[n.acting_username] }
|
||||
|
||||
notifications
|
||||
end
|
||||
|
|
|
@ -185,22 +185,23 @@ class PostTiming < ActiveRecord::Base
|
|||
|
||||
if join_table.length > 0
|
||||
sql = <<~SQL
|
||||
UPDATE post_timings t
|
||||
SET msecs = LEAST(t.msecs::bigint + x.msecs, 2^31 - 1)
|
||||
FROM (#{join_table.join(" UNION ALL ")}) x
|
||||
WHERE x.topic_id = t.topic_id AND
|
||||
x.post_number = t.post_number AND
|
||||
x.user_id = t.user_id
|
||||
RETURNING x.idx
|
||||
SQL
|
||||
UPDATE post_timings t
|
||||
SET msecs = LEAST(t.msecs::bigint + x.msecs, 2^31 - 1)
|
||||
FROM (#{join_table.join(" UNION ALL ")}) x
|
||||
WHERE x.topic_id = t.topic_id AND
|
||||
x.post_number = t.post_number AND
|
||||
x.user_id = t.user_id
|
||||
RETURNING x.idx
|
||||
SQL
|
||||
|
||||
existing = Set.new(DB.query_single(sql))
|
||||
|
||||
sql = <<~SQL
|
||||
SELECT 1 FROM topics
|
||||
WHERE deleted_at IS NULL AND
|
||||
archetype = 'regular' AND
|
||||
id = :topic_id
|
||||
SELECT 1
|
||||
FROM topics
|
||||
WHERE deleted_at IS NULL
|
||||
AND archetype = 'regular'
|
||||
AND id = :topic_id
|
||||
SQL
|
||||
|
||||
is_regular = DB.exec(sql, topic_id: topic_id) == 1
|
||||
|
@ -220,7 +221,8 @@ SQL
|
|||
|
||||
total_changed = 0
|
||||
if timings.length > 0
|
||||
total_changed = Notification.mark_posts_read(current_user, topic_id, timings.map { |t| t[0] })
|
||||
post_numbers = timings.map(&:first)
|
||||
total_changed = Notification.read!(current_user, topic_id:, post_numbers:)
|
||||
end
|
||||
|
||||
topic_time = max_time_per_post if topic_time > max_time_per_post
|
||||
|
|
|
@ -620,12 +620,9 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def reload
|
||||
@unread_notifications = nil
|
||||
@all_unread_notifications_count = nil
|
||||
@unread_total_notifications = nil
|
||||
@unread_notifications_count = {}
|
||||
@unread_pms = nil
|
||||
@unread_bookmarks = nil
|
||||
@unread_high_prios = nil
|
||||
@ignored_user_ids = nil
|
||||
@muted_user_ids = nil
|
||||
@belonging_to_group_ids = nil
|
||||
|
@ -640,81 +637,6 @@ class User < ActiveRecord::Base
|
|||
@muted_user_ids ||= muted_users.pluck(:id)
|
||||
end
|
||||
|
||||
def unread_notifications_of_type(notification_type, since: nil)
|
||||
# perf critical, much more efficient than AR
|
||||
sql = <<~SQL
|
||||
SELECT COUNT(*)
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE t.deleted_at IS NULL
|
||||
AND n.notification_type = :notification_type
|
||||
AND n.user_id = :user_id
|
||||
AND NOT read
|
||||
#{since ? "AND n.created_at > :since" : ""}
|
||||
SQL
|
||||
|
||||
# to avoid coalesce we do to_i
|
||||
DB.query_single(sql, user_id: id, notification_type: notification_type, since: since)[0].to_i
|
||||
end
|
||||
|
||||
def unread_notifications_of_priority(high_priority:)
|
||||
# perf critical, much more efficient than AR
|
||||
sql = <<~SQL
|
||||
SELECT COUNT(*)
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE t.deleted_at IS NULL
|
||||
AND n.high_priority = :high_priority
|
||||
AND n.user_id = :user_id
|
||||
AND NOT read
|
||||
SQL
|
||||
|
||||
# to avoid coalesce we do to_i
|
||||
DB.query_single(sql, user_id: id, high_priority: high_priority)[0].to_i
|
||||
end
|
||||
|
||||
MAX_UNREAD_BACKLOG = 400
|
||||
def grouped_unread_notifications
|
||||
results = DB.query(<<~SQL, user_id: self.id, limit: MAX_UNREAD_BACKLOG)
|
||||
SELECT X.notification_type AS type, COUNT(*) FROM (
|
||||
SELECT n.notification_type
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE t.deleted_at IS NULL
|
||||
AND n.user_id = :user_id
|
||||
AND NOT n.read
|
||||
LIMIT :limit
|
||||
) AS X
|
||||
GROUP BY X.notification_type
|
||||
SQL
|
||||
results.map! { |row| [row.type, row.count] }
|
||||
results.to_h
|
||||
end
|
||||
|
||||
def unread_high_priority_notifications
|
||||
@unread_high_prios ||= unread_notifications_of_priority(high_priority: true)
|
||||
end
|
||||
|
||||
def new_personal_messages_notifications_count
|
||||
args = {
|
||||
user_id: self.id,
|
||||
seen_notification_id: self.seen_notification_id,
|
||||
private_message: Notification.types[:private_message],
|
||||
}
|
||||
|
||||
DB.query_single(<<~SQL, args).first
|
||||
SELECT COUNT(*)
|
||||
FROM notifications
|
||||
WHERE user_id = :user_id
|
||||
AND id > :seen_notification_id
|
||||
AND NOT read
|
||||
AND notification_type = :private_message
|
||||
SQL
|
||||
end
|
||||
|
||||
# PERF: This safeguard is in place to avoid situations where
|
||||
# a user with enormous amounts of unread data can issue extremely
|
||||
# expensive queries
|
||||
MAX_UNREAD_NOTIFICATIONS = 99
|
||||
|
||||
def self.max_unread_notifications
|
||||
|
@ -725,64 +647,76 @@ class User < ActiveRecord::Base
|
|||
@max_unread_notifications = val
|
||||
end
|
||||
|
||||
def unread_notifications
|
||||
@unread_notifications ||=
|
||||
begin
|
||||
# perf critical, much more efficient than AR
|
||||
sql = <<~SQL
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT 1 FROM
|
||||
notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE t.deleted_at IS NULL AND
|
||||
n.high_priority = FALSE AND
|
||||
n.user_id = :user_id AND
|
||||
n.id > :seen_notification_id AND
|
||||
NOT read
|
||||
LIMIT :limit
|
||||
) AS X
|
||||
SQL
|
||||
|
||||
DB.query_single(
|
||||
sql,
|
||||
user_id: id,
|
||||
seen_notification_id: seen_notification_id,
|
||||
limit: User.max_unread_notifications,
|
||||
)[
|
||||
0
|
||||
].to_i
|
||||
end
|
||||
def grouped_unread_notifications
|
||||
DB.query_array(<<~SQL, user_id: id, limit: MAX_UNREAD_NOTIFICATIONS).to_h
|
||||
SELECT X.notification_type, COUNT(*)
|
||||
FROM (
|
||||
SELECT n.notification_type
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE (n.topic_id IS NULL OR (t.id IS NOT NULL AND t.deleted_at IS NULL))
|
||||
AND n.user_id = :user_id
|
||||
AND NOT n.read
|
||||
LIMIT :limit
|
||||
) AS X
|
||||
GROUP BY X.notification_type
|
||||
SQL
|
||||
end
|
||||
|
||||
def all_unread_notifications_count
|
||||
@all_unread_notifications_count ||=
|
||||
begin
|
||||
sql = <<~SQL
|
||||
SELECT COUNT(*) FROM (
|
||||
SELECT 1 FROM
|
||||
notifications n
|
||||
LEFT JOIN topics t ON t.id = n.topic_id
|
||||
WHERE t.deleted_at IS NULL AND
|
||||
n.user_id = :user_id AND
|
||||
n.id > :seen_notification_id AND
|
||||
NOT read
|
||||
LIMIT :limit
|
||||
def unread_notifications_count(
|
||||
user_id: id,
|
||||
notification_type: nil,
|
||||
high_priority: nil,
|
||||
since: nil,
|
||||
seen_notification_id: nil,
|
||||
limit: nil
|
||||
)
|
||||
limit = [limit, User.max_unread_notifications].compact.min
|
||||
args = { user_id:, notification_type:, high_priority:, since:, seen_notification_id:, limit: }
|
||||
key = args.values.compact.join("-")
|
||||
|
||||
@unread_notifications_count ||= {}
|
||||
@unread_notifications_count[key] ||= begin
|
||||
DB.query_single(<<~SQL, args)[0].to_i
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT 1
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON n.topic_id = t.id
|
||||
WHERE (n.topic_id IS NULL OR (t.id IS NOT NULL AND t.deleted_at IS NULL))
|
||||
AND NOT n.read
|
||||
AND n.user_id = :user_id
|
||||
#{notification_type.blank? ? "" : "AND n.notification_type = :notification_type"}
|
||||
#{high_priority.nil? ? "" : "AND n.high_priority = :high_priority"}
|
||||
#{since.nil? ? "" : "AND n.created_at > :since"}
|
||||
#{seen_notification_id.nil? ? "" : "AND n.id > :seen_notification_id"}
|
||||
LIMIT :limit
|
||||
) AS X
|
||||
SQL
|
||||
|
||||
DB.query_single(
|
||||
sql,
|
||||
user_id: id,
|
||||
seen_notification_id: seen_notification_id,
|
||||
limit: User.max_unread_notifications,
|
||||
)[
|
||||
0
|
||||
].to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def total_unread_notifications
|
||||
@unread_total_notifications ||= notifications.where("read = false").count
|
||||
unread_notifications_count
|
||||
end
|
||||
|
||||
def unread_high_priority_notifications
|
||||
unread_notifications_count(high_priority: true)
|
||||
end
|
||||
|
||||
def unread_notifications
|
||||
unread_notifications_count(seen_notification_id:, high_priority: false)
|
||||
end
|
||||
|
||||
def all_unread_notifications_count
|
||||
unread_notifications_count(seen_notification_id:)
|
||||
end
|
||||
|
||||
def new_personal_messages_notifications_count
|
||||
unread_notifications_count(
|
||||
seen_notification_id:,
|
||||
notification_type: Notification.types[:private_message],
|
||||
)
|
||||
end
|
||||
|
||||
def reviewable_count
|
||||
|
@ -794,6 +728,7 @@ class User < ActiveRecord::Base
|
|||
query = query.where("notifications.id > ?", seen_notification_id) if seen_notification_id
|
||||
if max_notification_id = query.maximum(:id)
|
||||
update!(seen_notification_id: max_notification_id)
|
||||
publish_notifications_state
|
||||
true
|
||||
else
|
||||
false
|
||||
|
@ -802,13 +737,13 @@ class User < ActiveRecord::Base
|
|||
|
||||
def bump_last_seen_reviewable!
|
||||
query = Reviewable.unseen_list_for(self, preload: false)
|
||||
|
||||
query = query.where("reviewables.id > ?", last_seen_reviewable_id) if last_seen_reviewable_id
|
||||
max_reviewable_id = query.maximum(:id)
|
||||
|
||||
if max_reviewable_id
|
||||
if max_reviewable_id = query.maximum(:id)
|
||||
update!(last_seen_reviewable_id: max_reviewable_id)
|
||||
publish_reviewable_counts
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -829,49 +764,50 @@ class User < ActiveRecord::Base
|
|||
return if !self.allow_live_notifications?
|
||||
|
||||
# publish last notification json with the message so we can apply an update
|
||||
notification = notifications.visible.order("notifications.created_at desc").first
|
||||
json = NotificationSerializer.new(notification).as_json if notification
|
||||
notification = notifications.visible.order(created_at: :desc).first
|
||||
last_notification = NotificationSerializer.new(notification).as_json if notification
|
||||
|
||||
sql = (<<~SQL)
|
||||
SELECT * FROM (
|
||||
SELECT n.id, n.read FROM notifications n
|
||||
LEFT JOIN topics t ON n.topic_id = t.id
|
||||
WHERE
|
||||
t.deleted_at IS NULL AND
|
||||
n.high_priority AND
|
||||
n.user_id = :user_id AND
|
||||
NOT read
|
||||
recent = DB.query_array(<<~SQL, user_id: id)
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT n.id, n.read
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON n.topic_id = t.id
|
||||
WHERE (n.topic_id IS NULL OR (t.id IS NOT NULL AND t.deleted_at IS NULL))
|
||||
AND n.user_id = :user_id
|
||||
AND n.high_priority
|
||||
AND NOT read
|
||||
ORDER BY n.id DESC
|
||||
LIMIT 20
|
||||
) AS x
|
||||
|
||||
UNION ALL
|
||||
SELECT * FROM (
|
||||
SELECT n.id, n.read FROM notifications n
|
||||
LEFT JOIN topics t ON n.topic_id = t.id
|
||||
WHERE
|
||||
t.deleted_at IS NULL AND
|
||||
(n.high_priority = FALSE OR read) AND
|
||||
n.user_id = :user_id
|
||||
ORDER BY n.id DESC
|
||||
LIMIT 20
|
||||
|
||||
SELECT *
|
||||
FROM (
|
||||
SELECT n.id, n.read
|
||||
FROM notifications n
|
||||
LEFT JOIN topics t ON n.topic_id = t.id
|
||||
WHERE (n.topic_id IS NULL OR (t.id IS NOT NULL AND t.deleted_at IS NULL))
|
||||
AND n.user_id = :user_id
|
||||
AND (NOT n.high_priority OR read)
|
||||
ORDER BY n.id DESC
|
||||
LIMIT 20
|
||||
) AS y
|
||||
SQL
|
||||
|
||||
recent = DB.query(sql, user_id: id).map! { |r| [r.id, r.read] }
|
||||
|
||||
payload = {
|
||||
unread_notifications: unread_notifications,
|
||||
unread_high_priority_notifications: unread_high_priority_notifications,
|
||||
unread_notifications:,
|
||||
unread_high_priority_notifications:,
|
||||
read_first_notification: read_first_notification?,
|
||||
last_notification: json,
|
||||
recent: recent,
|
||||
seen_notification_id: seen_notification_id,
|
||||
last_notification:,
|
||||
recent:,
|
||||
seen_notification_id:,
|
||||
all_unread_notifications_count:,
|
||||
grouped_unread_notifications:,
|
||||
new_personal_messages_notifications_count:,
|
||||
}
|
||||
|
||||
payload[:all_unread_notifications_count] = all_unread_notifications_count
|
||||
payload[:grouped_unread_notifications] = grouped_unread_notifications
|
||||
payload[:new_personal_messages_notifications_count] = new_personal_messages_notifications_count
|
||||
|
||||
MessageBus.publish("/notification/#{id}", payload, user_ids: [id])
|
||||
end
|
||||
|
||||
|
|
|
@ -40,11 +40,10 @@ class UserBookmarkList
|
|||
def categories
|
||||
@categories ||=
|
||||
@bookmarks
|
||||
.map do |bm|
|
||||
.flat_map do |bm|
|
||||
category = bm.bookmarkable.try(:category) || bm.bookmarkable.try(:topic)&.category
|
||||
[category&.parent_category, category]
|
||||
end
|
||||
.flatten
|
||||
.compact
|
||||
.uniq
|
||||
end
|
||||
|
|
|
@ -40,26 +40,23 @@ class BookmarkQuery
|
|||
search_term_wildcard = @search_term.present? ? "%#{@search_term}%" : nil
|
||||
|
||||
queries =
|
||||
Bookmark
|
||||
.registered_bookmarkables
|
||||
.map do |bookmarkable|
|
||||
interim_results = bookmarkable.perform_list_query(@user, @guardian)
|
||||
Bookmark.registered_bookmarkables.filter_map do |bookmarkable|
|
||||
interim_results = bookmarkable.perform_list_query(@user, @guardian)
|
||||
|
||||
# this could occur if there is some security reason that the user cannot
|
||||
# access the bookmarkables that they have bookmarked, e.g. if they had 1 bookmark
|
||||
# on a topic and that topic was moved into a private category
|
||||
next if interim_results.blank?
|
||||
# this could occur if there is some security reason that the user cannot
|
||||
# access the bookmarkables that they have bookmarked, e.g. if they had 1 bookmark
|
||||
# on a topic and that topic was moved into a private category
|
||||
next if interim_results.blank?
|
||||
|
||||
if @search_term.present?
|
||||
interim_results =
|
||||
bookmarkable.perform_search_query(interim_results, search_term_wildcard, ts_query)
|
||||
end
|
||||
|
||||
# this is purely to make the query easy to read and debug, otherwise it's
|
||||
# all mashed up into a massive ball in MiniProfiler :)
|
||||
"---- #{bookmarkable.model} bookmarkable ---\n\n #{interim_results.to_sql}"
|
||||
if @search_term.present?
|
||||
interim_results =
|
||||
bookmarkable.perform_search_query(interim_results, search_term_wildcard, ts_query)
|
||||
end
|
||||
.compact
|
||||
|
||||
# this is purely to make the query easy to read and debug, otherwise it's
|
||||
# all mashed up into a massive ball in MiniProfiler :)
|
||||
"---- #{bookmarkable.model} bookmarkable ---\n\n #{interim_results.to_sql}"
|
||||
end
|
||||
|
||||
# same for interim results being blank, the user might have been locked out
|
||||
# from all their various bookmarks, in which case they will see nothing and
|
||||
|
@ -68,12 +65,7 @@ class BookmarkQuery
|
|||
|
||||
union_sql = queries.join("\n\nUNION\n\n")
|
||||
results = Bookmark.select("bookmarks.*").from("(\n\n#{union_sql}\n\n) as bookmarks")
|
||||
results =
|
||||
results.order(
|
||||
"(CASE WHEN bookmarks.pinned THEN 0 ELSE 1 END),
|
||||
bookmarks.reminder_at ASC,
|
||||
bookmarks.updated_at DESC",
|
||||
)
|
||||
results = results.order("NOT pinned ASC, reminder_at ASC, updated_at DESC")
|
||||
|
||||
@count = results.count
|
||||
|
||||
|
@ -86,82 +78,7 @@ class BookmarkQuery
|
|||
results = results.limit(@per_page).to_a
|
||||
|
||||
BookmarkQuery.preload(results, self)
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
def unread_notifications(limit: 20)
|
||||
reminder_notifications =
|
||||
Notification
|
||||
.for_user_menu(@user.id, limit: [limit, 100].min)
|
||||
.unread
|
||||
.where(notification_type: Notification.types[:bookmark_reminder])
|
||||
|
||||
reminder_bookmark_ids = reminder_notifications.map { |n| n.data_hash[:bookmark_id] }.compact
|
||||
|
||||
# We preload associations like we do above for the list to avoid
|
||||
# N1s in the can_see? guardian calls for each bookmark.
|
||||
bookmarks = Bookmark.where(user: @user, id: reminder_bookmark_ids)
|
||||
BookmarkQuery.preload(bookmarks, self)
|
||||
|
||||
# Any bookmarks that no longer exist, we need to find the associated
|
||||
# records using bookmarkable details.
|
||||
#
|
||||
# First we want to group these by type into a hash to reduce queries:
|
||||
#
|
||||
# {
|
||||
# "Post": {
|
||||
# 1234: <Post>,
|
||||
# 566: <Post>,
|
||||
# },
|
||||
# "Topic": {
|
||||
# 123: <Topic>,
|
||||
# 99: <Topic>,
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# We may not need to do this most of the time. It depends mostly on
|
||||
# a user's auto_delete_preference for bookmarks.
|
||||
deleted_bookmark_ids = reminder_bookmark_ids - bookmarks.map(&:id)
|
||||
deleted_bookmarkables =
|
||||
reminder_notifications
|
||||
.select do |notif|
|
||||
deleted_bookmark_ids.include?(notif.data_hash[:bookmark_id]) &&
|
||||
notif.data_hash[:bookmarkable_type].present?
|
||||
end
|
||||
.inject({}) do |hash, notif|
|
||||
hash[notif.data_hash[:bookmarkable_type]] ||= {}
|
||||
hash[notif.data_hash[:bookmarkable_type]][notif.data_hash[:bookmarkable_id]] = nil
|
||||
hash
|
||||
end
|
||||
|
||||
# Then, we can actually find the associated records for each type in the database.
|
||||
deleted_bookmarkables.each do |type, bookmarkable|
|
||||
records = Bookmark.registered_bookmarkable_from_type(type).model.where(id: bookmarkable.keys)
|
||||
records.each { |record| deleted_bookmarkables[type][record.id] = record }
|
||||
end
|
||||
|
||||
reminder_notifications.select do |notif|
|
||||
bookmark = bookmarks.find { |bm| bm.id == notif.data_hash[:bookmark_id] }
|
||||
|
||||
# This is the happy path, it's easiest to look up using a bookmark
|
||||
# that hasn't been deleted.
|
||||
if bookmark.present?
|
||||
bookmarkable = Bookmark.registered_bookmarkable_from_type(bookmark.bookmarkable_type)
|
||||
bookmarkable.can_see?(@guardian, bookmark)
|
||||
else
|
||||
# Otherwise, we have to use our cached records from the deleted
|
||||
# bookmarks' related bookmarkable (e.g. Post, Topic) to determine
|
||||
# secure access.
|
||||
bookmarkable =
|
||||
deleted_bookmarkables.dig(
|
||||
notif.data_hash[:bookmarkable_type],
|
||||
notif.data_hash[:bookmarkable_id],
|
||||
)
|
||||
bookmarkable.present? &&
|
||||
Bookmark.registered_bookmarkable_from_type(
|
||||
notif.data_hash[:bookmarkable_type],
|
||||
).can_see_bookmarkable?(@guardian, bookmarkable)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,8 +112,9 @@ after_initialize do
|
|||
first_post = topic.ordered_posts.first
|
||||
|
||||
notification = Notification.where(topic_id: topic.id, post_number: first_post.post_number).first
|
||||
|
||||
if notification.present?
|
||||
Notification.read(self, notification.id)
|
||||
Notification.read!(self, id: notification.id)
|
||||
self.reload
|
||||
self.publish_notifications_state
|
||||
end
|
||||
|
|
|
@ -329,15 +329,14 @@ RSpec.describe Notification do
|
|||
notification_type: Notification.types[:bookmark_reminder],
|
||||
)
|
||||
|
||||
other =
|
||||
Notification.create!(
|
||||
read: false,
|
||||
user_id: user.id,
|
||||
topic_id: t.id,
|
||||
post_number: 1,
|
||||
data: "{}",
|
||||
notification_type: Notification.types[:mentioned],
|
||||
)
|
||||
Notification.create!(
|
||||
read: false,
|
||||
user_id: user.id,
|
||||
topic_id: t.id,
|
||||
post_number: 1,
|
||||
data: "{}",
|
||||
notification_type: Notification.types[:mentioned],
|
||||
)
|
||||
|
||||
user.bump_last_seen_notification!
|
||||
user.reload
|
||||
|
@ -348,7 +347,7 @@ RSpec.describe Notification do
|
|||
end
|
||||
end
|
||||
|
||||
describe "mark_posts_read" do
|
||||
describe "read posts" do
|
||||
it "marks multiple posts as read if needed" do
|
||||
(1..3).map do |i|
|
||||
Notification.create!(
|
||||
|
@ -360,6 +359,7 @@ RSpec.describe Notification do
|
|||
notification_type: 1,
|
||||
)
|
||||
end
|
||||
|
||||
Notification.create!(
|
||||
read: true,
|
||||
user_id: user.id,
|
||||
|
@ -369,8 +369,8 @@ RSpec.describe Notification do
|
|||
notification_type: 1,
|
||||
)
|
||||
|
||||
expect { Notification.mark_posts_read(user, 2, [1, 2, 3, 4]) }.to change {
|
||||
Notification.where(read: true).count
|
||||
expect { Notification.read!(user, topic_id: 2, post_numbers: [1, 2, 3, 4]) }.to change {
|
||||
Notification.read.count
|
||||
}.by(3)
|
||||
end
|
||||
end
|
||||
|
@ -621,134 +621,60 @@ RSpec.describe Notification do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#recent_report" do
|
||||
let(:post) { Fabricate(:post) }
|
||||
describe "#consolidate_membership_requests" do
|
||||
fab!(:group) { Fabricate(:group, name: "XXsssssddd") }
|
||||
fab!(:user)
|
||||
fab!(:post)
|
||||
|
||||
def fab(type, read)
|
||||
@i ||= 0
|
||||
@i += 1
|
||||
Notification.create!(
|
||||
read: read,
|
||||
def create_membership_request_notification
|
||||
Notification.consolidate_or_create!(
|
||||
notification_type: Notification.types[:private_message],
|
||||
user_id: user.id,
|
||||
topic_id: post.topic_id,
|
||||
post_number: post.post_number,
|
||||
data: "[]",
|
||||
notification_type: type,
|
||||
created_at: @i.days.from_now,
|
||||
data: {
|
||||
topic_title: I18n.t("groups.request_membership_pm.title", group_name: group.name),
|
||||
original_post_id: post.id,
|
||||
}.to_json,
|
||||
updated_at: Time.zone.now,
|
||||
created_at: Time.zone.now,
|
||||
)
|
||||
end
|
||||
|
||||
def unread_pm
|
||||
fab(Notification.types[:private_message], false)
|
||||
before do
|
||||
PostCustomField.create!(post_id: post.id, name: "requested_group_id", value: group.id)
|
||||
2.times { create_membership_request_notification }
|
||||
end
|
||||
|
||||
def unread_bookmark_reminder
|
||||
fab(Notification.types[:bookmark_reminder], false)
|
||||
it "should consolidate membership requests to a new notification" do
|
||||
original_notification = create_membership_request_notification
|
||||
starting_count = SiteSetting.notification_consolidation_threshold
|
||||
|
||||
consolidated_notification = create_membership_request_notification
|
||||
expect { original_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
|
||||
expect(consolidated_notification.notification_type).to eq(
|
||||
Notification.types[:membership_request_consolidated],
|
||||
)
|
||||
|
||||
data = consolidated_notification.data_hash
|
||||
expect(data[:group_name]).to eq(group.name)
|
||||
expect(data[:count]).to eq(starting_count + 1)
|
||||
|
||||
updated_consolidated_notification = create_membership_request_notification
|
||||
|
||||
expect(updated_consolidated_notification.data_hash[:count]).to eq(starting_count + 2)
|
||||
end
|
||||
|
||||
def pm
|
||||
fab(Notification.types[:private_message], true)
|
||||
end
|
||||
it 'consolidates membership requests with "processed" false if user is in DND' do
|
||||
user.do_not_disturb_timings.create(starts_at: Time.now, ends_at: 3.days.from_now)
|
||||
|
||||
def regular
|
||||
fab(Notification.types[:liked], true)
|
||||
end
|
||||
create_membership_request_notification
|
||||
create_membership_request_notification
|
||||
|
||||
def liked_consolidated
|
||||
fab(Notification.types[:liked_consolidated], true)
|
||||
end
|
||||
|
||||
it "correctly finds visible notifications" do
|
||||
pm
|
||||
expect(Notification.visible.count).to eq(1)
|
||||
post.topic.trash!
|
||||
expect(Notification.visible.count).to eq(0)
|
||||
end
|
||||
|
||||
it "orders stuff by creation descending, bumping unread high priority (pms, bookmark reminders) to top" do
|
||||
# note we expect the final order to read bottom-up for this list of variables,
|
||||
# with unread pm + bookmark reminder at the top of that list
|
||||
a = unread_pm
|
||||
regular
|
||||
b = unread_bookmark_reminder
|
||||
c = pm
|
||||
d = regular
|
||||
|
||||
notifications = Notification.recent_report(user, 4)
|
||||
expect(notifications.map { |n| n.id }).to eq([b.id, a.id, d.id, c.id])
|
||||
end
|
||||
|
||||
describe "for a user that does not want to be notify on liked" do
|
||||
before do
|
||||
user.user_option.update!(
|
||||
like_notification_frequency: UserOption.like_notification_frequency_type[:never],
|
||||
)
|
||||
end
|
||||
|
||||
it "should not return any form of liked notifications" do
|
||||
notification = pm
|
||||
regular
|
||||
liked_consolidated
|
||||
|
||||
expect(Notification.recent_report(user)).to contain_exactly(notification)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#consolidate_membership_requests" do
|
||||
fab!(:group) { Fabricate(:group, name: "XXsssssddd") }
|
||||
fab!(:user)
|
||||
fab!(:post)
|
||||
|
||||
def create_membership_request_notification
|
||||
Notification.consolidate_or_create!(
|
||||
notification_type: Notification.types[:private_message],
|
||||
user_id: user.id,
|
||||
data: {
|
||||
topic_title: I18n.t("groups.request_membership_pm.title", group_name: group.name),
|
||||
original_post_id: post.id,
|
||||
}.to_json,
|
||||
updated_at: Time.zone.now,
|
||||
created_at: Time.zone.now,
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
PostCustomField.create!(post_id: post.id, name: "requested_group_id", value: group.id)
|
||||
2.times { create_membership_request_notification }
|
||||
end
|
||||
|
||||
it "should consolidate membership requests to a new notification" do
|
||||
original_notification = create_membership_request_notification
|
||||
starting_count = SiteSetting.notification_consolidation_threshold
|
||||
|
||||
consolidated_notification = create_membership_request_notification
|
||||
expect { original_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
|
||||
expect(consolidated_notification.notification_type).to eq(
|
||||
Notification.types[:membership_request_consolidated],
|
||||
)
|
||||
|
||||
data = consolidated_notification.data_hash
|
||||
expect(data[:group_name]).to eq(group.name)
|
||||
expect(data[:count]).to eq(starting_count + 1)
|
||||
|
||||
updated_consolidated_notification = create_membership_request_notification
|
||||
|
||||
expect(updated_consolidated_notification.data_hash[:count]).to eq(starting_count + 2)
|
||||
end
|
||||
|
||||
it 'consolidates membership requests with "processed" false if user is in DND' do
|
||||
user.do_not_disturb_timings.create(starts_at: Time.now, ends_at: 3.days.from_now)
|
||||
|
||||
create_membership_request_notification
|
||||
create_membership_request_notification
|
||||
|
||||
notification = Notification.last
|
||||
expect(notification.notification_type).to eq(
|
||||
Notification.types[:membership_request_consolidated],
|
||||
)
|
||||
expect(notification.shelved_notification).to be_present
|
||||
end
|
||||
notification = Notification.last
|
||||
expect(notification.notification_type).to eq(
|
||||
Notification.types[:membership_request_consolidated],
|
||||
)
|
||||
expect(notification.shelved_notification).to be_present
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -461,15 +461,17 @@ RSpec.describe NotificationsController do
|
|||
end
|
||||
|
||||
it "updates the `read` status" do
|
||||
expect(user.reload.unread_notifications).to eq(1)
|
||||
expect(user.reload.total_unread_notifications).to eq(1)
|
||||
user.reload
|
||||
expect(user.unread_notifications).to eq(1)
|
||||
expect(user.total_unread_notifications).to eq(1)
|
||||
|
||||
put "/notifications/mark-read.json"
|
||||
|
||||
expect(response.status).to eq(200)
|
||||
|
||||
user.reload
|
||||
expect(user.reload.unread_notifications).to eq(0)
|
||||
expect(user.reload.total_unread_notifications).to eq(0)
|
||||
expect(user.unread_notifications).to eq(0)
|
||||
expect(user.total_unread_notifications).to eq(0)
|
||||
end
|
||||
|
||||
describe "#create" do
|
||||
|
|
Loading…
Reference in New Issue