Permalink
Browse files

Basic reporting on the Dashboard

  • Loading branch information...
1 parent 45bab61 commit 5f97998a36ef6d4af5a6b18ed73e976b29a17a99 @kneath committed May 12, 2008
@@ -3,6 +3,7 @@ class Dashboard < Application
before :authenticate
def index
+ @days = Report.days_from_range(Date.today - 30, Date.today)
render
end
@@ -1,5 +1,28 @@
module Merb
module DashboardHelper
-
+ def google_bar_graph(collection, x_method, y_method, options = {})
+ options[:size] ||= "580x200"
+ options[:color] ||= "c6d9fd"
+
+ max = collection.max{ |a,b| a.send(y_method) <=> b.send(y_method) }.send(y_method)
+ labels = []
+ collection.each do |item|
+ labels << item.send(x_method)
+ end
+
+ query = "http://chart.apis.google.com/chart?cht=bvs&chs=#{options[:size]}&chco=#{options[:color]}"
+ query << "&chd=t:" + collection.collect{|d| d.send(y_method) }.join(",")
+ query << "&chds=0,#{max}"
+ query << "&chxt=x,y"
+ query << "&chxl=0:|#{labels.join('|')}|1:|0|#{number_with_delimiter max/4}|#{number_with_delimiter max*2/4}|#{number_with_delimiter max*3/4}|#{number_with_delimiter max}"
+ query << "&chg=" + [5000, 25, 1, 3].join(',')
+ '<img src="' + query + '" alt="Graph" />'
+ end
+
+ def ctr(clicks, impressions)
+ num = clicks.to_f/impressions.to_f*100
+ num = 0 if num.nan?
+ number_to_percentage(num, :precision => 2)
+ end
end
end
@@ -28,5 +28,74 @@ def destroy_button(url, text)
"<form action=\"#{url}\" method=\"post\" class=\"destroy\"><button type=\"submit\" class=\"destroy\">#{text}</button></form>"
end
+ # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You
+ # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters.
+ #
+ # ==== Options
+ # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ",").
+ # * <tt>separator</tt> - Sets the separator between the units (defaults to ".").
+ #
+ # ==== Examples
+ # number_with_delimiter(12345678) # => 12,345,678
+ # number_with_delimiter(12345678.05) # => 12,345,678.05
+ # number_with_delimiter(12345678, ".") # => 12.345.678
+ #
+ # number_with_delimiter(98765432.98, " ", ",")
+ # # => 98 765 432,98
+ def number_with_delimiter(number, delimiter=",", separator=".")
+ begin
+ parts = number.to_s.split('.')
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
+ parts.join separator
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ as a percentage string (e.g., 65%). You can customize the
+ # format in the +options+ hash.
+ #
+ # ==== Options
+ # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
+ # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ #
+ # ==== Examples
+ # number_to_percentage(100) # => 100.000%
+ # number_to_percentage(100, :precision => 0) # => 100%
+ #
+ # number_to_percentage(302.24398923423, :precision => 5)
+ # # => 302.24399%
+ def number_to_percentage(number, options = {})
+ options = options.stringify_keys
+ precision = options["precision"] || 3
+ separator = options["separator"] || "."
+
+ begin
+ number = number_with_precision(number, precision)
+ parts = number.split('.')
+ if parts.at(1).nil?
+ parts[0] + "%"
+ else
+ parts[0] + separator + parts[1].to_s + "%"
+ end
+ rescue
+ number
+ end
+ end
+
+ # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default
+ # level of precision is 3.
+ #
+ # ==== Examples
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, 2) # => 111.23
+ # number_with_precision(13, 5) # => 13.00000
+ # number_with_precision(389.32314, 0) # => 389
+ def number_with_precision(number, precision=3)
+ "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision)
+ rescue
+ number
+ end
+
end
end
View
@@ -0,0 +1,20 @@
+class DateRange
+
+ attr_accessor :start_date, :end_date, :impressions, :clicks
+
+ def initialize(start_date, end_date)
+ @start_date = start_date
+ @end_date = end_date
+ @impressions = 0
+ @clicks = 0
+ end
+
+ def date
+ @start_date
+ end
+
+ def short_date
+ @start_date.strftime("%e")
+ end
+
+end
View
@@ -0,0 +1,25 @@
+class Report
+
+ def self.days_from_range(start_date, end_date)
+ raise TypeError("Expected Date, got #{start_date.class.to_s}") unless start_date.is_a?(Date)
+ raise TypeError("Expected Date, got #{end_date.class.to_s}") unless end_date.is_a?(Date)
+
+ stats = {}
+ raw_stats = Impression.find_by_sql("SELECT DATE_FORMAT(created_at, \"%Y%j\") AS day_index, created_at, COUNT(*) AS impressions, SUM(clicked) AS clicks FROM impressions WHERE created_at > '#{start_date.to_formatted_s(:db)}' AND created_at < '#{end_date.to_formatted_s(:db)}' GROUP BY day_index")
+ raw_stats.each do |stat|
+ stats[stat.day_index.to_s] = stat
+ end
+
+ days = []
+ current_date = start_date
+ while (current_date < end_date) do
+ day = DateRange.new(current_date.to_time, (current_date + 1.day).to_time)
+ stat = stats[current_date.year.to_s + current_date.yday.to_s]
+ day.impressions, day.clicks = stat["impressions"].to_i, stat["clicks"].to_i unless stat.nil?
+ days << day
+ current_date += 1
+ end
+ days.reverse
+ end
+
+end
@@ -1 +1,20 @@
-You're in index of the Dashboard
+<h1>Overview</h1>
+
+<h3>Impressions in the past 30 days</h3>
+<p><%= google_bar_graph(@days, :short_date, :impressions) %></p>
+<table>
+ <tr>
+ <th>Date</th>
+ <th>Impressions</th>
+ <th>Clicks</th>
+ <th>CTR</th>
+ </tr>
+ <% for day in @days %>
+ <tr>
+ <td><%= day.date.to_formatted_s(:general) %></td>
+ <td><%= number_with_delimiter day.impressions %></td>
+ <td><%= number_with_delimiter day.clicks %></td>
+ <td><%= ctr(day.clicks, day.impressions) %></td>
+ </tr>
+ <% end %>
+</table>
View
@@ -31,5 +31,5 @@
r.default_routes
# Change this for your home page to be available at /
- # r.match('/').to(:controller => 'whatever', :action =>'index')
+ r.match('/').to(:controller => 'dashboard', :action =>'index')
end
@@ -60,6 +60,17 @@ h2 em{
color:#444;
}
+h3{
+ font-size:14px;
+}
+
+h4{
+ font-size:12px;
+ font-weight:normal;
+ color:#666;
+ text-transform:uppercase;
+}
+
a{
color:#0077aa;
text-decoration:none;
@@ -359,4 +370,30 @@ ul.full-width-spots li{
}
html>body*#wrap .spots button.destroy{
top:10px;
+}
+
+/*------------------------------------------------------------------------------------
+ Tables
+------------------------------------------------------------------------------------*/
+
+table{
+ width:100%;
+ border-spacing:0;
+ border-collapse:collapse;
+ border:1px solid #eee;
+ border-right:none;
+ border-bottom:none;
+}
+
+table th, table td{
+ padding:2px 5px;
+ text-align:left;
+ border:1px solid #eee;
+ border-top:none;
+ border-left:none;
+}
+
+table th{
+ text-transform:uppercase;
+ border-bottom:2px solid #eee;
}

0 comments on commit 5f97998

Please sign in to comment.