mirror of
https://github.com/discourse/discourse.git
synced 2025-03-19 23:55:29 +08:00
FEATURE: track user visits on mobile and display on admin dashboard in a new Mobile section
This commit is contained in:
parent
0330e17ffa
commit
782dd13e78
@ -13,7 +13,7 @@ export default Discourse.Route.extend({
|
|||||||
c.set('versionCheck', Discourse.VersionCheck.create(d.version_check));
|
c.set('versionCheck', Discourse.VersionCheck.create(d.version_check));
|
||||||
}
|
}
|
||||||
|
|
||||||
['global_reports', 'page_view_reports', 'private_message_reports', 'http_reports', 'user_reports'].forEach(name => {
|
['global_reports', 'page_view_reports', 'private_message_reports', 'http_reports', 'user_reports', 'mobile_reports'].forEach(name => {
|
||||||
c.set(name, d[name].map(r => Discourse.Report.create(r)));
|
c.set(name, d[name].map(r => Discourse.Report.create(r)));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -108,6 +108,28 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="dashboard-stats">
|
||||||
|
<table class="table table-condensed table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="title" title="{{i18n 'admin.dashboard.mobile_title'}}">{{i18n 'admin.dashboard.mobile_title'}}</th>
|
||||||
|
<th>{{i18n 'admin.dashboard.reports.today'}}</th>
|
||||||
|
<th>{{i18n 'admin.dashboard.reports.yesterday'}}</th>
|
||||||
|
<th>{{i18n 'admin.dashboard.reports.last_7_days'}}</th>
|
||||||
|
<th>{{i18n 'admin.dashboard.reports.last_30_days'}}</th>
|
||||||
|
<th>{{i18n 'admin.dashboard.reports.all'}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{#unless loading}}
|
||||||
|
{{#each r in mobile_reports}}
|
||||||
|
{{admin-report-counts report=r}}
|
||||||
|
{{/each}}
|
||||||
|
{{/unless}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="dashboard-stats">
|
<div class="dashboard-stats">
|
||||||
<table class="table table-condensed table-hover">
|
<table class="table table-condensed table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -370,7 +370,8 @@ class TopicsController < ApplicationController
|
|||||||
current_user,
|
current_user,
|
||||||
params[:topic_id].to_i,
|
params[:topic_id].to_i,
|
||||||
params[:topic_time].to_i,
|
params[:topic_time].to_i,
|
||||||
(params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
|
(params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]},
|
||||||
|
{mobile: view_context.mobile_view?}
|
||||||
)
|
)
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
@ -15,7 +15,7 @@ class AdminDashboardData
|
|||||||
'emails',
|
'emails',
|
||||||
]
|
]
|
||||||
|
|
||||||
PAGE_VIEW_REPORTS ||= ['page_view_total_reqs'] + ApplicationRequest.req_types.keys.select { |r| r =~ /^page_view_/ }.map { |r| r + "_reqs" }
|
PAGE_VIEW_REPORTS ||= ['page_view_total_reqs'] + ApplicationRequest.req_types.keys.select { |r| r =~ /^page_view_/ && r !~ /mobile/ }.map { |r| r + "_reqs" }
|
||||||
|
|
||||||
PRIVATE_MESSAGE_REPORTS ||= [
|
PRIVATE_MESSAGE_REPORTS ||= [
|
||||||
'user_to_user_private_messages',
|
'user_to_user_private_messages',
|
||||||
@ -29,7 +29,7 @@ class AdminDashboardData
|
|||||||
|
|
||||||
USER_REPORTS ||= ['users_by_trust_level']
|
USER_REPORTS ||= ['users_by_trust_level']
|
||||||
|
|
||||||
# TODO: MOBILE_REPORTS
|
MOBILE_REPORTS ||= ['mobile_visits'] + ApplicationRequest.req_types.keys.select {|r| r =~ /mobile/}.map { |r| r + "_reqs" }
|
||||||
|
|
||||||
def problems
|
def problems
|
||||||
[ rails_env_check,
|
[ rails_env_check,
|
||||||
@ -81,6 +81,7 @@ class AdminDashboardData
|
|||||||
private_message_reports: AdminDashboardData.reports(PRIVATE_MESSAGE_REPORTS),
|
private_message_reports: AdminDashboardData.reports(PRIVATE_MESSAGE_REPORTS),
|
||||||
http_reports: AdminDashboardData.reports(HTTP_REPORTS),
|
http_reports: AdminDashboardData.reports(HTTP_REPORTS),
|
||||||
user_reports: AdminDashboardData.reports(USER_REPORTS),
|
user_reports: AdminDashboardData.reports(USER_REPORTS),
|
||||||
|
mobile_reports: AdminDashboardData.reports(MOBILE_REPORTS),
|
||||||
admins: User.admins.count,
|
admins: User.admins.count,
|
||||||
moderators: User.moderators.count,
|
moderators: User.moderators.count,
|
||||||
suspended: User.suspended.count,
|
suspended: User.suspended.count,
|
||||||
|
@ -70,7 +70,7 @@ class PostTiming < ActiveRecord::Base
|
|||||||
|
|
||||||
MAX_READ_TIME_PER_BATCH = 60*1000.0
|
MAX_READ_TIME_PER_BATCH = 60*1000.0
|
||||||
|
|
||||||
def self.process_timings(current_user, topic_id, topic_time, timings)
|
def self.process_timings(current_user, topic_id, topic_time, timings, opts={})
|
||||||
current_user.user_stat.update_time_read!
|
current_user.user_stat.update_time_read!
|
||||||
|
|
||||||
max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
|
max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
|
||||||
@ -129,7 +129,8 @@ SQL
|
|||||||
end
|
end
|
||||||
|
|
||||||
topic_time = max_time_per_post if topic_time > max_time_per_post
|
topic_time = max_time_per_post if topic_time > max_time_per_post
|
||||||
TopicUser.update_last_read(current_user, topic_id, highest_seen, topic_time)
|
|
||||||
|
TopicUser.update_last_read(current_user, topic_id, highest_seen, topic_time, opts)
|
||||||
|
|
||||||
if total_changed > 0
|
if total_changed > 0
|
||||||
current_user.reload
|
current_user.reload
|
||||||
|
@ -88,6 +88,12 @@ class Report
|
|||||||
add_counts report, UserVisit, 'visited_at'
|
add_counts report, UserVisit, 'visited_at'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.report_mobile_visits(report)
|
||||||
|
basic_report_about report, UserVisit, :mobile_by_day, report.start_date, report.end_date
|
||||||
|
report.total = UserVisit.where(mobile: true).count
|
||||||
|
report.prev30Days = UserVisit.where(mobile: true).where("visited_at >= ? and visited_at < ?", report.start_date - 30.days, report.start_date).count
|
||||||
|
end
|
||||||
|
|
||||||
def self.report_signups(report)
|
def self.report_signups(report)
|
||||||
report_about report, User.real, :count_by_signup_date
|
report_about report, User.real, :count_by_signup_date
|
||||||
end
|
end
|
||||||
|
@ -137,7 +137,7 @@ class TopicUser < ActiveRecord::Base
|
|||||||
|
|
||||||
# Update the last read and the last seen post count, but only if it doesn't exist.
|
# Update the last read and the last seen post count, but only if it doesn't exist.
|
||||||
# This would be a lot easier if psql supported some kind of upsert
|
# This would be a lot easier if psql supported some kind of upsert
|
||||||
def update_last_read(user, topic_id, post_number, msecs)
|
def update_last_read(user, topic_id, post_number, msecs, opts={})
|
||||||
return if post_number.blank?
|
return if post_number.blank?
|
||||||
msecs = 0 if msecs.to_i < 0
|
msecs = 0 if msecs.to_i < 0
|
||||||
|
|
||||||
@ -192,7 +192,7 @@ class TopicUser < ActiveRecord::Base
|
|||||||
if before_last_read < post_number
|
if before_last_read < post_number
|
||||||
# The user read at least one new post
|
# The user read at least one new post
|
||||||
TopicTrackingState.publish_read(topic_id, post_number, user.id, after)
|
TopicTrackingState.publish_read(topic_id, post_number, user.id, after)
|
||||||
user.update_posts_read!(post_number - before_last_read)
|
user.update_posts_read!(post_number - before_last_read, mobile: opts[:mobile])
|
||||||
end
|
end
|
||||||
|
|
||||||
if before != after
|
if before != after
|
||||||
@ -207,7 +207,8 @@ class TopicUser < ActiveRecord::Base
|
|||||||
args[:new_status] = notification_levels[:tracking]
|
args[:new_status] = notification_levels[:tracking]
|
||||||
end
|
end
|
||||||
TopicTrackingState.publish_read(topic_id, post_number, user.id, args[:new_status])
|
TopicTrackingState.publish_read(topic_id, post_number, user.id, args[:new_status])
|
||||||
user.update_posts_read!(post_number)
|
|
||||||
|
user.update_posts_read!(post_number, mobile: opts[:mobile])
|
||||||
|
|
||||||
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, highest_seen_post_number, last_visited_at, first_visited_at, notification_level)
|
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, highest_seen_post_number, last_visited_at, first_visited_at, notification_level)
|
||||||
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now, :new_status
|
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now, :new_status
|
||||||
|
@ -368,6 +368,11 @@ class User < ActiveRecord::Base
|
|||||||
last_seen_at.present?
|
last_seen_at.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_visit_record!(date, opts={})
|
||||||
|
user_stat.update_column(:days_visited, user_stat.days_visited + 1)
|
||||||
|
user_visits.create!(visited_at: date, posts_read: opts[:posts_read] || 0, mobile: opts[:mobile] || false)
|
||||||
|
end
|
||||||
|
|
||||||
def visit_record_for(date)
|
def visit_record_for(date)
|
||||||
user_visits.find_by(visited_at: date)
|
user_visits.find_by(visited_at: date)
|
||||||
end
|
end
|
||||||
@ -376,14 +381,18 @@ class User < ActiveRecord::Base
|
|||||||
create_visit_record!(date) unless visit_record_for(date)
|
create_visit_record!(date) unless visit_record_for(date)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_posts_read!(num_posts, now=Time.zone.now, _retry=false)
|
def update_posts_read!(num_posts, opts={})
|
||||||
|
now = opts[:at] || Time.zone.now
|
||||||
|
_retry = opts[:retry] || false
|
||||||
|
|
||||||
if user_visit = visit_record_for(now.to_date)
|
if user_visit = visit_record_for(now.to_date)
|
||||||
user_visit.posts_read += num_posts
|
user_visit.posts_read += num_posts
|
||||||
|
user_visit.mobile = true if opts[:mobile]
|
||||||
user_visit.save
|
user_visit.save
|
||||||
user_visit
|
user_visit
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
create_visit_record!(now.to_date, num_posts)
|
create_visit_record!(now.to_date, posts_read: num_posts, mobile: opts.fetch(:mobile, false))
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
if !_retry
|
if !_retry
|
||||||
update_posts_read!(num_posts, now, _retry=true)
|
update_posts_read!(num_posts, now, _retry=true)
|
||||||
@ -844,11 +853,6 @@ class User < ActiveRecord::Base
|
|||||||
email_tokens.create(email: email)
|
email_tokens.create(email: email)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_visit_record!(date, posts_read=0)
|
|
||||||
user_stat.update_column(:days_visited, user_stat.days_visited + 1)
|
|
||||||
user_visits.create!(visited_at: date, posts_read: posts_read)
|
|
||||||
end
|
|
||||||
|
|
||||||
def ensure_password_is_hashed
|
def ensure_password_is_hashed
|
||||||
if @raw_password
|
if @raw_password
|
||||||
self.salt = SecureRandom.hex(16)
|
self.salt = SecureRandom.hex(16)
|
||||||
|
@ -1,8 +1,16 @@
|
|||||||
class UserVisit < ActiveRecord::Base
|
class UserVisit < ActiveRecord::Base
|
||||||
|
|
||||||
# A count of visits in the last month by day
|
def self.counts_by_day_query(start_date, end_date)
|
||||||
|
where('visited_at >= ? and visited_at <= ?', start_date.to_date, end_date.to_date).group(:visited_at).order(:visited_at)
|
||||||
|
end
|
||||||
|
|
||||||
|
# A count of visits in a date range by day
|
||||||
def self.by_day(start_date, end_date)
|
def self.by_day(start_date, end_date)
|
||||||
where('visited_at >= ? and visited_at <= ?', start_date.to_date, end_date.to_date).group(:visited_at).order(:visited_at).count
|
counts_by_day_query(start_date, end_date).count
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.mobile_by_day(start_date, end_date)
|
||||||
|
counts_by_day_query(start_date, end_date).where(mobile: true).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ensure_consistency!
|
def self.ensure_consistency!
|
||||||
@ -27,6 +35,7 @@ end
|
|||||||
# user_id :integer not null
|
# user_id :integer not null
|
||||||
# visited_at :date not null
|
# visited_at :date not null
|
||||||
# posts_read :integer default(0)
|
# posts_read :integer default(0)
|
||||||
|
# mobile :boolean default(FALSE)
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
@ -1689,6 +1689,7 @@ en:
|
|||||||
suspended: 'Suspended:'
|
suspended: 'Suspended:'
|
||||||
private_messages_short: "Msgs"
|
private_messages_short: "Msgs"
|
||||||
private_messages_title: "Messages"
|
private_messages_title: "Messages"
|
||||||
|
mobile_title: "Mobile"
|
||||||
space_free: "{{size}} free"
|
space_free: "{{size}} free"
|
||||||
uploads: "uploads"
|
uploads: "uploads"
|
||||||
backups: "backups"
|
backups: "backups"
|
||||||
|
@ -653,11 +653,11 @@ en:
|
|||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
yaxis: "Total API Requests"
|
yaxis: "Total API Requests"
|
||||||
page_view_logged_in_mobile_reqs:
|
page_view_logged_in_mobile_reqs:
|
||||||
title: "Mobile Logged In"
|
title: "Logged In API Requests"
|
||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
yaxis: "Mobile Logged In API Requests"
|
yaxis: "Mobile Logged In API Requests"
|
||||||
page_view_anon_mobile_reqs:
|
page_view_anon_mobile_reqs:
|
||||||
title: "Mobile Anon"
|
title: "Anon API Requests"
|
||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
yaxis: "Mobile Anon API Requests"
|
yaxis: "Mobile Anon API Requests"
|
||||||
http_background_reqs:
|
http_background_reqs:
|
||||||
@ -692,6 +692,10 @@ en:
|
|||||||
title: "Topics with no response"
|
title: "Topics with no response"
|
||||||
xaxis: "Day"
|
xaxis: "Day"
|
||||||
yaxis: "Total"
|
yaxis: "Total"
|
||||||
|
mobile_visits:
|
||||||
|
title: "User Visits"
|
||||||
|
xaxis: "Day"
|
||||||
|
yaxis: "Number of visits"
|
||||||
|
|
||||||
dashboard:
|
dashboard:
|
||||||
rails_env_warning: "Your server is running in %{env} mode."
|
rails_env_warning: "Your server is running in %{env} mode."
|
||||||
|
5
db/migrate/20150706215111_add_mobile_to_user_visits.rb
Normal file
5
db/migrate/20150706215111_add_mobile_to_user_visits.rb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
class AddMobileToUserVisits < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :user_visits, :mobile, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
@ -67,10 +67,10 @@ describe TrustLevel3Requirements do
|
|||||||
describe "days_visited" do
|
describe "days_visited" do
|
||||||
it "counts visits when posts were read no further back than 100 days ago" do
|
it "counts visits when posts were read no further back than 100 days ago" do
|
||||||
user.save
|
user.save
|
||||||
user.update_posts_read!(1, 2.days.ago)
|
user.update_posts_read!(1, at: 2.days.ago)
|
||||||
user.update_posts_read!(1, 3.days.ago)
|
user.update_posts_read!(1, at: 3.days.ago)
|
||||||
user.update_posts_read!(0, 4.days.ago)
|
user.update_posts_read!(0, at: 4.days.ago)
|
||||||
user.update_posts_read!(3, 101.days.ago)
|
user.update_posts_read!(3, at: 101.days.ago)
|
||||||
expect(tl3_requirements.days_visited).to eq(2)
|
expect(tl3_requirements.days_visited).to eq(2)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -106,10 +106,10 @@ describe TrustLevel3Requirements do
|
|||||||
describe "posts_read" do
|
describe "posts_read" do
|
||||||
it "counts posts read within the last 100 days" do
|
it "counts posts read within the last 100 days" do
|
||||||
user.save
|
user.save
|
||||||
user.update_posts_read!(3, 2.days.ago)
|
user.update_posts_read!(3, at: 2.days.ago)
|
||||||
user.update_posts_read!(1, 3.days.ago)
|
user.update_posts_read!(1, at: 3.days.ago)
|
||||||
user.update_posts_read!(0, 4.days.ago)
|
user.update_posts_read!(0, at: 4.days.ago)
|
||||||
user.update_posts_read!(5, 101.days.ago)
|
user.update_posts_read!(5, at: 101.days.ago)
|
||||||
expect(tl3_requirements.posts_read).to eq(4)
|
expect(tl3_requirements.posts_read).to eq(4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -127,8 +127,8 @@ describe TrustLevel3Requirements do
|
|||||||
describe "posts_read_all_time" do
|
describe "posts_read_all_time" do
|
||||||
it "counts posts read at any time" do
|
it "counts posts read at any time" do
|
||||||
user.save
|
user.save
|
||||||
user.update_posts_read!(3, 2.days.ago)
|
user.update_posts_read!(3, at: 2.days.ago)
|
||||||
user.update_posts_read!(1, 101.days.ago)
|
user.update_posts_read!(1, at: 101.days.ago)
|
||||||
expect(tl3_requirements.posts_read_all_time).to eq(4)
|
expect(tl3_requirements.posts_read_all_time).to eq(4)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -921,7 +921,7 @@ describe User do
|
|||||||
|
|
||||||
it "with no existing UserVisit record, creates a new UserVisit record and increments the posts_read count" do
|
it "with no existing UserVisit record, creates a new UserVisit record and increments the posts_read count" do
|
||||||
expect {
|
expect {
|
||||||
user_visit = user.update_posts_read!(3, 5.days.ago)
|
user_visit = user.update_posts_read!(3, at: 5.days.ago)
|
||||||
expect(user_visit.posts_read).to eq(3)
|
expect(user_visit.posts_read).to eq(3)
|
||||||
}.to change { UserVisit.count }.by(1)
|
}.to change { UserVisit.count }.by(1)
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user