mirror of
https://github.com/discourse/discourse.git
synced 2026-08-08 17:53:55 +08:00
This commit adds several pieces of functionality to help keep admins in the loop about upcoming changes. First of all, there is a new initializer on boot that will notify admins about newly available upcoming changes, as well as log removed changes and status movement of existing changes. * When there is a new upcoming change, we only notify admins about it when the status is the `promote_upcoming_changes_on_status` - 1, e.g. if `promote_upcoming_changes_on_status` is `beta` then we only tell admin about the change once it has reached `alpha`. This means we may log the `added` event in one deploy, but only actually notify admins in a subsequent deploy. * We log removed upcoming changes so we can automatically delete old site setting data in a future job as needed. We also now notify admins when upcoming changes are automatically promoted to enabled based on the site's `promote_upcoming_changes_on_status`: <img width="378" height="600" alt="image" src="https://github.com/user-attachments/assets/4200fbee-9990-4bbc-a378-85946e631e77" /> In addition, we now show an indicator in the admin sidebar if there are new upcoming changes that have been added since they last visited the upcoming change config page. This data is stored in a user custom field, because Redis is ephemeral, and storing in the User table is overkill because 99% of users are not staff: <img width="248" height="112" alt="image" src="https://github.com/user-attachments/assets/4c3d3cf7-ac39-45f8-a2c8-a049cb85b8e9" /> Finally, this commit moves both the Track and Promote initializer logic behind a `DistributedMutex`, we don't want multiple processes running the same logic here, it needs to be only once. --------- Co-authored-by: Loïc Guitaut <loic@discourse.org> Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com>
230 lines
6.3 KiB
Ruby
Vendored
230 lines
6.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe Notification::Action::BulkCreate do
|
|
describe ".call" do
|
|
subject(:action) { described_class.call(records:, **options) }
|
|
|
|
fab!(:user1, :user) { Fabricate(:user, last_seen_at: 1.hour.ago) }
|
|
fab!(:user2, :user) { Fabricate(:user, last_seen_at: 1.hour.ago) }
|
|
|
|
let(:options) { {} }
|
|
|
|
context "when records is empty" do
|
|
let(:records) { [] }
|
|
|
|
it "returns an empty array" do
|
|
expect(action).to eq([])
|
|
end
|
|
|
|
it "does not create any notifications" do
|
|
expect { action }.not_to change { Notification.count }
|
|
end
|
|
end
|
|
|
|
context "when records is nil" do
|
|
let(:records) { nil }
|
|
|
|
it "returns an empty array" do
|
|
expect(action).to eq([])
|
|
end
|
|
end
|
|
|
|
context "with a single notification" do
|
|
let(:records) do
|
|
[
|
|
{
|
|
user_id: user1.id,
|
|
notification_type: Notification.types[:custom],
|
|
data: { message: "test" }.to_json,
|
|
},
|
|
]
|
|
end
|
|
let(:notification) { Notification.find(action.first) }
|
|
|
|
it "creates the notification" do
|
|
expect { action }.to change { Notification.count }.by(1)
|
|
end
|
|
|
|
it "returns the notification ids" do
|
|
expect(action.length).to eq(1)
|
|
expect(notification).to be_present
|
|
end
|
|
|
|
it "sets the correct notification attributes" do
|
|
expect(notification).to have_attributes(
|
|
user: user1,
|
|
notification_type: Notification.types[:custom],
|
|
read: false,
|
|
data_hash: {
|
|
"message" => "test",
|
|
},
|
|
)
|
|
end
|
|
|
|
it "publishes notification state to the user" do
|
|
messages = MessageBus.track_publish("/notification/#{user1.id}") { action }
|
|
|
|
expect(messages.length).to eq(1)
|
|
end
|
|
|
|
it "triggers the notification_created event" do
|
|
events = DiscourseEvent.track_events(:notification_created) { action }
|
|
|
|
expect(events.length).to eq(1)
|
|
expect(events.first[:params].first.user_id).to eq(user1.id)
|
|
end
|
|
|
|
it "processes email via NotificationEmailer" do
|
|
NotificationEmailer.expects(:process_notification).once
|
|
action
|
|
end
|
|
end
|
|
|
|
context "with multiple notifications for different users" do
|
|
let(:records) do
|
|
[
|
|
{
|
|
user_id: user1.id,
|
|
notification_type: Notification.types[:custom],
|
|
data: { message: "test1" }.to_json,
|
|
},
|
|
{
|
|
user_id: user2.id,
|
|
notification_type: Notification.types[:custom],
|
|
data: { message: "test2" }.to_json,
|
|
},
|
|
]
|
|
end
|
|
|
|
it "creates all notifications" do
|
|
expect { action }.to change { Notification.count }.by(2)
|
|
end
|
|
|
|
it "returns all notification ids" do
|
|
notification_ids = action
|
|
expect(notification_ids.length).to eq(2)
|
|
end
|
|
|
|
it "publishes notification state to each user" do
|
|
messages1 = MessageBus.track_publish("/notification/#{user1.id}") { action }
|
|
expect(messages1.length).to eq(1)
|
|
end
|
|
|
|
it "triggers the notification_created event for each notification" do
|
|
events = DiscourseEvent.track_events(:notification_created) { action }
|
|
|
|
expect(events.length).to eq(2)
|
|
expect(events.map { |e| e[:params].first.user_id }).to contain_exactly(user1.id, user2.id)
|
|
end
|
|
end
|
|
|
|
context "with high_priority notification type" do
|
|
let(:records) do
|
|
[
|
|
{
|
|
user_id: user1.id,
|
|
notification_type: Notification.types[:private_message],
|
|
data: {}.to_json,
|
|
},
|
|
]
|
|
end
|
|
|
|
it "sets high_priority to true based on notification type" do
|
|
notification_ids = action
|
|
notification = Notification.find(notification_ids.first)
|
|
|
|
expect(notification.high_priority).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "with explicit high_priority option" do
|
|
let(:records) do
|
|
[
|
|
{
|
|
user_id: user1.id,
|
|
notification_type: Notification.types[:custom],
|
|
data: {}.to_json,
|
|
high_priority: true,
|
|
},
|
|
]
|
|
end
|
|
|
|
it "respects the explicit high_priority value" do
|
|
notification_ids = action
|
|
notification = Notification.find(notification_ids.first)
|
|
|
|
expect(notification.high_priority).to eq(true)
|
|
end
|
|
end
|
|
|
|
context "when user is in do not disturb mode" do
|
|
before do
|
|
Fabricate(
|
|
:do_not_disturb_timing,
|
|
user: user1,
|
|
starts_at: 1.hour.ago,
|
|
ends_at: 1.hour.from_now,
|
|
)
|
|
end
|
|
|
|
let(:records) do
|
|
[{ user_id: user1.id, notification_type: Notification.types[:custom], data: {}.to_json }]
|
|
end
|
|
|
|
it "creates a shelved notification instead of processing email" do
|
|
expect { action }.to change { ShelvedNotification.count }.by(1)
|
|
end
|
|
|
|
it "does not process email via NotificationEmailer" do
|
|
NotificationEmailer.expects(:process_notification).never
|
|
action
|
|
end
|
|
end
|
|
|
|
context "with skip_send_email option" do
|
|
let(:options) { { skip_send_email: true } }
|
|
let(:records) do
|
|
[{ user_id: user1.id, notification_type: Notification.types[:custom], data: {}.to_json }]
|
|
end
|
|
|
|
it "does not process email via NotificationEmailer" do
|
|
NotificationEmailer.expects(:process_notification).never
|
|
action
|
|
end
|
|
|
|
it "does not create shelved notifications" do
|
|
expect { action }.not_to change { ShelvedNotification.count }
|
|
end
|
|
|
|
it "still triggers the notification_created event" do
|
|
events = DiscourseEvent.track_events(:notification_created) { action }
|
|
|
|
expect(events.length).to eq(1)
|
|
end
|
|
end
|
|
|
|
context "with optional attributes" do
|
|
fab!(:topic)
|
|
|
|
let(:records) do
|
|
[
|
|
{
|
|
user_id: user1.id,
|
|
notification_type: Notification.types[:replied],
|
|
data: {}.to_json,
|
|
topic_id: topic.id,
|
|
post_number: 3,
|
|
},
|
|
]
|
|
end
|
|
|
|
it "sets topic_id and post_number" do
|
|
notification_ids = action
|
|
notification = Notification.find(notification_ids.first)
|
|
|
|
expect(notification.topic_id).to eq(topic.id)
|
|
expect(notification.post_number).to eq(3)
|
|
end
|
|
end
|
|
end
|
|
end
|