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.
This commit is contained in:
Robin Ward 2018-05-25 11:45:42 -04:00
parent c658fb6e31
commit 4195c7c9ea
7 changed files with 120 additions and 0 deletions

View File

@ -103,6 +103,16 @@ export default Ember.Controller.extend(CanCheckEmails, {
anonymize() { return this.get('model').anonymize(); }, anonymize() { return this.get('model').anonymize(); },
disableSecondFactor() { return this.get('model').disableSecondFactor(); }, 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() { destroy() {
const postCount = this.get('model.post_count'); const postCount = this.get('model.post_count');
if (postCount <= 5) { if (postCount <= 5) {

View File

@ -408,6 +408,21 @@
</div> </div>
{{/if}} {{/if}}
{{#if model.tl3_requirements.penalty_counts.total}}
<div class='display-row clear-penalty-history'>
<div class='field'>{{i18n 'admin.user.penalty_count'}}</div>
<div class='value'>{{model.tl3_requirements.penalty_counts.total}}</div>
{{#if currentUser.admin}}
<div class='controls'>
{{d-button label="admin.user.clear_penalty_history.title"
icon="times"
action=(action "clearPenaltyHistory")}}
{{i18n "admin.user.clear_penalty_history.description"}}
</div>
{{/if}}
</div>
{{/if}}
</section> </section>
{{#if currentUser.admin}} {{#if currentUser.admin}}

View File

@ -54,6 +54,46 @@ class Admin::UsersController < Admin::AdminController
end end
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 def suspend
guardian.ensure_can_suspend!(@user) guardian.ensure_can_suspend!(@user)
@user.suspended_till = params[:suspend_until] @user.suspended_till = params[:suspend_until]

View File

@ -76,6 +76,10 @@ class UserHistory < ActiveRecord::Base
create_badge: 57, create_badge: 57,
change_badge: 58, change_badge: 58,
delete_badge: 59, delete_badge: 59,
removed_silence_user: 60,
removed_suspend_user: 61,
removed_unsilence_user: 62,
removed_unsuspend_user: 63,
) )
end end
@ -90,6 +94,8 @@ class UserHistory < ActiveRecord::Base
:change_site_text, :change_site_text,
:suspend_user, :suspend_user,
:unsuspend_user, :unsuspend_user,
:removed_suspend_user,
:removed_unsuspend_user,
:grant_badge, :grant_badge,
:revoke_badge, :revoke_badge,
:check_email, :check_email,
@ -106,6 +112,8 @@ class UserHistory < ActiveRecord::Base
:create_category, :create_category,
:silence_user, :silence_user,
:unsilence_user, :unsilence_user,
:removed_silence_user,
:removed_unsilence_user,
:grant_admin, :grant_admin,
:revoke_admin, :revoke_admin,
:grant_moderation, :grant_moderation,

View File

@ -3365,6 +3365,8 @@ en:
change_site_text: "change site text" change_site_text: "change site text"
suspend_user: "suspend user" suspend_user: "suspend user"
unsuspend_user: "unsuspend user" unsuspend_user: "unsuspend user"
removed_suspend_user: "suspend user (removed)"
removed_unsuspend_user: "unsuspend user (removed)"
grant_badge: "grant badge" grant_badge: "grant badge"
revoke_badge: "revoke badge" revoke_badge: "revoke badge"
check_email: "check email" check_email: "check email"
@ -3379,6 +3381,8 @@ en:
create_category: "create category" create_category: "create category"
silence_user: "silence user" silence_user: "silence user"
unsilence_user: "unsilence user" unsilence_user: "unsilence user"
removed_silence_user: "silence user (removed)"
removed_unsilence_user: "unsilence user (removed)"
grant_admin: "grant admin" grant_admin: "grant admin"
revoke_admin: "revoke admin" revoke_admin: "revoke admin"
grant_moderation: "grant moderation" grant_moderation: "grant moderation"
@ -3563,6 +3567,10 @@ en:
penalty_post_delete: "Delete the post" penalty_post_delete: "Delete the post"
penalty_post_edit: "Edit the post" penalty_post_edit: "Edit the post"
penalty_post_none: "Do nothing" 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 # 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?" 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?"

View File

@ -103,6 +103,7 @@ Discourse::Application.routes.draw do
put "approve-bulk" => "users#approve_bulk" put "approve-bulk" => "users#approve_bulk"
delete "reject-bulk" => "users#reject_bulk" delete "reject-bulk" => "users#reject_bulk"
end end
delete "penalty_history", constraints: AdminConstraint.new
put "suspend" put "suspend"
put "delete_all_posts" put "delete_all_posts"
put "unsuspend" put "unsuspend"

View File

@ -47,4 +47,42 @@ RSpec.describe Admin::UsersController do
end end
end 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 end