docbrown/app/services/analytics_service.rb
Andy Zhao 9b1ef3938a Add caching for historical data (#2476)
* Add low-level caching for historical data

* Rename method
2019-04-18 09:43:30 -04:00

117 lines
4.3 KiB
Ruby

class AnalyticsService
def initialize(user_or_org, start_date: "", end_date: "", article_id: nil)
@user_or_org = user_or_org
@article_id = article_id
@start_date = Time.zone.parse(start_date.to_s)&.beginning_of_day
@end_date = Time.zone.parse(end_date.to_s)&.end_of_day || Time.current.end_of_day
load_data
end
def totals
total_views = article_data.sum(:page_views_count)
logged_in_page_view_data = page_view_data.where.not(user_id: nil)
average_read_time_in_seconds = average_read_time(logged_in_page_view_data)
{
comments: {
total: comment_data.size
},
reactions: {
total: reaction_data.size,
like: reaction_data.count { |rxn| rxn.category == "like" },
readinglist: reaction_data.count { |rxn| rxn.category == "readinglist" },
unicorn: reaction_data.count { |rxn| rxn.category == "unicorn" }
},
follows: {
total: follow_data.size
},
page_views: {
total: total_views,
average_read_time_in_seconds: average_read_time_in_seconds,
total_read_time_in_seconds: average_read_time_in_seconds * total_views
}
}
end
def stats_grouped_by_day
result = {}
(start_date.to_date..end_date.to_date).each do |date|
result[date.to_s(:iso)] = cached_data_for_date(date)
end
result
end
private
attr_reader :user_or_org, :start_date, :end_date, :article_data, :reaction_data, :comment_data, :follow_data, :page_view_data
def load_data
@article_data = Article.published.where("#{user_or_org.class.name.downcase}_id" => user_or_org.id)
if @article_id
@article_data = @article_data.where(id: @article_id)
raise UnauthorizedError if @article_data.blank?
article_ids = @article_id
else
article_ids = @article_data.pluck(:id)
end
if @start_date && @end_date
@reaction_data = Reaction.where(reactable_id: article_ids, reactable_type: "Article").
where(created_at: @start_date..@end_date).
where("points > 0")
@comment_data = Comment.where(commentable_id: article_ids, commentable_type: "Article").
where(created_at: @start_date..@end_date).
where("score > 0")
@page_view_data = PageView.where(article_id: article_ids).where(created_at: @start_date..@end_date)
else
@reaction_data = Reaction.where(reactable_id: article_ids, reactable_type: "Article").where("points > 0")
@comment_data = Comment.where(commentable_id: article_ids, commentable_type: "Article").where("score > 0")
@page_view_data = PageView.where(article_id: article_ids)
end
@follow_data = Follow.where(followable_type: user_or_org.class.name, followable_id: user_or_org.id)
end
def average_read_time(page_view_data)
page_view_data.size.positive? ? page_view_data.sum(:time_tracked_in_seconds) / page_view_data.size : 0
end
def cached_data_for_date(date)
expiration_date = if date == Time.current.to_date
30.minutes
else
7.days
end
Rails.cache.fetch("analytics-for-date-#{date}-#{user_or_org.class.name}-#{user_or_org.id}", expires_in: expiration_date) do
reaction_data_of_date = reaction_data.where("date(created_at) = ?", date)
logged_in_page_view_data = page_view_data.where("date(created_at) = ?", date).where.not(user_id: nil)
average_read_time_in_seconds = average_read_time(logged_in_page_view_data)
total_views = page_view_data.where("date(created_at) = ?", date).sum(:counts_for_number_of_views)
{
comments: {
total: comment_data.where("date(created_at) = ?", date).size
},
reactions: {
total: reaction_data_of_date.size,
like: reaction_data_of_date.where("category = ?", "like").size,
readinglist: reaction_data_of_date.where("category = ?", "readinglist").size,
unicorn: reaction_data_of_date.where("category = ?", "unicorn").size
},
page_views: {
total: total_views,
total_read_time_in_seconds: average_read_time_in_seconds * total_views,
average_read_time_in_seconds: average_read_time_in_seconds
},
follows: {
total: follow_data.where("date(created_at) = ?", date).size
}
}
end
end
end