add stats page

This commit is contained in:
Peter Bhat Harkins 2020-07-03 18:29:24 -05:00
parent 3cdae3fae5
commit 0a5b1b1023
8 changed files with 134 additions and 1 deletions

View File

@ -39,6 +39,7 @@ gem "oauth" # for twitter-posting bot
gem "mail" # for parsing incoming mail
gem "ruumba" # tests views
gem "sitemap_generator" # for better search engine indexing
gem "svg-graph", require: 'SVG/Graph/TimeSeries' # for charting, note workaround in lib/time_series.rb
gem 'transaction_retry' # mitigate https://github.com/lobsters/lobsters-ansible/issues/39
group :test, :development do

View File

@ -231,6 +231,7 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
svg-graph (2.2.0)
thor (1.0.1)
thread_safe (0.3.6)
transaction_isolation (1.0.5)
@ -296,6 +297,7 @@ DEPENDENCIES
scenic-mysql_adapter
sitemap_generator
sprockets-rails (= 2.3.3)
svg-graph
transaction_retry
uglifier (>= 1.3.0)
vcr

View File

@ -0,0 +1,77 @@
class StatsController < ApplicationController
FIRST_MONTH = Time.new(2012, 7, 3).utc.freeze
TIMESCALE_DIVISIONS = "1 year".freeze
def index
@title = "Stats"
@users_graph = monthly_graph("users_graph", {
:graph_title => "Users joining by month",
:scale_y_divisions => 50,
}) {
User.group("date_format(created_at, '%Y-%m')")
}
@stories_graph = monthly_graph("stories_graph", {
:graph_title => "Stories submitted by month",
:scale_y_divisions => 100,
}) {
Story.group("date_format(created_at, '%Y-%m')")
}
@comments_graph = monthly_graph("comments_graph", {
:graph_title => "Comments posted by month",
:scale_y_divisions => 1_000,
}) {
Comment.group("date_format(created_at, '%Y-%m')")
}
@votes_graph = monthly_graph("votes_graph", {
:graph_title => "Votes cast by month",
:scale_y_divisions => 10_000,
}) {
Vote.group("date_format(updated_at, '%Y-%m')")
}
end
private
def monthly_graph(cache_key, opts)
Rails.cache.fetch(cache_key, expires_in: 1.day) {
defaults = {
:width => 800,
:height => 300,
:graph_title => "Graph",
:show_graph_title => false,
:no_css => false,
:key => false,
:scale_x_integers => true,
:scale_y_integers => false,
:show_data_values => false,
:show_x_guidelines => false,
:show_x_title => false,
:x_title => "Time",
:show_y_title => false,
:y_title => "Users",
:y_title_text_direction => :bt,
:stagger_x_labels => false,
:x_label_format => "%Y-%m",
:y_label_format => "%Y-%m",
:min_x_value => FIRST_MONTH,
:timescale_divisions => TIMESCALE_DIVISIONS,
:add_popups => true,
:popup_format => "%Y-%m",
:area_fill => false,
:min_y_value => 0,
:number_format => "%d",
:show_lines => false,
}
graph = TimeSeries.new(defaults.merge(opts))
graph.add_data(
data: yield.count.to_a.flatten,
template: "%Y-%m",
)
graph.burn_svg_only
}
end
end

View File

@ -148,7 +148,8 @@
<p id="queries">
The Lobsters community is in a sweet spot that it's large enough to be worth asking questions about and small enough the answers make sense.
If you're curious about stats, Peter is happy to run queries against the <a href="https://github.com/lobsters/lobsters/blob/master/db/schema.rb">database</a> and Rails/MySQL/nginx logs (but not write them for you),
We have <a href="/stats">some basic stats</a> available,
and Peter is happy to run queries against the <a href="https://github.com/lobsters/lobsters/blob/master/db/schema.rb">database</a> and Rails/MySQL/nginx logs (but not write them for you),
as long as they don't reveal personal info like IPs, browsing, and voting or create “worst-of” leaderboards celebrating most-downvoted users/comments/stories.
If you're an academic researcher, please <a href="https://lobste.rs/s/cqnzl5/lobste_rs_access_pattern_statistics_for">be like MIT</a>, not like <a href="https://github.com/lobsters/lobsters/issues/517">UChicago</a> secretly experimenting on maintainers without IRB review.
</p>

View File

@ -0,0 +1,28 @@
<div class="box wide">
<h2>New users by month</h2>
<%= raw @users_graph %>
<h2>Stories submitted by month</h2>
<%= raw @stories_graph %>
<h2>Comments posted by month</h2>
<%= raw @comments_graph %>
<h2>Votes cast by month</h2>
<%= raw @votes_graph %>
<p>
Want more info?
<a href="/about#queries">Write a query</a>.
</p>
<style type="text/css">
.graphBackground { fill: #ffffff; }
.axis { stroke: #cccccc; }
.dataPoint1 { color: #ac130d; }
.line1 { color: #ac130d; }
.guideLines { stroke: #ffffff; stroke-dasharray: none; }
</style>
</div>

View File

@ -58,6 +58,7 @@ module Lobsters
config.after_initialize do
require "#{Rails.root}/lib/monkey.rb"
require "#{Rails.root}/lib/time_series.rb"
end
config.generators do |g|

View File

@ -209,5 +209,7 @@ Rails.application.routes.draw do
get "/about" => "home#about"
get "/chat" => "home#chat"
get "/stats" => "stats#index"
post '/csp-violation-report' => 'csp#violation_report'
end

21
lib/time_series.rb Normal file
View File

@ -0,0 +1,21 @@
class TimeSeries < SVG::Graph::TimeSeries
include ActionView::Helpers::NumberHelper
# these two methods are a patch on the gem's lack of time zone awareness
def format x, y, description
[
Time.at(x).utc.strftime(popup_format),
number_with_delimiter(y),
description,
].compact.join(', ')
end
def get_x_labels
get_x_values.collect {|v| Time.at(v).utc.strftime(x_label_format) }
end
# improves y axis labels with commas
def get_y_labels
get_y_values.collect {|v| number_with_delimiter(v.to_i) }
end
end