From 9c5ee4923b7b039162acdc5661da03d29688b428 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotlarek Date: Thu, 3 Dec 2020 10:43:19 +1100 Subject: [PATCH] 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 --- .../app/components/topic-timer-info.js | 5 +++- app/jobs/regular/toggle_topic_closed.rb | 7 +++-- app/models/topic.rb | 8 +++-- app/models/topic_timer.rb | 17 ++++++++++- app/services/topic_status_updater.rb | 11 ++++--- lib/post_creator.rb | 4 +-- spec/components/post_creator_spec.rb | 30 +++++++++++++++++++ 7 files changed, 68 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/topic-timer-info.js b/app/assets/javascripts/discourse/app/components/topic-timer-info.js index 146ca10b2d5..0a86d508a83 100644 --- a/app/assets/javascripts/discourse/app/components/topic-timer-info.js +++ b/app/assets/javascripts/discourse/app/components/topic-timer-info.js @@ -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`; diff --git a/app/jobs/regular/toggle_topic_closed.rb b/app/jobs/regular/toggle_topic_closed.rb index d636360e943..cc10d5a9be7 100644 --- a/app/jobs/regular/toggle_topic_closed.rb +++ b/app/jobs/regular/toggle_topic_closed.rb @@ -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 diff --git a/app/models/topic.rb b/app/models/topic.rb index 2977ed9642d..7cf68865200 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -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 diff --git a/app/models/topic_timer.rb b/app/models/topic_timer.rb index 89da22a24fa..441e631aa1c 100644 --- a/app/models/topic_timer.rb +++ b/app/models/topic_timer.rb @@ -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 diff --git a/app/services/topic_status_updater.rb b/app/services/topic_status_updater.rb index 795538cbd0c..b1dce64a7cf 100644 --- a/app/services/topic_status_updater.rb +++ b/app/services/topic_status_updater.rb @@ -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 diff --git a/lib/post_creator.rb b/lib/post_creator.rb index b0f066ac2b6..7884c225817 100644 --- a/lib/post_creator.rb +++ b/lib/post_creator.rb @@ -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) diff --git a/spec/components/post_creator_spec.rb b/spec/components/post_creator_spec.rb index bbd35d4c24f..310ba1aea7e 100644 --- a/spec/components/post_creator_spec.rb +++ b/spec/components/post_creator_spec.rb @@ -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) }