discourse/app/services/sidebar_site_settings_backfiller.rb
Alan Guo Xiang Tan 1b56a55f50
DEV: Sidebar default tags and categories are determined at user creation (#18620)
The previous sidebar default tags and categories implementation did not
allow for a user to configure their sidebar to have no categories or
tags. This commit changes how the defaults are applied. When a user is being created,
we create the SidebarSectionLink records based on the `default_sidebar_categories` and
`default_sidebar_tags` site settings. SidebarSectionLink records are
only created for categories and tags which the user has visibility on at
the point of user creation.

With this change, we're also adding the ability for admins to apply
changes to the `default_sidebar_categories` and `default_sidebar_tags`
site settings historically when changing their site setting. When a new
category/tag has been added to the default, the new category/tag will be
added to the sidebar for all users if the admin elects to apply the changes historically.
Like wise when a tag/category is removed, the tag/category will be
removed from the sidebar for all users if the admin elects to apply the
changes historically.

Internal Ref: /t/73500
2022-10-27 06:38:50 +08:00

94 lines
3.1 KiB
Ruby

# frozen_string_literal: true
# A service class that backfills the changes to the default sidebar categories and tags site settings.
#
# When a category/tag is removed from the site settings, the `SidebarSectionLink` records associated with the category/tag
# are deleted.
#
# When a category/tag is added to the site settings, a `SidebarSectionLink` record for the associated category/tag are
# created for all users that do not already have a `SidebarSectionLink` record for the category/tag.
class SidebarSiteSettingsBackfiller
def initialize(setting_name, previous_value:, new_value:)
@setting_name = setting_name
@linkable_klass, previous_ids, new_ids =
case setting_name
when "default_sidebar_categories"
[
Category,
previous_value.split("|"),
new_value.split("|")
]
when "default_sidebar_tags"
klass = Tag
[
klass,
klass.where(name: previous_value.split("|")).pluck(:id),
klass.where(name: new_value.split("|")).pluck(:id)
]
else
raise 'Invalid setting_name'
end
@added_ids = new_ids - previous_ids
@removed_ids = previous_ids - new_ids
end
def backfill!
DistributedMutex.synchronize("backfill_sidebar_site_settings_#{@setting_name}") do
SidebarSectionLink.where(linkable_type: @linkable_klass.to_s, linkable_id: @removed_ids).delete_all
User.real.where(staged: false).select(:id).find_in_batches do |users|
rows = []
users.each do |user|
@added_ids.each do |linkable_id|
rows << { user_id: user[:id], linkable_type: @linkable_klass.to_s, linkable_id: linkable_id }
end
end
SidebarSectionLink.insert_all!(rows) if rows.present?
end
end
end
def number_of_users_to_backfill
select_statements = []
if @removed_ids.present?
select_statements.push(<<~SQL)
SELECT
sidebar_section_links.user_id
FROM sidebar_section_links
WHERE sidebar_section_links.linkable_type = '#{@linkable_klass.to_s}'
AND sidebar_section_links.linkable_id IN (#{@removed_ids.join(",")})
SQL
end
if @added_ids.present?
# Returns the ids of users that will receive the new additions by excluding the users that already have the additions
# Note that we want to avoid doing a left outer join against the "sidebar_section_links" table as PG will end up having
# to do a full table join for both tables first which is less efficient and can be slow on large sites.
select_statements.push(<<~SQL)
SELECT
users.id
FROM users
WHERE users.id NOT IN (
SELECT
sidebar_section_links.user_id
FROM sidebar_section_links
WHERE sidebar_section_links.linkable_type = '#{@linkable_klass.to_s}'
AND sidebar_section_links.linkable_id IN (#{@added_ids.join(",")})
) AND users.id > 0 AND NOT users.staged
SQL
end
DB.query_single(<<~SQL)[0]
SELECT
COUNT(*)
FROM (#{select_statements.join("\nUNION DISTINCT\n")}) AS user_ids
SQL
end
end