2019-04-29 20:27:42 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2022-07-27 22:27:38 -04:00
|
|
|
RSpec.describe Notification do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
|
|
|
fab!(:coding_horror)
|
2021-12-15 12:41:14 -05:00
|
|
|
|
2016-12-21 23:29:34 -05:00
|
|
|
before { NotificationEmailer.enable }
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2015-01-05 11:04:23 -05:00
|
|
|
it { is_expected.to validate_presence_of :notification_type }
|
|
|
|
it { is_expected.to validate_presence_of :data }
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2015-01-05 11:04:23 -05:00
|
|
|
it { is_expected.to belong_to :user }
|
|
|
|
it { is_expected.to belong_to :topic }
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2016-01-08 05:53:52 -05:00
|
|
|
describe "#types" do
|
2022-07-27 12:14:14 -04:00
|
|
|
context "when verifying enum sequence" do
|
2016-01-08 05:53:52 -05:00
|
|
|
before { @types = Notification.types }
|
|
|
|
|
2020-08-19 06:07:51 -04:00
|
|
|
it "has a correct position for each type" do
|
2016-01-08 05:53:52 -05:00
|
|
|
expect(@types[:mentioned]).to eq(1)
|
2020-08-19 06:07:51 -04:00
|
|
|
expect(@types[:replied]).to eq(2)
|
|
|
|
expect(@types[:quoted]).to eq(3)
|
|
|
|
expect(@types[:edited]).to eq(4)
|
|
|
|
expect(@types[:liked]).to eq(5)
|
|
|
|
expect(@types[:private_message]).to eq(6)
|
|
|
|
expect(@types[:invited_to_private_message]).to eq(7)
|
|
|
|
expect(@types[:invitee_accepted]).to eq(8)
|
|
|
|
expect(@types[:posted]).to eq(9)
|
|
|
|
expect(@types[:moved_post]).to eq(10)
|
|
|
|
expect(@types[:linked]).to eq(11)
|
|
|
|
expect(@types[:granted_badge]).to eq(12)
|
|
|
|
expect(@types[:invited_to_topic]).to eq(13)
|
|
|
|
expect(@types[:custom]).to eq(14)
|
2016-01-08 05:53:52 -05:00
|
|
|
expect(@types[:group_mentioned]).to eq(15)
|
2020-08-19 06:07:51 -04:00
|
|
|
expect(@types[:group_message_summary]).to eq(16)
|
|
|
|
expect(@types[:watching_first_post]).to eq(17)
|
|
|
|
expect(@types[:topic_reminder]).to eq(18)
|
|
|
|
expect(@types[:liked_consolidated]).to eq(19)
|
|
|
|
expect(@types[:post_approved]).to eq(20)
|
|
|
|
expect(@types[:code_review_commit_approved]).to eq(21)
|
|
|
|
expect(@types[:membership_request_accepted]).to eq(22)
|
|
|
|
expect(@types[:membership_request_consolidated]).to eq(23)
|
|
|
|
expect(@types[:bookmark_reminder]).to eq(24)
|
|
|
|
expect(@types[:reaction]).to eq(25)
|
|
|
|
expect(@types[:votes_released]).to eq(26)
|
|
|
|
expect(@types[:event_reminder]).to eq(27)
|
|
|
|
expect(@types[:event_invitation]).to eq(28)
|
2021-09-14 10:57:38 -04:00
|
|
|
expect(@types[:chat_mention]).to eq(29)
|
|
|
|
expect(@types[:chat_message]).to eq(30)
|
2022-03-16 16:08:10 -04:00
|
|
|
expect(@types[:assigned]).to eq(34)
|
2016-01-08 05:53:52 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-05-13 21:59:55 -04:00
|
|
|
describe "post" do
|
|
|
|
let(:topic) { Fabricate(:topic) }
|
|
|
|
let(:post_args) { { user: topic.user, topic: topic } }
|
|
|
|
|
|
|
|
describe "replies" do
|
2014-03-18 00:22:39 -04:00
|
|
|
def process_alerts(post)
|
|
|
|
PostAlerter.post_created(post)
|
|
|
|
end
|
|
|
|
|
|
|
|
let(:post) { process_alerts(Fabricate(:post, post_args.merge(raw: "Hello @CodingHorror"))) }
|
2013-05-13 21:59:55 -04:00
|
|
|
|
|
|
|
it "notifies the poster on reply" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
2014-03-18 00:22:39 -04:00
|
|
|
reply = Fabricate(:basic_reply, user: coding_horror, topic: post.topic)
|
|
|
|
process_alerts(reply)
|
2015-01-05 11:04:23 -05:00
|
|
|
}.to change(post.user.notifications, :count).by(1)
|
2013-05-13 21:59:55 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't notify the poster when they reply to their own post" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
2014-03-18 00:22:39 -04:00
|
|
|
reply = Fabricate(:basic_reply, user: post.user, topic: post.topic)
|
|
|
|
process_alerts(reply)
|
2015-01-05 11:04:23 -05:00
|
|
|
}.not_to change(post.user.notifications, :count)
|
2013-05-13 21:59:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "watching" do
|
|
|
|
it "does notify watching users of new posts" do
|
2014-03-18 00:22:39 -04:00
|
|
|
post = PostAlerter.post_created(Fabricate(:post, post_args))
|
2021-12-15 12:41:14 -05:00
|
|
|
user2 = coding_horror
|
2013-05-13 21:59:55 -04:00
|
|
|
post_args[:topic].notify_watch!(user2)
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
2014-03-18 00:22:39 -04:00
|
|
|
PostAlerter.post_created(Fabricate(:post, user: post.user, topic: post.topic))
|
2015-01-05 11:04:23 -05:00
|
|
|
}.to change(user2.notifications, :count).by(1)
|
2013-05-13 21:59:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "muting" do
|
|
|
|
it "does not notify users of new posts" do
|
|
|
|
post = Fabricate(:post, post_args)
|
|
|
|
user = post_args[:user]
|
2021-12-15 12:41:14 -05:00
|
|
|
user2 = coding_horror
|
2013-05-13 21:59:55 -04:00
|
|
|
|
|
|
|
post_args[:topic].notify_muted!(user)
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
2013-05-13 21:59:55 -04:00
|
|
|
Fabricate(:post, user: user2, topic: post.topic, raw: "hello @" + user.username)
|
2022-07-19 10:03:03 -04:00
|
|
|
}.not_to change(user.notifications, :count)
|
2013-05-13 21:59:55 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2016-02-01 13:12:10 -05:00
|
|
|
|
2020-05-07 00:35:32 -04:00
|
|
|
describe "high priority creation" do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
2020-05-07 00:35:32 -04:00
|
|
|
|
|
|
|
it "automatically marks the notification as high priority if it is a high priority type" do
|
|
|
|
notif =
|
|
|
|
Notification.create(
|
|
|
|
user: user,
|
|
|
|
notification_type: Notification.types[:bookmark_reminder],
|
|
|
|
data: {
|
2023-01-09 06:18:21 -05:00
|
|
|
},
|
2020-05-07 00:35:32 -04:00
|
|
|
)
|
|
|
|
expect(notif.high_priority).to eq(true)
|
|
|
|
notif =
|
|
|
|
Notification.create(
|
|
|
|
user: user,
|
|
|
|
notification_type: Notification.types[:private_message],
|
|
|
|
data: {
|
2023-01-09 06:18:21 -05:00
|
|
|
},
|
2020-05-07 00:35:32 -04:00
|
|
|
)
|
|
|
|
expect(notif.high_priority).to eq(true)
|
|
|
|
notif =
|
|
|
|
Notification.create(user: user, notification_type: Notification.types[:liked], data: {})
|
|
|
|
expect(notif.high_priority).to eq(false)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "allows manually specifying a notification is high priority" do
|
|
|
|
notif =
|
|
|
|
Notification.create(
|
|
|
|
user: user,
|
|
|
|
notification_type: Notification.types[:liked],
|
|
|
|
data: {
|
|
|
|
},
|
|
|
|
high_priority: true,
|
|
|
|
)
|
|
|
|
expect(notif.high_priority).to eq(true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
describe "unread counts" do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
2013-02-05 14:16:51 -05:00
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with a regular notification" do
|
2013-02-05 14:16:51 -05:00
|
|
|
it "increases unread_notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
|
|
|
Fabricate(:notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :unread_notifications)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2014-10-13 06:26:30 -04:00
|
|
|
it "increases total_unread_notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
|
|
|
Fabricate(:notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :total_unread_notifications)
|
2014-10-13 06:26:30 -04:00
|
|
|
end
|
|
|
|
|
2023-08-01 00:44:39 -04:00
|
|
|
it "doesn't increase unread_high_priority_notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
|
|
|
Fabricate(:notification, user: user)
|
|
|
|
user.reload
|
2023-08-01 00:44:39 -04:00
|
|
|
}.not_to change(user, :unread_high_priority_notifications)
|
2013-02-25 11:42:20 -05:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with a private message" do
|
2013-02-05 14:16:51 -05:00
|
|
|
it "doesn't increase unread_notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
|
|
|
Fabricate(:private_message_notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.not_to change(user, :unread_notifications)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2014-10-13 06:26:30 -04:00
|
|
|
it "increases total_unread_notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect {
|
|
|
|
Fabricate(:notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :total_unread_notifications)
|
2014-10-13 06:26:30 -04:00
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
it "increases unread_high_priority_notifications" do
|
|
|
|
expect {
|
|
|
|
Fabricate(:private_message_notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :unread_high_priority_notifications)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "with a bookmark reminder message" do
|
2020-03-31 19:09:20 -04:00
|
|
|
it "doesn't increase unread_notifications" do
|
|
|
|
expect {
|
|
|
|
Fabricate(:bookmark_reminder_notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.not_to change(user, :unread_notifications)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "increases total_unread_notifications" do
|
|
|
|
expect {
|
|
|
|
Fabricate(:notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :total_unread_notifications)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "increases unread_high_priority_notifications" do
|
|
|
|
expect {
|
|
|
|
Fabricate(:bookmark_reminder_notification, user: user)
|
|
|
|
user.reload
|
|
|
|
}.to change(user, :unread_high_priority_notifications)
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "message bus" do
|
2021-11-21 23:59:10 -05:00
|
|
|
fab!(:user) { Fabricate(:user, last_seen_at: 1.day.ago) }
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
it "updates the notification count on create" do
|
2013-05-16 01:03:03 -04:00
|
|
|
Notification.any_instance.expects(:refresh_notification_count).returns(nil)
|
2013-02-05 14:16:51 -05:00
|
|
|
Fabricate(:notification)
|
|
|
|
end
|
|
|
|
|
2020-02-14 03:19:40 -05:00
|
|
|
it "works" do
|
|
|
|
messages =
|
|
|
|
MessageBus.track_publish do
|
|
|
|
user.notifications.create!(notification_type: Notification.types[:mentioned], data: "{}")
|
|
|
|
user.notifications.create!(notification_type: Notification.types[:mentioned], data: "{}")
|
|
|
|
end
|
|
|
|
|
|
|
|
expect(messages.size).to eq(2)
|
|
|
|
expect(messages[0].channel).to eq("/notification/#{user.id}")
|
|
|
|
expect(messages[0].data[:unread_notifications]).to eq(1)
|
|
|
|
expect(messages[1].channel).to eq("/notification/#{user.id}")
|
|
|
|
expect(messages[1].data[:unread_notifications]).to eq(2)
|
|
|
|
end
|
|
|
|
|
2019-10-21 14:33:58 -04:00
|
|
|
it "works for partial model instances" do
|
|
|
|
NotificationEmailer.disable
|
2020-02-14 03:19:40 -05:00
|
|
|
partial_user = User.select(:id).find_by(id: user.id)
|
2019-10-21 14:33:58 -04:00
|
|
|
partial_user.notifications.create!(
|
|
|
|
notification_type: Notification.types[:mentioned],
|
|
|
|
data: "{}",
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2022-07-27 12:14:14 -04:00
|
|
|
context "when destroying" do
|
2013-02-05 14:16:51 -05:00
|
|
|
let!(:notification) { Fabricate(:notification) }
|
|
|
|
|
|
|
|
it "updates the notification count on destroy" do
|
2013-05-16 01:03:03 -04:00
|
|
|
Notification.any_instance.expects(:refresh_notification_count).returns(nil)
|
2017-03-20 00:06:37 -04:00
|
|
|
notification.destroy!
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-25 11:42:20 -05:00
|
|
|
describe "private message" do
|
|
|
|
before do
|
2013-02-05 14:16:51 -05:00
|
|
|
@topic = Fabricate(:private_message_topic)
|
2013-03-23 11:02:59 -04:00
|
|
|
@post = Fabricate(:post, topic: @topic, user: @topic.user)
|
2013-02-05 14:16:51 -05:00
|
|
|
@target = @post.topic.topic_allowed_users.reject { |a| a.user_id == @post.user_id }[0].user
|
2016-03-21 23:50:12 -04:00
|
|
|
|
|
|
|
TopicUser.change(
|
|
|
|
@target.id,
|
|
|
|
@topic.id,
|
|
|
|
notification_level: TopicUser.notification_levels[:watching],
|
|
|
|
)
|
|
|
|
|
|
|
|
PostAlerter.post_created(@post)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
|
2023-06-13 16:02:21 -04:00
|
|
|
it "should create and roll up private message notifications" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(@target.notifications.first.notification_type).to eq(
|
|
|
|
Notification.types[:private_message],
|
|
|
|
)
|
|
|
|
expect(@post.user.unread_notifications).to eq(0)
|
|
|
|
expect(@post.user.total_unread_notifications).to eq(0)
|
2023-08-01 00:44:39 -04:00
|
|
|
expect(@target.unread_high_priority_notifications).to eq(1)
|
2014-01-20 00:18:43 -05:00
|
|
|
|
|
|
|
Fabricate(:post, topic: @topic, user: @topic.user)
|
|
|
|
@target.reload
|
2023-08-01 00:44:39 -04:00
|
|
|
expect(@target.unread_high_priority_notifications).to eq(1)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe ".post" do
|
|
|
|
let(:post) { Fabricate(:post) }
|
|
|
|
let!(:notification) do
|
|
|
|
Fabricate(:notification, user: post.user, topic: post.topic, post_number: post.post_number)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
|
|
|
|
it "returns the post" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(notification.post).to eq(post)
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "data" do
|
|
|
|
let(:notification) { Fabricate.build(:notification) }
|
2013-02-25 11:42:20 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
it "should have a data hash" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(notification.data_hash).to be_present
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
2013-02-25 11:42:20 -05:00
|
|
|
|
2013-02-05 14:16:51 -05:00
|
|
|
it "should have the data within the json" do
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(notification.data_hash[:poison]).to eq("ivy")
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-05-16 02:37:47 -04:00
|
|
|
describe "saw_regular_notification_id" do
|
|
|
|
it "correctly updates the read state" do
|
2015-02-18 21:20:25 -05:00
|
|
|
t = Fabricate(:topic)
|
|
|
|
|
2014-01-20 00:18:43 -05:00
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
2013-05-16 02:37:47 -04:00
|
|
|
user_id: user.id,
|
2015-02-18 21:20:25 -05:00
|
|
|
topic_id: t.id,
|
2013-05-16 02:37:47 -04:00
|
|
|
post_number: 1,
|
2014-02-03 20:57:52 -05:00
|
|
|
data: "{}",
|
2013-05-16 02:37:47 -04:00
|
|
|
notification_type: Notification.types[:private_message],
|
|
|
|
)
|
2023-01-09 06:18:21 -05:00
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: t.id,
|
|
|
|
post_number: 1,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: Notification.types[:bookmark_reminder],
|
|
|
|
)
|
2023-01-09 06:18:21 -05:00
|
|
|
|
2013-05-16 02:37:47 -04:00
|
|
|
other =
|
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
2015-02-18 21:20:25 -05:00
|
|
|
topic_id: t.id,
|
2013-05-16 02:37:47 -04:00
|
|
|
post_number: 1,
|
2014-02-03 20:57:52 -05:00
|
|
|
data: "{}",
|
2013-05-16 02:37:47 -04:00
|
|
|
notification_type: Notification.types[:mentioned],
|
|
|
|
)
|
|
|
|
|
2022-09-12 14:19:25 -04:00
|
|
|
user.bump_last_seen_notification!
|
2013-05-16 02:37:47 -04:00
|
|
|
user.reload
|
|
|
|
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(user.unread_notifications).to eq(0)
|
2020-03-31 19:09:20 -04:00
|
|
|
expect(user.total_unread_notifications).to eq(3)
|
|
|
|
expect(user.unread_high_priority_notifications).to eq(2)
|
2013-05-16 02:37:47 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-02-25 11:42:20 -05:00
|
|
|
describe "mark_posts_read" do
|
|
|
|
it "marks multiple posts as read if needed" do
|
2014-01-20 00:18:43 -05:00
|
|
|
(1..3).map do |i|
|
2014-02-03 20:56:28 -05:00
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: i,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
2013-02-25 02:42:42 -05:00
|
|
|
end
|
2014-02-03 20:56:28 -05:00
|
|
|
Notification.create!(
|
|
|
|
read: true,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: 4,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
2013-02-25 02:42:42 -05:00
|
|
|
|
2015-12-16 10:44:52 -05:00
|
|
|
expect { Notification.mark_posts_read(user, 2, [1, 2, 3, 4]) }.to change {
|
|
|
|
Notification.where(read: true).count
|
|
|
|
}.by(3)
|
2013-02-25 02:42:42 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
describe "#ensure_consistency!" do
|
2013-05-16 03:50:14 -04:00
|
|
|
it "deletes notifications if post is missing or deleted" do
|
2016-12-21 23:29:34 -05:00
|
|
|
NotificationEmailer.disable
|
|
|
|
|
2013-05-16 03:50:14 -04:00
|
|
|
p = Fabricate(:post)
|
|
|
|
p2 = Fabricate(:post)
|
|
|
|
|
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: p.user_id,
|
|
|
|
topic_id: p.topic_id,
|
|
|
|
post_number: p.post_number,
|
|
|
|
data: "[]",
|
|
|
|
notification_type: Notification.types[:private_message],
|
|
|
|
)
|
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: p2.user_id,
|
|
|
|
topic_id: p2.topic_id,
|
|
|
|
post_number: p2.post_number,
|
|
|
|
data: "[]",
|
|
|
|
notification_type: Notification.types[:private_message],
|
|
|
|
)
|
2020-03-31 19:09:20 -04:00
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: p2.user_id,
|
|
|
|
topic_id: p2.topic_id,
|
|
|
|
post_number: p2.post_number,
|
|
|
|
data: "[]",
|
|
|
|
notification_type: Notification.types[:bookmark_reminder],
|
|
|
|
)
|
2013-05-16 03:50:14 -04:00
|
|
|
|
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: p2.user_id,
|
|
|
|
topic_id: p2.topic_id,
|
|
|
|
post_number: p2.post_number,
|
|
|
|
data: "[]",
|
|
|
|
notification_type: Notification.types[:liked],
|
|
|
|
)
|
2013-07-09 15:20:18 -04:00
|
|
|
p2.trash!(p.user)
|
2013-05-16 03:50:14 -04:00
|
|
|
|
|
|
|
# we may want to make notification "trashable" but for now we nuke pm notifications from deleted topics/posts
|
|
|
|
Notification.ensure_consistency!
|
|
|
|
|
2015-01-05 11:04:23 -05:00
|
|
|
expect(Notification.count).to eq(2)
|
2013-05-16 03:50:14 -04:00
|
|
|
end
|
2021-07-19 15:52:12 -04:00
|
|
|
|
2022-03-30 10:56:35 -04:00
|
|
|
it "does not delete notifications that do not have a topic_id" do
|
2021-07-19 15:52:12 -04:00
|
|
|
Notification.create!(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: nil,
|
|
|
|
post_number: nil,
|
|
|
|
data: "[]",
|
2022-03-30 10:56:35 -04:00
|
|
|
notification_type: Notification.types[:chat_mention],
|
|
|
|
high_priority: true,
|
|
|
|
)
|
2021-07-19 15:52:12 -04:00
|
|
|
expect { Notification.ensure_consistency! }.to_not change { Notification.count }
|
|
|
|
end
|
2013-05-16 03:50:14 -04:00
|
|
|
end
|
|
|
|
|
2020-12-18 10:03:51 -05:00
|
|
|
describe "do not disturb" do
|
|
|
|
it "calls NotificationEmailer.process_notification when user is not in 'do not disturb'" do
|
|
|
|
notification =
|
|
|
|
Notification.new(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: 1,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
|
|
|
NotificationEmailer.expects(:process_notification).with(notification)
|
|
|
|
notification.save!
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't call NotificationEmailer.process_notification when user is in 'do not disturb'" do
|
|
|
|
freeze_time
|
|
|
|
Fabricate(
|
|
|
|
:do_not_disturb_timing,
|
|
|
|
user: user,
|
|
|
|
starts_at: Time.zone.now,
|
|
|
|
ends_at: 1.day.from_now,
|
|
|
|
)
|
|
|
|
|
|
|
|
notification =
|
|
|
|
Notification.new(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: 1,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
|
|
|
NotificationEmailer.expects(:process_notification).with(notification).never
|
|
|
|
notification.save!
|
|
|
|
end
|
|
|
|
end
|
2013-02-05 14:16:51 -05:00
|
|
|
end
|
2014-02-13 01:12:17 -05:00
|
|
|
|
|
|
|
# pulling this out cause I don't want an observer
|
2022-07-27 22:27:38 -04:00
|
|
|
RSpec.describe Notification do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
2022-09-12 14:19:25 -04:00
|
|
|
|
|
|
|
describe ".prioritized_list" do
|
|
|
|
def create(**opts)
|
|
|
|
opts[:user] = user if !opts[:user]
|
|
|
|
Fabricate(:notification, user: user, **opts)
|
|
|
|
end
|
|
|
|
|
|
|
|
fab!(:unread_high_priority_1) do
|
|
|
|
create(high_priority: true, read: false, created_at: 8.minutes.ago)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2022-09-12 14:19:25 -04:00
|
|
|
fab!(:read_high_priority_1) do
|
|
|
|
create(high_priority: true, read: true, created_at: 7.minutes.ago)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2022-09-12 14:19:25 -04:00
|
|
|
fab!(:unread_regular_1) { create(high_priority: false, read: false, created_at: 6.minutes.ago) }
|
|
|
|
fab!(:read_regular_1) { create(high_priority: false, read: true, created_at: 5.minutes.ago) }
|
2022-11-15 21:32:05 -05:00
|
|
|
fab!(:unread_like) do
|
|
|
|
create(
|
|
|
|
high_priority: false,
|
|
|
|
read: false,
|
|
|
|
created_at: 130.seconds.ago,
|
|
|
|
notification_type: Notification.types[:liked],
|
|
|
|
)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2022-09-12 14:19:25 -04:00
|
|
|
|
|
|
|
fab!(:unread_high_priority_2) do
|
|
|
|
create(high_priority: true, read: false, created_at: 1.minutes.ago)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2022-09-12 14:19:25 -04:00
|
|
|
fab!(:read_high_priority_2) do
|
|
|
|
create(high_priority: true, read: true, created_at: 2.minutes.ago)
|
2023-01-09 06:18:21 -05:00
|
|
|
end
|
2022-09-12 14:19:25 -04:00
|
|
|
fab!(:unread_regular_2) { create(high_priority: false, read: false, created_at: 3.minutes.ago) }
|
|
|
|
fab!(:read_regular_2) { create(high_priority: false, read: true, created_at: 4.minutes.ago) }
|
|
|
|
|
|
|
|
it "puts unread high_priority on top followed by unread normal notifications and then everything else in reverse chronological order" do
|
|
|
|
expect(Notification.prioritized_list(user).map(&:id)).to eq(
|
|
|
|
[
|
|
|
|
unread_high_priority_2,
|
|
|
|
unread_high_priority_1,
|
|
|
|
unread_regular_2,
|
|
|
|
unread_regular_1,
|
|
|
|
read_high_priority_2,
|
2022-11-15 21:32:05 -05:00
|
|
|
unread_like,
|
2022-09-12 14:19:25 -04:00
|
|
|
read_regular_2,
|
|
|
|
read_regular_1,
|
|
|
|
read_high_priority_1,
|
|
|
|
].map(&:id),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't include notifications from other users" do
|
|
|
|
another_user_notification = create(high_priority: true, read: false, user: Fabricate(:user))
|
|
|
|
expect(Notification.prioritized_list(user).map(&:id)).to contain_exactly(
|
|
|
|
*[
|
|
|
|
unread_high_priority_2,
|
|
|
|
unread_high_priority_1,
|
|
|
|
unread_regular_2,
|
|
|
|
unread_regular_1,
|
|
|
|
read_high_priority_2,
|
2022-11-15 21:32:05 -05:00
|
|
|
unread_like,
|
2022-09-12 14:19:25 -04:00
|
|
|
read_regular_2,
|
|
|
|
read_regular_1,
|
|
|
|
read_high_priority_1,
|
|
|
|
].map(&:id),
|
|
|
|
)
|
|
|
|
expect(
|
|
|
|
Notification.prioritized_list(another_user_notification.user).map(&:id),
|
|
|
|
).to contain_exactly(another_user_notification.id)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't include notifications from deleted topics" do
|
|
|
|
unread_high_priority_1.topic.trash!
|
|
|
|
unread_regular_2.topic.trash!
|
|
|
|
read_regular_1.topic.trash!
|
|
|
|
expect(Notification.prioritized_list(user).map(&:id)).to contain_exactly(
|
|
|
|
*[
|
|
|
|
unread_high_priority_2,
|
|
|
|
unread_regular_1,
|
|
|
|
read_high_priority_2,
|
2022-11-15 21:32:05 -05:00
|
|
|
unread_like,
|
2022-09-12 14:19:25 -04:00
|
|
|
read_regular_2,
|
|
|
|
read_high_priority_1,
|
|
|
|
].map(&:id),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "doesn't include like notifications if the user doesn't want like notifications" do
|
|
|
|
user.user_option.update!(
|
|
|
|
like_notification_frequency: UserOption.like_notification_frequency_type[:never],
|
|
|
|
)
|
|
|
|
unread_regular_1.update!(notification_type: Notification.types[:liked])
|
|
|
|
read_regular_2.update!(notification_type: Notification.types[:liked_consolidated])
|
|
|
|
expect(Notification.prioritized_list(user).map(&:id)).to eq(
|
|
|
|
[
|
|
|
|
unread_high_priority_2,
|
|
|
|
unread_high_priority_1,
|
|
|
|
unread_regular_2,
|
|
|
|
read_high_priority_2,
|
|
|
|
read_regular_1,
|
|
|
|
read_high_priority_1,
|
|
|
|
].map(&:id),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "respects the count param" do
|
|
|
|
expect(Notification.prioritized_list(user, count: 1).map(&:id)).to eq(
|
|
|
|
[unread_high_priority_2].map(&:id),
|
|
|
|
)
|
PERF: Add indexes to speed up notifications queries by user menu (#26048)
Why this change?
There are two problematic queries in question here when loading
notifications in various tabs in the user menu:
```
SELECT "notifications".*
FROM "notifications"
LEFT JOIN topics ON notifications.topic_id = topics.id
WHERE "notifications"."user_id" = 1338 AND (topics.id IS NULL OR topics.deleted_at IS NULL)
ORDER BY notifications.high_priority AND NOT notifications.read DESC,
NOT notifications.read AND notifications.notification_type NOT IN (5,19,25) DESC,
notifications.created_at DESC
LIMIT 30;
```
and
```
EXPLAIN ANALYZE SELECT "notifications".*
FROM "notifications"
LEFT JOIN topics ON notifications.topic_id = topics.id
WHERE "notifications"."user_id" = 1338
AND (topics.id IS NULL OR topics.deleted_at IS NULL)
AND "notifications"."notification_type" IN (5, 19, 25)
ORDER BY notifications.high_priority AND NOT notifications.read DESC, NOT notifications.read DESC, notifications.created_at DESC LIMIT 30;
```
For a particular user, the queries takes about 40ms and 26ms
respectively on one of our production instance where the user has 10K notifications while the site has 600K notifications in total.
What does this change do?
1. Adds the `index_notifications_user_menu_ordering` index to the `notifications` table which is
indexed on `(user_id, (high_priority AND NOT read) DESC, (NOT read)
DESC, created_at DESC)`.
1. Adds a second index `index_notifications_user_menu_ordering_deprioritized_likes` to the `notifications`
table which is indexed on `(user_id, (high_priority AND NOT read) DESC, (NOT read AND notification_type NOT IN (5,19,25)) DESC, created_at DESC)`. Note that we have to hardcode the like typed notifications type here as it is being used in an ordering clause.
With the two indexes above, both queries complete in roughly 0.2ms. While I acknowledge that there will be some overhead in insert,update or delete operations. I believe this trade-off is worth it since viewing notifications in the user menu is something that is at the core of using a Discourse forum so we should optimise this experience as much as possible.
2024-03-06 03:52:19 -05:00
|
|
|
|
2022-09-12 14:19:25 -04:00
|
|
|
expect(Notification.prioritized_list(user, count: 3).map(&:id)).to eq(
|
|
|
|
[unread_high_priority_2, unread_high_priority_1, unread_regular_2].map(&:id),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "can filter the list by specific types" do
|
|
|
|
unread_regular_1.update!(notification_type: Notification.types[:liked])
|
|
|
|
read_regular_2.update!(notification_type: Notification.types[:liked_consolidated])
|
|
|
|
expect(
|
|
|
|
Notification.prioritized_list(
|
|
|
|
user,
|
|
|
|
types: [Notification.types[:liked], Notification.types[:liked_consolidated]],
|
2022-11-15 21:32:05 -05:00
|
|
|
).map(&:id),
|
|
|
|
).to eq([unread_like, unread_regular_1, read_regular_2].map(&:id))
|
2022-09-12 14:19:25 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
it "includes like notifications when filtering by like types even if the user doesn't want like notifications" do
|
|
|
|
user.user_option.update!(
|
|
|
|
like_notification_frequency: UserOption.like_notification_frequency_type[:never],
|
|
|
|
)
|
|
|
|
unread_regular_1.update!(notification_type: Notification.types[:liked])
|
|
|
|
read_regular_2.update!(notification_type: Notification.types[:liked_consolidated])
|
|
|
|
expect(
|
|
|
|
Notification.prioritized_list(
|
|
|
|
user,
|
|
|
|
types: [Notification.types[:liked], Notification.types[:liked_consolidated]],
|
2022-11-15 21:32:05 -05:00
|
|
|
).map(&:id),
|
|
|
|
).to eq([unread_like, unread_regular_1, read_regular_2].map(&:id))
|
2022-09-12 14:19:25 -04:00
|
|
|
expect(
|
|
|
|
Notification.prioritized_list(user, types: [Notification.types[:liked]]).map(&:id),
|
2022-11-15 21:32:05 -05:00
|
|
|
).to contain_exactly(unread_like.id, unread_regular_1.id)
|
2022-09-12 14:19:25 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-02-13 01:12:17 -05:00
|
|
|
describe "#recent_report" do
|
|
|
|
let(:post) { Fabricate(:post) }
|
|
|
|
|
|
|
|
def fab(type, read)
|
|
|
|
@i ||= 0
|
|
|
|
@i += 1
|
|
|
|
Notification.create!(
|
|
|
|
read: read,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: post.topic_id,
|
|
|
|
post_number: post.post_number,
|
|
|
|
data: "[]",
|
|
|
|
notification_type: type,
|
|
|
|
created_at: @i.days.from_now,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def unread_pm
|
|
|
|
fab(Notification.types[:private_message], false)
|
|
|
|
end
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
def unread_bookmark_reminder
|
|
|
|
fab(Notification.types[:bookmark_reminder], false)
|
|
|
|
end
|
|
|
|
|
2014-02-13 01:12:17 -05:00
|
|
|
def pm
|
|
|
|
fab(Notification.types[:private_message], true)
|
|
|
|
end
|
|
|
|
|
|
|
|
def regular
|
|
|
|
fab(Notification.types[:liked], true)
|
|
|
|
end
|
|
|
|
|
2019-01-16 04:08:59 -05:00
|
|
|
def liked_consolidated
|
|
|
|
fab(Notification.types[:liked_consolidated], true)
|
|
|
|
end
|
|
|
|
|
2015-06-22 16:14:22 -04:00
|
|
|
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
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
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
|
2014-02-13 01:12:17 -05:00
|
|
|
a = unread_pm
|
2020-03-31 19:09:20 -04:00
|
|
|
regular
|
|
|
|
b = unread_bookmark_reminder
|
2014-02-13 01:12:17 -05:00
|
|
|
c = pm
|
|
|
|
d = regular
|
|
|
|
|
2020-03-31 19:09:20 -04:00
|
|
|
notifications = Notification.recent_report(user, 4)
|
|
|
|
expect(notifications.map { |n| n.id }).to eq([b.id, a.id, d.id, c.id])
|
2014-02-13 01:12:17 -05:00
|
|
|
end
|
2019-01-16 04:08:59 -05:00
|
|
|
|
|
|
|
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
|
2019-11-27 17:32:35 -05:00
|
|
|
|
|
|
|
describe "#consolidate_membership_requests" do
|
|
|
|
fab!(:group) { Fabricate(:group, name: "XXsssssddd") }
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
|
|
|
fab!(:post)
|
2019-11-27 17:32:35 -05:00
|
|
|
|
|
|
|
def create_membership_request_notification
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
Notification.consolidate_or_create!(
|
2019-11-27 17:32:35 -05:00
|
|
|
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)
|
2019-12-05 04:06:06 -05:00
|
|
|
2.times { create_membership_request_notification }
|
2019-11-27 17:32:35 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
it "should consolidate membership requests to a new notification" do
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
original_notification = create_membership_request_notification
|
|
|
|
starting_count = SiteSetting.notification_consolidation_threshold
|
2019-11-27 17:32:35 -05:00
|
|
|
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
consolidated_notification = create_membership_request_notification
|
|
|
|
expect { original_notification.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
2019-11-27 17:32:35 -05:00
|
|
|
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
expect(consolidated_notification.notification_type).to eq(
|
|
|
|
Notification.types[:membership_request_consolidated],
|
|
|
|
)
|
2019-11-27 17:32:35 -05:00
|
|
|
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
data = consolidated_notification.data_hash
|
2019-11-27 17:32:35 -05:00
|
|
|
expect(data[:group_name]).to eq(group.name)
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
expect(data[:count]).to eq(starting_count + 1)
|
2019-11-27 17:32:35 -05:00
|
|
|
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
updated_consolidated_notification = create_membership_request_notification
|
2019-11-27 17:32:35 -05:00
|
|
|
|
REFACTOR: Improve support for consolidating notifications. (#14904)
* REFACTOR: Improve support for consolidating notifications.
Before this commit, we didn't have a single way of consolidating notifications. For notifications like group summaries, we manually removed old ones before creating a new one. On the other hand, we used an after_create callback for likes and group membership requests, which caused unnecessary work, as we need to delete the record we created to replace it with a consolidated one.
We now have all the consolidation rules centralized in a single place: the consolidation planner class. Other parts of the app looking to create a consolidable notification can do so by calling Notification#consolidate_or_save!, instead of the default Notification#create! method.
Finally, we added two more rules: one for re-using existing group summaries and another for deleting duplicated dashboard problems PMs notifications when the user is tracking the moderator's inbox. Setting the threshold to one forces the planner to apply this rule every time.
I plan to add plugin support for adding custom rules in another PR to keep this one relatively small.
* DEV: Introduces a plugin API for consolidating notifications.
This commit removes the `Notification#filter_by_consolidation_data` scope since plugins could have to define their criteria. The Plan class now receives two blocks, one to query for an already consolidated notification, which we'll try to update, and another to query for existing ones to consolidate.
It also receives a consolidation window, which accepts an ActiveSupport::Duration object, and filter notifications created since that value.
2021-11-30 11:36:14 -05:00
|
|
|
expect(updated_consolidated_notification.data_hash[:count]).to eq(starting_count + 2)
|
2019-11-27 17:32:35 -05:00
|
|
|
end
|
2021-01-07 11:49:49 -05:00
|
|
|
|
|
|
|
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],
|
|
|
|
)
|
2021-01-27 11:29:24 -05:00
|
|
|
expect(notification.shelved_notification).to be_present
|
2021-01-07 11:49:49 -05:00
|
|
|
end
|
2019-11-27 17:32:35 -05:00
|
|
|
end
|
2014-02-13 01:12:17 -05:00
|
|
|
end
|
2020-02-23 19:42:50 -05:00
|
|
|
|
|
|
|
describe "purge_old!" do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
2020-02-23 19:42:50 -05:00
|
|
|
fab!(:notification1) { Fabricate(:notification, user: user) }
|
|
|
|
fab!(:notification2) { Fabricate(:notification, user: user) }
|
|
|
|
fab!(:notification3) { Fabricate(:notification, user: user) }
|
|
|
|
fab!(:notification4) { Fabricate(:notification, user: user) }
|
|
|
|
|
|
|
|
it "does nothing if set to 0" do
|
|
|
|
SiteSetting.max_notifications_per_user = 0
|
|
|
|
Notification.purge_old!
|
|
|
|
|
|
|
|
expect(Notification.where(user_id: user.id).count).to eq(4)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "correctly limits" do
|
|
|
|
SiteSetting.max_notifications_per_user = 2
|
|
|
|
Notification.purge_old!
|
|
|
|
|
|
|
|
expect(Notification.where(user_id: user.id).pluck(:id)).to contain_exactly(
|
|
|
|
notification4.id,
|
|
|
|
notification3.id,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
end
|
2021-01-07 11:49:49 -05:00
|
|
|
|
2021-01-27 11:29:24 -05:00
|
|
|
describe "do not disturb" do
|
2023-11-09 17:47:59 -05:00
|
|
|
fab!(:user)
|
2021-01-07 11:49:49 -05:00
|
|
|
|
2021-01-27 11:29:24 -05:00
|
|
|
it "creates a shelved_notification record when created while user is in DND" do
|
2021-01-07 11:49:49 -05:00
|
|
|
user.do_not_disturb_timings.create(starts_at: Time.now, ends_at: 3.days.from_now)
|
|
|
|
notification =
|
|
|
|
Notification.create(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: 1,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
2021-01-27 11:29:24 -05:00
|
|
|
expect(notification.shelved_notification).to be_present
|
2021-01-07 11:49:49 -05:00
|
|
|
end
|
|
|
|
|
2021-01-27 11:29:24 -05:00
|
|
|
it "doesn't create a shelved_notification record when created while user is isn't DND" do
|
2021-01-07 11:49:49 -05:00
|
|
|
notification =
|
|
|
|
Notification.create(
|
|
|
|
read: false,
|
|
|
|
user_id: user.id,
|
|
|
|
topic_id: 2,
|
|
|
|
post_number: 1,
|
|
|
|
data: "{}",
|
|
|
|
notification_type: 1,
|
|
|
|
)
|
2021-01-27 11:29:24 -05:00
|
|
|
expect(notification.shelved_notification).to be_nil
|
2021-01-07 11:49:49 -05:00
|
|
|
end
|
|
|
|
end
|
2023-12-07 12:30:44 -05:00
|
|
|
|
|
|
|
describe ".populate_acting_user" do
|
|
|
|
fab!(:user1) { Fabricate(:user) }
|
|
|
|
fab!(:user2) { Fabricate(:user) }
|
|
|
|
fab!(:user3) { Fabricate(:user) }
|
|
|
|
fab!(:user4) { Fabricate(:user) }
|
|
|
|
fab!(:notification1) do
|
|
|
|
Fabricate(:notification, user: user, data: { username: user1.username }.to_json)
|
|
|
|
end
|
|
|
|
fab!(:notification2) do
|
|
|
|
Fabricate(:notification, user: user, data: { display_username: user2.username }.to_json)
|
|
|
|
end
|
|
|
|
fab!(:notification3) do
|
|
|
|
Fabricate(:notification, user: user, data: { mentioned_by_username: user3.username }.to_json)
|
|
|
|
end
|
|
|
|
fab!(:notification4) do
|
|
|
|
Fabricate(:notification, user: user, data: { invited_by_username: user4.username }.to_json)
|
|
|
|
end
|
|
|
|
|
|
|
|
it "Sets the acting_user correctly for each notification" do
|
|
|
|
Notification.populate_acting_user(
|
|
|
|
[notification1, notification2, notification3, notification4],
|
|
|
|
)
|
|
|
|
|
|
|
|
expect(notification1.acting_user).to eq(user1)
|
|
|
|
expect(notification2.acting_user).to eq(user2)
|
|
|
|
expect(notification3.acting_user).to eq(user3)
|
|
|
|
expect(notification4.acting_user).to eq(user4)
|
|
|
|
end
|
|
|
|
end
|
2014-02-13 01:12:17 -05:00
|
|
|
end
|