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