diff --git a/app/assets/javascripts/admin/templates/users_list.hbs b/app/assets/javascripts/admin/templates/users_list.hbs index e5d1a413a..36ae00e22 100644 --- a/app/assets/javascripts/admin/templates/users_list.hbs +++ b/app/assets/javascripts/admin/templates/users_list.hbs @@ -9,6 +9,7 @@
  • {{#link-to 'adminUsersList.show' 'staff'}}{{i18n admin.users.nav.staff}}{{/link-to}}
  • {{#link-to 'adminUsersList.show' 'suspended'}}{{i18n admin.users.nav.suspended}}{{/link-to}}
  • {{#link-to 'adminUsersList.show' 'blocked'}}{{i18n admin.users.nav.blocked}}{{/link-to}}
  • +
  • {{#link-to 'adminUsersList.show' 'suspect'}}{{i18n admin.users.nav.suspect}}{{/link-to}}
  • diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 6649d2223..efd9b3be4 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -32,7 +32,7 @@ class Admin::UsersController < Admin::AdminController StaffActionLogger.new(current_user).log_show_emails(users) end - render_serialized(users, AdminUserSerializer) + render_serialized(users, AdminUserListSerializer) end def show diff --git a/app/serializers/admin_user_list_serializer.rb b/app/serializers/admin_user_list_serializer.rb new file mode 100644 index 000000000..0f04ce90f --- /dev/null +++ b/app/serializers/admin_user_list_serializer.rb @@ -0,0 +1,78 @@ +class AdminUserListSerializer < BasicUserSerializer + + attributes :email, + :active, + :admin, + :moderator, + :last_seen_age, + :last_emailed_age, + :created_at_age, + :username_lower, + :trust_level, + :trust_level_locked, + :flag_level, + :username, + :title, + :avatar_template, + :can_approve, + :approved, + :suspended_at, + :suspended_till, + :suspended, + :blocked, + :time_read + + [:days_visited, :posts_read_count, :topics_entered, :post_count].each do |sym| + attributes sym + define_method sym do + object.user_stat.send(sym) + end + end + + def include_email? + # staff members can always see their email + (scope.is_staff? && object.id == scope.user.id) || scope.can_see_emails? + end + + alias_method :include_associated_accounts?, :include_email? + + def suspended + object.suspended? + end + + def can_impersonate + scope.can_impersonate?(object) + end + + def last_emailed_age + return nil if object.last_emailed_at.blank? + AgeWords.age_words(Time.now - object.last_emailed_at) + end + + def last_seen_age + return nil if object.last_seen_at.blank? + AgeWords.age_words(Time.now - object.last_seen_at) + end + + def time_read + return nil if object.user_stat.time_read.blank? + AgeWords.age_words(object.user_stat.time_read) + end + + def created_at_age + AgeWords.age_words(Time.now - object.created_at) + end + + def can_approve + scope.can_approve?(object) + end + + def include_can_approve? + SiteSetting.must_approve_users + end + + def include_approved? + SiteSetting.must_approve_users + end + +end diff --git a/app/serializers/admin_user_serializer.rb b/app/serializers/admin_user_serializer.rb index 9d40ee61d..717316039 100644 --- a/app/serializers/admin_user_serializer.rb +++ b/app/serializers/admin_user_serializer.rb @@ -1,88 +1,16 @@ -class AdminUserSerializer < BasicUserSerializer +require_dependency 'admin_user_list_serializer' - attributes :email, - :active, - :admin, - :moderator, - :last_seen_age, - :last_emailed_age, - :created_at_age, - :username_lower, - :trust_level, - :trust_level_locked, - :flag_level, - :username, - :title, - :avatar_template, - :can_approve, - :approved, - :suspended_at, - :suspended_till, - :suspended, - :ip_address, - :registration_ip_address, +class AdminUserSerializer < AdminUserListSerializer + + attributes :associated_accounts, :can_send_activation_email, :can_activate, - :can_deactivate, - :blocked, - :time_read, - :associated_accounts + :ip_address, + :registration_ip_address, + :can_send_activation_email has_one :single_sign_on_record, serializer: SingleSignOnRecordSerializer, embed: :objects - [:days_visited, :posts_read_count, :topics_entered, :post_count].each do |sym| - attributes sym - define_method sym do - object.user_stat.send(sym) - end - end - - def include_email? - # staff members can always see their email - (scope.is_staff? && object.id == scope.user.id) || scope.can_see_emails? - end - - alias_method :include_associated_accounts?, :include_email? - - def suspended - object.suspended? - end - - def can_impersonate - scope.can_impersonate?(object) - end - - def last_emailed_age - return nil if object.last_emailed_at.blank? - AgeWords.age_words(Time.now - object.last_emailed_at) - end - - def last_seen_age - return nil if object.last_seen_at.blank? - AgeWords.age_words(Time.now - object.last_seen_at) - end - - def time_read - return nil if object.user_stat.time_read.blank? - AgeWords.age_words(object.user_stat.time_read) - end - - def created_at_age - AgeWords.age_words(Time.now - object.created_at) - end - - def can_approve - scope.can_approve?(object) - end - - def include_can_approve? - SiteSetting.must_approve_users - end - - def include_approved? - SiteSetting.must_approve_users - end - def can_send_activation_email scope.can_send_activation_email?(object) end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 15a332039..a22f8aab1 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -1895,6 +1895,7 @@ en: staff: 'Staff' suspended: 'Suspended' blocked: 'Blocked' + suspect: 'Suspect' approved: "Approved?" approved_selected: one: "approve user" @@ -1916,6 +1917,7 @@ en: moderators: 'Moderators' blocked: 'Blocked Users' suspended: 'Suspended Users' + suspect: 'Suspect Users' reject_successful: one: "Successfully rejected 1 user." other: "Successfully rejected %{count} users." diff --git a/lib/admin_user_index_query.rb b/lib/admin_user_index_query.rb index 281049910..f2420f15a 100644 --- a/lib/admin_user_index_query.rb +++ b/lib/admin_user_index_query.rb @@ -11,15 +11,7 @@ class AdminUserIndexQuery attr_reader :params, :trust_levels def find_users(limit=100) - find_users_query.includes(:user_stat) - .includes(:single_sign_on_record) - .includes(:facebook_user_info) - .includes(:twitter_user_info) - .includes(:github_user_info) - .includes(:google_user_info) - .includes(:oauth2_user_info) - .includes(:user_open_ids) - .limit(limit) + find_users_query.includes(:user_stat).limit(limit) end def count_users @@ -32,10 +24,10 @@ class AdminUserIndexQuery if params[:query] == "active" order << "COALESCE(last_seen_at, to_date('1970-01-01', 'YYYY-MM-DD')) DESC" else - order << "created_at DESC" + order << "users.created_at DESC" end - order << "username" + order << "users.username" klass.order(order.reject(&:blank?).join(",")) end @@ -47,6 +39,20 @@ class AdminUserIndexQuery end end + def suspect_users + where_conds = [] + + # One signal: no reading yet the user has bio text + where_conds << "user_stats.posts_read_count = 0 AND user_stats.topics_entered = 0 AND COALESCE(user_profiles.bio_raw, '') = ''" + # Another surprising signal: Username ends with a number + where_conds << "users.username ~ '[0-9]+$'" + + @query.activated + .references(:user_stats) + .includes(:user_profile) + .where(where_conds.map {|c| "(#{c})"}.join(" AND ")) + end + def filter_by_query_classification case params[:query] when 'staff' then @query.where("admin or moderator") @@ -55,6 +61,7 @@ class AdminUserIndexQuery when 'blocked' then @query.blocked when 'suspended' then @query.suspended when 'pending' then @query.not_suspended.where(approved: false) + when 'suspect' then suspect_users end end