mirror of
https://github.com/discourse/discourse.git
synced 2024-11-26 04:56:18 +08:00
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:
parent
3655062c60
commit
1476e17c35
80
app/jobs/regular/create_linked_topic.rb
Normal file
80
app/jobs/regular/create_linked_topic.rb
Normal 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
|
22
app/models/linked_topic.rb
Normal file
22
app/models/linked_topic.rb
Normal 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
|
||||||
|
#
|
|
@ -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
|
||||||
|
|
|
@ -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}"
|
||||||
|
|
|
@ -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
|
||||||
|
|
16
db/migrate/20201027110546_create_linked_topics.rb
Normal file
16
db/migrate/20201027110546_create_linked_topics.rb
Normal 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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
58
spec/jobs/create_linked_topic_spec.rb
Normal file
58
spec/jobs/create_linked_topic_spec.rb
Normal 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
|
Loading…
Reference in New Issue
Block a user