diff --git a/app/assets/javascripts/discourse/app/components/reviewable-item.js b/app/assets/javascripts/discourse/app/components/reviewable-item.js
index 01488c3ecd..9843038de4 100644
--- a/app/assets/javascripts/discourse/app/components/reviewable-item.js
+++ b/app/assets/javascripts/discourse/app/components/reviewable-item.js
@@ -156,6 +156,12 @@ export default Component.extend({
performResult.reviewable_count
);
}
+ if (performResult.unseen_reviewable_count !== undefined) {
+ this.currentUser.set(
+ "unseen_reviewable_count",
+ performResult.unseen_reviewable_count
+ );
+ }
if (this.attrs.remove) {
this.attrs.remove(performResult.remove_reviewable_ids);
diff --git a/app/assets/javascripts/discourse/app/components/site-header.js b/app/assets/javascripts/discourse/app/components/site-header.js
index 71cd70a4a3..b5a4dc205d 100644
--- a/app/assets/javascripts/discourse/app/components/site-header.js
+++ b/app/assets/javascripts/discourse/app/components/site-header.js
@@ -30,7 +30,9 @@ const SiteHeaderComponent = MountWidget.extend(
@observes(
"currentUser.unread_notifications",
"currentUser.unread_high_priority_notifications",
- "currentUser.reviewable_count",
+ "currentUser.all_unread_notifications_count",
+ "currentUser.reviewable_count", // TODO: remove this when redesigned_user_menu_enabled is removed
+ "currentUser.unseen_reviewable_count",
"session.defaultColorSchemeIsDark",
"session.darkModeAvailable"
)
diff --git a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
index eed101e32f..be4d54ed2a 100644
--- a/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
+++ b/app/assets/javascripts/discourse/app/initializers/subscribe-user-notifications.js
@@ -22,38 +22,49 @@ export default {
const user = container.lookup("service:current-user");
const bus = container.lookup("service:message-bus");
const appEvents = container.lookup("service:app-events");
+ const siteSettings = container.lookup("service:site-settings");
if (user) {
- bus.subscribe("/reviewable_counts", (data) => {
- user.set("reviewable_count", data.reviewable_count);
+ const channel = user.enable_redesigned_user_menu
+ ? `/reviewable_counts/${user.id}`
+ : "/reviewable_counts";
+ bus.subscribe(channel, (data) => {
+ if (data.reviewable_count >= 0) {
+ user.set("reviewable_count", data.reviewable_count);
+ }
+ if (user.redesigned_user_menu_enabled) {
+ user.set("unseen_reviewable_count", data.unseen_reviewable_count);
+ }
});
bus.subscribe(
`/notification/${user.get("id")}`,
(data) => {
const store = container.lookup("service:store");
- const oldUnread = user.get("unread_notifications");
- const oldHighPriority = user.get(
- "unread_high_priority_notifications"
- );
+ const oldUnread = user.unread_notifications;
+ const oldHighPriority = user.unread_high_priority_notifications;
+ const oldAllUnread = user.all_unread_notifications_count;
user.setProperties({
unread_notifications: data.unread_notifications,
unread_high_priority_notifications:
data.unread_high_priority_notifications,
read_first_notification: data.read_first_notification,
+ all_unread_notifications_count: data.all_unread_notifications_count,
});
if (
oldUnread !== data.unread_notifications ||
- oldHighPriority !== data.unread_high_priority_notifications
+ oldHighPriority !== data.unread_high_priority_notifications ||
+ oldAllUnread !== data.all_unread_notifications_count
) {
appEvents.trigger("notifications:changed");
if (
site.mobileView &&
(data.unread_notifications - oldUnread > 0 ||
- data.unread_high_priority_notifications - oldHighPriority > 0)
+ data.unread_high_priority_notifications - oldHighPriority > 0 ||
+ data.all_unread_notifications_count - oldAllUnread > 0)
) {
appEvents.trigger("header:update-topic", null, 5000);
}
@@ -74,9 +85,9 @@ export default {
);
if (staleIndex === -1) {
- // high priority and unread notifications are first
let insertPosition = 0;
+ // high priority and unread notifications are first
if (!lastNotification.high_priority || lastNotification.read) {
const nextPosition = oldNotifications.findIndex(
(n) => !n.high_priority || n.read
@@ -122,7 +133,6 @@ export default {
});
const site = container.lookup("site:main");
- const siteSettings = container.lookup("service:site-settings");
const router = container.lookup("router:main");
bus.subscribe("/categories", (data) => {
diff --git a/app/assets/javascripts/discourse/app/routes/review-index.js b/app/assets/javascripts/discourse/app/routes/review-index.js
index 67c16c6a31..4d89d8154a 100644
--- a/app/assets/javascripts/discourse/app/routes/review-index.js
+++ b/app/assets/javascripts/discourse/app/routes/review-index.js
@@ -22,6 +22,12 @@ export default DiscourseRoute.extend({
if (meta.reviewable_count !== undefined) {
this.currentUser.set("reviewable_count", meta.reviewable_count);
}
+ if (meta.unseen_reviewable_count !== undefined) {
+ this.currentUser.set(
+ "unseen_reviewable_count",
+ meta.unseen_reviewable_count
+ );
+ }
controller.setProperties({
reviewables: model,
@@ -59,7 +65,10 @@ export default DiscourseRoute.extend({
}
});
- this.messageBus.subscribe("/reviewable_counts", (data) => {
+ const channel = this.currentUser.enable_redesigned_user_menu
+ ? `/reviewable_counts/${this.currentUser.id}`
+ : "/reviewable_counts";
+ this.messageBus.subscribe(channel, (data) => {
if (data.updates) {
this.controller.reviewables.forEach((reviewable) => {
const updates = data.updates[reviewable.id];
diff --git a/app/assets/javascripts/discourse/app/widgets/hamburger-menu.js b/app/assets/javascripts/discourse/app/widgets/hamburger-menu.js
index dcdfb2d646..463c5bf5d1 100644
--- a/app/assets/javascripts/discourse/app/widgets/hamburger-menu.js
+++ b/app/assets/javascripts/discourse/app/widgets/hamburger-menu.js
@@ -355,7 +355,11 @@ export default createWidget("hamburger-menu", {
},
html(attrs, state) {
- if (!state.loaded) {
+ if (
+ this.currentUser &&
+ !this.currentUser.redesigned_user_menu_enabled &&
+ !state.loaded
+ ) {
this.refreshReviewableCount(state);
}
diff --git a/app/assets/javascripts/discourse/app/widgets/header.js b/app/assets/javascripts/discourse/app/widgets/header.js
index f5724e5503..0c445bbe37 100644
--- a/app/assets/javascripts/discourse/app/widgets/header.js
+++ b/app/assets/javascripts/discourse/app/widgets/header.js
@@ -77,78 +77,109 @@ createWidget("header-notifications", {
if (user.isInDoNotDisturb()) {
contents.push(h("div.do-not-disturb-background", iconNode("moon")));
} else {
- const unreadNotifications = user.get("unread_notifications");
- if (!!unreadNotifications) {
- contents.push(
- this.attach("link", {
- action: attrs.action,
- className: "badge-notification unread-notifications",
- rawLabel: unreadNotifications,
- omitSpan: true,
- title: "notifications.tooltip.regular",
- titleOptions: { count: unreadNotifications },
- })
- );
- }
-
- const unreadHighPriority = user.get("unread_high_priority_notifications");
- if (!!unreadHighPriority) {
- // highlight the avatar if the first ever PM is not read
- if (
- !user.get("read_first_notification") &&
- !user.get("enforcedSecondFactor")
- ) {
- if (!attrs.active && attrs.ringBackdrop) {
- contents.push(h("span.ring"));
- contents.push(h("span.ring-backdrop-spotlight"));
- contents.push(
- h(
- "span.ring-backdrop",
- {},
- h("h1.ring-first-notification", {}, [
- h(
- "span",
- { className: "first-notification" },
- I18n.t("user.first_notification")
- ),
- h("span", { className: "read-later" }, [
- this.attach("link", {
- action: "readLater",
- className: "read-later-link",
- label: "user.skip_new_user_tips.read_later",
- }),
- ]),
- h("span", {}, [
- I18n.t("user.skip_new_user_tips.not_first_time"),
- " ",
- this.attach("link", {
- action: "skipNewUserTips",
- className: "skip-new-user-tips",
- label: "user.skip_new_user_tips.skip_link",
- title: "user.skip_new_user_tips.description",
- }),
- ]),
- ])
- )
- );
+ if (this.currentUser.redesigned_user_menu_enabled) {
+ const unread = user.all_unread_notifications_count || 0;
+ const reviewables = user.unseen_reviewable_count || 0;
+ const count = unread + reviewables;
+ if (count > 0) {
+ if (this._shouldHighlightAvatar()) {
+ this._addAvatarHighlight(contents);
}
+ contents.push(
+ this.attach("link", {
+ action: attrs.action,
+ className: "badge-notification unread-notifications",
+ rawLabel: count,
+ omitSpan: true,
+ title: "notifications.tooltip.regular",
+ titleOptions: { count },
+ })
+ );
+ }
+ } else {
+ const unreadNotifications = user.unread_notifications;
+ if (!!unreadNotifications) {
+ contents.push(
+ this.attach("link", {
+ action: attrs.action,
+ className: "badge-notification unread-notifications",
+ rawLabel: unreadNotifications,
+ omitSpan: true,
+ title: "notifications.tooltip.regular",
+ titleOptions: { count: unreadNotifications },
+ })
+ );
}
- // add the counter for the unread high priority
- contents.push(
- this.attach("link", {
- action: attrs.action,
- className: "badge-notification unread-high-priority-notifications",
- rawLabel: unreadHighPriority,
- omitSpan: true,
- title: "notifications.tooltip.high_priority",
- titleOptions: { count: unreadHighPriority },
- })
- );
+ const unreadHighPriority = user.unread_high_priority_notifications;
+ if (!!unreadHighPriority) {
+ if (this._shouldHighlightAvatar()) {
+ this._addAvatarHighlight(contents);
+ }
+
+ // add the counter for the unread high priority
+ contents.push(
+ this.attach("link", {
+ action: attrs.action,
+ className:
+ "badge-notification unread-high-priority-notifications",
+ rawLabel: unreadHighPriority,
+ omitSpan: true,
+ title: "notifications.tooltip.high_priority",
+ titleOptions: { count: unreadHighPriority },
+ })
+ );
+ }
}
}
return contents;
},
+
+ _shouldHighlightAvatar() {
+ const attrs = this.attrs;
+ const { user } = attrs;
+ return (
+ !user.read_first_notification &&
+ !user.enforcedSecondFactor &&
+ !attrs.active &&
+ attrs.ringBackdrop
+ );
+ },
+
+ _addAvatarHighlight(contents) {
+ contents.push(h("span.ring"));
+ contents.push(h("span.ring-backdrop-spotlight"));
+ contents.push(
+ h(
+ "span.ring-backdrop",
+ {},
+ h("h1.ring-first-notification", {}, [
+ h(
+ "span",
+ { className: "first-notification" },
+ I18n.t("user.first_notification")
+ ),
+ h("span", { className: "read-later" }, [
+ this.attach("link", {
+ action: "readLater",
+ className: "read-later-link",
+ label: "user.skip_new_user_tips.read_later",
+ }),
+ ]),
+ h("span", {}, [
+ I18n.t("user.skip_new_user_tips.not_first_time"),
+ " ",
+ this.attach("link", {
+ action: "skipNewUserTips",
+ className: "skip-new-user-tips",
+ label: "user.skip_new_user_tips.skip_link",
+ title: "user.skip_new_user_tips.description",
+ }),
+ ]),
+ ])
+ )
+ );
+ },
});
createWidget(
@@ -255,7 +286,10 @@ createWidget("header-icons", {
contents() {
let { currentUser } = this;
- if (currentUser && currentUser.reviewable_count) {
+ if (
+ currentUser?.reviewable_count &&
+ !this.currentUser.redesigned_user_menu_enabled
+ ) {
return h(
"div.badge-notification.reviewables",
{
diff --git a/app/assets/javascripts/discourse/tests/integration/components/site-header-test.js b/app/assets/javascripts/discourse/tests/integration/components/site-header-test.js
index 44d2477df6..26e767d981 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/site-header-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/site-header-test.js
@@ -1,6 +1,6 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { click, render, waitUntil } from "@ember/test-helpers";
+import { click, render, settled, waitUntil } from "@ember/test-helpers";
import { count, exists, query } from "discourse/tests/helpers/qunit-helpers";
import pretender, { response } from "discourse/tests/helpers/create-pretender";
import { hbs } from "ember-cli-htmlbars";
@@ -50,8 +50,35 @@ module("Integration | Component | site-header", function (hooks) {
await click("header.d-header");
});
+ test("displaying unread and reviewable notifications count when user's notifications and reviewables count are updated", async function (assert) {
+ this.currentUser.set("all_unread_notifications_count", 1);
+ this.currentUser.set("redesigned_user_menu_enabled", true);
+
+ await render(hbs``);
+ let unreadBadge = query(
+ ".header-dropdown-toggle.current-user .unread-notifications"
+ );
+ assert.strictEqual(unreadBadge.textContent, "1");
+
+ this.currentUser.set("all_unread_notifications_count", 5);
+ await settled();
+
+ unreadBadge = query(
+ ".header-dropdown-toggle.current-user .unread-notifications"
+ );
+ assert.strictEqual(unreadBadge.textContent, "5");
+
+ this.currentUser.set("unseen_reviewable_count", 3);
+ await settled();
+
+ unreadBadge = query(
+ ".header-dropdown-toggle.current-user .unread-notifications"
+ );
+ assert.strictEqual(unreadBadge.textContent, "8");
+ });
+
test("user avatar is highlighted when the user receives the first notification", async function (assert) {
- this.currentUser.set("all_unread_notifications", 1);
+ this.currentUser.set("all_unread_notifications_count", 1);
this.currentUser.set("redesigned_user_menu_enabled", true);
this.currentUser.set("read_first_notification", false);
await render(hbs``);
@@ -60,7 +87,7 @@ module("Integration | Component | site-header", function (hooks) {
test("user avatar is not highlighted when the user receives notifications beyond the first one", async function (assert) {
this.currentUser.set("redesigned_user_menu_enabled", true);
- this.currentUser.set("all_unread_notifications", 1);
+ this.currentUser.set("all_unread_notifications_count", 1);
this.currentUser.set("read_first_notification", true);
await render(hbs``);
assert.ok(!exists(".ring-first-notification"));
@@ -75,6 +102,13 @@ module("Integration | Component | site-header", function (hooks) {
assert.strictEqual(pendingReviewablesBadge.textContent, "1");
});
+ test("hamburger menu icon doesn't show pending reviewables count when revamped user menu is enabled", async function (assert) {
+ this.currentUser.set("reviewable_count", 1);
+ this.currentUser.set("redesigned_user_menu_enabled", true);
+ await render(hbs``);
+ assert.ok(!exists(".hamburger-dropdown .badge-notification"));
+ });
+
test("clicking outside the revamped menu closes it", async function (assert) {
this.currentUser.set("redesigned_user_menu_enabled", true);
await render(hbs``);
diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb
index eabc490a31..21a1a66221 100644
--- a/app/controllers/notifications_controller.rb
+++ b/app/controllers/notifications_controller.rb
@@ -44,6 +44,22 @@ class NotificationsController < ApplicationController
current_user.publish_notifications_state
end
+ if !params.has_key?(:silent) && params[:bump_last_seen_reviewable] && !@readonly_mode
+ 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 objects returned by the current_user
+ # and/or methods are changed by the time the deferred block is
+ # executed
+ user = User.find_by(id: current_user_id)
+ next if user.blank?
+ new_guardian = Guardian.new(user)
+ if new_guardian.can_see_review_queue?
+ user.bump_last_seen_reviewable!
+ end
+ end
+ end
+
render_json_dump(
notifications: serialize_data(notifications, NotificationSerializer),
seen_notification_id: current_user.seen_notification_id
diff --git a/app/controllers/reviewables_controller.rb b/app/controllers/reviewables_controller.rb
index 0f42edd248..d0385343aa 100644
--- a/app/controllers/reviewables_controller.rb
+++ b/app/controllers/reviewables_controller.rb
@@ -58,7 +58,8 @@ class ReviewablesController < ApplicationController
end,
meta: filters.merge(
total_rows_reviewables: total_rows, types: meta_types, reviewable_types: Reviewable.types,
- reviewable_count: current_user.reviewable_count
+ reviewable_count: current_user.reviewable_count,
+ unseen_reviewable_count: current_user.unseen_reviewable_count
)
}
if (offset + PER_PAGE) < total_rows
diff --git a/app/jobs/regular/notify_reviewable.rb b/app/jobs/regular/notify_reviewable.rb
index 9c3e729fe2..8d0c4f7c27 100644
--- a/app/jobs/regular/notify_reviewable.rb
+++ b/app/jobs/regular/notify_reviewable.rb
@@ -1,20 +1,13 @@
# frozen_string_literal: true
class Jobs::NotifyReviewable < ::Jobs::Base
-
+ # remove all the legacy stuff here when redesigned_user_menu_enabled is
+ # removed
def execute(args)
return unless reviewable = Reviewable.find_by(id: args[:reviewable_id])
@contacted = Set.new
- counts = Hash.new(0)
-
- Reviewable.default_visible.pending.each do |r|
- counts[:admins] += 1
- counts[:moderators] += 1 if r.reviewable_by_moderator?
- counts[r.reviewable_by_group_id] += 1 if r.reviewable_by_group_id
- end
-
all_updates = Hash.new { |h, k| h[k] = {} }
if args[:updated_reviewable_ids].present?
@@ -30,41 +23,63 @@ class Jobs::NotifyReviewable < ::Jobs::Base
end
end
- # admins
- notify(
- User.real.admins.pluck(:id),
+ counts = Hash.new(0)
+
+ Reviewable.default_visible.pending.each do |r|
+ counts[:admins] += 1
+ counts[:moderators] += 1 if r.reviewable_by_moderator?
+ counts[r.reviewable_by_group_id] += 1 if r.reviewable_by_group_id
+ end
+
+ redesigned_menu_enabled_user_ids = User.redesigned_user_menu_enabled_user_ids
+
+ new_menu_admins = User.real.admins.where(id: redesigned_menu_enabled_user_ids)
+ notify_users(new_menu_admins, all_updates[:admins])
+
+ legacy_menu_admins = User.real.admins.where("id NOT IN (?)", @contacted).pluck(:id)
+ notify_legacy(
+ legacy_menu_admins,
count: counts[:admins],
updates: all_updates[:admins],
)
- # moderators
if reviewable.reviewable_by_moderator?
- notify(
- User.real.moderators.where("id NOT IN (?)", @contacted).pluck(:id),
+ new_menu_mods = User
+ .real
+ .moderators
+ .where("id IN (?)", redesigned_menu_enabled_user_ids - @contacted.to_a)
+ notify_users(new_menu_mods, all_updates[:moderators])
+
+ legacy_menu_mods = User.real.moderators.where("id NOT IN (?)", @contacted).pluck(:id)
+ notify_legacy(
+ legacy_menu_mods,
count: counts[:moderators],
updates: all_updates[:moderators],
)
end
- # category moderators
if SiteSetting.enable_category_group_moderation? && (group = reviewable.reviewable_by_group)
- group.users.includes(:group_users).where("users.id NOT IN (?)", @contacted).find_each do |user|
+ users = group.users.includes(:group_users).where("users.id NOT IN (?)", @contacted)
+ users.find_each do |user|
count = 0
updates = {}
-
user.group_users.each do |gu|
- count += counts[gu.group_id] || 0
- updates.merge!(all_updates[gu.group_id] || {})
+ updates.merge!(all_updates[gu.group_id])
+ count += counts[gu.group_id]
+ end
+ if redesigned_menu_enabled_user_ids.include?(user.id)
+ notify_user(user, updates)
+ else
+ notify_legacy([user.id], count: count, updates: updates)
end
-
- notify([user.id], count: count, updates: updates)
end
+ @contacted += users.pluck(:id)
end
end
protected
- def notify(user_ids, count:, updates:)
+ def notify_legacy(user_ids, count:, updates:)
return if user_ids.blank?
data = { reviewable_count: count }
@@ -74,4 +89,18 @@ class Jobs::NotifyReviewable < ::Jobs::Base
@contacted += user_ids
end
+ def notify_users(users, updates)
+ users.find_each { |user| notify_user(user, updates) }
+ @contacted += users.pluck(:id)
+ end
+
+ def notify_user(user, updates)
+ data = {
+ reviewable_count: user.reviewable_count,
+ unseen_reviewable_count: user.unseen_reviewable_count
+ }
+ data[:updates] = updates if updates.present?
+
+ user.publish_reviewable_counts(data)
+ end
end
diff --git a/app/models/reviewable.rb b/app/models/reviewable.rb
index d27924fc59..b1b5f518f4 100644
--- a/app/models/reviewable.rb
+++ b/app/models/reviewable.rb
@@ -541,6 +541,17 @@ class Reviewable < ActiveRecord::Base
result
end
+ def self.unseen_list_for(user, preload: true, limit: nil)
+ results = list_for(user, preload: preload, limit: limit)
+ if user.last_seen_reviewable_id
+ results = results.where(
+ "reviewables.id > ?",
+ user.last_seen_reviewable_id
+ )
+ end
+ results
+ end
+
def self.recent_list_with_pending_first(user, limit: 30)
min_score = Reviewable.min_score_for_priority
diff --git a/app/models/user.rb b/app/models/user.rb
index e8b4adc193..319221e209 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -487,6 +487,7 @@ class User < ActiveRecord::Base
def reload
@unread_notifications = nil
+ @all_unread_notifications_count = nil
@unread_total_notifications = nil
@unread_pms = nil
@unread_bookmarks = nil
@@ -587,6 +588,29 @@ class User < ActiveRecord::Base
end
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
+ ) 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
+
def total_unread_notifications
@unread_total_notifications ||= notifications.where("read = false").count
end
@@ -595,6 +619,10 @@ class User < ActiveRecord::Base
Reviewable.list_for(self).count
end
+ def unseen_reviewable_count
+ Reviewable.unseen_list_for(self).count
+ end
+
def saw_notification_id(notification_id)
if seen_notification_id.to_i < notification_id.to_i
update_columns(seen_notification_id: notification_id.to_i)
@@ -604,6 +632,29 @@ class User < ActiveRecord::Base
end
end
+ def bump_last_seen_reviewable!
+ query = Reviewable.unseen_list_for(self, preload: false)
+
+ if last_seen_reviewable_id
+ query = query.where("id > ?", last_seen_reviewable_id)
+ end
+ max_reviewable_id = query.maximum(:id)
+
+ if max_reviewable_id
+ update!(last_seen_reviewable_id: max_reviewable_id)
+ publish_reviewable_counts(unseen_reviewable_count: self.unseen_reviewable_count)
+ MessageBus.publish(
+ "/reviewable_counts",
+ { unseen_reviewable_count: self.unseen_reviewable_count },
+ user_ids: [self.id]
+ )
+ end
+ end
+
+ def publish_reviewable_counts(data)
+ MessageBus.publish("/reviewable_counts/#{self.id}", data, user_ids: [self.id])
+ end
+
TRACK_FIRST_NOTIFICATION_READ_DURATION = 1.week.to_i
def read_first_notification?
@@ -663,6 +714,10 @@ class User < ActiveRecord::Base
seen_notification_id: seen_notification_id,
}
+ if self.redesigned_user_menu_enabled?
+ payload[:all_unread_notifications_count] = all_unread_notifications_count
+ end
+
MessageBus.publish("/notification/#{id}", payload, user_ids: [id])
end
diff --git a/app/serializers/current_user_serializer.rb b/app/serializers/current_user_serializer.rb
index d341d01438..df807662f8 100644
--- a/app/serializers/current_user_serializer.rb
+++ b/app/serializers/current_user_serializer.rb
@@ -7,6 +7,7 @@ class CurrentUserSerializer < BasicUserSerializer
:unread_notifications,
:unread_private_messages,
:unread_high_priority_notifications,
+ :all_unread_notifications_count,
:read_first_notification?,
:admin?,
:notification_channel_position,
@@ -43,6 +44,7 @@ class CurrentUserSerializer < BasicUserSerializer
:dismissed_banner_key,
:is_anonymous,
:reviewable_count,
+ :unseen_reviewable_count,
:read_faq?,
:automatically_unpin_topics,
:mailing_list_mode,
@@ -338,4 +340,12 @@ class CurrentUserSerializer < BasicUserSerializer
def likes_notifications_disabled
object.user_option&.likes_notifications_disabled?
end
+
+ def include_all_unread_notifications_count?
+ redesigned_user_menu_enabled
+ end
+
+ def include_unseen_reviewable_count?
+ redesigned_user_menu_enabled
+ end
end
diff --git a/app/serializers/reviewable_perform_result_serializer.rb b/app/serializers/reviewable_perform_result_serializer.rb
index e0a7695eb1..6cd1f6fbdf 100644
--- a/app/serializers/reviewable_perform_result_serializer.rb
+++ b/app/serializers/reviewable_perform_result_serializer.rb
@@ -10,7 +10,8 @@ class ReviewablePerformResultSerializer < ApplicationSerializer
:created_post_topic_id,
:remove_reviewable_ids,
:version,
- :reviewable_count
+ :reviewable_count,
+ :unseen_reviewable_count
)
def success
@@ -42,6 +43,14 @@ class ReviewablePerformResultSerializer < ApplicationSerializer
end
def reviewable_count
- Reviewable.list_for(scope.user).count
+ scope.user.reviewable_count
+ end
+
+ def unseen_reviewable_count
+ scope.user.unseen_reviewable_count
+ end
+
+ def include_unseen_reviewable_count?
+ scope.user.redesigned_user_menu_enabled?
end
end
diff --git a/spec/jobs/notify_reviewable_spec.rb b/spec/jobs/notify_reviewable_spec.rb
index d54c08f704..3ade8a2282 100644
--- a/spec/jobs/notify_reviewable_spec.rb
+++ b/spec/jobs/notify_reviewable_spec.rb
@@ -1,102 +1,174 @@
# frozen_string_literal: true
RSpec.describe Jobs::NotifyReviewable do
- describe '.execute' do
- fab!(:admin) { Fabricate(:admin, moderator: true) }
- fab!(:moderator) { Fabricate(:moderator) }
+ # remove all the legacy stuff here when redesigned_user_menu_enabled is
+ # removed
+ describe '#execute' do
+ fab!(:legacy_menu_admin) { Fabricate(:admin, moderator: true) }
+ fab!(:legacy_menu_mod) { Fabricate(:moderator) }
fab!(:group_user) { Fabricate(:group_user) }
- let(:user) { group_user.user }
- let(:group) { group_user.group }
+ fab!(:legacy_menu_user) { group_user.user }
- it "will notify users of new reviewable content" do
+ fab!(:group) { group_user.group }
+
+ fab!(:new_menu_admin) { Fabricate(:admin, moderator: true) }
+ fab!(:new_menu_mod) { Fabricate(:moderator) }
+ fab!(:new_menu_user) { Fabricate(:user, groups: [group]) }
+
+ before do
+ [new_menu_admin, new_menu_mod, new_menu_user].each(&:enable_redesigned_user_menu)
+ end
+
+ after do
+ [new_menu_admin, new_menu_mod, new_menu_user].each(&:disable_redesigned_user_menu)
+ end
+
+ it "will notify users of new reviewable content and respects backward compatibility for the legacy user menu" do
SiteSetting.enable_category_group_moderation = true
- GroupUser.create!(group_id: group.id, user_id: moderator.id)
+ GroupUser.create!(group_id: group.id, user_id: legacy_menu_mod.id)
# Content for admins only
- r1 = Fabricate(:reviewable, reviewable_by_moderator: false)
- messages = MessageBus.track_publish("/reviewable_counts") do
- described_class.new.execute(reviewable_id: r1.id)
+ admin_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false)
+ new_menu_admin.update!(last_seen_reviewable_id: admin_reviewable.id)
+ messages = MessageBus.track_publish do
+ described_class.new.execute(reviewable_id: admin_reviewable.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(1)
- expect(messages.any? { |m| m.user_ids.include?(moderator.id) }).to eq(false)
- expect(messages.any? { |m| m.user_ids.include?(user.id) }).to eq(false)
+ expect(messages.size).to eq(2)
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids.include?(legacy_menu_admin.id) }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(1)
+ expect(legacy_menu_admin_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_admin_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_admin_msg = messages.find { |m| m.user_ids == [new_menu_admin.id] }
+ expect(new_menu_admin_msg.data[:reviewable_count]).to eq(1)
+ expect(new_menu_admin_msg.channel).to eq("/reviewable_counts/#{new_menu_admin.id}")
+ expect(new_menu_admin_msg.data[:unseen_reviewable_count]).to eq(0)
+
+ expect(messages.any? { |m| m.user_ids.include?(legacy_menu_mod.id) }).to eq(false)
+ expect(messages.any? { |m| m.user_ids.include?(legacy_menu_user.id) }).to eq(false)
+ expect(messages.any? { |m| m.user_ids.include?(new_menu_mod.id) }).to eq(false)
+ expect(messages.any? { |m| m.user_ids.include?(new_menu_user.id) }).to eq(false)
# Content for moderators
- r2 = Fabricate(:reviewable, reviewable_by_moderator: true)
- messages = MessageBus.track_publish("/reviewable_counts") do
- described_class.new.execute(reviewable_id: r2.id)
+ mod_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true)
+ messages = MessageBus.track_publish do
+ described_class.new.execute(reviewable_id: mod_reviewable.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(2)
- mod_msg = messages.find { |m| m.user_ids.include?(moderator.id) }
- expect(mod_msg.data[:reviewable_count]).to eq(1)
- expect(mod_msg.user_ids).to_not include(admin.id)
- expect(messages.any? { |m| m.user_ids.include?(user.id) }).to eq(false)
+ expect(messages.size).to eq(4)
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids == [legacy_menu_admin.id] }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(2)
+ expect(legacy_menu_admin_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_admin_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_admin_msg = messages.find { |m| m.user_ids == [new_menu_admin.id] }
+ expect(new_menu_admin_msg.data[:reviewable_count]).to eq(2)
+ expect(new_menu_admin_msg.channel).to eq("/reviewable_counts/#{new_menu_admin.id}")
+ expect(new_menu_admin_msg.data[:unseen_reviewable_count]).to eq(1)
+
+ legacy_menu_mod_msg = messages.find { |m| m.user_ids == [legacy_menu_mod.id] }
+ expect(legacy_menu_mod_msg.data[:reviewable_count]).to eq(1)
+ expect(legacy_menu_mod_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_mod_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_mod_msg = messages.find { |m| m.user_ids == [new_menu_mod.id] }
+ expect(new_menu_mod_msg.data[:reviewable_count]).to eq(1)
+ expect(new_menu_mod_msg.channel).to eq("/reviewable_counts/#{new_menu_mod.id}")
+ expect(new_menu_mod_msg.data[:unseen_reviewable_count]).to eq(1)
+
+ expect(messages.any? { |m| m.user_ids.include?(legacy_menu_user.id) }).to eq(false)
+ expect(messages.any? { |m| m.user_ids.include?(new_menu_user.id) }).to eq(false)
+
+ new_menu_mod.update!(last_seen_reviewable_id: mod_reviewable.id)
# Content for a group
- r3 = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group)
- messages = MessageBus.track_publish("/reviewable_counts") do
- described_class.new.execute(reviewable_id: r3.id)
+ group_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group)
+ messages = MessageBus.track_publish do
+ described_class.new.execute(reviewable_id: group_reviewable.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(3)
- mod_messages = messages.select { |m| m.user_ids.include?(moderator.id) }
- expect(mod_messages.size).to eq(1)
- expect(mod_messages[0].data[:reviewable_count]).to eq(2)
- group_msg = messages.find { |m| m.user_ids.include?(user.id) }
- expect(group_msg.data[:reviewable_count]).to eq(1)
+ expect(messages.size).to eq(6)
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids == [legacy_menu_admin.id] }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(3)
+ expect(legacy_menu_admin_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_admin_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_admin_msg = messages.find { |m| m.user_ids == [new_menu_admin.id] }
+ expect(new_menu_admin_msg.data[:reviewable_count]).to eq(3)
+ expect(new_menu_admin_msg.channel).to eq("/reviewable_counts/#{new_menu_admin.id}")
+ expect(new_menu_admin_msg.data[:unseen_reviewable_count]).to eq(2)
+
+ legacy_menu_mod_msg = messages.find { |m| m.user_ids == [legacy_menu_mod.id] }
+ expect(legacy_menu_mod_msg.data[:reviewable_count]).to eq(2)
+ expect(legacy_menu_mod_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_mod_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_mod_msg = messages.find { |m| m.user_ids == [new_menu_mod.id] }
+ expect(new_menu_mod_msg.data[:reviewable_count]).to eq(2)
+ expect(new_menu_mod_msg.channel).to eq("/reviewable_counts/#{new_menu_mod.id}")
+ expect(new_menu_mod_msg.data[:unseen_reviewable_count]).to eq(1)
+
+ legacy_menu_user_msg = messages.find { |m| m.user_ids == [legacy_menu_user.id] }
+ expect(legacy_menu_user_msg.data[:reviewable_count]).to eq(1)
+ expect(legacy_menu_user_msg.channel).to eq("/reviewable_counts")
+ expect(legacy_menu_user_msg.data.key?(:unseen_reviewable_count)).to eq(false)
+
+ new_menu_user_msg = messages.find { |m| m.user_ids == [new_menu_user.id] }
+ expect(new_menu_user_msg.data[:reviewable_count]).to eq(1)
+ expect(new_menu_user_msg.channel).to eq("/reviewable_counts/#{new_menu_user.id}")
+ expect(new_menu_user_msg.data[:unseen_reviewable_count]).to eq(1)
end
it "won't notify a group when disabled" do
SiteSetting.enable_category_group_moderation = false
- GroupUser.create!(group_id: group.id, user_id: moderator.id)
+ GroupUser.create!(group_id: group.id, user_id: legacy_menu_mod.id)
+ GroupUser.create!(group_id: group.id, user_id: new_menu_mod.id)
r3 = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group)
messages = MessageBus.track_publish("/reviewable_counts") do
described_class.new.execute(reviewable_id: r3.id)
end
- group_msg = messages.find { |m| m.user_ids.include?(user.id) }
+ group_msg = messages.find { |m| m.user_ids.include?(legacy_menu_user.id) }
+ expect(group_msg).to be_blank
+ group_msg = messages.find { |m| m.user_ids.include?(new_menu_user.id) }
expect(group_msg).to be_blank
end
- it "respects visibility" do
+ it "respects priority" do
SiteSetting.enable_category_group_moderation = true
Reviewable.set_priorities(medium: 2.0)
SiteSetting.reviewable_default_visibility = 'medium'
- GroupUser.create!(group_id: group.id, user_id: moderator.id)
+ GroupUser.create!(group_id: group.id, user_id: legacy_menu_mod.id)
# Content for admins only
r1 = Fabricate(:reviewable, reviewable_by_moderator: false)
messages = MessageBus.track_publish("/reviewable_counts") do
described_class.new.execute(reviewable_id: r1.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(0)
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids.include?(legacy_menu_admin.id) }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(0)
# Content for moderators
r2 = Fabricate(:reviewable, reviewable_by_moderator: true)
messages = MessageBus.track_publish("/reviewable_counts") do
described_class.new.execute(reviewable_id: r2.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(0)
- mod_msg = messages.find { |m| m.user_ids.include?(moderator.id) }
- expect(mod_msg.data[:reviewable_count]).to eq(0)
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids.include?(legacy_menu_admin.id) }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(0)
+ legacy_menu_mod_msg = messages.find { |m| m.user_ids.include?(legacy_menu_mod.id) }
+ expect(legacy_menu_mod_msg.data[:reviewable_count]).to eq(0)
# Content for a group
r3 = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group)
messages = MessageBus.track_publish("/reviewable_counts") do
described_class.new.execute(reviewable_id: r3.id)
end
- admin_msg = messages.find { |m| m.user_ids.include?(admin.id) }
- expect(admin_msg.data[:reviewable_count]).to eq(0)
- mod_messages = messages.select { |m| m.user_ids.include?(moderator.id) }
+ legacy_menu_admin_msg = messages.find { |m| m.user_ids.include?(legacy_menu_admin.id) }
+ expect(legacy_menu_admin_msg.data[:reviewable_count]).to eq(0)
+ mod_messages = messages.select { |m| m.user_ids.include?(legacy_menu_mod.id) }
expect(mod_messages.size).to eq(1)
expect(mod_messages[0].data[:reviewable_count]).to eq(0)
- group_msg = messages.find { |m| m.user_ids.include?(user.id) }
+ group_msg = messages.find { |m| m.user_ids.include?(legacy_menu_user.id) }
expect(group_msg.data[:reviewable_count]).to eq(0)
end
end
diff --git a/spec/models/reviewable_spec.rb b/spec/models/reviewable_spec.rb
index 72acf5e78f..c404f14ef9 100644
--- a/spec/models/reviewable_spec.rb
+++ b/spec/models/reviewable_spec.rb
@@ -244,6 +244,63 @@ RSpec.describe Reviewable, type: :model do
end
end
+ describe ".unseen_list_for" do
+ fab!(:admin) { Fabricate(:admin) }
+ fab!(:moderator) { Fabricate(:moderator) }
+ fab!(:group) { Fabricate(:group) }
+ fab!(:user) { Fabricate(:user, groups: [group]) }
+ fab!(:admin_reviewable) { Fabricate(:reviewable, reviewable_by_moderator: false) }
+ fab!(:mod_reviewable) { Fabricate(:reviewable, reviewable_by_moderator: true) }
+ fab!(:group_reviewable) {
+ Fabricate(:reviewable, reviewable_by_group: group, reviewable_by_moderator: false)
+ }
+
+ context "for admins" do
+ it "returns a list of pending reviewables that haven't been seen by the user" do
+ list = Reviewable.unseen_list_for(admin, preload: false)
+ expect(list).to contain_exactly(admin_reviewable, mod_reviewable, group_reviewable)
+ admin_reviewable.update!(status: Reviewable.statuses[:approved])
+ list = Reviewable.unseen_list_for(admin, preload: false)
+ expect(list).to contain_exactly(mod_reviewable, group_reviewable)
+ admin.update!(last_seen_reviewable_id: group_reviewable.id)
+ expect(Reviewable.unseen_list_for(admin, preload: false).empty?).to eq(true)
+ end
+ end
+
+ context "for moderators" do
+ it "returns a list of pending reviewables that haven't been seen by the user" do
+ list = Reviewable.unseen_list_for(moderator, preload: false)
+ expect(list).to contain_exactly(mod_reviewable)
+
+ group_reviewable.update!(reviewable_by_moderator: true)
+
+ list = Reviewable.unseen_list_for(moderator, preload: false)
+ expect(list).to contain_exactly(mod_reviewable, group_reviewable)
+
+ moderator.update!(last_seen_reviewable_id: mod_reviewable.id)
+
+ list = Reviewable.unseen_list_for(moderator, preload: false)
+ expect(list).to contain_exactly(group_reviewable)
+ end
+ end
+
+ context "for group moderators" do
+ before do
+ SiteSetting.enable_category_group_moderation = true
+ end
+
+ it "returns a list of pending reviewables that haven't been seen by the user" do
+ list = Reviewable.unseen_list_for(user, preload: false)
+ expect(list).to contain_exactly(group_reviewable)
+
+ user.update!(last_seen_reviewable_id: group_reviewable.id)
+
+ list = Reviewable.unseen_list_for(user, preload: false)
+ expect(list).to be_empty
+ end
+ end
+ end
+
describe ".recent_list_with_pending_first" do
fab!(:pending_reviewable1) do
Fabricate(
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 0e7381c683..0ff81b202d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2057,6 +2057,24 @@ RSpec.describe User do
expect(message).to eq(nil)
end
+
+ context "with redesigned_user_menu_enabled on" do
+ it "adds all_unread_notifications_count to the payload" do
+ user.update!(admin: true)
+ user.enable_redesigned_user_menu
+ Fabricate(:notification, user: user)
+ Fabricate(:notification, notification_type: 15, high_priority: true, read: false, user: user)
+ messages = MessageBus.track_publish("/notification/#{user.id}") do
+ user.publish_notifications_state
+ end
+ expect(messages.size).to eq(1)
+
+ message = messages.first
+ expect(message.data[:all_unread_notifications_count]).to eq(2)
+ ensure
+ user.disable_redesigned_user_menu
+ end
+ end
end
describe "silenced?" do
@@ -2780,4 +2798,112 @@ RSpec.describe User do
expect(user.whisperer?).to eq(false)
end
end
+
+ describe "#all_unread_notifications_count" do
+ it "returns count of unseen and unread high priority and normal priority notifications" do
+ Fabricate(:notification, user: user, high_priority: true, read: false)
+ n2 = Fabricate(:notification, user: user, high_priority: false, read: false)
+ expect(user.all_unread_notifications_count).to eq(2)
+
+ n2.update!(read: true)
+ user.reload
+
+ expect(user.all_unread_notifications_count).to eq(1)
+
+ user.update!(seen_notification_id: n2.id)
+ user.reload
+
+ expect(user.all_unread_notifications_count).to eq(0)
+
+ n3 = Fabricate(:notification, user: user)
+ user.reload
+
+ expect(user.all_unread_notifications_count).to eq(1)
+
+ n3.topic.trash!(Fabricate(:admin))
+ user.reload
+
+ expect(user.all_unread_notifications_count).to eq(0)
+ end
+ end
+
+ describe "#unseen_reviewable_count" do
+ fab!(:admin_reviewable) { Fabricate(:reviewable, reviewable_by_moderator: false) }
+ fab!(:mod_reviewable) { Fabricate(:reviewable, reviewable_by_moderator: true) }
+ fab!(:group_reviewable) { Fabricate(:reviewable, reviewable_by_moderator: false, reviewable_by_group: group) }
+
+ it "doesn't include reviewables that can't be seen by the user" do
+ SiteSetting.enable_category_group_moderation = true
+ expect(user.unseen_reviewable_count).to eq(0)
+ user.groups << group
+ user.save!
+ expect(user.unseen_reviewable_count).to eq(1)
+ user.update!(moderator: true)
+ expect(user.unseen_reviewable_count).to eq(2)
+ user.update!(admin: true)
+ expect(user.unseen_reviewable_count).to eq(3)
+ end
+
+ it "returns count of unseen reviewables" do
+ user.update!(admin: true)
+ expect(user.unseen_reviewable_count).to eq(3)
+ user.update!(last_seen_reviewable_id: mod_reviewable.id)
+ expect(user.unseen_reviewable_count).to eq(1)
+ user.update!(last_seen_reviewable_id: group_reviewable.id)
+ expect(user.unseen_reviewable_count).to eq(0)
+ end
+ end
+
+ describe "#bump_last_seen_reviewable!" do
+ it "doesn't error if there are no reviewables" do
+ Reviewable.destroy_all
+ user.bump_last_seen_reviewable!
+ expect(user.last_seen_reviewable_id).to eq(nil)
+ end
+
+ it "picks the reviewable of the largest id" do
+ user.update!(admin: true)
+ Fabricate(
+ :reviewable,
+ created_at: 3.minutes.ago,
+ updated_at: 3.minutes.ago,
+ score: 100
+ )
+ reviewable2 = Fabricate(
+ :reviewable,
+ created_at: 30.minutes.ago,
+ updated_at: 30.minutes.ago,
+ score: 10
+ )
+ user.bump_last_seen_reviewable!
+ expect(user.last_seen_reviewable_id).to eq(reviewable2.id)
+ end
+
+ it "stays at the maximum reviewable if there are no new reviewables" do
+ user.update!(admin: true)
+ reviewable = Fabricate(:reviewable)
+ user.bump_last_seen_reviewable!
+ expect(user.last_seen_reviewable_id).to eq(reviewable.id)
+ user.bump_last_seen_reviewable!
+ expect(user.last_seen_reviewable_id).to eq(reviewable.id)
+ end
+
+ it "respects reviewables security" do
+ admin = Fabricate(:admin)
+ moderator = Fabricate(:moderator)
+ group = Fabricate(:group)
+ user.update!(groups: [group])
+ SiteSetting.enable_category_group_moderation = true
+
+ group_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false, reviewable_by_group: group)
+ mod_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true)
+ admin_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false)
+
+ [admin, moderator, user].each(&:bump_last_seen_reviewable!)
+
+ expect(admin.last_seen_reviewable_id).to eq(admin_reviewable.id)
+ expect(moderator.last_seen_reviewable_id).to eq(mod_reviewable.id)
+ expect(user.last_seen_reviewable_id).to eq(group_reviewable.id)
+ end
+ end
end
diff --git a/spec/requests/notifications_controller_spec.rb b/spec/requests/notifications_controller_spec.rb
index 4e44c9f499..4b87537ba6 100644
--- a/spec/requests/notifications_controller_spec.rb
+++ b/spec/requests/notifications_controller_spec.rb
@@ -87,6 +87,60 @@ RSpec.describe NotificationsController do
Discourse.clear_redis_readonly!
end
+ it "should not bump last seen reviewable in readonly mode" do
+ user.update!(admin: true)
+ Fabricate(:reviewable)
+ Discourse.received_redis_readonly!
+ expect {
+ get "/notifications.json", params: { recent: true }
+ expect(response.status).to eq(200)
+ }.not_to change { user.reload.last_seen_reviewable_id }
+ ensure
+ Discourse.clear_redis_readonly!
+ end
+
+ it "should not bump last seen reviewable if the user can't seen reviewables" do
+ Fabricate(:reviewable)
+ expect {
+ get "/notifications.json", params: { recent: true, bump_last_seen_reviewable: true }
+ expect(response.status).to eq(200)
+ }.not_to change { user.reload.last_seen_reviewable_id }
+ end
+
+ it "should not bump last seen reviewable if the silent param is present" do
+ user.update!(admin: true)
+ Fabricate(:reviewable)
+ expect {
+ get "/notifications.json", params: {
+ recent: true,
+ silent: true,
+ bump_last_seen_reviewable: true
+ }
+ expect(response.status).to eq(200)
+ }.not_to change { user.reload.last_seen_reviewable_id }
+ end
+
+ it "should not bump last seen reviewable if the bump_last_seen_reviewable param is not present" do
+ user.update!(admin: true)
+ Fabricate(:reviewable)
+ expect {
+ get "/notifications.json", params: { recent: true, silent: true }
+ expect(response.status).to eq(200)
+ }.not_to change { user.reload.last_seen_reviewable_id }
+ end
+
+ it "bumps last_seen_reviewable_id" do
+ user.update!(admin: true)
+ expect(user.last_seen_reviewable_id).to eq(nil)
+ reviewable = Fabricate(:reviewable)
+ get "/notifications.json", params: { recent: true, bump_last_seen_reviewable: true }
+ expect(user.reload.last_seen_reviewable_id).to eq(reviewable.id)
+
+ reviewable2 = Fabricate(:reviewable)
+ get "/notifications.json", params: { recent: true, bump_last_seen_reviewable: true }
+ expect(user.reload.last_seen_reviewable_id).to eq(reviewable2.id)
+ end
+
it "get notifications with all filters" do
notification = Fabricate(:notification, user: user)
notification2 = Fabricate(:notification, user: user)
diff --git a/spec/requests/reviewables_controller_spec.rb b/spec/requests/reviewables_controller_spec.rb
index f959ce6fa7..daa6ae1191 100644
--- a/spec/requests/reviewables_controller_spec.rb
+++ b/spec/requests/reviewables_controller_spec.rb
@@ -76,6 +76,7 @@ RSpec.describe ReviewablesController do
expect(json['users'].any? { |u| u['id'] == reviewable.target_created_by_id }).to eq(true)
expect(json['meta']['reviewable_count']).to eq(1)
+ expect(json['meta']['unseen_reviewable_count']).to eq(1)
expect(json['meta']['status']).to eq("pending")
end