From 4195c7c9ea1117bef9c3362eb38d818f8303419e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Fri, 25 May 2018 11:45:42 -0400 Subject: [PATCH] FEATURE: Ability to clear a user's penalty history You can do this manually if you want to allow them to reach TL3 without their penalty history counting against them. --- .../admin/controllers/admin-user-index.js.es6 | 10 +++++ .../admin/templates/user-index.hbs | 15 +++++++ app/controllers/admin/users_controller.rb | 40 +++++++++++++++++++ app/models/user_history.rb | 8 ++++ config/locales/client.en.yml | 8 ++++ config/routes.rb | 1 + spec/requests/admin/users_controller_spec.rb | 38 ++++++++++++++++++ 7 files changed, 120 insertions(+) diff --git a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 index cbba833b659..0d2ae55508e 100644 --- a/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-user-index.js.es6 @@ -103,6 +103,16 @@ export default Ember.Controller.extend(CanCheckEmails, { anonymize() { return this.get('model').anonymize(); }, disableSecondFactor() { return this.get('model').disableSecondFactor(); }, + + clearPenaltyHistory() { + let user = this.get('model'); + return ajax(`/admin/users/${user.get('id')}/penalty_history`, { + type: 'DELETE' + }).then(() => { + user.set('tl3_requirements.penalty_counts.total', 0); + }).catch(popupAjaxError); + }, + destroy() { const postCount = this.get('model.post_count'); if (postCount <= 5) { diff --git a/app/assets/javascripts/admin/templates/user-index.hbs b/app/assets/javascripts/admin/templates/user-index.hbs index aec1b986f20..e36f7cd4b5c 100644 --- a/app/assets/javascripts/admin/templates/user-index.hbs +++ b/app/assets/javascripts/admin/templates/user-index.hbs @@ -408,6 +408,21 @@ {{/if}} + {{#if model.tl3_requirements.penalty_counts.total}} +
+
{{i18n 'admin.user.penalty_count'}}
+
{{model.tl3_requirements.penalty_counts.total}}
+ {{#if currentUser.admin}} +
+ {{d-button label="admin.user.clear_penalty_history.title" + icon="times" + action=(action "clearPenaltyHistory")}} + {{i18n "admin.user.clear_penalty_history.description"}} +
+ {{/if}} +
+ {{/if}} + {{#if currentUser.admin}} diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c43a3e8f05f..7b7f42096c0 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -54,6 +54,46 @@ class Admin::UsersController < Admin::AdminController end end + # DELETE action to delete penalty history for a user + def penalty_history + + # We don't delete any history, we merely remove the action type + # with a removed type. It can still be viewed in the logs but + # will not affect TL3 promotions. + sql = <<~SQL + UPDATE user_histories + SET action = CASE + WHEN action = :silence_user THEN :removed_silence_user + WHEN action = :unsilence_user THEN :removed_unsilence_user + WHEN action = :suspend_user THEN :removed_suspend_user + WHEN action = :unsuspend_user THEN :removed_unsuspend_user + END + WHERE target_user_id = :user_id + AND action IN ( + :silence_user, + :suspend_user, + :unsilence_user, + :unsuspend_user + ) + SQL + + UserHistory.exec_sql( + sql, + UserHistory.actions.slice( + :silence_user, + :suspend_user, + :unsilence_user, + :unsuspend_user, + :removed_silence_user, + :removed_unsilence_user, + :removed_suspend_user, + :removed_unsuspend_user + ).merge(user_id: params[:user_id].to_i) + ) + + render json: success_json + end + def suspend guardian.ensure_can_suspend!(@user) @user.suspended_till = params[:suspend_until] diff --git a/app/models/user_history.rb b/app/models/user_history.rb index 0a247331157..f500d831793 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -76,6 +76,10 @@ class UserHistory < ActiveRecord::Base create_badge: 57, change_badge: 58, delete_badge: 59, + removed_silence_user: 60, + removed_suspend_user: 61, + removed_unsilence_user: 62, + removed_unsuspend_user: 63, ) end @@ -90,6 +94,8 @@ class UserHistory < ActiveRecord::Base :change_site_text, :suspend_user, :unsuspend_user, + :removed_suspend_user, + :removed_unsuspend_user, :grant_badge, :revoke_badge, :check_email, @@ -106,6 +112,8 @@ class UserHistory < ActiveRecord::Base :create_category, :silence_user, :unsilence_user, + :removed_silence_user, + :removed_unsilence_user, :grant_admin, :revoke_admin, :grant_moderation, diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index e936696bf8d..7e88bdbade5 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3365,6 +3365,8 @@ en: change_site_text: "change site text" suspend_user: "suspend user" unsuspend_user: "unsuspend user" + removed_suspend_user: "suspend user (removed)" + removed_unsuspend_user: "unsuspend user (removed)" grant_badge: "grant badge" revoke_badge: "revoke badge" check_email: "check email" @@ -3379,6 +3381,8 @@ en: create_category: "create category" silence_user: "silence user" unsilence_user: "unsilence user" + removed_silence_user: "silence user (removed)" + removed_unsilence_user: "unsilence user (removed)" grant_admin: "grant admin" revoke_admin: "revoke admin" grant_moderation: "grant moderation" @@ -3563,6 +3567,10 @@ en: penalty_post_delete: "Delete the post" penalty_post_edit: "Edit the post" penalty_post_none: "Do nothing" + penalty_count: "Penalty Count" + clear_penalty_history: + title: "Clear Penalty History" + description: "users with penalties cannot reach TL3" # keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details delete_all_posts_confirm_MF: "You are about to delete {POSTS, plural, one {1 post} other {# posts}} and {TOPICS, plural, one {1 topic} other {# topics}}. Are you sure?" diff --git a/config/routes.rb b/config/routes.rb index 71bcbaab242..8765881d63d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -103,6 +103,7 @@ Discourse::Application.routes.draw do put "approve-bulk" => "users#approve_bulk" delete "reject-bulk" => "users#reject_bulk" end + delete "penalty_history", constraints: AdminConstraint.new put "suspend" put "delete_all_posts" put "unsuspend" diff --git a/spec/requests/admin/users_controller_spec.rb b/spec/requests/admin/users_controller_spec.rb index 8d665aea2ea..e13942e7792 100644 --- a/spec/requests/admin/users_controller_spec.rb +++ b/spec/requests/admin/users_controller_spec.rb @@ -47,4 +47,42 @@ RSpec.describe Admin::UsersController do end end end + + describe "#penalty_history" do + let(:moderator) { Fabricate(:moderator) } + let(:logger) { StaffActionLogger.new(admin) } + + it "doesn't allow moderators to clear a user's history" do + sign_in(moderator) + delete "/admin/users/#{user.id}/penalty_history.json" + expect(response.code).to eq("404") + end + + def find_logs(action) + UserHistory.where(target_user_id: user.id, action: UserHistory.actions[action]) + end + + it "allows admins to clear a user's history" do + logger.log_user_suspend(user, "suspend reason") + logger.log_user_unsuspend(user) + logger.log_unsilence_user(user) + logger.log_silence_user(user) + + sign_in(admin) + delete "/admin/users/#{user.id}/penalty_history.json" + expect(response.code).to eq("200") + + expect(find_logs(:suspend_user)).to be_blank + expect(find_logs(:unsuspend_user)).to be_blank + expect(find_logs(:silence_user)).to be_blank + expect(find_logs(:unsilence_user)).to be_blank + + expect(find_logs(:removed_suspend_user)).to be_present + expect(find_logs(:removed_unsuspend_user)).to be_present + expect(find_logs(:removed_silence_user)).to be_present + expect(find_logs(:removed_unsilence_user)).to be_present + end + + end + end