From f74370aab51c569c46448d4dd868354854e89b4a Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Sat, 19 Nov 2011 02:41:06 +0100 Subject: [PATCH] first refactoring of stats controller --- app/controllers/application_controller.rb | 8 +- app/controllers/stats_controller.rb | 498 +++++++++------------- app/models/todo.rb | 6 +- config/locales/en.yml | 1 + test/functional/stats_controller_test.rb | 5 - test/unit/todo_test2.rb | 57 +++ 6 files changed, 260 insertions(+), 315 deletions(-) create mode 100644 test/unit/todo_test2.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f2c55bb3..9255fc3a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -260,24 +260,26 @@ class ApplicationController < ActionController::Base self.class.prefered_auth? end + # all completed todos [today@00:00, today@now] def get_done_today(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES}) start_of_this_day = Time.zone.now.beginning_of_day completed_todos.completed_after(start_of_this_day).all(includes) end + # all completed todos [begin_of_week, start_of_today] def get_done_this_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES}) start_of_this_week = Time.zone.now.beginning_of_week start_of_this_day = Time.zone.now.beginning_of_day - completed_todos.completed_after(start_of_this_week).completed_before(start_of_this_day).all(includes) + completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).all(includes) end + # all completed todos [begin_of_month, begin_of_week] def get_done_this_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES}) start_of_this_month = Time.zone.now.beginning_of_month start_of_this_week = Time.zone.now.beginning_of_week - completed_todos.completed_after(start_of_this_month).completed_before(start_of_this_week).all(includes) + completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).all(includes) end - private def parse_date_per_user_prefs( s ) diff --git a/app/controllers/stats_controller.rb b/app/controllers/stats_controller.rb index fac58ec2..41edf230 100644 --- a/app/controllers/stats_controller.rb +++ b/app/controllers/stats_controller.rb @@ -3,258 +3,124 @@ class StatsController < ApplicationController helper :todos, :projects, :recurring_todos append_before_filter :init, :exclude => [] - + def index - @page_title = 'TRACKS::Statistics' + @page_title = t('stats.index_title') @tags_count = get_total_number_of_tags_of_user @unique_tags_count = get_unique_tags_of_user.size - @hidden_contexts = @contexts.hidden - @first_action = @actions.find(:first, :order => "created_at ASC") - + @hidden_contexts = current_user.contexts.hidden + @first_action = current_user.todos.find(:first, :order => "created_at ASC") + get_stats_actions get_stats_contexts get_stats_projects - get_stats_tags - + get_stats_tags + render :layout => 'standard' - end - + end + def actions_done_last12months_data - @actions = @user.todos - # get actions created and completed in the past 12+3 months. +3 for running # average - @actions_done_last12months = @actions.find(:all, { - :select => "completed_at", - :conditions => ["completed_at > ? AND completed_at IS NOT NULL", @cut_off_year_plus3] - }) - @actions_created_last12months = @actions.find(:all, { - :select => "created_at", - :conditions => ["created_at > ?", @cut_off_year_plus3] - }) - - # convert to hash to be able to fill in non-existing days in - # @actions_done_last12months and count the total actions done in the past - # 12 months to be able to calculate percentage - - # use 0 to initialise action count to zero - @actions_done_last12months_hash = Hash.new(0) - @actions_done_last12months.each do |r| - months = (@today.year - r.completed_at.year)*12 + (@today.month - r.completed_at.month) + @actions_done_last12months = current_user.todos.completed_after(@cut_off_year_plus3).find(:all, { :select => "completed_at" }) + @actions_created_last12months = current_user.todos.created_after(@cut_off_year_plus3).find(:all, { :select => "created_at"}) - @actions_done_last12months_hash[months] += 1 - end - - # convert to hash to be able to fill in non-existing days in - # @actions_created_last12months and count the total actions done in the - # past 12 months to be able to calculate percentage - - # use 0 to initialise action count to zero - @actions_created_last12months_hash = Hash.new(0) - @actions_created_last12months.each do |r| - months = (@today.year - r.created_at.year)*12 + (@today.month - r.created_at.month) - - @actions_created_last12months_hash[months] += 1 - end - - @sum_actions_done_last12months=0 - @sum_actions_created_last12months=0 + # convert to hash to be able to fill in non-existing months + @actions_done_last12months_hash = convert_to_hash(@actions_done_last12months, :completed_at) + @actions_created_last12months_hash = convert_to_hash(@actions_created_last12months, :created_at) # find max for graph in both hashes - @max=0 - 0.upto 13 do |i| - @sum_actions_done_last12months += @actions_done_last12months_hash[i] - @max = @actions_done_last12months_hash[i] if @actions_done_last12months_hash[i] > @max - end - 0.upto 13 do |i| + @sum_actions_done_last12months, @sum_actions_created_last12months, @max = 0, 0, 0 + 0.upto 13 do |i| + @sum_actions_done_last12months += @actions_done_last12months_hash[i] @sum_actions_created_last12months += @actions_created_last12months_hash[i] - @max = @actions_created_last12months_hash[i] if @actions_created_last12months_hash[i] > @max + @max = [@actions_done_last12months_hash[i], @actions_created_last12months_hash[i], @max].max end - - # find running avg for month i by calculating avg of month i and the two - # after them. Ignore current month because you do not have full data for - # it - @actions_done_avg_last12months_hash = Hash.new("null") - 1.upto(12) { |i| - @actions_done_avg_last12months_hash[i] = (@actions_done_last12months_hash[i] + - @actions_done_last12months_hash[i+1] + - @actions_done_last12months_hash[i+2])/3.0 - } # find running avg for month i by calculating avg of month i and the two - # after them. Ignore current month because you do not have full data for - # it - @actions_created_avg_last12months_hash = Hash.new("null") - 1.upto(12) { |i| - @actions_created_avg_last12months_hash[i] = (@actions_created_last12months_hash[i] + - @actions_created_last12months_hash[i+1] + - @actions_created_last12months_hash[i+2])/3.0 - } - - # interpolate avg for this month. Assume 31 days in this month - days_passed_this_month = Time.new.day/1.0 - @interpolated_actions_created_this_month = ( - @actions_created_last12months_hash[0]/days_passed_this_month*31.0+ - @actions_created_last12months_hash[1]+ - @actions_created_last12months_hash[2]) / 3.0 - - @interpolated_actions_done_this_month = ( - @actions_done_last12months_hash[0]/days_passed_this_month*31.0 + - @actions_done_last12months_hash[1]+ - @actions_done_last12months_hash[2]) / 3.0 - + # after them. Ignore current month [0] because you do not have full data for it + @actions_done_avg_last12months_hash, @actions_created_avg_last12months_hash = Hash.new("null"), Hash.new("null") + 1.upto(12) do |i| + @actions_done_avg_last12months_hash[i] = three_month_avg(@actions_done_last12months_hash, i) + @actions_created_avg_last12months_hash[i] = three_month_avg(@actions_created_last12months_hash, i) + end + + # interpolate avg for current month. + percent_of_month = Time.zone.now.day.to_f / Time.zone.now.end_of_month.day.to_f + @interpolated_actions_created_this_month = interpolate_avg(@actions_created_last12months_hash, percent_of_month) + @interpolated_actions_done_this_month = interpolate_avg(@actions_done_last12months_hash, percent_of_month) + render :layout => false end - + def actions_done_last_years + @page_title = t('stats.index_title') @chart_width = 900 @chart_height = 400 end - + def actions_done_lastyears_data - @actions = @user.todos - - # get actions created and completed in the past 12+3 months. +3 for running - # average - @actions_done_last_months = @actions.find(:all, { - :select => "completed_at", - :conditions => ["completed_at IS NOT NULL"] - }) - @actions_created_last_months = @actions.find(:all, { - :select => "created_at", - }) - - @month_count = 0 - - # convert to hash to be able to fill in non-existing days in - # @actions_done_last12months and count the total actions done in the past - # 12 months to be able to calculate percentage - - # use 0 to initialise action count to zero - @actions_done_last_months_hash = Hash.new(0) - @actions_done_last_months.each do |r| - months = (@today.year - r.completed_at.year)*12 + (@today.month - r.completed_at.month) - @month_count = months if months > @month_count - @actions_done_last_months_hash[months] += 1 - end - - # convert to hash to be able to fill in non-existing days in - # @actions_created_last12months and count the total actions done in the - # past 12 months to be able to calculate percentage + @actions = current_user.todos - # use 0 to initialise action count to zero - @actions_created_last_months_hash = Hash.new(0) - @actions_created_last_months.each do |r| - months = (@today.year - r.created_at.year)*12 + (@today.month - r.created_at.month) - @month_count = months if months > @month_count - @actions_created_last_months_hash[months] += 1 - end + @actions_done_last_months = current_user.todos.completed.find(:all, { :select => "completed_at", :order => "completed_at DESC" }) + @actions_created_last_months = current_user.todos.find(:all, { :select => "created_at", :order => "created_at DESC" }) - @sum_actions_done_last_months=0 - @sum_actions_created_last_months=0 + @month_count = [difference_in_months(@today, @actions_created_last_months.last.created_at), + difference_in_months(@today, @actions_done_last_months.last.completed_at)].max + + # convert to hash to be able to fill in non-existing months + @actions_done_last_months_hash = convert_to_hash(@actions_done_last_months, :completed_at) + @actions_created_last_months_hash = convert_to_hash(@actions_created_last_months, :created_at) # find max for graph in both hashes - @max=0 + @sum_actions_done_last_months, @sum_actions_created_last_months, @max = 0, 0, 0 0.upto @month_count do |i| - @sum_actions_done_last_months += @actions_done_last_months_hash[i] - @max = @actions_done_last_months_hash[i] if @actions_done_last_months_hash[i] > @max - end - 0.upto @month_count do |i| + @sum_actions_done_last_months += @actions_done_last_months_hash[i] @sum_actions_created_last_months += @actions_created_last_months_hash[i] - @max = @actions_created_last_months_hash[i] if @actions_created_last_months_hash[i] > @max + @max = [@actions_done_last_months_hash[i], @actions_created_last_months_hash[i], @max].max end - - # find running avg for month i by calculating avg of month i and the two - # after them. Ignore current month because you do not have full data for - # it - @actions_done_avg_last_months_hash = Hash.new("null") - 1.upto(@month_count) { |i| - @actions_done_avg_last_months_hash[i] = (@actions_done_last_months_hash[i] + - @actions_done_last_months_hash[i+1] + - @actions_done_last_months_hash[i+2])/3.0 - } - # correct last two months - @actions_done_avg_last_months_hash[@month_count] = @actions_done_avg_last_months_hash[@month_count] * 3 - @actions_done_avg_last_months_hash[@month_count-1] = @actions_done_avg_last_months_hash[@month_count-1] * 3 / 2 if @month_count > 1 # find running avg for month i by calculating avg of month i and the two - # after them. Ignore current month because you do not have full data for - # it - @actions_created_avg_last_months_hash = Hash.new("null") - 1.upto(@month_count) { |i| - @actions_created_avg_last_months_hash[i] = (@actions_created_last_months_hash[i] + - @actions_created_last_months_hash[i+1] + - @actions_created_last_months_hash[i+2])/3.0 - } + # after them. Ignore current month because you do not have full data for it + @actions_done_avg_last_months_hash, @actions_created_avg_last_months_hash = Hash.new("null"), Hash.new("null") + 1.upto(@month_count) do |i| + @actions_done_avg_last_months_hash[i] = three_month_avg(@actions_done_last_months_hash, i) + @actions_created_avg_last_months_hash[i] = three_month_avg(@actions_created_last_months_hash, i) + end + # correct last two months - @actions_created_avg_last_months_hash[@month_count] = @actions_created_avg_last_months_hash[@month_count] * 3 - @actions_created_avg_last_months_hash[@month_count-1] = @actions_created_avg_last_months_hash[@month_count-1] * 3 / 2 if @month_count > 1 - - # interpolate avg for this month. Assume 31 days in this month - days_passed_this_month = Time.new.day/1.0 - @interpolated_actions_created_this_month = ( - @actions_created_last_months_hash[0]/days_passed_this_month*31.0+ - @actions_created_last_months_hash[1]+ - @actions_created_last_months_hash[2]) / 3.0 - - @interpolated_actions_done_this_month = ( - @actions_done_last_months_hash[0]/days_passed_this_month*31.0 + - @actions_done_last_months_hash[1]+ - @actions_done_last_months_hash[2]) / 3.0 - + correct_last_two_months(@actions_done_avg_last_months_hash, @month_count) + correct_last_two_months(@actions_created_avg_last_months_hash, @month_count) + + # interpolate avg for this month. + percent_of_month = Time.zone.now.day.to_f / Time.zone.now.end_of_month.day.to_f + @interpolated_actions_created_this_month = interpolate_avg(@actions_created_last_months_hash, percent_of_month) + @interpolated_actions_done_this_month = interpolate_avg(@actions_done_last_months_hash, percent_of_month) + render :layout => false end - + def actions_done_last30days_data # get actions created and completed in the past 30 days. - @actions_done_last30days = @actions.find(:all, { - :select => "completed_at", - :conditions => ["completed_at > ? AND completed_at IS NOT NULL", @cut_off_month] - }) - @actions_created_last30days = @actions.find(:all, { - :select => "created_at", - :conditions => ["created_at > ?", @cut_off_month] - }) - + @actions_done_last30days = current_user.todos.completed_after(@cut_off_month).find(:all, { :select => "completed_at" }) + @actions_created_last30days = current_user.todos.created_after(@cut_off_month).find(:all, { :select => "created_at" }) + # convert to hash to be able to fill in non-existing days in # @actions_done_last30days and count the total actions done in the past 30 # days to be able to calculate percentage - @sum_actions_done_last30days=0 - - # use 0 to initialise action count to zero - @actions_done_last30days_hash = Hash.new(0) - @actions_done_last30days.each do |r| - # only use date part of completed_at - action_date = Time.utc(r.completed_at.year, r.completed_at.month, r.completed_at.day, 0,0) - days = ((@today - action_date) / @seconds_per_day).to_i - - @actions_done_last30days_hash[days] += 1 - @sum_actions_done_last30days+=1 - end - - # convert to hash to be able to fill in non-existing days in - # @actions_done_last30days and count the total actions done in the past 30 - # days to be able to calculate percentage - @sum_actions_created_last30days=0 - - # use 0 to initialise action count to zero - @actions_created_last30days_hash = Hash.new(0) - @actions_created_last30days.each do |r| - # only use date part of created_at - action_date = Time.utc(r.created_at.year, r.created_at.month, r.created_at.day, 0,0) - days = ((@today - action_date) / @seconds_per_day).to_i - - @actions_created_last30days_hash[days] += 1 - @sum_actions_created_last30days += 1 - end + @actions_done_last30days_hash = convert_to_hash(@actions_done_last30days, :completed_at, :difference_in_days) + @actions_created_last30days_hash = convert_to_hash(@actions_created_last30days, :created_at, :difference_in_days) # find max for graph in both hashes - @max=0 - 0.upto(30) { |i| @max = @actions_done_last30days_hash[i] if @actions_done_last30days_hash[i] > @max } - 0.upto(30) { |i| @max = @actions_created_last30days_hash[i] if @actions_created_last30days_hash[i] > @max } - + @sum_actions_done_last30days, @sum_actions_created_last30days, @max = 0, 0, 0 + 0.upto(30) do |i| + @sum_actions_done_last30days += @actions_done_last30days_hash[i] + @sum_actions_created_last30days += @actions_created_last30days_hash[i] + @max = [ @actions_done_last30days_hash[i], @actions_created_last30days_hash[i], @max].max + end + render :layout => false end @@ -263,25 +129,25 @@ class StatsController < ApplicationController :select => "completed_at, created_at", :conditions => "completed_at IS NOT NULL" }) - + # convert to hash to be able to fill in non-existing days in # @actions_completion_time also convert days to weeks (/7) - + @max_days, @max_actions, @sum_actions=0,0,0 @actions_completion_time_hash = Hash.new(0) @actions_completion_time.each do |r| days = (r.completed_at - r.created_at) / @seconds_per_day weeks = (days/7).to_i - @actions_completion_time_hash[weeks] += 1 + @actions_completion_time_hash[weeks] += 1 @max_days=days if days > @max_days - @max_actions = @actions_completion_time_hash[weeks] if @actions_completion_time_hash[weeks] > @max_actions + @max_actions = @actions_completion_time_hash[weeks] if @actions_completion_time_hash[weeks] > @max_actions @sum_actions += 1 end - + # stop the chart after 10 weeks @cut_off = 10 - + render :layout => false end @@ -293,7 +159,7 @@ class StatsController < ApplicationController # convert to hash to be able to fill in non-existing days in # @actions_running_time also convert days to weeks (/7) - + @max_days, @max_actions, @sum_actions=0,0,0 @actions_running_time_hash = Hash.new(0) @actions_running_time.each do |r| @@ -301,15 +167,15 @@ class StatsController < ApplicationController weeks = (days/7).to_i @actions_running_time_hash[weeks] += 1 - + @max_days=days if days > @max_days @max_actions = @actions_running_time_hash[weeks] if @actions_running_time_hash[weeks] > @max_actions @sum_actions += 1 end - + # cut off chart at 52 weeks = one year @cut_off=52 - + render :layout => false end @@ -320,7 +186,7 @@ class StatsController < ApplicationController # - actions not part of a hidden context # - actions not deferred (show_from must be null) # - actions not pending/blocked - + @actions_running_time = @actions.find_by_sql([ "SELECT t.created_at "+ "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 "+ @@ -330,10 +196,10 @@ class StatsController < ApplicationController "AND NOT (p.state='hidden' OR p.state='pending' OR c.hide=?) " + "ORDER BY t.created_at ASC", @user.id, true] ) - + # convert to hash to be able to fill in non-existing days in # @actions_running_time also convert days to weeks (/7) - + @max_days, @max_actions, @sum_actions=0,0,0 @actions_running_time_hash = Hash.new(0) @actions_running_time.each do |r| @@ -341,19 +207,19 @@ class StatsController < ApplicationController weeks = (days/7).to_i # RAILS_DEFAULT_LOGGER.error("\n" + total.to_s + " - " + days + "\n") @actions_running_time_hash[weeks] += 1 - + @max_days=days if days > @max_days @max_actions = @actions_running_time_hash[weeks] if @actions_running_time_hash[weeks] > @max_actions @sum_actions += 1 end - + # cut off chart at 52 weeks = one year @cut_off=52 - + render :layout => false end - + def context_total_actions_data # get total action count per context Went from GROUP BY c.id to c.name for # compatibility with postgresql. Since the name is forced to be unique, this @@ -383,14 +249,14 @@ class StatsController < ApplicationController @actions_per_context[size-1]['total']+=@all_actions_per_context[i]['total'].to_i end end - + @sum=0 0.upto @all_actions_per_context.size()-1 do |i| @sum += @all_actions_per_context[i]['total'].to_i end - - @truncate_chars = 15 - + + @truncate_chars = 15 + render :layout => false end @@ -407,7 +273,7 @@ class StatsController < ApplicationController "GROUP BY c.name, c.id "+ "ORDER BY total DESC" ) - + pie_cutoff=10 size = @all_actions_per_context.size() size = pie_cutoff if size > pie_cutoff @@ -424,27 +290,27 @@ class StatsController < ApplicationController @actions_per_context[size-1]['total']+=@all_actions_per_context[i]['total'].to_i end end - + @sum=0 0.upto @all_actions_per_context.size()-1 do |i| @sum += @all_actions_per_context[i]['total'].to_i end - + @truncate_chars = 15 - + render :layout => false end def actions_day_of_week_all_data @actions = @user.todos - + @actions_creation_day = @actions.find(:all, { :select => "created_at" }) - + @actions_completion_day = @actions.find(:all, { :select => "completed_at", - :conditions => "completed_at IS NOT NULL" + :conditions => "completed_at IS NOT NULL" }) # convert to hash to be able to fill in non-existing days @@ -457,7 +323,7 @@ class StatsController < ApplicationController # find max @max=0 0.upto(6) { |i| @max = @actions_creation_day_array[i] if @actions_creation_day_array[i] > @max} - + # convert to hash to be able to fill in non-existing days @actions_completion_day_array = Array.new(7) { |i| 0} @@ -467,7 +333,7 @@ class StatsController < ApplicationController @actions_completion_day_array[dayofweek] += 1 end 0.upto(6) { |i| @max = @actions_completion_day_array[i] if @actions_completion_day_array[i] > @max} - + render :layout => false end @@ -476,7 +342,7 @@ class StatsController < ApplicationController :select => "created_at", :conditions => ["created_at > ?", @cut_off_month] }) - + @actions_completion_day = @actions.find(:all, { :select => "completed_at", :conditions => ["completed_at IS NOT NULL AND completed_at > ?", @cut_off_month] @@ -500,17 +366,17 @@ class StatsController < ApplicationController @actions_completion_day_array[dayofweek] += 1 end 0.upto(6) { |i| @max = @actions_completion_day_array[i] if @actions_completion_day_array[i] > @max} - + render :layout => false end def actions_time_of_day_all_data @actions_creation_hour = @actions.find(:all, { :select => "created_at" - }) + }) @actions_completion_hour = @actions.find(:all, { - :select => "completed_at", - :conditions => "completed_at IS NOT NULL" + :select => "completed_at", + :conditions => "completed_at IS NOT NULL" }) # convert to hash to be able to fill in non-existing days @@ -524,12 +390,12 @@ class StatsController < ApplicationController # 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 |r| + @actions_completion_hour.each do |r| hour = r.completed_at.hour @actions_completion_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_completion_hour_array[i] if @actions_completion_hour_array[i] > @max} - + render :layout => false end @@ -538,7 +404,7 @@ class StatsController < ApplicationController :select => "created_at", :conditions => ["created_at > ?", @cut_off_month] }) - + @actions_completion_hour = @actions.find(:all, { :select => "completed_at", :conditions => ["completed_at IS NOT NULL AND completed_at > ?", @cut_off_month] @@ -555,32 +421,32 @@ class StatsController < ApplicationController # 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 |r| + @actions_completion_hour.each do |r| hour = r.completed_at.hour @actions_completion_hour_array[hour] += 1 end 0.upto(23) { |i| @max = @actions_completion_hour_array[i] if @actions_completion_hour_array[i] > @max} - + render :layout => false end - + def show_selected_actions_from_chart @page_title = t('stats.action_selection_title') @count = 99 @source_view = 'stats' - + case params['id'] when 'avrt', 'avrt_end' # actions_visible_running_time - + # HACK: because open flash chart uses & to denote the end of a parameter, # we cannot use URLs with multiple parameters (that would use &). So we # revert to using two id's for the same selection. avtr_end means that the # last bar of the chart is selected. avtr is used for all other bars - + week_from = params['index'].to_i week_to = week_from+1 - + @chart_name = "actions_visible_running_time_data" @page_title = t('stats.actions_selected_from_week') @further = false @@ -602,18 +468,18 @@ class StatsController < ApplicationController "ORDER BY t.created_at ASC", @user.id, true] ) - @selected_todo_ids, @count = get_ids_from(@actions_running_time, week_from, week_to, params['id']== 'avrt_end') - @actions = @user.todos + @selected_todo_ids, @count = get_ids_from(@actions_running_time, week_from, week_to, params['id']== 'avrt_end') + @actions = @user.todos @selected_actions = @actions.find(:all, { :conditions => "id in (" + @selected_todo_ids + ")" }) - + render :action => "show_selection_from_chart" - + when 'art', 'art_end' week_from = params['index'].to_i week_to = week_from+1 - + @chart_name = "actions_running_time_data" @page_title = "Actions selected from week " @further = false @@ -631,11 +497,11 @@ class StatsController < ApplicationController :conditions => "completed_at IS NULL" }) - @selected_todo_ids, @count = get_ids_from(@actions_running_time, week_from, week_to, params['id']=='art_end') + @selected_todo_ids, @count = get_ids_from(@actions_running_time, week_from, week_to, params['id']=='art_end') @selected_actions = @actions.find(:all, { :conditions => "id in (" + @selected_todo_ids + ")" }) - + render :action => "show_selection_from_chart" else # render error @@ -643,11 +509,11 @@ class StatsController < ApplicationController end end - def done + def done @source_view = 'done' - + init_not_done_counts - + @done_recently = current_user.todos.completed.all(:limit => 10, :order => 'completed_at DESC', :include => Todo::DEFAULT_INCLUDES) @last_completed_projects = current_user.projects.completed.all(:limit => 10, :order => 'completed_at DESC', :include => [:todos, :notes]) @last_completed_contexts = [] @@ -680,45 +546,37 @@ class StatsController < ApplicationController end def init - @actions = @user.todos - @projects = @user.projects - @contexts = @user.contexts + @me = self # for meta programming + @actions = current_user.todos + @projects = current_user.projects + @contexts = current_user.contexts # default chart dimensions @chart_width=460 @chart_height=250 @pie_width=@chart_width @pie_height=325 - + # get the current date wih time set to 0:0 - now = Time.new - @today = Time.utc(now.year, now.month, now.day, 0,0) + @today = Time.zone.now.beginning_of_day # define the number of seconds in a day @seconds_per_day = 60*60*24 # define cut_off date and discard the time for a month, 3 months and a year - cut_off_time = 13.months.ago() - @cut_off_year = Time.utc(cut_off_time.year, cut_off_time.month, cut_off_time.day,0,0) - - cut_off_time = 16.months.ago() - @cut_off_year_plus3 = Time.utc(cut_off_time.year, cut_off_time.month, cut_off_time.day,0,0) - - cut_off_time = 31.days.ago - @cut_off_month = Time.utc(cut_off_time.year, cut_off_time.month, cut_off_time.day,0,0) - - cut_off_time = 91.days.ago - @cut_off_3months = Time.utc(cut_off_time.year, cut_off_time.month, cut_off_time.day,0,0) - + @cut_off_year = 13.months.ago.beginning_of_day + @cut_off_year_plus3 = 16.months.ago.beginning_of_day + @cut_off_month = 1.month.ago.beginning_of_day + @cut_off_3months = 3.months.ago.beginning_of_day end - + def get_stats_actions # time to complete @completed_actions = @actions.find(:all, { :select => "completed_at, created_at", :conditions => "completed_at IS NOT NULL", }) - + actions_sum, actions_max, actions_min = 0,0,-1 @completed_actions.each do |r| actions_sum += (r.completed_at - r.created_at) @@ -727,34 +585,34 @@ class StatsController < ApplicationController actions_min = (r.completed_at - r.created_at) if actions_min == -1 actions_min = (r.completed_at - r.created_at) if (r.completed_at - r.created_at) < actions_min end - + sum_actions = @completed_actions.size sum_actions = 1 if sum_actions==0 - + @actions_avg_ttc = (actions_sum/sum_actions)/@seconds_per_day @actions_max_ttc = actions_max/@seconds_per_day @actions_min_ttc = actions_min/@seconds_per_day - + min_ttc_sec = Time.utc(2000,1,1,0,0)+actions_min @actions_min_ttc_sec = (min_ttc_sec).strftime("%H:%M:%S") @actions_min_ttc_sec = (actions_min / @seconds_per_day).round.to_s + " days " + @actions_min_ttc_sec if actions_min > @seconds_per_day - + # get count of actions created and actions done in the past 30 days. @sum_actions_done_last30days = @actions.count(:all, { - :conditions => ["completed_at > ? AND completed_at IS NOT NULL", @cut_off_month] + :conditions => ["completed_at > ? AND completed_at IS NOT NULL", @cut_off_month] }) @sum_actions_created_last30days = @actions.count(:all, { :conditions => ["created_at > ?", @cut_off_month] }) - + # get count of actions done in the past 12 months. @sum_actions_done_last12months = @actions.count(:all, { :conditions => ["completed_at > ? AND completed_at IS NOT NULL", @cut_off_year] }) @sum_actions_created_last12months = @actions.count(:all, { - :conditions => ["created_at > ?", @cut_off_year] - }) + :conditions => ["created_at > ?", @cut_off_year] + }) end def get_stats_contexts @@ -782,7 +640,7 @@ class StatsController < ApplicationController "AND t.user_id="+@user.id.to_s+" "+ "GROUP BY c.id, c.name ORDER BY total DESC " + "LIMIT 5" - ) + ) end def get_stats_projects @@ -799,10 +657,10 @@ class StatsController < ApplicationController "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 - + # using GROUP BY p.name (was: p.id) for compatibility with Postgresql. Since # you cannot create two contexts with the same name, this will work. @projects_and_actions_last30days = @projects.find_by_sql([ @@ -815,7 +673,7 @@ class StatsController < ApplicationController "ORDER BY count DESC " + "LIMIT 10", @cut_off_month, @cut_off_month, @user.id] ) - + # get the first 10 projects and their running time (creation date versus # now()) @projects_and_runtime_sql = @projects.find_by_sql( @@ -835,16 +693,16 @@ class StatsController < ApplicationController @projects_and_runtime[i]=[r.id, r.name, days.to_i+1] i += 1 end - + end - + def get_stats_tags # tag cloud code inspired by this article # http://www.juixe.com/techknow/index.php/2006/07/15/acts-as-taggable-tag-cloud/ levels=10 # TODO: parameterize limit - + # Get the tag cloud for all tags for actions query = "SELECT tags.id, name, count(*) AS count" query << " FROM taggings, tags, todos" @@ -856,7 +714,7 @@ class StatsController < ApplicationController 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 @@ -888,20 +746,20 @@ class StatsController < ApplicationController } @tags_divisor_90days = ((max_90days - @tags_min_90days) / levels) + 1 - + end - + def get_ids_from (actions_running_time, week_from, week_to, at_end) count=0 selected_todo_ids = "" - + actions_running_time.each do |r| days = (@today - r.created_at) / @seconds_per_day weeks = (days/7).to_i if at_end if weeks >= week_from selected_todo_ids += r.id.to_s+"," - count+=1 + count+=1 end else if weeks.between?(week_from, week_to-1) @@ -910,11 +768,41 @@ class StatsController < ApplicationController end end end - + # strip trailing comma selected_todo_ids = selected_todo_ids[0..selected_todo_ids.length-2] - + return selected_todo_ids, count end - + + def convert_to_hash(records, date_method, difference_method=:difference_in_months) + # use 0 to initialise action count to zero + hash = Hash.new(0) + records.each { |t| hash[self.send(difference_method, @today, t.send(date_method))] += 1 } + return hash + end + + # assumes date1 > date2 + def difference_in_months(date1, date2) + return (date1.year - date2.year)*12 + (date1.month - date2.month) + end + + # assumes date1 > date2 + def difference_in_days(date1, date2) + return ((date1.at_midnight-date2.at_midnight)/@seconds_per_day).to_i + end + + def three_month_avg(hash, i) + return (hash[i] + hash[i+1] + hash[i+2])/3.0 + end + + def interpolate_avg(hash, percent) + return (hash[0]*percent + hash[1] + hash[2]) / 3.0 + end + + def correct_last_two_months(month_data, count) + month_data[count] = month_data[count] * 3 + month_data[count-1] = month_data[count-1] * 3 / 2 if count > 1 + end + end \ No newline at end of file diff --git a/app/models/todo.rb b/app/models/todo.rb index 9122afae..6f6cd1f4 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -41,8 +41,10 @@ class Todo < ActiveRecord::Base named_scope :with_tag, lambda { |tag_id| {:joins => :taggings, :conditions => ["taggings.tag_id = ? ", tag_id] } } named_scope :with_tags, lambda { |tag_ids| {:conditions => ["EXISTS(SELECT * from taggings t WHERE t.tag_id IN (?) AND t.taggable_id=todos.id AND t.taggable_type='Todo')", tag_ids] } } named_scope :of_user, lambda { |user_id| {:conditions => ["todos.user_id = ? ", user_id] } } - named_scope :completed_after, lambda { |date| {:conditions => ["todos.completed_at > ? ", date] } } - named_scope :completed_before, lambda { |date| {:conditions => ["todos.completed_at < ? ", date] } } + named_scope :completed_after, lambda { |date| {:conditions => ["todos.completed_at > ?", date] } } + named_scope :completed_before, lambda { |date| {:conditions => ["todos.completed_at < ?", date] } } + named_scope :created_after, lambda { |date| {:conditions => ["todos.created_at > ?", date] } } + named_scope :created_before, lambda { |date| {:conditions => ["todos.created_at < ?", date] } } STARRED_TAG_NAME = "starred" DEFAULT_INCLUDES = [ :project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo ] diff --git a/config/locales/en.yml b/config/locales/en.yml index d9460627..2908cc27 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -233,6 +233,7 @@ en: one: 1 error prohibited this %{model} from being saved other: "%{count} errors prohibited this %{model} from being saved" stats: + index_title: TRACKS::Statistics tag_cloud_title: Tag cloud for all actions tag_cloud_description: This tag cloud includes tags of all actions (completed, not completed, visible and/or hidden) tag_cloud_90days_title: Tag cloud actions in past 90 days diff --git a/test/functional/stats_controller_test.rb b/test/functional/stats_controller_test.rb index 44ca1575..14ebec73 100755 --- a/test/functional/stats_controller_test.rb +++ b/test/functional/stats_controller_test.rb @@ -13,11 +13,6 @@ class StatsControllerTest < ActionController::TestCase @response = ActionController::TestResponse.new end - # Replace this with your real tests. - def test_truth - assert true - end - def test_get_index_when_not_logged_in get :index assert_redirected_to :controller => 'login', :action => 'login' diff --git a/test/unit/todo_test2.rb b/test/unit/todo_test2.rb new file mode 100644 index 00000000..323b7432 --- /dev/null +++ b/test/unit/todo_test2.rb @@ -0,0 +1,57 @@ +require File.expand_path(File.dirname(__FILE__) + '/../test_helper') +require 'date' + +class TodoTest < ActiveSupport::TestCase + fixtures :todos, :recurring_todos, :users, :contexts, :preferences, :tags, :taggings, :projects + + def setup + @not_completed1 = Todo.find(1).reload + @not_completed2 = Todo.find(2).reload + @completed = Todo.find(8).reload + end + + # test named_scopes + def test_find_completed + # Given 2 completed todos, one completed now and one completed 2 months ago + @not_completed1.toggle_completion! + @completed.completed_at = 2.months.ago + @completed.save! + + completed_old = @completed + completed_now = @not_completed1 + + # When I use the finders + recent_completed_todos = Todo.completed_after(1.month.ago).find(:all) + older_completed_todos = Todo.completed_before(1.month.ago).find(:all) + + # Then completed1 should be before and completed2 should be after a month ago + assert older_completed_todos.include?(completed_old) + assert recent_completed_todos.include?(completed_now) + + # And completed1 should not be after and completed2 should not be before a month ago + assert !older_completed_todos.include?(completed_now) + assert !recent_completed_todos.include?(completed_old) + end + + def test_find_created + # Given 2 created todos, one created now and one created 2 months ago + user = @completed.user + todo_old = user.todos.create!({:description => "created long long ago", :context => @completed.context}) + todo_old.created_at = 2.months.ago + todo_old.save! + todo_now = user.todos.create!({:description => "just created", :context => @completed.context}) + + # When I use the finders + recent_created_todos = Todo.created_after(1.month.ago).find(:all) + older_created_todos = Todo.created_before(1.month.ago).find(:all) + + # Then todo1 should be before and todo2 should be after a month ago + assert older_created_todos.include?(todo_old) + assert recent_created_todos.include?(todo_now) + + # And todo1 should not be after and todo2 should not be before a month ago + assert !older_created_todos.include?(todo_now) + assert !recent_created_todos.include?(todo_old) + end + +end \ No newline at end of file