mirror of
https://github.com/discourse/discourse.git
synced 2026-08-02 09:29:37 -05:00
There are several problems with running these upcoming change checks added in https://github.com/discourse/discourse/commit/fb9bb3198384b76af9947ad011dda86885e6c536: * The app is slower to boot because there is DB work to be done * We sometimes swap out database configs at runtime in our hosting, which does not call the initializer again * We have to think about readonly states for the DB * There are race conditions because of blue-green deployments to consider To solve these problems, we move the tracking and promotion into a scheduled job that runs every twenty minutes, since the notification of admins about upcoming changes are not time-sensitive. This avoids a whole host of problems. In addition, we are making upcoming change settings declarative via SiteSettingExtension. Now, instead of actually changing the DB value of the change when promoting, we just return true for the setting if it has met the promotion status. However, if admins have changed the value of the setting in the DB, we do still use that value instead. Finally we fall back to the default value of the setting. I've also added a change to track modified site settings. Before this we had no way to tell if a setting in `current` inside `SiteSettingExtension` was modified by an admin or not. This change tracks modified settings in a new `modified` object and their values. --------- Co-authored-by: Loïc Guitaut <loic@discourse.org>
185 lines
5.9 KiB
Ruby
185 lines
5.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module UpcomingChanges
|
|
def self.user_enabled_reasons
|
|
@user_enabled_reasons ||=
|
|
::Enum.new(
|
|
enabled_for_everyone: :enabled_for_everyone,
|
|
enabled_for_no_one: :enabled_for_no_one,
|
|
in_specific_groups: :in_specific_groups,
|
|
not_in_specific_groups: :not_in_specific_groups,
|
|
)
|
|
end
|
|
|
|
def self.statuses
|
|
@statuses ||=
|
|
::Enum.new(
|
|
conceptual: -100,
|
|
experimental: 0,
|
|
alpha: 100,
|
|
beta: 200,
|
|
stable: 300,
|
|
permanent: 500,
|
|
never: 9999,
|
|
)
|
|
end
|
|
|
|
def self.previous_status_value(status)
|
|
status_value = self.statuses[status.to_sym]
|
|
self.statuses.values.select { |value| value < status_value }.max || -100
|
|
end
|
|
|
|
def self.previous_status(status)
|
|
self.statuses.keys.select { |key| self.statuses[key] < self.statuses[status.to_sym] }.last ||
|
|
:conceptual
|
|
end
|
|
|
|
def self.image_exists?(change_setting_name)
|
|
File.exist?(File.join(Rails.public_path, self.image_path(change_setting_name)))
|
|
end
|
|
|
|
def self.image_path(change_setting_name)
|
|
plugin_name = SiteSetting.plugins[change_setting_name.to_sym]
|
|
if plugin_name.present?
|
|
File.join("plugins", plugin_name, "images", "upcoming_changes", "#{change_setting_name}.png")
|
|
else
|
|
File.join("images", "upcoming_changes", "#{change_setting_name}.png")
|
|
end
|
|
end
|
|
|
|
def self.image_data(change_setting_name)
|
|
width, height = nil, nil
|
|
|
|
File.open(File.join(Rails.public_path, image_path(change_setting_name)), "rb") do |file|
|
|
image_info = FastImage.new(file)
|
|
width, height = image_info.size
|
|
end
|
|
|
|
{ url: "#{Discourse.base_url}/#{image_path(change_setting_name)}", width:, height: }
|
|
end
|
|
|
|
def self.change_metadata(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
SiteSetting.upcoming_change_metadata[change_setting_name] || {}
|
|
end
|
|
|
|
def self.not_yet_stable?(change_setting_name)
|
|
change_status_value(change_setting_name) < UpcomingChanges.statuses[:stable]
|
|
end
|
|
|
|
def self.stable_or_permanent?(change_setting_name)
|
|
change_status_value(change_setting_name) >= UpcomingChanges.statuses[:stable]
|
|
end
|
|
|
|
def self.meets_or_exceeds_status?(change_setting_name, status)
|
|
change_status_value(change_setting_name) >= UpcomingChanges.statuses[status]
|
|
end
|
|
|
|
def self.change_status_value(change_setting_name)
|
|
UpcomingChanges.statuses[change_status(change_setting_name)]
|
|
end
|
|
|
|
def self.change_status(change_setting_name)
|
|
change_metadata(change_setting_name)[:status]
|
|
end
|
|
|
|
def self.history_for(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
UserHistory.where(
|
|
action: UserHistory.actions[:upcoming_change_toggled],
|
|
subject: change_setting_name,
|
|
).order(created_at: :desc)
|
|
end
|
|
|
|
def self.resolved_value(change_setting_name)
|
|
# An admin has modified the setting and a value is stored
|
|
# in the database, since the default for upcoming changes
|
|
# is false.
|
|
#
|
|
# If the change is permanent though, the admin has no choice
|
|
# in the matter.
|
|
if SiteSetting.modified.key?(change_setting_name) &&
|
|
UpcomingChanges.change_status(change_setting_name) != :permanent
|
|
SiteSetting.current[change_setting_name]
|
|
|
|
# The change has reached the promotion status and is forcibly
|
|
# enabled, admins can still disable it.
|
|
elsif UpcomingChanges.meets_or_exceeds_status?(
|
|
change_setting_name,
|
|
SiteSetting.promote_upcoming_changes_on_status.to_sym,
|
|
) || UpcomingChanges.change_status(change_setting_name) == :permanent
|
|
true
|
|
else
|
|
# Otherwise use the default value, which for upcoming changes
|
|
# is false.
|
|
SiteSetting.defaults[change_setting_name]
|
|
end
|
|
end
|
|
|
|
def self.has_groups?(change_setting_name)
|
|
group_ids_for(change_setting_name).present?
|
|
end
|
|
|
|
def self.group_ids_for(change_setting_name)
|
|
change_setting_name = change_setting_name.to_sym
|
|
SiteSetting.site_setting_group_ids[change_setting_name].presence || []
|
|
end
|
|
|
|
def self.enabled_for_user?(change_setting_name, user)
|
|
change_setting_name = change_setting_name.to_sym
|
|
setting_enabled = SiteSetting.public_send(change_setting_name)
|
|
|
|
# Anon users can only have upcoming changes enabled if it's set for Everyone
|
|
if user.blank?
|
|
return false if UpcomingChanges.has_groups?(change_setting_name)
|
|
else
|
|
if UpcomingChanges.has_groups?(change_setting_name)
|
|
return(
|
|
setting_enabled && user.in_any_groups?(UpcomingChanges.group_ids_for(change_setting_name))
|
|
)
|
|
end
|
|
end
|
|
|
|
setting_enabled
|
|
end
|
|
|
|
def self.stats_for_user(user:, acting_guardian:)
|
|
guardian_visible_group_ids = Group.visible_groups(acting_guardian.user).pluck(:id)
|
|
user_belonging_to_group_ids = user.belonging_to_group_ids
|
|
|
|
SiteSetting.upcoming_change_site_settings.filter_map do |name|
|
|
next if UpcomingChanges.change_status(name) == :conceptual
|
|
enabled = user.upcoming_change_enabled?(name)
|
|
has_groups = UpcomingChanges.has_groups?(name)
|
|
|
|
specific_groups = []
|
|
reason =
|
|
if has_groups
|
|
visible_group_ids =
|
|
UpcomingChanges.group_ids_for(name) & guardian_visible_group_ids &
|
|
user_belonging_to_group_ids
|
|
|
|
specific_groups = Group.where(id: visible_group_ids).pluck(:name)
|
|
if enabled
|
|
UpcomingChanges.user_enabled_reasons[:in_specific_groups]
|
|
else
|
|
UpcomingChanges.user_enabled_reasons[:not_in_specific_groups]
|
|
end
|
|
elsif enabled
|
|
UpcomingChanges.user_enabled_reasons[:enabled_for_everyone]
|
|
else
|
|
UpcomingChanges.user_enabled_reasons[:enabled_for_no_one]
|
|
end
|
|
|
|
{
|
|
name:,
|
|
humanized_name: SiteSetting.humanized_name(name),
|
|
description: SiteSetting.description(name),
|
|
enabled:,
|
|
specific_groups:,
|
|
reason:,
|
|
}
|
|
end
|
|
end
|
|
end
|