2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-04 01:15:08 +08:00
discourse/app/services/upcoming_changes/notify_promotion.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

125 lines
3.8 KiB
Ruby

# frozen_string_literal: true
# Notify admins of a specific upcoming change's promotion,
# which occurs when the change has reached the promotion status
# defined by SiteSetting.promote_upcoming_changes_on_status.
#
# Since the site setting is not actually changed in the database
# when an upcoming change is automatically promoted, we also
# fire off a DiscourseEvent that developers can listen to
# in 015-track-upcoming-change-toggle.rb.
#
# Admins will only be notified once for each upcoming change,
# both via a staff action log and a Notification in the UI.
# We don't need to notify admins if they have manually opted in
# or out of the change, since that overrides the automatic promotion.
class UpcomingChanges::NotifyPromotion
include Service::Base
params do
attribute :setting_name, :symbol
attribute :admin_user_ids, :array
attribute :changes_already_notified_about_promotion, :array, default: []
validates :setting_name, presence: true
validates :admin_user_ids, presence: true
end
policy :setting_is_available
policy :meets_or_exceeds_status
policy :change_has_not_already_been_notified_about_promotion
policy :admin_has_not_manually_toggled
try do
step :log_promotion
model :existing_notifications, optional: true
model :bulk_notification_new_records
step :notify_admins
step :create_event
step :trigger_discourse_event
end
private
def setting_is_available(params:)
SiteSetting.respond_to?(params.setting_name)
end
def meets_or_exceeds_status(params:)
UpcomingChanges.meets_or_exceeds_status?(
params.setting_name,
SiteSetting.promote_upcoming_changes_on_status.to_sym,
)
end
def change_has_not_already_been_notified_about_promotion(params:)
!params.changes_already_notified_about_promotion.include?(params.setting_name)
end
def admin_has_not_manually_toggled(params:)
!SiteSetting.modified.key?(params.setting_name)
end
def log_promotion(params:, guardian:)
context =
I18n.t(
"staff_action_logs.upcoming_changes.log_promoted",
change_status: UpcomingChanges.change_status(params.setting_name).to_s.titleize,
base_path: Discourse.base_path,
)
StaffActionLogger.new(Discourse.system_user).log_upcoming_change_toggle(
params.setting_name,
false,
true,
{ context: },
)
end
def fetch_existing_notifications(params:)
Notification.where(
notification_type: Notification.types[:upcoming_change_automatically_promoted],
user_id: params.admin_user_ids,
read: false,
)
end
def fetch_bulk_notification_new_records(params:, existing_notifications:)
existing_by_user = existing_notifications.to_a.index_by(&:user_id)
params.admin_user_ids.map do |admin_id|
{
user_id: admin_id,
notification_type: Notification.types[:upcoming_change_automatically_promoted],
data:
UpcomingChanges::Action::NotificationDataMerger.call(
existing_notification: existing_by_user[admin_id],
new_change_name: params.setting_name,
).to_json,
}
end
end
def notify_admins(params:, bulk_notification_new_records:, existing_notifications:)
merge_with_existing = existing_notifications.to_a.any?
Notification.transaction do
existing_notifications.delete_all if merge_with_existing
Notification::Action::BulkCreate.call(
records: bulk_notification_new_records,
skip_send_email: merge_with_existing,
)
end
end
def create_event(params:)
UpcomingChangeEvent.create!(
event_type: :admins_notified_automatic_promotion,
upcoming_change_name: params.setting_name,
acting_user: Discourse.system_user,
)
end
def trigger_discourse_event(params:)
DiscourseEvent.trigger(:upcoming_change_enabled, params.setting_name)
end
end