diff --git a/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6 b/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6
index 0c8795dd1df..9cd0e00e75a 100644
--- a/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6
+++ b/app/assets/javascripts/discourse/controllers/edit-topic-auto-close.js.es6
@@ -5,6 +5,8 @@ import ModalFunctionality from 'discourse/mixins/modal-functionality';
export default Ember.Controller.extend(ModalFunctionality, {
auto_close_valid: true,
auto_close_invalid: Em.computed.not('auto_close_valid'),
+ disable_submit: Em.computed.or('auto_close_invalid', 'loading'),
+ loading: false,
@observes("model.details.auto_close_at", "model.details.auto_close_hours")
setAutoCloseTime() {
@@ -29,7 +31,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
setAutoClose(time) {
const self = this;
- this.send('hideModal');
+ this.set('loading', true);
Discourse.ajax({
url: `/t/${this.get('model.id')}/autoclose`,
type: 'PUT',
@@ -40,16 +42,34 @@ export default Ember.Controller.extend(ModalFunctionality, {
timezone_offset: (new Date().getTimezoneOffset())
}
}).then(result => {
+ self.set('loading', false);
if (result.success) {
this.send('closeModal');
this.set('model.details.auto_close_at', result.auto_close_at);
this.set('model.details.auto_close_hours', result.auto_close_hours);
} else {
- bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
+ bootbox.alert(I18n.t('composer.auto_close.error'));
}
}).catch(() => {
- bootbox.alert(I18n.t('composer.auto_close.error'), function() { self.send('reopenModal'); } );
+ // TODO - incorrectly responds to network errors as bad input
+ bootbox.alert(I18n.t('composer.auto_close.error'));
+ self.set('loading', false);
});
- }
+ },
+
+ willCloseImmediately: function() {
+ if (!this.get('model.details.auto_close_based_on_last_post')) {
+ return false;
+ }
+ let closeDate = new Date(this.get('model.last_posted_at'));
+ closeDate.setHours(closeDate.getHours() + this.get('model.auto_close_time'));
+ return closeDate < new Date();
+ }.property('model.details.auto_close_based_on_last_post', 'model.auto_close_time', 'model.last_posted_at'),
+
+ willCloseI18n: function() {
+ if (this.get('model.details.auto_close_based_on_last_post')) {
+ return I18n.t('topic.auto_close_immediate', {hours: this.get('model.auto_close_time')});
+ }
+ }.property('model.details.auto_close_based_on_last_post', 'model.auto_close_time')
});
diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6
index cd83dd9fb64..6f58ae24d8a 100644
--- a/app/assets/javascripts/discourse/models/topic.js.es6
+++ b/app/assets/javascripts/discourse/models/topic.js.es6
@@ -340,6 +340,13 @@ const Topic = RestModel.extend({
keys.forEach(key => this.set(key, json[key]));
},
+ reload() {
+ const self = this;
+ return Discourse.ajax('/t/' + this.get('id'), { type: 'GET' }).then(function(topic_json) {
+ self.updateFromJson(topic_json);
+ });
+ },
+
isPinnedUncategorized: function() {
return this.get('pinned') && this.get('category.isUncategorizedCategory');
}.property('pinned', 'category.isUncategorizedCategory'),
diff --git a/app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs b/app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs
index fcf524920c1..cc1f423cc0e 100644
--- a/app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs
+++ b/app/assets/javascripts/discourse/templates/modal/edit-topic-auto-close.hbs
@@ -4,10 +4,17 @@
autoCloseValid=auto_close_valid
autoCloseBasedOnLastPost=model.details.auto_close_based_on_last_post
limited=model.details.auto_close_based_on_last_post }}
+ {{#if willCloseImmediately}}
+
+ {{fa-icon "warning"}}
+ {{willCloseI18n}}
+
+ {{/if}}
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 03f415c9c1a..9e4bd7d4c6e 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -835,10 +835,12 @@ class Topic < ActiveRecord::Base
self.auto_close_at = utc.local(now.year, now.month, now.day, m[1].to_i, m[2].to_i)
self.auto_close_at += offset_minutes * 60 if offset_minutes
self.auto_close_at += 1.day if self.auto_close_at < now
+ self.auto_close_hours = -1
elsif arg.is_a?(String) && arg.include?("-") && timestamp = utc.parse(arg)
# a timestamp in client's time zone, like "2015-5-27 12:00"
self.auto_close_at = timestamp
self.auto_close_at += offset_minutes * 60 if offset_minutes
+ self.auto_close_hours = -1
self.errors.add(:auto_close_at, :invalid) if timestamp < Time.zone.now
else
num_hours = arg.to_f
@@ -864,6 +866,10 @@ class Topic < ActiveRecord::Base
else
self.auto_close_user ||= (self.user.staff? || self.user.trust_level == TrustLevel[4] ? self.user : Discourse.system_user)
end
+
+ if self.auto_close_at.try(:<, Time.zone.now)
+ auto_close(auto_close_user)
+ end
end
self
diff --git a/app/models/topic_status_update.rb b/app/models/topic_status_update.rb
index 64dcc256e80..d3befc9f0b7 100644
--- a/app/models/topic_status_update.rb
+++ b/app/models/topic_status_update.rb
@@ -52,7 +52,16 @@ TopicStatusUpdate = Struct.new(:topic, :user) do
end
def message_for_autoclosed(locale_key)
- num_minutes = topic.auto_close_started_at ? ((Time.zone.now - topic.auto_close_started_at) / 1.minute).round : topic.age_in_minutes
+ num_minutes = ((
+ if topic.auto_close_based_on_last_post
+ topic.auto_close_hours.hours
+ elsif topic.auto_close_started_at
+ Time.zone.now - topic.auto_close_started_at
+ else
+ Time.zone.now - topic.created_at
+ end
+ ) / 1.minute).round
+
if num_minutes.minutes >= 2.days
I18n.t("#{locale_key}_days", count: (num_minutes.minutes / 1.day).round)
else
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 4e8946d1bb6..eb01802f6a1 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1142,6 +1142,7 @@ en:
auto_close_title: 'Auto-Close Settings'
auto_close_save: "Save"
auto_close_remove: "Don't Auto-Close This Topic"
+ auto_close_immediate: "The last post in the topic is already %{hours} hours old, so the topic will be closed immediately."
progress:
title: topic progress
diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb
index 08da959738e..c0d18be282b 100644
--- a/spec/controllers/topics_controller_spec.rb
+++ b/spec/controllers/topics_controller_spec.rb
@@ -1036,6 +1036,46 @@ describe TopicsController do
Topic.any_instance.expects(:set_auto_close).with(nil, anything)
xhr :put, :autoclose, topic_id: @topic.id, auto_close_time: nil, auto_close_based_on_last_post: false, timezone_offset: -240
end
+
+ it "will close a topic when the time expires" do
+ topic = Fabricate(:topic)
+ Timecop.freeze(20.hours.ago) do
+ create_post(topic: topic, raw: "This is the body of my cool post in the topic, but it's a bit old now")
+ end
+ topic.save
+
+ Jobs.expects(:enqueue_at).at_least_once
+ xhr :put, :autoclose, topic_id: topic.id, auto_close_time: 24, auto_close_based_on_last_post: true
+
+ topic.reload
+ expect(topic.closed).to eq(false)
+ expect(topic.posts.last.raw).to match(/cool post/)
+
+ Timecop.freeze(5.hours.from_now) do
+ Jobs::CloseTopic.new.execute({topic_id: topic.id, user_id: @admin.id})
+ end
+
+ topic.reload
+ expect(topic.closed).to eq(true)
+ expect(topic.posts.last.raw).to match(/automatically closed/)
+ end
+
+ it "will immediately close if the last post is old enough" do
+ topic = Fabricate(:topic)
+ Timecop.freeze(20.hours.ago) do
+ create_post(topic: topic)
+ end
+ topic.save
+ Topic.reset_highest(topic.id)
+ topic.reload
+
+ xhr :put, :autoclose, topic_id: topic.id, auto_close_time: 10, auto_close_based_on_last_post: true
+
+ topic.reload
+ expect(topic.closed).to eq(true)
+ expect(topic.posts.last.raw).to match(/after the last reply/)
+ expect(topic.posts.last.raw).to match(/10 hours/)
+ end
end
end
diff --git a/spec/models/topic_status_update_spec.rb b/spec/models/topic_status_update_spec.rb
index dc69ebfbca7..549adb9e872 100644
--- a/spec/models/topic_status_update_spec.rb
+++ b/spec/models/topic_status_update_spec.rb
@@ -3,18 +3,21 @@
require 'rails_helper'
require_dependency 'post_destroyer'
+# TODO - test pinning, create_moderator_post
+
describe TopicStatusUpdate do
let(:user) { Fabricate(:user) }
let(:admin) { Fabricate(:admin) }
it "avoids notifying on automatically closed topics" do
- # TODO: TopicStatusUpdate should supress message bus updates from the users it "pretends to read"
+ # TODO: TopicStatusUpdate should suppress message bus updates from the users it "pretends to read"
post = PostCreator.create(user,
raw: "this is a test post 123 this is a test post",
title: "hello world title",
)
# TODO needed so counts sync up, PostCreator really should not give back out-of-date Topic
+ post.topic.set_auto_close('10')
post.topic.reload
TopicStatusUpdate.new(post.topic, admin).update!("autoclosed", true)
@@ -27,6 +30,7 @@ describe TopicStatusUpdate do
it "adds an autoclosed message" do
topic = create_topic
+ topic.set_auto_close('10')
TopicStatusUpdate.new(topic, admin).update!("autoclosed", true)
@@ -39,13 +43,14 @@ describe TopicStatusUpdate do
it "adds an autoclosed message based on last post" do
topic = create_topic
topic.auto_close_based_on_last_post = true
+ topic.set_auto_close('10')
TopicStatusUpdate.new(topic, admin).update!("autoclosed", true)
last_post = topic.posts.last
expect(last_post.post_type).to eq(Post.types[:small_action])
expect(last_post.action_code).to eq('autoclosed.enabled')
- expect(last_post.raw).to eq(I18n.t("topic_statuses.autoclosed_enabled_lastpost_minutes", count: 0))
+ expect(last_post.raw).to eq(I18n.t("topic_statuses.autoclosed_enabled_lastpost_hours", count: 10))
end
end