From 8c26ea7cb596c97e37213c0cc994e66ee5fd27b0 Mon Sep 17 00:00:00 2001 From: lukemelia Date: Thu, 13 Sep 2007 03:21:37 +0000 Subject: [PATCH] Added the fantastic statistics work contributed by lrbalt! This is a work in progress. It's not rendering correctly for me in Firefox but is OK in Safari and there is at least one other minor error. Contribute bug reports and fixes to ticket #406. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@587 a4c988fc-2ded-0310-b66e-134b36920a42 --- tracks/app/controllers/stats_controller.rb | 516 ++++++++++++++++++ tracks/app/helpers/stats_helper.rb | 2 + tracks/app/views/layouts/standard.rhtml | 1 + tracks/app/views/stats/_actions.rhtml | 29 + tracks/app/views/stats/_chart.rhtml | 5 + tracks/app/views/stats/_contexts.rhtml | 30 + tracks/app/views/stats/_projects.rhtml | 42 ++ tracks/app/views/stats/_tags.rhtml | 15 + tracks/app/views/stats/_totals.rhtml | 13 + .../stats/actions_completion_time_data.rhtml | 26 + .../actions_day_of_week_30days_data.rhtml | 23 + .../stats/actions_day_of_week_all_data.rhtml | 23 + .../actions_done_last12months_data.rhtml | 27 + .../stats/actions_done_last12monthsdata.rhtml | 18 + .../stats/actions_done_last30days_data.rhtml | 42 ++ .../stats/actions_done_last30daysdata.rhtml | 28 + .../stats/actions_running_time_data.rhtml | 27 + .../actions_time_of_day_30days_data.rhtml | 23 + .../stats/actions_time_of_day_all_data.rhtml | 23 + .../actions_visible_running_time_data.rhtml | 27 + .../stats/context_running_actions_data.rhtml | 16 + .../stats/context_total_actions_data.rhtml | 16 + tracks/app/views/stats/index.rhtml | 23 + tracks/public/images/stats.gif | Bin 0 -> 84 bytes tracks/public/open-flash-chart.swf | Bin 0 -> 40635 bytes .../test/functional/stats_controller_test.rb | 18 + 26 files changed, 1013 insertions(+) create mode 100755 tracks/app/controllers/stats_controller.rb create mode 100755 tracks/app/helpers/stats_helper.rb create mode 100755 tracks/app/views/stats/_actions.rhtml create mode 100755 tracks/app/views/stats/_chart.rhtml create mode 100755 tracks/app/views/stats/_contexts.rhtml create mode 100755 tracks/app/views/stats/_projects.rhtml create mode 100755 tracks/app/views/stats/_tags.rhtml create mode 100755 tracks/app/views/stats/_totals.rhtml create mode 100755 tracks/app/views/stats/actions_completion_time_data.rhtml create mode 100755 tracks/app/views/stats/actions_day_of_week_30days_data.rhtml create mode 100755 tracks/app/views/stats/actions_day_of_week_all_data.rhtml create mode 100755 tracks/app/views/stats/actions_done_last12months_data.rhtml create mode 100755 tracks/app/views/stats/actions_done_last12monthsdata.rhtml create mode 100755 tracks/app/views/stats/actions_done_last30days_data.rhtml create mode 100755 tracks/app/views/stats/actions_done_last30daysdata.rhtml create mode 100755 tracks/app/views/stats/actions_running_time_data.rhtml create mode 100755 tracks/app/views/stats/actions_time_of_day_30days_data.rhtml create mode 100755 tracks/app/views/stats/actions_time_of_day_all_data.rhtml create mode 100755 tracks/app/views/stats/actions_visible_running_time_data.rhtml create mode 100755 tracks/app/views/stats/context_running_actions_data.rhtml create mode 100755 tracks/app/views/stats/context_total_actions_data.rhtml create mode 100755 tracks/app/views/stats/index.rhtml create mode 100755 tracks/public/images/stats.gif create mode 100755 tracks/public/open-flash-chart.swf create mode 100755 tracks/test/functional/stats_controller_test.rb diff --git a/tracks/app/controllers/stats_controller.rb b/tracks/app/controllers/stats_controller.rb new file mode 100755 index 00000000..ce68554c --- /dev/null +++ b/tracks/app/controllers/stats_controller.rb @@ -0,0 +1,516 @@ +class StatsController < ApplicationController + + def index + @page_title = 'TRACKS::Statistics' + @projects = @user.projects + @contexts = @user.contexts + @actions = @user.todos + @tags = @user.tags + @unique_tags = @tags.find(:all, {:group=>"tag_id"}) + @hidden_contexts = @contexts.select{ |c| c.hide? } + @first_action = @actions.find(:first, :order => "created_at asc") + + # default chart dimensions + @chart_width=450 + @chart_height=250 + @pie_width=@chart_width + @pie_height=325 + + get_stats_actions + get_stats_contexts + get_stats_projects + get_stats_tags + + render :layout => 'standard' + end + + def actions_done_last12months_data + @actions = @user.todos + + @actions_done_last12months = @actions.count( + :all, { + :group => "period_diff(extract(year_month from now()), extract(year_month from completed_at))", + :conditions => "period_diff(extract(year_month from now()), extract(year_month from completed_at)) <= 12 and not completed_at is null" + }) + @actions_created_last12months = @actions.count(:all, { + :group => "period_diff(extract(year_month from now()), extract(year_month from created_at))", + :conditions => "period_diff(extract(year_month from now()), extract(year_month from created_at)) <= 12" + }) + + # find max count for graph + @max=0 + + # convert to hash to be able to fill in non-existing days in @actions_done_last12months + @sum_actions_done_last12months=0 + @actions_done_last12months_hash = Hash.new(0) + @actions_done_last12months.each do |month, count| + @actions_done_last12months_hash[month] = count + @sum_actions_done_last12months+= count.to_i + if count.to_i > @max + @max = count.to_i + end + end + + @sum_actions_created_last12months=0 + @actions_created_last12months_hash = Hash.new(0) + @actions_created_last12months.each do |month, count| + @actions_created_last12months_hash[month] = count + @sum_actions_created_last12months+= count.to_i + if count.to_i > @max + @max = count.to_i + end + end + + render :layout => false + end + + def actions_done_last30days_data + @actions = @user.todos + + # get count of actions done in the past 30 days. Results in a array of arrays + @actions_done_last30days = @actions.count(:all, { + :group => "datediff(now(), completed_at)", + :conditions => "datediff(now(), completed_at) <= 30 and not completed_at is null" + }) + @actions_created_last30days = @actions.count(:all, { + :group => "datediff(now(), created_at)", + :conditions => "datediff(now(), created_at) <= 30" + }) + + @max=0 + + # convert to hash to be albe to fill in non-existing days in @actions_done_last30days + @sum_actions_done_last30days=0 + + @actions_done_last30days_hash = Hash.new(0) + @actions_done_last30days.each do |day, count| + @actions_done_last30days_hash[day] = count + @sum_actions_done_last30days+= count.to_i + if count.to_i > @max + @max = count.to_i + end + end + + @sum_actions_created_last30days=0 + # convert to hash to be albe to fill in non-existing days in @actions_done_last30days + @actions_created_last30days_hash = Hash.new(0) + @actions_created_last30days.each do |day, count| + @actions_created_last30days_hash[day] = count + @sum_actions_created_last30days+= count.to_i + if count.to_i > @max + @max = count.to_i + end + end + + render :layout => false + end + + def actions_completion_time_data + @actions = @user.todos + + @actions_completion_time = @actions.count(:all, { + :group => "datediff(completed_at, created_at)", + :conditions => "not completed_at is null", :order => "datediff(completed_at, created_at) ASC" + }) + + # convert to hash to be able to fill in non-existing days in @actions_completion_time + # also convert days to weeks (/7) + + @max_days=0 + @max_actions=0 + @actions_completion_time_hash = Hash.new(0) + @actions_completion_time.each do |days, total| + # RAILS_DEFAULT_LOGGER.error("\n" + total.to_s + " - " + days + "\n") + @actions_completion_time_hash[days.to_i/7] = @actions_completion_time_hash[days.to_i/7] + total + if days.to_i > @max_days + @max_days=days.to_i + end + if @actions_completion_time_hash[days.to_i/7] > @max_actions + @max_actions = @actions_completion_time_hash[days.to_i/7] + end + end + + @cut_off = 10 + + render :layout => false + end + + def actions_running_time_data + @actions = @user.todos + + @actions_running_time = @actions.count(:all, { + :group => "datediff(now(), created_at)", + :conditions => "completed_at is null", :order => "datediff(now(), created_at) ASC" + }) + + # convert to hash to be able to fill in non-existing days in @actions_running_time + # also convert days to weeks (/7) + + @max_days=0 + @max_actions=0 + @actions_running_time_hash = Hash.new(0) + @actions_running_time.each do |days, total| + # RAILS_DEFAULT_LOGGER.error("\n" + total.to_s + " - " + days + "\n") + @actions_running_time_hash[days.to_i/7] = @actions_running_time_hash[days.to_i/7] + total + if days.to_i > @max_days + @max_days=days.to_i + end + if @actions_running_time_hash[days.to_i/7] > @max_actions + @max_actions = @actions_running_time_hash[days.to_i/7] + end + end + + # cut off chart at 52 weeks = one year + @cut_off=52 + + render :layout => false + end + + def actions_visible_running_time_data + @actions = @user.todos + + @actions_running_time = @actions.count(:all, { + :group => "datediff(now(), created_at)", + :conditions => "completed_at is null", :order => "datediff(now(), created_at) ASC" + }) + + @actions_running_time = @actions.find_by_sql( + "SELECT datediff(now(), t.created_at) as days, count(*) as total "+ + "FROM todos t LEFT OUTER JOIN projects p ON t.project_id = p.id LEFT OUTER JOIN contexts c ON t.context_id = c.id "+ + "WHERE t.user_id="+@user.id.to_s+" "+ + "AND t.completed_at is null " + + "AND NOT (p.state='hidden' OR c.hide=1) " + + "GROUP BY days ORDER BY days DESC" + ) + + # convert to hash to be able to fill in non-existing days in @actions_running_time + # also convert days to weeks (/7) + + @max_days=0 + @max_actions=0 + @actions_running_time_hash = Hash.new(0) + @actions_running_time.each do |a| + # RAILS_DEFAULT_LOGGER.error("\n" + total.to_s + " - " + days + "\n") + @actions_running_time_hash[a.days.to_i/7] += a.total.to_i + if a.days.to_i > @max_days + @max_days=a.days.to_i + end + if @actions_running_time_hash[a.days.to_i/7] > @max_actions + @max_actions = @actions_running_time_hash[a.days.to_i/7] + end + end + + # cut off chart at 52 weeks = one year + @cut_off=52 + + render :layout => false + end + + + def context_total_actions_data + @contexts = @user.contexts + + # SELECT c.name, c.hide, count(*) as totaal + # FROM contexts c, todos t + # where t.context_id=c.id group by c.id order by totaal desc; + + # get total action count per context + @actions_per_context = @contexts.find_by_sql( + "SELECT c.name as name, count(*) as total "+ + "FROM contexts c, todos t "+ + "WHERE t.context_id=c.id "+ + "AND t.user_id="+@user.id.to_s+" "+ + "GROUP BY c.id ORDER BY total DESC" + ) + + @sum=0 + 0.upto @actions_per_context.size()-1 do |i| + @sum += @actions_per_context[i]['total'].to_i + end + + render :layout => false + end + + def context_running_actions_data + @contexts = @user.contexts + + # SELECT c.name, c.hide, count(*) as totaal + # FROM contexts c, todos t + # where t.context_id=c.id group by c.id order by totaal desc; + + # get uncompleted action count per visible context + @actions_per_context = @contexts.find_by_sql( + "SELECT c.name as name, count(*) as total "+ + "FROM contexts c, todos t "+ + "WHERE t.context_id=c.id AND t.completed_at IS NULL AND NOT c.hide "+ + "AND t.user_id="+@user.id.to_s+" "+ + "GROUP BY c.id ORDER BY total DESC" + ) + + @sum=0 + 0.upto @actions_per_context.size()-1 do |i| + @sum += @actions_per_context[i]['total'].to_i + end + + render :layout => false + end + + def actions_day_of_week_all_data + @actions = @user.todos + + @actions_creation_day = @actions.count(:all, {:group => "dayofweek(created_at)" }) + @actions_completion_day = @actions.count(:all, {:group => "dayofweek(completed_at)", :conditions => "not completed_at is null" }) + + # convert to hash to be able to fill in non-existing days + @max=0 + @actions_creation_day_array = Array.new(7) { |i| 0} + @actions_creation_day.each do |dayofweek, total| + # dayofweek: sunday=1..saterday=7 + @max = total.to_i > @max ? total.to_i : @max + @actions_creation_day_array[dayofweek.to_i-1]=total.to_i + end + + # convert to hash to be able to fill in non-existing days + @actions_completion_day_array = Array.new(7) { |i| 0} + @actions_completion_day.each do |dayofweek, total| + # dayofweek: sunday=1..saterday=7 + @max = total.to_i > @max ? total.to_i : @max + @actions_completion_day_array[dayofweek.to_i-1]=total.to_i + end + + render :layout => false + end + + def actions_day_of_week_30days_data + @actions = @user.todos + + @actions_creation_day = @actions.count( + :all, { + :group => "dayofweek(created_at)", + :conditions => "datediff(now(), created_at) <= 30" + }) + + @actions_completion_day = @actions.count( + :all, {:group => "dayofweek(completed_at)", + :conditions => "not completed_at is null and datediff(now(), created_at) <= 30" + }) + + # convert to hash to be able to fill in non-existing days + @max=0 + @actions_creation_day_array = Array.new(7) { |i| 0} + @actions_creation_day.each do |dayofweek, total| + # dayofweek: sunday=1..saterday=7 + @max = total.to_i > @max ? total.to_i : @max + @actions_creation_day_array[dayofweek.to_i-1]=total.to_i + end + + # convert to hash to be able to fill in non-existing days + @actions_completion_day_array = Array.new(7) { |i| 0} + @actions_completion_day.each do |dayofweek, total| + # dayofweek: sunday=1..saterday=7 + @max = total.to_i > @max ? total.to_i : @max + @actions_completion_day_array[dayofweek.to_i-1]=total.to_i + end + + render :layout => false + end + + def actions_time_of_day_all_data + @actions = @user.todos + + @actions_creation_hour = @actions.count(:all, {:group => "hour(convert_tz(created_at, @@session.time_zone, '+2:00'))" }) + @actions_completion_hour = @actions.count(:all, {:group => "hour(convert_tz(created_at, @@session.time_zone, '+2:00'))", :conditions => "not completed_at is null" }) + + # convert to hash to be able to fill in non-existing days + @max=0 + @actions_creation_hour_array = Array.new(24) { |i| 0} + @actions_creation_hour.each do |hour, total| + @max = total.to_i > @max ? total.to_i : @max + @actions_creation_hour_array[hour.to_i]=total.to_i + end + + # convert to hash to be able to fill in non-existing days + @actions_completion_hour_array = Array.new(24) { |i| 0} + @actions_completion_hour.each do |hour, total| + @max = total.to_i > @max ? total.to_i : @max + @actions_completion_hour_array[hour.to_i]=total.to_i + end + + render :layout => false + end + + def actions_time_of_day_30days_data + @actions = @user.todos + + # TODO: find out how to find current timezone + @actions_creation_hour = @actions.count( + :all, { + :group => "hour(convert_tz(created_at, @@session.time_zone, '+2:00'))", + :conditions => "datediff(now(), created_at) <= 30" + }) + + # TODO: find out how to find current timezone + @actions_completion_hour = @actions.count( + :all, {:group => "hour(convert_tz(completed_at, @@session.time_zone, '+2:00'))", + :conditions => "not completed_at is null and datediff(now(), completed_at) <= 30" + }) + + # convert to hash to be able to fill in non-existing days + @max=0 + @actions_creation_hour_array = Array.new(24) { |i| 0} + @actions_creation_hour.each do |hour, total| + @max = total.to_i > @max ? total.to_i : @max + @actions_creation_hour_array[hour.to_i]=total.to_i + end + + # convert to hash to be able to fill in non-existing days + @actions_completion_hour_array = Array.new(24) { |i| 0} + @actions_completion_hour.each do |hour, total| + @max = total.to_i > @max ? total.to_i : @max + @actions_completion_hour_array[hour.to_i]=total.to_i + end + + render :layout => false + end + + private + + def get_stats_actions + # time to complete + @actions_avg_ttc = @actions.average("datediff(completed_at, created_at)", {:conditions => "not completed_at is null"} ) + @actions_max_ttc = @actions.maximum("datediff(completed_at, created_at)", {:conditions => "not completed_at is null"} ) + @actions_min_ttc = @actions.minimum("datediff(completed_at, created_at)", {:conditions => "not completed_at is null"} ) + @actions_min_ttc_sec = @actions.minimum("timediff(completed_at, created_at)", {:conditions => "not completed_at is null"} ) + + # get count of actions created and actions done in the past 30 days. Results in a array of arrays + @actions_done_last30days = @actions.count(:all, { + :group => "datediff(now(), completed_at)", + :conditions => "datediff(now(), completed_at) <= 30" + }) + @actions_created_last30days = @actions.count(:all, { + :group => "datediff(now(), created_at)", + :conditions => "datediff(now(), created_at) <= 30" + }) + + @sum_actions_done_last30days=0 + # convert to hash to be albe to fill in non-existing days in @actions_done_last30days + @actions_done_last30days_hash = Hash.new(0) + @actions_done_last30days.each do |day, count| + @actions_done_last30days_hash[day] = count + @sum_actions_done_last30days+= count.to_i + end + @sum_actions_created_last30days=0 + # convert to hash to be albe to fill in non-existing days in @actions_done_last30days + @actions_created_last30days_hash = Hash.new(0) + @actions_created_last30days.each do |day, count| + @actions_created_last30days_hash[day] = count + @sum_actions_created_last30days+= count.to_i + end + + # get count of actions done in the past 12 months. Results in a array of arrays + @actions_done_last12months = @actions.count(:all, { + :group => "period_diff(extract(year_month from now()), extract(year_month from completed_at))", + :conditions => "period_diff(extract(year_month from now()), extract(year_month from completed_at)) <= 12 and not completed_at is null" + }) + @actions_created_last12months = @actions.count(:all, { + :group => "period_diff(extract(year_month from now()), extract(year_month from created_at))", + :conditions => "period_diff(extract(year_month from now()), extract(year_month from created_at)) <= 12" + }) + + # convert to hash to be albe to fill in non-existing days in @actions_done_last12months + @sum_actions_done_last12months=0 + @actions_done_last12months_hash = Hash.new(0) + @actions_done_last12months.each do |month, count| + @actions_done_last12months_hash[month] = count + @sum_actions_done_last12months+= count.to_i + end + @sum_actions_created_last12months=0 + @actions_created_last12months_hash = Hash.new(0) + @actions_created_last12months.each do |month, count| + @actions_created_last12months_hash[month] = count + @sum_actions_created_last12months+= count.to_i + end + + end + + def get_stats_contexts + # get action count per context for TOP 5 + @actions_per_context = @contexts.find_by_sql( + "SELECT c.name as name, count(*) as total "+ + "FROM contexts c, todos t "+ + "WHERE t.context_id=c.id "+ + "AND t.user_id="+@user.id.to_s+" "+ + "GROUP BY c.id ORDER BY total DESC " + + "LIMIT 5" + ) + + # get uncompleted action count per visible context for TOP 5 + @running_actions_per_context = @contexts.find_by_sql( + "SELECT c.name as name, count(*) as total "+ + "FROM contexts c, todos t "+ + "WHERE t.context_id=c.id AND t.completed_at IS NULL AND NOT c.hide "+ + "AND t.user_id="+@user.id.to_s+" "+ + "GROUP BY c.id ORDER BY total DESC " + + "LIMIT 5" + ) + end + + def get_stats_projects + # get the first 10 projects and their action count (all actions) + @projects_and_actions = @projects.find_by_sql( + "SELECT p.name, count(*) as count "+ + "FROM projects p, todos t "+ + "WHERE p.id = t.project_id "+ + "AND p.user_id="+@user.id.to_s+" "+ + "GROUP BY p.id "+ + "ORDER BY count DESC " + + "LIMIT 10" + ) + + # get the first 10 projects with their actions count of actions that + # have been created or completed the past 30 days + @projects_and_actions_last30days = @projects.find_by_sql( + "SELECT p.name, count(*) AS count "+ + "FROM todos t, projects p "+ + "WHERE t.project_id = p.id AND "+ + " (datediff(now(), t.created_at) < 30 OR "+ + " datediff(now(), t.completed_at) < 30) "+ + "AND p.user_id="+@user.id.to_s+" "+ + "GROUP BY p.id "+ + "ORDER BY count DESC" + ) + + # get the first 10 projects and their running time (creation date versus now()) + @projects_and_runtime = @projects.find_by_sql( + "SELECT name, datediff(now(),created_at) AS days "+ + "FROM projects p "+ + "WHERE state='active' "+ + "AND p.user_id="+@user.id.to_s+" "+ + "ORDER BY days DESC "+ + "LIMIT 10" + ) + + end + + def get_stats_tags + # todo: parameterize limit + query = "select tags.id, name, count(*) as count" + query << " from taggings, tags" + query << " where tags.id = tag_id" + query << " AND taggings.user_id="+@user.id.to_s+" " + query << " group by tag_id" + query << " order by count DESC, name" + query << " limit 100" + @tags_for_cloud = Tag.find_by_sql(query).sort_by { |tag| tag.name.downcase } + + max, @tags_min = 0, 0 + @tags_for_cloud.each { |t| + max = t.count.to_i if t.count.to_i > max + @tags_min = t.count.to_i if t.count.to_i < @tags_min + } + + # 10 = number of levels + @tags_divisor = ((max - @tags_min) / 10) + 1 + + end +end diff --git a/tracks/app/helpers/stats_helper.rb b/tracks/app/helpers/stats_helper.rb new file mode 100755 index 00000000..65e2f8bb --- /dev/null +++ b/tracks/app/helpers/stats_helper.rb @@ -0,0 +1,2 @@ +module StatsHelper +end diff --git a/tracks/app/views/layouts/standard.rhtml b/tracks/app/views/layouts/standard.rhtml index 6a65dc24..e467511e 100644 --- a/tracks/app/views/layouts/standard.rhtml +++ b/tracks/app/views/layouts/standard.rhtml @@ -55,6 +55,7 @@ window.onload=function(){
  • <%= navigation_link("Admin", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %>
  • <% end -%>
  • <%= navigation_link(image_tag("feed-icon.png", :size => "16X16", :border => 0), {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %>
  • +
  • <%= navigation_link(image_tag("stats.gif", :size => "16X16", :border => 0), {:controller => "stats", :action => "index"}, :title => "See your statistics" ) %>
  • diff --git a/tracks/app/views/stats/_actions.rhtml b/tracks/app/views/stats/_actions.rhtml new file mode 100755 index 00000000..091dd254 --- /dev/null +++ b/tracks/app/views/stats/_actions.rhtml @@ -0,0 +1,29 @@ +

    Of all your completed actions, the average time to complete is <%= @actions_avg_ttc %> days. +The Max-/minimum days to complete is <%= @actions_max_ttc%>/<%= @actions_min_ttc %>. +The minimum time to complete is <%= @actions_min_ttc_sec %>

    + +

    In the last 30 days you created on average <%=@sum_actions_created_last30days/30%> actions +and completed on average <%=@sum_actions_done_last30days/30%> actions per day. +In the last 12 months you created on average <%=@sum_actions_created_last12months/12 %> actions +and completed on average <%=@sum_actions_done_last12months/12%> actions per month. +


    +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_done_last30days_data'} -%> +  +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_done_last12months_data'} -%> + +


    +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_completion_time_data'} -%> +


    +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_visible_running_time_data'} -%> +  +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_running_time_data'} -%> + +


    +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_day_of_week_all_data'} -%> +  +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_day_of_week_30days_data'} -%> + +


    +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_time_of_day_all_data'} -%> +  +<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => '/stats/actions_time_of_day_30days_data'} -%> \ No newline at end of file diff --git a/tracks/app/views/stats/_chart.rhtml b/tracks/app/views/stats/_chart.rhtml new file mode 100755 index 00000000..8167023b --- /dev/null +++ b/tracks/app/views/stats/_chart.rhtml @@ -0,0 +1,5 @@ + + + + + diff --git a/tracks/app/views/stats/_contexts.rhtml b/tracks/app/views/stats/_contexts.rhtml new file mode 100755 index 00000000..ee165c73 --- /dev/null +++ b/tracks/app/views/stats/_contexts.rhtml @@ -0,0 +1,30 @@ +


    +<%= render :partial => 'chart', :locals => {:width => @pie_width, :height => @pie_height, :data => '/stats/context_total_actions_data'} -%> +  +<%= render :partial => 'chart', :locals => {:width => @pie_width, :height => @pie_height, :data => '/stats/context_running_actions_data'} -%> + +

    Top 5 Contexts
    +<% + 1.upto 5 do |i| + %><%=i-%> - + <%= i <= @actions_per_context.size ? @actions_per_context[i-1]['name'] : "n/a"%> + ( + <%= i <= @actions_per_context.size ? @actions_per_context[i-1]['total'] : "n/a"%> + ) +
    <% + end +-%> +

    + +

    Top 5 Visible Contexts with uncomplete actions
    +<% + 1.upto 5 do |i| + %><%=i-%> - + <%= i <= @running_actions_per_context.size ? @running_actions_per_context[i-1]['name'] : "n/a"-%> + ( + <%= i <= @running_actions_per_context.size ? @running_actions_per_context[i-1]['total'] : "n/a"-%> + ) +
    <% + end +-%> +

    \ No newline at end of file diff --git a/tracks/app/views/stats/_projects.rhtml b/tracks/app/views/stats/_projects.rhtml new file mode 100755 index 00000000..6d6b65e1 --- /dev/null +++ b/tracks/app/views/stats/_projects.rhtml @@ -0,0 +1,42 @@ +
    +

    Top 10 projects
    +<% i=0 + @projects_and_actions.each do |p| + i+=1 -%> + <%= i -%> - <%= p.name %> (<%=p.count %> actions)
    +<% end + if i < 10 + i.upto 10 do |j| %> + <%= i -%> - n/a (n/a)
    + <% end + end +%> + +
    +

    Top 10 project in past 30 days
    +<% i=0 + @projects_and_actions_last30days.each do |p| + i+=1 -%> + <%= i -%> - <%= p.name %> (<%=p.count %> actions)
    +<% end + if i < 10 + i.upto 10 do |j| %> + <%= i -%> - n/a (n/a)
    + <% end + end +%> + +
    +

    Top 10 longest running projects
    +<% i=0 + @projects_and_runtime.each do |p| + i+=1 -%> + <%= i -%> - <%= p.name %> (<%=p.days %> days)
    +<% end + if i < 10 + i.upto 10 do |j| %> + <%= i -%> - n/a (n/a)
    + <% end + end +%> +
    \ No newline at end of file diff --git a/tracks/app/views/stats/_tags.rhtml b/tracks/app/views/stats/_tags.rhtml new file mode 100755 index 00000000..26b9e989 --- /dev/null +++ b/tracks/app/views/stats/_tags.rhtml @@ -0,0 +1,15 @@ +
    +

    Tag Cloud

    +

    + <% if @tags_for_cloud.size < 1 + %> no tags available <% + else + @tags_for_cloud.each do |t| %> + <%= link_to t.name, + {:controller => "todos", :action => "tag", :id => t.name}, + {:style => "font-size: " + (9 + 2*(t.count.to_i-@tags_min)/@tags_divisor).to_s + "pt", + :title => t.count+" actions"} + -%> <% + end + end-%> +

    \ No newline at end of file diff --git a/tracks/app/views/stats/_totals.rhtml b/tracks/app/views/stats/_totals.rhtml new file mode 100755 index 00000000..5fc50382 --- /dev/null +++ b/tracks/app/views/stats/_totals.rhtml @@ -0,0 +1,13 @@ +

    You have <%= @projects.count%> projects. +Of those <%= @projects.count(:conditions => "state = 'active'")%> are active projects, +<%= @projects.count(:conditions => "state = 'hidden'")%> hidden projects and +<%= @projects.count(:conditions => "state = 'completed'")%> completed projects

    + +

    You have <%= @contexts.count%> contexts. +Of those <%= @contexts.count(:conditions => "hide = 'false'")%> are visible contexts and +<%= @contexts.count(:conditions => "hide = 'true'") %> are hidden contexts + +

    You have <%= @actions.reject{ |t| t.completed? }.length %> uncompleted actions of which +<%= @actions.select{ |t| t.deferred? }.length %> are deferred actions. +

    Since your first action on <%= format_date(@first_action.created_at) %> you have a total of <%= @actions.count %> actions. <%= @actions.select { |t| t.completed? }.length %> of these are completed. +

    You have <%= @tags.count-%> tags placed on actions. Of those tags, <%= @unique_tags.size -%> are unique. \ No newline at end of file diff --git a/tracks/app/views/stats/actions_completion_time_data.rhtml b/tracks/app/views/stats/actions_completion_time_data.rhtml new file mode 100755 index 00000000..2065c6f0 --- /dev/null +++ b/tracks/app/views/stats/actions_completion_time_data.rhtml @@ -0,0 +1,26 @@ +&title=Completion time (all completed actions),{font-size:16},& +&y_legend=Actions,12,0x736AFF& +&x_legend=Running time of an action (weeks),12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0& +&values= +<% @count = @max_days > @cut_off*7 ? @cut_off : @max_days/7 + 0.upto @count-1 do |i| -%> +<%= @actions_completion_time_hash[i] -%>, +<% end -%> +<% + @sum=0 + @count.upto @max_days/7 do |i| + @sum += @actions_completion_time_hash[i] + end -%> +<%=@sum%>& +&x_labels=within 1, +<% 1.upto @count-1 do |i| -%> +<%= i %>-<%= i+1 %>, +<% end -%> +> <%= @count %>& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=1+@max_actions+@max_actions/10-%>& +&x_label_style=9,,2,1& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_day_of_week_30days_data.rhtml b/tracks/app/views/stats/actions_day_of_week_30days_data.rhtml new file mode 100755 index 00000000..9505652e --- /dev/null +++ b/tracks/app/views/stats/actions_day_of_week_30days_data.rhtml @@ -0,0 +1,23 @@ +&title=Day of week (past 30 days),{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Day of week,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Created,8& +&filled_bar_2=50,0x0066CC,0x0066CC,Completed,8& +&values=<% +0.upto 5 do |i| -%> + <%=@actions_creation_day_array[i] -%>, +<% end -%><%=@actions_creation_day_array[6]%>& +&values_2=<% +0.upto 5 do |i| -%> + <%=@actions_completion_day_array[i] -%>, +<% end -%><%=@actions_completion_day_array[6]%>& +&x_labels= <% +0.upto 5 do |i| -%> + <%=Date::DAYNAMES[i] -%>, +<% end -%><%=Date::DAYNAMES[6]%>& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+1 -%>& +&x_label_style=9,,2,1& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_day_of_week_all_data.rhtml b/tracks/app/views/stats/actions_day_of_week_all_data.rhtml new file mode 100755 index 00000000..fee11d73 --- /dev/null +++ b/tracks/app/views/stats/actions_day_of_week_all_data.rhtml @@ -0,0 +1,23 @@ +&title=Day of week (all actions),{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Day of week,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Created,8& +&filled_bar_2=50,0x0066CC,0x0066CC,Completed,8& +&values=<% +0.upto 5 do |i| -%> + <%=@actions_creation_day_array[i] -%>, +<% end -%><%=@actions_creation_day_array[6]%>& +&values_2=<% +0.upto 5 do |i| -%> + <%=@actions_completion_day_array[i] -%>, +<% end -%><%=@actions_completion_day_array[6]%>& +&x_labels= <% +0.upto 5 do |i| -%> + <%=Date::DAYNAMES[i] -%>, +<% end -%><%=Date::DAYNAMES[6]%>& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+1 -%>& +&x_label_style=9,,2,1& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_done_last12months_data.rhtml b/tracks/app/views/stats/actions_done_last12months_data.rhtml new file mode 100755 index 00000000..e3520071 --- /dev/null +++ b/tracks/app/views/stats/actions_done_last12months_data.rhtml @@ -0,0 +1,27 @@ +&title=Actions in the last 12 months,{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Months ago,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Completed,9& +&filled_bar_2=50,0x0066CC,0x0066CC,Created,9& +&line_3=3,0xFF0000, Avg completed, 9& +&line_4=3,0x00FF00, Avg created, 9& +&values= +<% 0.upto 11 do |i| -%> +<%= @actions_done_last12months_hash[i.to_s]%>, +<% end -%><%= @actions_done_last12months_hash["12"]%>& +&values_2= +<% 0.upto 11 do |i| -%> +<%= @actions_created_last12months_hash[i.to_s]%>, +<% end -%><%= @actions_created_last12months_hash["12"]%>& +&values_3=<%0.upto 11 do |i| -%><%=@sum_actions_done_last12months/12-%>,<%end-%><%=@sum_actions_done_last12months/12-%>& +&values_4=<%0.upto 11 do |i| -%><%=@sum_actions_created_last12months/12-%>,<%end-%><%=@sum_actions_created_last12months/12-%>& +&x_labels=<%0.upto 11 do |i| -%> +<%= Date::MONTHNAMES[ (Time.now.mon - i -1 ) % 12 + 1 ] -%>, +<% end -%> +<%= Date::MONTHNAMES[(Time.now.mon - 12 -1 ) % 12 + 1] -%>& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+@max/10+1-%>& +&x_label_style=9,,2,& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_done_last12monthsdata.rhtml b/tracks/app/views/stats/actions_done_last12monthsdata.rhtml new file mode 100755 index 00000000..75399b1e --- /dev/null +++ b/tracks/app/views/stats/actions_done_last12monthsdata.rhtml @@ -0,0 +1,18 @@ +&title=Actions completed in the last 12 months,16,& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Months ago,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0& +&line_2=3,0xFF0000& +&values= +<% 0.upto 11 do |i| -%> +<%= @actions_done_last12months_hash[i.to_s]%>, +<% end -%><%= @actions_done_last12months_hash["12"]%>& +&values_2=<%0.upto 11 do |i| -%><%=@sum_actions_done_last12months/12-%>,<%end-%><%=@sum_actions_done_last12months/12-%>& +&x_labels=<%0.upto 11 do |i| -%> +<%= Date::MONTHNAMES[ (Time.now.mon - i -1 ) % 12 + 1 ] -%>, +<% end -%> +<%= Date::MONTHNAMES[(Time.now.mon - 12 -1 ) % 12 + 1] -%>& +&y_min=0& +&y_max=<%=@max-%>& +&x_label_style=9,,2,& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_done_last30days_data.rhtml b/tracks/app/views/stats/actions_done_last30days_data.rhtml new file mode 100755 index 00000000..c333da31 --- /dev/null +++ b/tracks/app/views/stats/actions_done_last30days_data.rhtml @@ -0,0 +1,42 @@ +&title=Actions in the last 30 days,{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Number of days ago,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Completed,9& +&filled_bar_2=50,0x0066CC,0x0066CC,Created,9& +&line_3=3,0xFF0000, Avg completed, 9& +&line_4=3,0x00FF00, Avg created, 9& +&values= +<% 0.upto 29 do |i| -%> +<%= @actions_done_last30days_hash[i.to_s]%>, +<% end -%><%= @actions_done_last30days_hash["30"]%>& +&values_2= +<% 0.upto 29 do |i| -%> +<%= @actions_created_last30days_hash[i.to_s]%>, +<% end -%><%= @actions_created_last30days_hash["30"]%>& +&values_3= +<%0.upto 29 do |i| -%> +<%=@sum_actions_done_last30days/30-%>, +<%end-%> +<%=@sum_actions_done_last30days/30-%>& +&values_4= +<%0.upto 29 do |i| -%> +<%=@sum_actions_created_last30days/30-%>, +<%end-%> +<%=@sum_actions_created_last30days/30-%>& +&x_labels= +<%0.upto 29 do |i| + seconds = i * 24 * 60 * 60 + delta = Time.now-seconds +-%> +<%= delta.strftime("%a %d-%m") -%>, +<% end + seconds = 29*25*60*60 + delta = Time.now-seconds-%> +<%= delta.strftime("%a %d-%m") -%>& +&y_min=0& +<% # max + 10% for some extra space at the top + # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+@max/10+1 -%>& +&x_label_style=9,,2,3& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_done_last30daysdata.rhtml b/tracks/app/views/stats/actions_done_last30daysdata.rhtml new file mode 100755 index 00000000..4f2ff047 --- /dev/null +++ b/tracks/app/views/stats/actions_done_last30daysdata.rhtml @@ -0,0 +1,28 @@ +&title=Actions completed in the last 30 days,16,& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Number of days ago,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0& +&line_2=3,0xFF0000& +&values= +<% 0.upto 29 do |i| -%> +<%= @actions_done_last30days_hash[i.to_s]%>, +<% end -%><%= @actions_done_last30days_hash["30"]%>& +&values_2= +<%0.upto 29 do |i| -%> +<%=@sum_actions_done_last30days/30-%>, +<%end-%> +<%=@sum_actions_done_last30days/30-%>& +&x_labels= +<%0.upto 29 do |i| + seconds = i * 24 * 60 * 60 + delta = Time.now-seconds +-%> +<%= delta.strftime("%a %d-%m") -%>, +<% end + seconds = 29*25*60*60 + delta = Time.now-seconds-%> +<%= delta.strftime("%a %d-%m") -%>& +&y_min=0& +&y_max=<%=@max -%>& +&x_label_style=9,,2,3& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_running_time_data.rhtml b/tracks/app/views/stats/actions_running_time_data.rhtml new file mode 100755 index 00000000..30f3c651 --- /dev/null +++ b/tracks/app/views/stats/actions_running_time_data.rhtml @@ -0,0 +1,27 @@ +&title=Current running time of all uncompleted actions,{font-size:16},& +&y_legend=Actions,12,0x736AFF& +&x_legend=Running time of an action (weeks),12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0& +&values= +<% @count = @max_days > @cut_off*7 ? @cut_off : @max_days/7 + 0.upto @count-1 do |i| -%> +<%= @actions_running_time_hash[i] -%>, +<% end -%> +<% + @sum=0 + @count.upto @max_days/7 do |i| + @sum += @actions_running_time_hash[i] + end -%> +<%=@sum%>& +&x_labels=< 1, +<% 1.upto @count-1 do |i| -%> +<%= i %>-<%= i+1 %>, +<% end -%> +><%=@count-%>& +&y_min=0& +<% @max_actions = @sum > @max_actions ? @sum : @max_actions -%> +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=1+@max_actions+@max_actions/10-%>& +&x_label_style=9,,2,2& diff --git a/tracks/app/views/stats/actions_time_of_day_30days_data.rhtml b/tracks/app/views/stats/actions_time_of_day_30days_data.rhtml new file mode 100755 index 00000000..12a459c2 --- /dev/null +++ b/tracks/app/views/stats/actions_time_of_day_30days_data.rhtml @@ -0,0 +1,23 @@ +&title=Time of day (last 30 days),{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Time of Day,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Created,8& +&filled_bar_2=50,0x0066CC,0x0066CC,Completed,8& +&values=<% +0.upto 22 do |i| -%> + <%=@actions_creation_hour_array[i] -%>, +<% end -%><%=@actions_creation_hour_array[23]%>& +&values_2=<% +0.upto 22 do |i| -%> + <%=@actions_completion_hour_array[i] -%>, +<% end -%><%=@actions_completion_hour_array[23]%>& +&x_labels= <% +0.upto 22 do |i| -%> + <%=i-%>, +<% end -%>23& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+1 -%>& +&x_label_style=9,,1,1& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_time_of_day_all_data.rhtml b/tracks/app/views/stats/actions_time_of_day_all_data.rhtml new file mode 100755 index 00000000..c1b4ff3b --- /dev/null +++ b/tracks/app/views/stats/actions_time_of_day_all_data.rhtml @@ -0,0 +1,23 @@ +&title=Time of day (all actions),{font-size:16},& +&y_legend=Number of actions,12,0x736AFF& +&x_legend=Time of Day,12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0,Created,8& +&filled_bar_2=50,0x0066CC,0x0066CC,Completed,8& +&values=<% +0.upto 22 do |i| -%> + <%=@actions_creation_hour_array[i] -%>, +<% end -%><%=@actions_creation_hour_array[23]%>& +&values_2=<% +0.upto 22 do |i| -%> + <%=@actions_completion_hour_array[i] -%>, +<% end -%><%=@actions_completion_hour_array[23]%>& +&x_labels= <% +0.upto 22 do |i| -%> + <%=i-%>, +<% end -%>23& +&y_min=0& +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=@max+1 -%>& +&x_label_style=9,,1,1& \ No newline at end of file diff --git a/tracks/app/views/stats/actions_visible_running_time_data.rhtml b/tracks/app/views/stats/actions_visible_running_time_data.rhtml new file mode 100755 index 00000000..8aceae13 --- /dev/null +++ b/tracks/app/views/stats/actions_visible_running_time_data.rhtml @@ -0,0 +1,27 @@ +&title=Current running time of uncompleted visible actions,{font-size:16},& +&y_legend=Actions,12,0x736AFF& +&x_legend=Running time of an action (weeks),12,0x736AFF& +&y_ticks=5,10,5& +&filled_bar=50,0x9933CC,0x8010A0& +&values= +<% @count = @max_days > @cut_off*7 ? @cut_off : @max_days/7 + 0.upto @count-1 do |i| -%> +<%= @actions_running_time_hash[i] -%>, +<% end -%> +<% + @sum=0 + @count.upto @max_days/7 do |i| + @sum += @actions_running_time_hash[i] + end -%> +<%=@sum%>& +&x_labels=< 1, +<% 1.upto @count-1 do |i| -%> +<%= i %>-<%= i+1 %>, +<% end -%> +><%=@count-%>& +&y_min=0& +<% @max_actions = @sum > @max_actions ? @sum : @max_actions -%> +<% # add one to @max for people who have no actions completed yet. + # OpenFlashChart cannot handle y_max=0 -%> +&y_max=<%=1+@max_actions+@max_actions/10-%>& +&x_label_style=9,,2,2& diff --git a/tracks/app/views/stats/context_running_actions_data.rhtml b/tracks/app/views/stats/context_running_actions_data.rhtml new file mode 100755 index 00000000..a174be7e --- /dev/null +++ b/tracks/app/views/stats/context_running_actions_data.rhtml @@ -0,0 +1,16 @@ +&title=Spread of running actions for visible contexts,{font-size:16}& +&pie=60,#505050,#000000,true,1& +&x_axis_steps=1& &y_ticks=5,10,5& &line=3,#87421F& &y_min=0& &y_max=20& +&values=<% +0.upto @actions_per_context.size()-2 do | i | + %><%=@actions_per_context[i]['total'].to_i*100/@sum%>,<% +end +-%><%=@actions_per_context[@actions_per_context.size()-1]['total'].to_i*100/@sum%>& +&pie_labels=<% +0.upto @actions_per_context.size()-2 do | i | + %><%=@actions_per_context[i]['name']%>,<% +end +-%><%=@actions_per_context[@actions_per_context.size()-1]['name']%>& +&colours=#d01f3c,#356aa0,#C79810,#c61fd0,#1fc6d0,#1fd076,#72d01f,#c6d01f,#d0941f& +&tool_tip=#x_label#: #val#%25& +&x_label_style=9,,2,1& \ No newline at end of file diff --git a/tracks/app/views/stats/context_total_actions_data.rhtml b/tracks/app/views/stats/context_total_actions_data.rhtml new file mode 100755 index 00000000..4091fe5b --- /dev/null +++ b/tracks/app/views/stats/context_total_actions_data.rhtml @@ -0,0 +1,16 @@ +&title=Spread of actions for all context,{font-size:16}& +&pie=70,#505050,#000000,true,1& +&x_axis_steps=1& &y_ticks=5,10,5& &line=3,#87421F& &y_min=0& &y_max=20& +&values=<% +0.upto @actions_per_context.size()-2 do | i | + %><%=@actions_per_context[i]['total'].to_i*100/@sum%>,<% +end +-%><%=@actions_per_context[@actions_per_context.size()-1]['total'].to_i*100/@sum%>& +&pie_labels=<% +0.upto @actions_per_context.size()-2 do | i | + %><%=@actions_per_context[i]['name']%>,<% +end +-%><%=@actions_per_context[@actions_per_context.size()-1]['name']%>& +&colours=#d01f3c,#356aa0,#C79810,#c61fd0,#1fc6d0,#1fd076,#72d01f,#c6d01f,#d0941f& +&tool_tip=#x_label#: #val#%25& +&x_label_style=9,,2,1& \ No newline at end of file diff --git a/tracks/app/views/stats/index.rhtml b/tracks/app/views/stats/index.rhtml new file mode 100755 index 00000000..1ffc5357 --- /dev/null +++ b/tracks/app/views/stats/index.rhtml @@ -0,0 +1,23 @@ +

    Totals

    + +<%= render :partial => 'totals' -%> + + +

    +

    Actions

    + +<%= render :partial => 'actions' -%> + +

    +

    Contexts

    + +<%= render :partial => 'contexts' -%> + +

    +

    Projects

    + +<%= render :partial => 'projects' -%> + +

    Tags

    + +<%= render :partial => 'tags' -%> diff --git a/tracks/public/images/stats.gif b/tracks/public/images/stats.gif new file mode 100755 index 0000000000000000000000000000000000000000..217bc493bd03f74735c714debb343fe4b754da6d GIT binary patch literal 84 zcmZ?wbhEHb6krfwn8?I1apJ_aFM9v~|Ifg{p!k!8k%57UK?leN$_fD4s#E$`o|e6T nf&1f`64fhH9_Kb5b9GWJ<$9U5ZN=(C;ttop#Xj|AWUvMRgtZ>M literal 0 HcmV?d00001 diff --git a/tracks/public/open-flash-chart.swf b/tracks/public/open-flash-chart.swf new file mode 100755 index 0000000000000000000000000000000000000000..5a884344b647f6062a257abccd541fed1ff1a69b GIT binary patch literal 40635 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 Xk|PI64DE})n%pv>PH_Bxc9edSvswwg literal 0 HcmV?d00001 diff --git a/tracks/test/functional/stats_controller_test.rb b/tracks/test/functional/stats_controller_test.rb new file mode 100755 index 00000000..a109f49c --- /dev/null +++ b/tracks/test/functional/stats_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'stats_controller' + +# Re-raise errors caught by the controller. +class StatsController; def rescue_action(e) raise e end; end + +class StatsControllerTest < Test::Unit::TestCase + def setup + @controller = StatsController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end