From b9072e8292833f11f032d26f5814fa188f9ab3b3 Mon Sep 17 00:00:00 2001 From: Gerhard Schlager Date: Fri, 10 Aug 2018 02:51:03 +0200 Subject: [PATCH] FEATURE: Add "Reset Bump Date" action to topic admin wrench (#6246) --- .../discourse/controllers/topic.js.es6 | 4 ++ .../javascripts/discourse/models/topic.js.es6 | 6 +++ .../components/topic-footer-buttons.hbs | 1 + .../javascripts/discourse/templates/topic.hbs | 4 ++ .../discourse/widgets/topic-admin-menu.js.es6 | 9 +++++ app/controllers/topics_controller.rb | 14 ++++++- app/models/topic.rb | 10 +++++ config/locales/client.en.yml | 1 + config/routes.rb | 1 + lib/guardian/topic_guardian.rb | 4 ++ spec/models/topic_spec.rb | 23 ++++++++++++ spec/requests/topics_controller_spec.rb | 37 +++++++++++++++++++ 12 files changed, 113 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/controllers/topic.js.es6 b/app/assets/javascripts/discourse/controllers/topic.js.es6 index fab0f360540..ea4d8e0ed3d 100644 --- a/app/assets/javascripts/discourse/controllers/topic.js.es6 +++ b/app/assets/javascripts/discourse/controllers/topic.js.es6 @@ -930,6 +930,10 @@ export default Ember.Controller.extend(BufferedContent, { removeFeaturedLink() { this.set("buffered.featured_link", null); + }, + + resetBumpDate() { + this.get("content").resetBumpDate(); } }, diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index 68be7f9b016..4be65438569 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -565,6 +565,12 @@ const Topic = RestModel.extend({ window.location.reload(); }) .catch(popupAjaxError); + }, + + resetBumpDate() { + return ajax(`/t/${this.get("id")}/reset-bump-date`, { type: "PUT" }).catch( + popupAjaxError + ); } }); diff --git a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs index 1f5fd34ae75..0c0442cc4c9 100644 --- a/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs +++ b/app/assets/javascripts/discourse/templates/components/topic-footer-buttons.hbs @@ -13,6 +13,7 @@ showTopicStatusUpdate=showTopicStatusUpdate showFeatureTopic=showFeatureTopic showChangeTimestamp=showChangeTimestamp + resetBumpDate=resetBumpDate convertToPublicTopic=convertToPublicTopic convertToPrivateMessage=convertToPrivateMessage}} {{/if}} diff --git a/app/assets/javascripts/discourse/templates/topic.hbs b/app/assets/javascripts/discourse/templates/topic.hbs index 94870de470a..1ebdb92ac31 100644 --- a/app/assets/javascripts/discourse/templates/topic.hbs +++ b/app/assets/javascripts/discourse/templates/topic.hbs @@ -94,6 +94,7 @@ showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + resetBumpDate=(action "resetBumpDate") convertToPublicTopic=(action "convertToPublicTopic") convertToPrivateMessage=(action "convertToPrivateMessage")}} {{/if}} @@ -121,6 +122,7 @@ showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + resetBumpDate=(action "resetBumpDate") convertToPublicTopic=(action "convertToPublicTopic") convertToPrivateMessage=(action "convertToPrivateMessage")}} {{else}} @@ -144,6 +146,7 @@ showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + resetBumpDate=(action "resetBumpDate") convertToPublicTopic=(action "convertToPublicTopic") convertToPrivateMessage=(action "convertToPrivateMessage")}} {{/if}} @@ -256,6 +259,7 @@ showTopicStatusUpdate=(action "topicRouteAction" "showTopicStatusUpdate") showFeatureTopic=(action "topicRouteAction" "showFeatureTopic") showChangeTimestamp=(action "topicRouteAction" "showChangeTimestamp") + resetBumpDate=(action "resetBumpDate") convertToPublicTopic=(action "convertToPublicTopic") convertToPrivateMessage=(action "convertToPrivateMessage") toggleBookmark=(action "toggleBookmark") diff --git a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 index c66887995c7..52b0616abe7 100644 --- a/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 +++ b/app/assets/javascripts/discourse/widgets/topic-admin-menu.js.es6 @@ -203,6 +203,15 @@ export default createWidget("topic-admin-menu", { }); } + if (this.currentUser.get("staff")) { + buttons.push({ + className: "topic-admin-reset-bump-date", + action: "resetBumpDate", + icon: "anchor", + label: "actions.reset_bump_date" + }); + } + if (!isPrivateMessage) { buttons.push({ className: "topic-admin-archive", diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index a4ffc8aaf28..39f94a98ec4 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -33,7 +33,8 @@ class TopicsController < ApplicationController :move_to_inbox, :convert_topic, :bookmark, - :publish + :publish, + :reset_bump_date ] before_action :consider_user_for_promotion, only: :show @@ -720,6 +721,17 @@ class TopicsController < ApplicationController render_json_error(ex) end + def reset_bump_date + params.require(:id) + guardian.ensure_can_update_bumped_at! + + topic = Topic.find_by(id: params[:id]) + raise Discourse::NotFound.new unless topic + + topic.reset_bumped_at + render body: nil + end + private def topic_params diff --git a/app/models/topic.rb b/app/models/topic.rb index f83e1a84a6b..c54ab04f77c 100644 --- a/app/models/topic.rb +++ b/app/models/topic.rb @@ -1381,6 +1381,16 @@ class Topic < ActiveRecord::Base @is_category_topic ||= Category.exists?(topic_id: self.id.to_i) end + def reset_bumped_at + post = ordered_posts.where( + user_deleted: false, + hidden: false, + post_type: Topic.visible_post_types + ).last + + update!(bumped_at: post.created_at) + end + private def update_category_topic_count_by(num) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 89ea74359ea..9c6f2f74823 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1862,6 +1862,7 @@ en: reset_read: "Reset Read Data" make_public: "Make Public Topic" make_private: "Make Personal Message" + reset_bump_date: "Reset Bump Date" feature: pin: "Pin Topic" diff --git a/config/routes.rb b/config/routes.rb index 441f4482402..15214adabba 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -628,6 +628,7 @@ Discourse::Application.routes.draw do put "t/:id/convert-topic/:type" => "topics#convert_topic" put "t/:id/publish" => "topics#publish" put "t/:id/shared-draft" => "topics#update_shared_draft" + put "t/:id/reset-bump-date" => "topics#reset_bump_date" put "topics/bulk" put "topics/reset-new" => 'topics#reset_new' post "topics/timings" diff --git a/lib/guardian/topic_guardian.rb b/lib/guardian/topic_guardian.rb index cd915f5fe35..8a7fd9d1172 100644 --- a/lib/guardian/topic_guardian.rb +++ b/lib/guardian/topic_guardian.rb @@ -146,4 +146,8 @@ module TopicGuardian return false unless SiteSetting.topic_featured_link_enabled Category.where(id: category_id || SiteSetting.uncategorized_category_id, topic_featured_link_allowed: true).exists? end + + def can_update_bumped_at? + is_staff? + end end diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb index 663e67056c6..9e6b5afce5c 100644 --- a/spec/models/topic_spec.rb +++ b/spec/models/topic_spec.rb @@ -2296,4 +2296,27 @@ describe Topic do end end end + + describe "#reset_bumped_at" do + it "ignores hidden and deleted posts when resetting the topic's bump date" do + post = create_post(created_at: 10.hours.ago) + topic = post.topic + + expect { topic.reset_bumped_at }.to_not change { topic.bumped_at } + + post = Fabricate(:post, topic: topic, post_number: 2, created_at: 9.hours.ago) + Fabricate(:post, topic: topic, post_number: 3, created_at: 8.hours.ago, deleted_at: 1.hour.ago) + Fabricate(:post, topic: topic, post_number: 4, created_at: 7.hours.ago, hidden: true) + Fabricate(:post, topic: topic, post_number: 5, created_at: 6.hours.ago, user_deleted: true) + Fabricate(:post, topic: topic, post_number: 6, created_at: 5.hours.ago, post_type: Post.types[:whisper]) + + expect { topic.reset_bumped_at }.to change { topic.bumped_at }.to(post.reload.created_at) + + post = Fabricate(:post, topic: topic, post_number: 7, created_at: 4.hours.ago, post_type: Post.types[:moderator_action]) + expect { topic.reset_bumped_at }.to change { topic.bumped_at }.to(post.reload.created_at) + + post = Fabricate(:post, topic: topic, post_number: 8, created_at: 3.hours.ago, post_type: Post.types[:small_action]) + expect { topic.reset_bumped_at }.to change { topic.bumped_at }.to(post.reload.created_at) + end + end end diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index a4e43d0bf34..ece66eef134 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -2266,4 +2266,41 @@ RSpec.describe TopicsController do end + describe "#reset_bump_date" do + context "errors" do + let(:topic) { Fabricate(:topic) } + + it "needs you to be logged in" do + put "/t/#{topic.id}/reset-bump-date.json" + expect(response.status).to eq(403) + end + + [:user, :trust_level_4].each do |user| + it "denies access for #{user}" do + sign_in(Fabricate(user)) + put "/t/#{topic.id}/reset-bump-date.json" + expect(response.status).to eq(403) + end + end + + it "should fail for non-existend topic" do + sign_in(Fabricate(:admin)) + put "/t/1/reset-bump-date.json" + expect(response.status).to eq(404) + end + end + + [:admin, :moderator].each do |user| + it "should reset bumped_at as #{user}" do + sign_in(Fabricate(user)) + topic = Fabricate(:topic, bumped_at: 1.hour.ago) + timestamp = 1.day.ago + Fabricate(:post, topic: topic, created_at: timestamp) + + put "/t/#{topic.id}/reset-bump-date.json" + expect(response.status).to eq(200) + expect(topic.reload.bumped_at).to be_within_one_second_of(timestamp) + end + end + end end