FEATURE: silently close topic (#11392)

New TopicTimer to silently close topic. It will be used by discourse-solved plugin

Meta: https://meta.discourse.org/t/allow-auto-close-for-solved-to-do-so-silently/169300
This commit is contained in:
Krzysztof Kotlarek 2020-12-03 10:43:19 +11:00 committed by GitHub
parent 1c87038255
commit 9c5ee4923b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 68 additions and 14 deletions

View File

@ -126,7 +126,10 @@ export default Component.extend({
},
_noticeKey() {
const statusType = this.statusType;
let statusType = this.statusType;
if (statusType === "silent_close") {
statusType = "close";
}
if (this.basedOnLastPost) {
return `topic.status_update_notice.auto_${statusType}_based_on_last_post`;

View File

@ -5,6 +5,7 @@ module Jobs
def execute(args)
topic_timer = TopicTimer.find_by(id: args[:topic_timer_id] || args[:topic_status_update_id])
state = !!args[:state]
timer_type = args[:silent] ? :silent_close : :close
if topic_timer.blank? || topic_timer.execute_at > Time.zone.now
return
@ -25,16 +26,16 @@ module Jobs
by_user: Discourse.system_user
)
else
topic.update_status('autoclosed', state, user)
topic.update_status('autoclosed', state, user, { silent: args[:silent] })
end
topic.inherit_auto_close_from_category if state == false
topic.inherit_auto_close_from_category(timer_type: timer_type) if state == false
else
topic_timer.destroy!
topic.reload
if topic_timer.based_on_last_post
topic.inherit_auto_close_from_category
topic.inherit_auto_close_from_category(timer_type: timer_type)
end
end
end

View File

@ -368,7 +368,7 @@ class Topic < ActiveRecord::Base
self.last_post_user_id ||= user_id
end
def inherit_auto_close_from_category
def inherit_auto_close_from_category(timer_type: :close)
if !self.closed &&
!@ignore_category_auto_close &&
self.category &&
@ -379,7 +379,7 @@ class Topic < ActiveRecord::Base
duration = based_on_last_post ? self.category.auto_close_hours : nil
self.set_or_create_timer(
TopicTimer.types[:close],
TopicTimer.types[timer_type],
self.category.auto_close_hours,
by_user: Discourse.system_user,
based_on_last_post: based_on_last_post,
@ -902,6 +902,7 @@ class Topic < ActiveRecord::Base
action_code: opts[:action_code],
no_bump: opts[:bump].blank?,
topic_id: self.id,
silent: opts[:silent],
skip_validations: true,
custom_fields: opts[:custom_fields],
import_mode: opts[:import_mode])
@ -1299,12 +1300,13 @@ class Topic < ActiveRecord::Base
# * by_user: User who is setting the topic's status update.
# * based_on_last_post: True if time should be based on timestamp of the last post.
# * category_id: Category that the update will apply to.
def set_or_create_timer(status_type, time, by_user: nil, based_on_last_post: false, category_id: SiteSetting.uncategorized_category_id, duration: nil)
def set_or_create_timer(status_type, time, by_user: nil, based_on_last_post: false, category_id: SiteSetting.uncategorized_category_id, duration: nil, silent: nil)
return delete_topic_timer(status_type, by_user: by_user) if time.blank? && duration.blank?
public_topic_timer = !!TopicTimer.public_types[status_type]
topic_timer_options = { topic: self, public_type: public_topic_timer }
topic_timer_options.merge!(user: by_user) unless public_topic_timer
topic_timer_options.merge!(silent: silent) if silent
topic_timer = TopicTimer.find_or_initialize_by(topic_timer_options)
topic_timer.status_type = status_type

View File

@ -50,7 +50,8 @@ class TopicTimer < ActiveRecord::Base
delete: 4,
reminder: 5,
bump: 6,
delete_replies: 7
delete_replies: 7,
silent_close: 8
)
end
@ -97,6 +98,10 @@ class TopicTimer < ActiveRecord::Base
end
alias_method :cancel_auto_open_job, :cancel_auto_close_job
def cancel_auto_silent_close_job
Jobs.cancel_scheduled_job(:toggle_topic_closed, topic_timer_id: id)
end
def cancel_auto_publish_to_category_job
Jobs.cancel_scheduled_job(:publish_topic_to_category, topic_timer_id: id)
end
@ -143,6 +148,16 @@ class TopicTimer < ActiveRecord::Base
)
end
def schedule_auto_silent_close_job(time)
topic.update_status('closed', false, user) if topic&.closed
Jobs.enqueue_at(time, :toggle_topic_closed,
topic_timer_id: id,
silent: true,
state: true
)
end
def schedule_auto_publish_to_category_job(time)
Jobs.enqueue_at(time, :publish_topic_to_category, topic_timer_id: id)
end

View File

@ -11,7 +11,7 @@ TopicStatusUpdater = Struct.new(:topic, :user) do
updated = change(status, opts)
if updated
highest_post_number = topic.highest_post_number
create_moderator_post_for(status, opts[:message])
create_moderator_post_for(status, opts)
update_read_state_for(status, highest_post_number)
end
end
@ -49,6 +49,7 @@ TopicStatusUpdater = Struct.new(:topic, :user) do
if @topic_status_update
if status.manually_closing_topic? || status.closing_topic?
topic.delete_topic_timer(TopicTimer.types[:close])
topic.delete_topic_timer(TopicTimer.types[:silent_close])
elsif status.manually_opening_topic? || status.opening_topic?
topic.delete_topic_timer(TopicTimer.types[:open])
end
@ -65,8 +66,9 @@ TopicStatusUpdater = Struct.new(:topic, :user) do
result
end
def create_moderator_post_for(status, message = nil)
topic.add_moderator_post(user, message || message_for(status), options_for(status))
def create_moderator_post_for(status, opts)
message = opts[:message]
topic.add_moderator_post(user, message || message_for(status), options_for(status, opts))
topic.reload
end
@ -110,9 +112,10 @@ TopicStatusUpdater = Struct.new(:topic, :user) do
end
end
def options_for(status)
def options_for(status, opts = {})
{ bump: status.opening_topic?,
post_type: Post.types[:small_action],
silent: opts[:silent],
action_code: status.action_code }
end

View File

@ -36,6 +36,7 @@ class PostCreator
# hidden_reason_id - Reason for hiding the post (optional)
# skip_validations - Do not validate any of the content in the post
# draft_key - the key of the draft we are creating (will be deleted on success)
# silent - Do not update topic stats and fields like last_post_user_id
#
# When replying to a topic:
# topic_id - topic we're replying to
@ -506,13 +507,12 @@ class PostCreator
def update_topic_stats
attrs = { updated_at: Time.now }
if @post.post_type != Post.types[:whisper]
if @post.post_type != Post.types[:whisper] && !@opts[:silent]
attrs[:last_posted_at] = @post.created_at
attrs[:last_post_user_id] = @post.user_id
attrs[:word_count] = (@topic.word_count || 0) + @post.word_count
attrs[:excerpt] = @post.excerpt_for_topic if new_topic?
attrs[:bumped_at] = @post.created_at unless @post.no_bump
@topic.update_columns(attrs)
end
@topic.update_columns(attrs)

View File

@ -581,6 +581,36 @@ describe PostCreator do
end
end
context 'silent' do
fab!(:topic) { Fabricate(:topic, user: user) }
it 'silent do not mess up the public view' do
freeze_time DateTime.parse('2010-01-01 12:00')
first = PostCreator.new(
user,
topic_id: topic.id,
raw: 'this is the first post'
).create
freeze_time 1.year.from_now
PostCreator.new(user,
topic_id: topic.id,
reply_to_post_number: 1,
silent: true,
post_type: Post.types[:regular],
raw: 'this is a whispered reply').create
topic.reload
# silent post should not muck up that number
expect(topic.last_posted_at).to eq_time(first.created_at)
expect(topic.last_post_user_id).to eq(first.user_id)
expect(topic.word_count).to eq(5)
end
end
context 'uniqueness' do
fab!(:topic) { Fabricate(:topic, user: user) }