FEATURE: new setting to create a linked topic on autoclosing mega topics (#11001)

This commit adds a site setting `auto_close_topics_create_linked_topic`
which when enabled works in conjunction with `auto_close_topics_post_count`
setting and creates a new linked topic for the topic just closed.

The auto-created new topic contains a link for all the previous topics
and the topic titles are appended with `(Part {n})`.

The setting is enabled by default.
This commit is contained in:
Arpit Jalan 2020-11-02 12:18:48 +05:30 committed by GitHub
parent 3655062c60
commit 1476e17c35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 215 additions and 0 deletions

View File

@ -0,0 +1,80 @@
# frozen_string_literal: true
module Jobs
class CreateLinkedTopic < ::Jobs::Base
def execute(args)
reference_post = Post.find_by(id: args[:post_id])
return unless reference_post.present?
parent_topic = reference_post.topic
return unless parent_topic.present?
parent_topic_id = parent_topic.id
parent_title = parent_topic.title
ActiveRecord::Base.transaction do
linked_topic_record = parent_topic.linked_topic
if linked_topic_record.present?
raw_title = parent_title.delete_suffix(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: "", count: linked_topic_record.sequence))
original_topic_id = linked_topic_record.original_topic_id
sequence = linked_topic_record.sequence + 1
else
raw_title = parent_title
# update parent topic title to append title_suffix_locale
parent_title = I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: parent_title, count: 1)
parent_topic.title = parent_title
parent_topic.save!
# create linked topic record
original_topic_id = parent_topic_id
LinkedTopic.create!(topic_id: parent_topic_id, original_topic_id: original_topic_id, sequence: 1)
sequence = 2
end
# fetch previous topic titles
previous_topics = ""
linked_topic_ids = LinkedTopic.where(original_topic_id: original_topic_id).pluck(:topic_id)
Topic.where(id: linked_topic_ids).order(:id).each do |topic|
previous_topics += "- [#{topic.title}](#{topic.url})\n"
end
# create new topic
new_topic_title = I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: sequence)
new_topic_raw = I18n.t('create_linked_topic.post_raw', parent_title: "[#{parent_title}](#{reference_post.full_url})", previous_topics: previous_topics)
system_user = Discourse.system_user
new_post = PostCreator.create!(
system_user,
title: new_topic_title,
raw: new_topic_raw,
skip_validations: true)
new_topic = new_post.topic
new_topic_id = new_topic.id
# create linked_topic record
LinkedTopic.create!(topic_id: new_topic_id, original_topic_id: original_topic_id, sequence: sequence)
# copy over topic tracking state from old topic
params = {
old_topic_id: parent_topic_id,
new_topic_id: new_topic_id
}
DB.exec(<<~SQL, params)
INSERT INTO topic_users(user_id, topic_id, notification_level,
notifications_reason_id)
SELECT tu.user_id,
:new_topic_id AS topic_id,
tu.notification_level,
tu.notifications_reason_id
FROM topic_users tu
JOIN topics t ON (t.id = :new_topic_id)
WHERE tu.topic_id = :old_topic_id
AND tu.notification_level != 1
ON CONFLICT (topic_id, user_id) DO NOTHING
SQL
# add moderator post to old topic
parent_topic.add_moderator_post(system_user, I18n.t('create_linked_topic.moderator_post_raw', new_title: "[#{new_topic_title}](#{new_topic.url})"))
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class LinkedTopic < ActiveRecord::Base
belongs_to :topic
end
# == Schema Information
#
# Table name: linked_topics
#
# id :bigint not null, primary key
# topic_id :bigint not null
# original_topic_id :bigint not null
# sequence :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_linked_topics_on_topic_id_and_original_topic_id (topic_id,original_topic_id) UNIQUE
# index_linked_topics_on_topic_id_and_sequence (topic_id,sequence) UNIQUE
#

View File

@ -242,6 +242,7 @@ class Topic < ActiveRecord::Base
has_one :first_post, -> { where post_number: 1 }, class_name: 'Post' has_one :first_post, -> { where post_number: 1 }, class_name: 'Post'
has_one :topic_search_data has_one :topic_search_data
has_one :topic_embed, dependent: :destroy has_one :topic_embed, dependent: :destroy
has_one :linked_topic, dependent: :destroy
belongs_to :image_upload, class_name: 'Upload' belongs_to :image_upload, class_name: 'Upload'
has_many :topic_thumbnails, through: :image_upload has_many :topic_thumbnails, through: :image_upload

View File

