From c8111ada6e7a1e72b6c80f8606323444b3e3afee Mon Sep 17 00:00:00 2001 From: riking Date: Sat, 13 Sep 2014 13:55:26 -0700 Subject: [PATCH] FEATURE: Allow admins to lock users from TL3 promotion/demotion Also, update the display logic for the leader promotion screen to account for the demotion grace period. --- .../javascripts/admin/models/admin_user.js | 18 +++++++ .../admin/models/leader_requirements.js | 6 ++- .../admin_user_leader_requirements_route.js | 10 ++++ .../templates/user_leader_requirements.hbs | 47 +++++++++++++++---- app/controllers/admin/users_controller.rb | 14 ++++++ app/jobs/scheduled/leader_promotions.rb | 6 +-- app/models/trust_level3_requirements.rb | 13 ++++- app/models/user.rb | 8 ++++ .../trust_level3_requirements_serializer.rb | 6 +++ config/locales/client.en.yml | 9 +++- config/locales/server.en.yml | 1 + config/routes.rb | 1 + 12 files changed, 121 insertions(+), 18 deletions(-) diff --git a/app/assets/javascripts/admin/models/admin_user.js b/app/assets/javascripts/admin/models/admin_user.js index d1f7a4aac..2f35fcae9 100644 --- a/app/assets/javascripts/admin/models/admin_user.js +++ b/app/assets/javascripts/admin/models/admin_user.js @@ -164,6 +164,24 @@ Discourse.AdminUser = Discourse.User.extend({ this.set('trustLevel.id', this.get('originalTrustLevel')); }, + lockTrustLevel: function(locked) { + Discourse.ajax("/admin/users/" + this.id + "/trust_level_lock", { + type: 'PUT', + data: { locked: !!locked } + }).then(function() { + // succeeded + window.location.reload(); + }, function(e) { + // failure + var error; + if (e.responseJSON && e.responseJSON.errors) { + error = e.responseJSON.errors[0]; + } + error = error || I18n.t('admin.user.trust_level_change_failed', { error: "http: " + e.status + " - " + e.body }); + bootbox.alert(error); + }); + }, + isSuspended: Em.computed.equal('suspended', true), canSuspend: Em.computed.not('staff'), diff --git a/app/assets/javascripts/admin/models/leader_requirements.js b/app/assets/javascripts/admin/models/leader_requirements.js index a590ae532..b1a300108 100644 --- a/app/assets/javascripts/admin/models/leader_requirements.js +++ b/app/assets/javascripts/admin/models/leader_requirements.js @@ -18,7 +18,8 @@ Discourse.LeaderRequirements = Discourse.Model.extend({ flagged_posts: this.get('num_flagged_posts') <= this.get('max_flagged_posts'), flagged_by_users: this.get('num_flagged_by_users') <= this.get('max_flagged_by_users'), likes_given: this.get('num_likes_given') >= this.get('min_likes_given'), - likes_received: this.get('num_likes_received') >= this.get('min_likes_received') + likes_received: this.get('num_likes_received') >= this.get('min_likes_received'), + level_locked: this.get('trust_level_locked') }; }.property('days_visited', 'min_days_visited', 'num_topics_replied_to', 'min_topics_replied_to', @@ -29,5 +30,6 @@ Discourse.LeaderRequirements = Discourse.Model.extend({ 'posts_read_all_time', 'min_posts_read_all_time', 'num_flagged_by_users', 'max_flagged_by_users', 'num_likes_given', 'min_likes_given', - 'num_likes_received', 'min_likes_received') + 'num_likes_received', 'min_likes_received', + 'trust_level_locked') }); diff --git a/app/assets/javascripts/admin/routes/admin_user_leader_requirements_route.js b/app/assets/javascripts/admin/routes/admin_user_leader_requirements_route.js index 83a851721..8afe74ab1 100644 --- a/app/assets/javascripts/admin/routes/admin_user_leader_requirements_route.js +++ b/app/assets/javascripts/admin/routes/admin_user_leader_requirements_route.js @@ -10,5 +10,15 @@ Discourse.AdminUserLeaderRequirementsRoute = Discourse.Route.extend({ model: function() { return this.modelFor('adminUser'); + }, + + actions: { + lock_trust_level: function() { + this.modelFor('adminUser').lockTrustLevel(true); + }, + + unlock_trust_level: function() { + this.modelFor('adminUser').lockTrustLevel(false); + } } }); diff --git a/app/assets/javascripts/admin/templates/user_leader_requirements.hbs b/app/assets/javascripts/admin/templates/user_leader_requirements.hbs index d5fda5312..4998bffdc 100644 --- a/app/assets/javascripts/admin/templates/user_leader_requirements.hbs +++ b/app/assets/javascripts/admin/templates/user_leader_requirements.hbs @@ -85,21 +85,50 @@ {{num_likes_received}} {{min_likes_received}} + + {{i18n admin.user.tl3_requirements.trust_level_locked}} + + {{#if trust_level_locked}} + {{i18n yes_value}} + {{i18n admin.user.tl3_requirements.unlock_tl}} + {{else}} + {{i18n no_value}} + {{i18n admin.user.tl3_requirements.lock_tl}} + {{/if}} + {{/with}}

- {{#if leaderRequirements.requirements_met}} - {{i18n admin.user.tl3_requirements.qualifies}} - {{#unless isLeader}} - {{i18n admin.user.tl3_requirements.will_be_promoted}} - {{/unless}} - {{else}} - {{i18n admin.user.tl3_requirements.does_not_qualify}} - {{#if suspended}} - {{i18n user.suspended_notice date="suspendedTillDate"}} + {{#if isLeader}} + {{#if leaderRequirements.requirements_lost}} + {{! tl implicitly not locked }} + {{#if leaderRequirements.on_grace_period}} + {{i18n admin.user.tl3_requirements.on_grace_period}} + {{else}} {{! not on grace period }} + {{i18n admin.user.tl3_requirements.does_not_qualify}} + {{i18n admin.user.tl3_requirements.will_be_demoted}} + {{/if}} + {{else}} {{! requirements not lost - remains leader }} + {{#if leaderRequirements.trust_level_locked}} + {{i18n admin.user.tl3_requirements.locked_will_not_be_demoted}} + {{else}} {{! tl not locked }} + {{i18n admin.user.tl3_requirements.qualifies}} + {{/if}} + {{/if}} + {{else}} {{! is not leader }} + {{#if leaderRequirements.requirements_met}} + {{! met & not leader - will be promoted}} + {{i18n admin.user.tl3_requirements.qualifies}} + {{i18n admin.user.tl3_requirements.will_be_promoted}} + {{else}} {{! requirements not met - remains regular }} + {{#if leaderRequirements.trust_level_locked}} + {{i18n admin.user.tl3_requirements.locked_will_not_be_promoted}} + {{else}} + {{i18n admin.user.tl3_requirements.does_not_qualify}} + {{/if}} {{/if}} {{/if}}

diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 00eb87dd7..d5a4daeaa 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -17,6 +17,7 @@ class Admin::UsersController < Admin::AdminController :block, :unblock, :trust_level, + :trust_level_lock, :add_group, :remove_group, :primary_group, @@ -134,6 +135,19 @@ class Admin::UsersController < Admin::AdminController render_json_error(e.message) end + def trust_level_lock + guardian.ensure_can_change_trust_level!(@user) + + new_lock = params[:locked] + unless new_lock =~ /t|f|true|false/ + return render_json_error I18n.t('errors.invalid_boolaen') + end + + @user.trust_level_locked = !!(new_lock =~ /t|true/) + @user.save + render nothing: true + end + def approve guardian.ensure_can_approve!(@user) @user.approve(current_user) diff --git a/app/jobs/scheduled/leader_promotions.rb b/app/jobs/scheduled/leader_promotions.rb index 8c15eb949..b7e067f61 100644 --- a/app/jobs/scheduled/leader_promotions.rb +++ b/app/jobs/scheduled/leader_promotions.rb @@ -8,11 +8,7 @@ module Jobs demoted_user_ids = [] User.real.where(trust_level: TrustLevel[3]).find_each do |u| # Don't demote too soon after being promoted - next if UserHistory.for(u, :auto_trust_level_change) - .where('created_at >= ?', SiteSetting.tl3_promotion_min_duration.to_i.days.ago) - .where(previous_value: TrustLevel[2].to_s) - .where(new_value: TrustLevel[3].to_s) - .exists? + next if user.on_leader_grace_period? if Promotion.tl3_lost?(u) demoted_user_ids << u.id diff --git a/app/models/trust_level3_requirements.rb b/app/models/trust_level3_requirements.rb index f9ea136db..552cc5c1b 100644 --- a/app/models/trust_level3_requirements.rb +++ b/app/models/trust_level3_requirements.rb @@ -16,13 +16,15 @@ class TrustLevel3Requirements :posts_read_all_time, :min_posts_read_all_time, :num_flagged_posts, :max_flagged_posts, :num_likes_given, :min_likes_given, - :num_likes_received, :min_likes_received + :num_likes_received, :min_likes_received, + :trust_level_locked, :on_grace_period def initialize(user) @user = user end def requirements_met? + return false if trust_level_locked !@user.suspended? && days_visited >= min_days_visited && num_topics_replied_to >= min_topics_replied_to && @@ -37,6 +39,7 @@ class TrustLevel3Requirements end def requirements_lost? + return false if trust_level_locked @user.suspended? || days_visited < min_days_visited * LOW_WATER_MARK || num_topics_replied_to < min_topics_replied_to * LOW_WATER_MARK || @@ -50,6 +53,14 @@ class TrustLevel3Requirements num_likes_received < min_likes_received * LOW_WATER_MARK end + def trust_level_locked + @user.trust_level_locked + end + + def on_grace_period + @user.on_leader_grace_period? + end + def days_visited @user.user_visits.where("visited_at > ? and posts_read > 0", TIME_PERIOD.days.ago).count end diff --git a/app/models/user.rb b/app/models/user.rb index b00762de5..eadcd59c0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -574,6 +574,14 @@ class User < ActiveRecord::Base @lq ||= TrustLevel3Requirements.new(self) end + def on_leader_grace_period? + UserHistory.for(self, :auto_trust_level_change) + .where('created_at >= ?', SiteSetting.tl3_promotion_min_duration.to_i.days.ago) + .where(previous_value: TrustLevel[2].to_s) + .where(new_value: TrustLevel[3].to_s) + .exists? + end + def should_be_redirected_to_top redirected_to_top_reason.present? end diff --git a/app/serializers/trust_level3_requirements_serializer.rb b/app/serializers/trust_level3_requirements_serializer.rb index b5f2c7e1e..7c9f5f02d 100644 --- a/app/serializers/trust_level3_requirements_serializer.rb +++ b/app/serializers/trust_level3_requirements_serializer.rb @@ -1,6 +1,8 @@ class TrustLevel3RequirementsSerializer < ApplicationSerializer attributes :time_period, :requirements_met, + :requirements_lost, + :trust_level_locked, :on_grace_period, :days_visited, :min_days_visited, :num_topics_replied_to, :min_topics_replied_to, :topics_viewed, :min_topics_viewed, @@ -19,4 +21,8 @@ class TrustLevel3RequirementsSerializer < ApplicationSerializer def requirements_met object.requirements_met? end + + def requirements_lost + object.requirements_lost? + end end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f6cbf7c12..0ba24a19e 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1975,9 +1975,16 @@ en: flagged_by_users: "Users Who Flagged" likes_given: "Likes Given" likes_received: "Likes Received" + trust_level_locked: "Trust Level Locked" + lock_tl: "Lock" + unlock_tl: "Unlock" qualifies: "Qualifies for trust level 3." - will_be_promoted: "Will be promoted within 24 hours." does_not_qualify: "Doesn't qualify for trust level 3." + will_be_promoted: "Will be promoted within 24 hours." + will_be_demoted: "Will be demoted within 24 hours." + on_grace_period: "Currently in promotion grace period, will not be demoted." + locked_will_not_be_promoted: "Trust level locked. Will never be promoted." + locked_will_not_be_demoted: "Trust level locked. Will never be demoted." sso: title: "Single Sign On" external_id: "External ID" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index ffd024e2f..aa6af53ad 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -41,6 +41,7 @@ en: errors: messages: too_long_validation: "is limited to %{max} characters; you entered %{length}." + invalid_boolean: "Invalid boolean." embed: load_from_remote: "There was an error loading that post." diff --git a/config/routes.rb b/config/routes.rb index 1b8d92f89..0a98a95df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -72,6 +72,7 @@ Discourse::Application.routes.draw do put "block" put "unblock" put "trust_level" + put "trust_level_lock" put "primary_group" post "groups" => "users#add_group", constraints: AdminConstraint.new delete "groups/:group_id" => "users#remove_group", constraints: AdminConstraint.new