2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-04 01:15:08 +08:00
discourse/spec/services/upcoming_changes/notify_promotions_spec.rb
Martin Brennan 0070b55cdf
UX: Consolidate upcoming change notifications for admins (#37976)
We notify admins both when an upcoming change is available
for preview (at promotion status - 1) and when an upcoming
change is automatically promoted.

There are cases where multiple changes might be deployed at the same
time, and we want to avoid sending multiple notifications to admins in a
short period of time.

This PR consolidates the notifications for both events into a single
notification that includes all relevant changes. So for new upcoming
changes, admins will receive a single notification that lists all the
changes that are now available for preview. For automatic promotions,
admins will receive a single notification that lists all the changes
that were automatically promoted.

In addition, this PR follows up on
https://github.com/discourse/discourse/pull/38051 and
directly links the notifications to a filtered list of upcoming changes
by
setting name.

<img width="338" alt="image"
src="https://github.com/user-attachments/assets/6febc7d7-0122-4f3b-8cbe-04d03721a709"
/>

<img width="332" alt="image"
src="https://github.com/user-attachments/assets/46f2f886-8a6f-46f5-8038-bce3305d788f"
/>



### Testing

* Start with a fresh DB
* Make sure `upcoming_change_verbose_logging` and
`enable_upcoming_changes` are true
* Choose a couple of upcoming changes and update their statuses in
`site_settings.yml` to `beta`
* Log in to the site and go to http://localhost:4200/sidekiq/scheduler
and find the CheckUpcomingChanges job and run it
* You should receive a notification for the changes becoming available,
do not read the notification
* Change another upcoming change to beta and run the job again
* You should not get another notification, the third change should be
merged into the first notification
* Now update some of the same changes to `stable` status
* Run the job again
* You should get a new notification saying the changes were
automatically enabled
* Now update another of the same changes to `stable` status
* You should not get another notification, the third change should be
merged into the notification for enabled changes

---------

Co-authored-by: awesomerobot <kris.aubuchon@discourse.org>
2026-03-02 10:48:27 +10:00

281 lines
9.8 KiB
Ruby

# frozen_string_literal: true
RSpec.describe UpcomingChanges::NotifyPromotions do
describe ".call" do
subject(:result) { described_class.call }
fab!(:admin)
fab!(:admin_2, :admin)
let(:enable_upload_debug_mode_status) { :stable }
let(:show_user_menu_avatars_status) { :beta }
before do
SiteSetting.promote_upcoming_changes_on_status = :stable
SiteSetting.stubs(:upcoming_change_site_settings).returns(
%i[enable_upload_debug_mode show_user_menu_avatars],
)
mock_upcoming_change_metadata(
{
enable_upload_debug_mode: {
impact: "other,developers",
status: enable_upload_debug_mode_status,
impact_type: "other",
impact_role: "developers",
},
show_user_menu_avatars: {
impact: "feature,all_members",
status: show_user_menu_avatars_status,
impact_type: "feature",
impact_role: "all_members",
},
},
)
end
context "when there is an error when trying to process a change" do
before do
StaffActionLogger
.any_instance
.stubs(:log_upcoming_change_toggle)
.raises(StandardError, "test error")
end
it "returns the errors" do
expect(result[:change_notification_statuses]).to match(
enable_upload_debug_mode: {
success: false,
error: "test error",
error_key: :unexpected_error,
backtrace: a_kind_of(Array),
},
show_user_menu_avatars: {
success: false,
error: "Setting show_user_menu_avatars does not meet or exceed the promotion status",
error_key: :does_not_meet_or_exceed_promotion_status,
},
)
end
end
context "when everything is ok" do
it { is_expected.to run_successfully }
it "returns a state of all settings as success or failure, along with the related error message" do
expect(result[:change_notification_statuses]).to match(
enable_upload_debug_mode: {
success: true,
},
show_user_menu_avatars: {
success: false,
error: "Setting show_user_menu_avatars does not meet or exceed the promotion status",
error_key: :does_not_meet_or_exceed_promotion_status,
},
)
end
it "logs the change context in the staff action log" do
expect { result }.to change {
UserHistory.where(
action: UserHistory.actions[:upcoming_change_toggled],
subject: "enable_upload_debug_mode",
).count
}.by(1)
expect(UserHistory.last.context).to eq(
I18n.t(
"staff_action_logs.upcoming_changes.log_promoted",
change_status: UpcomingChanges.change_status(:enable_upload_debug_mode).to_s.titleize,
base_path: Discourse.base_path,
),
)
end
it "notifies admins about the upcoming change" do
expect { result }.to change {
Notification
.where(
notification_type: Notification.types[:upcoming_change_automatically_promoted],
user_id: [admin.id, admin_2.id],
)
.where("data::text LIKE ?", "%enable_upload_debug_mode%")
.count
}.by(2)
notification = Notification.where("data::text LIKE ?", "%enable_upload_debug_mode%").last
data = JSON.parse(notification.data)
expect(data["upcoming_change_names"]).to eq(["enable_upload_debug_mode"])
expect(data["upcoming_change_humanized_names"]).to eq(["Enable upload debug mode"])
expect(data["count"]).to eq(1)
end
it "creates an admins_notified_automatic_promotion event" do
expect { result }.to change {
UpcomingChangeEvent.where(
event_type: :admins_notified_automatic_promotion,
upcoming_change_name: :enable_upload_debug_mode,
).count
}.by(1)
end
it "triggers DiscourseEvent for the promoted setting" do
events = DiscourseEvent.track_events { result }
event =
events.find do |e|
e[:event_name] == :upcoming_change_enabled &&
e[:params].first == :enable_upload_debug_mode
end
expect(event).to be_present
expect(event[:params]).to eq([:enable_upload_debug_mode])
end
context "when multiple settings meet promotion criteria" do
let(:show_user_menu_avatars_status) { :stable }
it "processes all eligible settings into consolidated notifications" do
result
notifications =
Notification.where(
notification_type: Notification.types[:upcoming_change_automatically_promoted],
user_id: [admin.id, admin_2.id],
)
expect(notifications.count).to eq(2)
data = JSON.parse(notifications.first.data)
expect(data["upcoming_change_names"]).to contain_exactly(
"enable_upload_debug_mode",
"show_user_menu_avatars",
)
expect(data["count"]).to eq(2)
end
it "creates events for all promoted settings" do
expect { result }.to change {
UpcomingChangeEvent.where(
event_type: :admins_notified_automatic_promotion,
upcoming_change_name: %i[enable_upload_debug_mode show_user_menu_avatars],
).count
}.by(2)
end
it "triggers DiscourseEvent for all promoted settings" do
events = DiscourseEvent.track_events { result }
promoted_events = events.select { |e| e[:event_name] == :upcoming_change_enabled }
expect(promoted_events.length).to eq(2)
expect(promoted_events.map { |e| e[:params].first }).to contain_exactly(
:enable_upload_debug_mode,
:show_user_menu_avatars,
)
end
end
context "when there are no upcoming changes" do
before { SiteSetting.stubs(:upcoming_change_site_settings).returns([]) }
it "does not create any notifications" do
expect { result }.not_to change { Notification.count }
end
it "does not trigger any events" do
events = DiscourseEvent.track_events { result }
expect(events.select { |e| e[:event_name] == :upcoming_change_enabled }).to be_empty
end
end
context "when settings do not meet promotion status" do
let(:enable_upload_debug_mode_status) { :beta }
let(:show_user_menu_avatars_status) { :alpha }
it "does not create any notifications" do
expect { result }.not_to change { Notification.count }
end
it "does not trigger any events" do
events = DiscourseEvent.track_events { result }
expect(events.select { |e| e[:event_name] == :upcoming_change_enabled }).to be_empty
end
it "returns the correct error and error key" do
expect(result[:change_notification_statuses][:enable_upload_debug_mode]).to match(
success: false,
error: "Setting enable_upload_debug_mode does not meet or exceed the promotion status",
error_key: :does_not_meet_or_exceed_promotion_status,
)
end
end
context "when settings are already notified about promotion" do
before do
UpcomingChangeEvent.create!(
event_type: :admins_notified_automatic_promotion,
upcoming_change_name: :enable_upload_debug_mode,
acting_user: Discourse.system_user,
)
end
it "does not notify admins again for the already-notified setting" do
expect { result }.not_to change {
Notification
.where(notification_type: Notification.types[:upcoming_change_automatically_promoted])
.where("data::text LIKE ?", "%enable_upload_debug_mode%")
.count
}
end
it "does not trigger event for the already-notified setting" do
events = DiscourseEvent.track_events { result }
expect(
events.select do |e|
e[:event_name] == :upcoming_change_enabled &&
e[:params].first == :enable_upload_debug_mode
end,
).to be_empty
end
it "returns the correct error and error key" do
expect(result[:change_notification_statuses][:enable_upload_debug_mode]).to match(
success: false,
error: "Setting enable_upload_debug_mode has already notified admins about promotion",
error_key: :already_notified_about_promotion,
)
end
end
context "when settings are opted out" do
before { SiteSetting.enable_upload_debug_mode = false }
it "does not notify admins for opted-out settings" do
expect { result }.not_to change {
Notification
.where(notification_type: Notification.types[:upcoming_change_automatically_promoted])
.where("data::text LIKE ?", "%enable_upload_debug_mode%")
.count
}
end
it "does not trigger event for opted-out settings" do
events = DiscourseEvent.track_events { result }
expect(
events.select do |e|
e[:event_name] == :upcoming_change_enabled &&
e[:params].first == :enable_upload_debug_mode
end,
).to be_empty
end
it "returns the correct error and error key" do
expect(result[:change_notification_statuses][:enable_upload_debug_mode]).to match(
success: false,
error:
"Setting enable_upload_debug_mode has been manually opted in or out by an admin, we did not notify admins about promotion",
error_key: :already_manually_toggled,
)
end
end
end
end
end