@ -2140,6 +2140,7 @@ en:
notify_about_queued_posts_after: "If there are posts that have been waiting to be reviewed for more than this many hours, send a notification to all moderators. Set to 0 to disable these notifications." notify_about_queued_posts_after: "If there are posts that have been waiting to be reviewed for more than this many hours, send a notification to all moderators. Set to 0 to disable these notifications."
auto_close_messages_post_count: "Maximum number of posts allowed in a message before it is automatically closed (0 to disable)" auto_close_messages_post_count: "Maximum number of posts allowed in a message before it is automatically closed (0 to disable)"
auto_close_topics_post_count: "Maximum number of posts allowed in a topic before it is automatically closed (0 to disable)" auto_close_topics_post_count: "Maximum number of posts allowed in a topic before it is automatically closed (0 to disable)"
auto_close_topics_create_linked_topic: "Create a new linked topic when a topic is auto-closed based on 'auto close topics post count' setting"
code_formatting_style: "Code button in composer will default to this code formatting style" code_formatting_style: "Code button in composer will default to this code formatting style"
@ -4936,3 +4937,10 @@ en:
%{keys} %{keys}
No action is required at this time, however, it is considered good security practice to cycle all your important credentials every few years. No action is required at this time, however, it is considered good security practice to cycle all your important credentials every few years.
create_linked_topic:
topic_title_with_sequence:
one: "%{topic_title} (Part %{count})"
other: "%{topic_title} (Part %{count})"
post_raw: "Continuing the discussion from %{parent_title}.\n\nPrevious discussions:\n\n%{previous_topics}"
moderator_post_raw: "Continue discussion here: %{new_title}"

View File

@ -930,6 +930,8 @@ posting:
default: 500 default: 500
auto_close_topics_post_count: auto_close_topics_post_count:
default: 10000 default: 10000
auto_close_topics_create_linked_topic:
default: true
code_formatting_style: code_formatting_style:
client: true client: true
type: enum type: enum

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class CreateLinkedTopics < ActiveRecord::Migration[6.0]
def change
create_table :linked_topics do |t|
t.bigint :topic_id, null: false
t.bigint :original_topic_id, null: false
t.integer :sequence, null: false
t.timestamps
end
add_index :linked_topics, [:topic_id, :original_topic_id], unique: true
add_index :linked_topics, [:topic_id, :sequence], unique: true
end
end

View File

@ -381,6 +381,11 @@ class PostCreator
locale: SiteSetting.default_locale locale: SiteSetting.default_locale
) )
) )
if SiteSetting.auto_close_topics_create_linked_topic?
# enqueue a job to create a linked topic
Jobs.enqueue_in(5.seconds, :create_linked_topic, post_id: @post.id)
end
end end
end end

View File

@ -411,6 +411,29 @@ describe PostCreator do
locale: :en locale: :en
)) ))
end end
describe "auto_close_topics_create_linked_topic is enabled" do
before do
SiteSetting.auto_close_topics_create_linked_topic = true
end
it "enqueues a job to create a new linked topic" do
freeze_time
post
post_2 = PostCreator.new(
topic.user,
topic_id: topic.id,
raw: "this is a second post"
).create
topic.reload
expect(topic.closed).to eq(true)
expect(topic_timer.reload.deleted_at).to eq_time(Time.zone.now)
expect(job_enqueued?(job: :create_linked_topic, args: { post_id: post_2.id })).to eq(true)
end
end
end end
end end
end end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'rails_helper'
require 'jobs/regular/create_linked_topic'
describe Jobs::CreateLinkedTopic do
it "returns when the post cannot be found" do
expect { Jobs::CreateLinkedTopic.new.perform(post_id: 1, sync_exec: true) }.not_to raise_error
end
context 'with a post' do
fab!(:topic) { Fabricate(:topic) }
fab!(:post) do
Fabricate(:post, topic: topic)
end
fab!(:user_1) { Fabricate(:user) }
fab!(:user_2) { Fabricate(:user) }
let :watching do
TopicUser.notification_levels[:watching]
end
let :tracking do
TopicUser.notification_levels[:tracking]
end
let :muted do
TopicUser.notification_levels[:muted]
end
before do
SiteSetting.auto_close_topics_create_linked_topic = true
Fabricate(:topic_user, notification_level: tracking, topic: topic, user: user_1)
Fabricate(:topic_user, notification_level: muted, topic: topic, user: user_2)
end
it 'creates a linked topic' do
Jobs::CreateLinkedTopic.new.execute(post_id: post.id)
raw_title = topic.title
topic.reload
new_topic = Topic.last
linked_topic = new_topic.linked_topic
expect(topic.title).to include(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 1))
expect(topic.posts.last.raw).to eq(I18n.t('create_linked_topic.moderator_post_raw', new_title: "[#{new_topic.title}](#{new_topic.url})"))
expect(new_topic.title).to include(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 2))
expect(new_topic.first_post.raw).to include(topic.url)
expect(new_topic.topic_users.count).to eq(3)
expect(new_topic.topic_users.pluck(:notification_level)).to contain_exactly(muted, tracking, watching)
expect(linked_topic.topic_id).to eq(new_topic.id)
expect(linked_topic.original_topic_id).to eq(topic.id)
expect(linked_topic.sequence).to eq(2)
end
end
end