add stats page
This commit is contained in:
parent
3cdae3fae5
commit
0a5b1b1023
1
Gemfile
1
Gemfile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue