FIX: Automatically generate category channel slugs (#18879)

This commit automatically ensures that category channels
have slugs when they are created or updated based on the
channel name, category name, or existing slug. The behaviour
has been copied from the Category model.

We also include a backfill here with a simplified version
of Slug.for with deduplication to fill the slugs for already
created Category chat channels.

The channel slug is also now used for chat notifications,
and for the UI and navigation for chat. `slugifyChannel`
is still used, but now does the following fallback:

* Uses channel.slug if it is present
* Uses channel.escapedTitle if it is present
* Uses channel.title if it is present

In future we may want to remove this altogether
and always rely on the slug being present, but this
is currently not possible because we are not generating
slugs for DM channels at this point.
This commit is contained in:
Martin Brennan
2022-11-09 10:28:31 +10:00
committed by GitHub
parent 3bab7a44d4
commit c6764d8c74
34 changed files with 367 additions and 75 deletions

View File

@ -0,0 +1,62 @@
# frozen_string_literal: true
class BackfillChannelSlugs < ActiveRecord::Migration[7.0]
def up
channels = DB.query(<<~SQL)
SELECT chat_channels.id, COALESCE(chat_channels.name, categories.name) AS title, NULL as slug
FROM chat_channels
INNER JOIN categories ON categories.id = chat_channels.chatable_id
WHERE chat_channels.chatable_type = 'Category' AND chat_channels.slug IS NULL
SQL
return if channels.count.zero?
DB.exec("CREATE TEMPORARY TABLE tmp_chat_channel_slugs(id int, slug text)")
taken_slugs = {}
channels.each do |channel|
# Simplified version of Slug.for generation that doesn't take into
# account different encodings to make things a little easier.
title = channel.title
if title.blank?
channel.slug = "channel-#{channel.id}"
else
channel.slug =
title
.downcase
.chomp
.tr("'", "")
.parameterize
.tr("_", "-")
.truncate(255, omission: "")
.squeeze("-")
.gsub(/\A-+|-+\z/, "")
end
# Deduplicate slugs with the channel IDs, we can always improve
# slugs later on.
channel.slug = "#{channel.slug}-#{channel.id}" if taken_slugs.key?(channel.slug)
taken_slugs[channel.slug] = true
end
values_to_insert =
channels.map { |channel| "(#{channel.id}, '#{PG::Connection.escape_string(channel.slug)}')" }
DB.exec(
"INSERT INTO tmp_chat_channel_slugs
VALUES #{values_to_insert.join(",\n")}",
)
DB.exec(<<~SQL)
UPDATE chat_channels cc
SET slug = tmp.slug
FROM tmp_chat_channel_slugs tmp
WHERE cc.id = tmp.id AND cc.slug IS NULL
SQL
DB.exec("DROP TABLE tmp_chat_channel_slugs(id int, slug text)")
end
def down
raise ActiveRecord::IrreversibleMigration
end
end