this patch add 'down-drilling' for the chart with visible running actions on the stats page. Also fixes a bug that invisible actions were counted in that chart

git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@719 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
lrbalt 2008-02-29 19:54:32 +00:00
parent 52bade4b0d
commit 1abacfbded
5 changed files with 155 additions and 35 deletions

View file

@ -1,5 +1,7 @@
class StatsController < ApplicationController class StatsController < ApplicationController
helper :todos
append_before_filter :init, :exclude => [] append_before_filter :init, :exclude => []
def index def index
@ -9,12 +11,6 @@ class StatsController < ApplicationController
@hidden_contexts = @contexts.find(:all, {:conditions => ["hide = ? ", true]}) @hidden_contexts = @contexts.find(:all, {:conditions => ["hide = ? ", true]})
@first_action = @actions.find(:first, :order => "created_at ASC") @first_action = @actions.find(:first, :order => "created_at ASC")
# default chart dimensions
@chart_width=460
@chart_height=250
@pie_width=@chart_width
@pie_height=325
get_stats_actions get_stats_actions
get_stats_contexts get_stats_contexts
get_stats_projects get_stats_projects
@ -38,8 +34,8 @@ class StatsController < ApplicationController
}) })
# convert to hash to be able to fill in non-existing days in # 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 # @actions_done_last12months and count the total actions done in the past
# months to be able to calculate percentage # 12 months to be able to calculate percentage
# use 0 to initialise action count to zero # use 0 to initialise action count to zero
@actions_done_last12months_hash = Hash.new(0) @actions_done_last12months_hash = Hash.new(0)
@ -50,8 +46,8 @@ class StatsController < ApplicationController
end end
# convert to hash to be able to fill in non-existing days in # 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 # @actions_created_last12months and count the total actions done in the
# 12 months to be able to calculate percentage # past 12 months to be able to calculate percentage
# use 0 to initialise action count to zero # use 0 to initialise action count to zero
@actions_created_last12months_hash = Hash.new(0) @actions_created_last12months_hash = Hash.new(0)
@ -76,7 +72,8 @@ class StatsController < ApplicationController
end end
# find running avg for month i by calculating avg of month i and the two # 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 # after them. Ignore current month because you do not have full data for
# it
@actions_done_avg_last12months_hash = Hash.new("null") @actions_done_avg_last12months_hash = Hash.new("null")
1.upto(12) { |i| 1.upto(12) { |i|
@actions_done_avg_last12months_hash[i] = (@actions_done_last12months_hash[i] + @actions_done_avg_last12months_hash[i] = (@actions_done_last12months_hash[i] +
@ -85,7 +82,8 @@ class StatsController < ApplicationController
} }
# find running avg for month i by calculating avg of month i and the two # 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 # after them. Ignore current month because you do not have full data for
# it
@actions_created_avg_last12months_hash = Hash.new("null") @actions_created_avg_last12months_hash = Hash.new("null")
1.upto(12) { |i| 1.upto(12) { |i|
@actions_created_avg_last12months_hash[i] = (@actions_created_last12months_hash[i] + @actions_created_avg_last12months_hash[i] = (@actions_created_last12months_hash[i] +
@ -215,11 +213,18 @@ class StatsController < ApplicationController
end end
def actions_visible_running_time_data def actions_visible_running_time_data
# running means
# - not completed (completed_at must be null) visible means
# - actions not part of a hidden project
# - actions not part of a hidden context
# - actions not deferred (show_from must be null)
@actions_running_time = @actions.find_by_sql([ @actions_running_time = @actions.find_by_sql([
"SELECT t.created_at "+ "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 "+ "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=? "+ "WHERE t.user_id=? "+
"AND t.completed_at is null " + "AND t.completed_at IS NULL " +
"AND t.show_from IS NULL " +
"AND NOT (p.state='hidden' OR c.hide=?) " + "AND NOT (p.state='hidden' OR c.hide=?) " +
"ORDER BY t.created_at ASC", @user.id, true] "ORDER BY t.created_at ASC", @user.id, true]
) )
@ -248,9 +253,9 @@ class StatsController < ApplicationController
def context_total_actions_data def context_total_actions_data
# get total action count per context # get total action count per context Went from GROUP BY c.id to c.name for
# Went from GROUP BY c.id to c.name for compatibility with postgresql. Since # compatibility with postgresql. Since the name is forced to be unique, this
# the name is forced to be unique, this should work. # should work.
@all_actions_per_context = @contexts.find_by_sql( @all_actions_per_context = @contexts.find_by_sql(
"SELECT c.name AS name, c.id as id, count(*) AS total "+ "SELECT c.name AS name, c.id as id, count(*) AS total "+
"FROM contexts c, todos t "+ "FROM contexts c, todos t "+
@ -457,6 +462,79 @@ class StatsController < ApplicationController
render :layout => false render :layout => false
end end
def show_selected_actions_from_chart
@page_title = "TRACKS::Action selection"
@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 = "Actions selected from week "
if params['id'] == 'avrt_end'
@page_title += week_from.to_s + " and further"
else
@page_title += week_from.to_s + " - " + week_to.to_s + ""
end
# get all running actions that are visible
@actions_running_time = @actions.find_by_sql([
"SELECT t.id, 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 "+
"WHERE t.user_id=? "+
"AND t.completed_at IS NULL " +
"AND t.show_from IS NULL " +
"AND NOT (p.state='hidden' OR c.hide=?) " +
"ORDER BY t.created_at ASC", @user.id, true]
)
@selected_todo_ids = ""
@count=0
@actions_running_time.each do |r|
days = (@today - r.created_at) / @seconds_per_day
weeks = (days/7).to_i
if params['id'] == 'avrt_end'
if weeks >= week_from
@selected_todo_ids += r.id.to_s+","
@count+=1
end
else
if weeks.between?(week_from, week_to-1)
@selected_todo_ids += r.id.to_s+","
@count+=1
end
end
end
# strip trailing comma
@selected_todo_ids = @selected_todo_ids[0..@selected_todo_ids.length-2]
@actions = @user.todos
# get actions created and completed in the past 12+3 months. +3 for
# running average
@selected_actions = @actions.find(:all, {
:conditions => "id in (" + @selected_todo_ids + ")"
})
render :action => "show_selection_from_chart"
else
# render error
render_failure "404 NOT FOUND. Unknown query selected"
end
end
private private
def init def init
@ -465,6 +543,12 @@ class StatsController < ApplicationController
@contexts = @user.contexts @contexts = @user.contexts
@tags = @user.tags @tags = @user.tags
# 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 # get the current date wih time set to 0:0
now = Time.new now = Time.new
@today = Time.utc(now.year, now.month, now.day, 0,0) @today = Time.utc(now.year, now.month, now.day, 0,0)
@ -535,8 +619,8 @@ class StatsController < ApplicationController
def get_stats_contexts def get_stats_contexts
# get action count per context for TOP 5 # get action count per context for TOP 5
# #
# Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql. Since # Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql.
# the name is forced to be unique, this should work. # Since the name is forced to be unique, this should work.
@actions_per_context = @contexts.find_by_sql( @actions_per_context = @contexts.find_by_sql(
"SELECT c.id AS id, c.name AS name, count(*) AS total "+ "SELECT c.id AS id, c.name AS name, count(*) AS total "+
"FROM contexts c, todos t "+ "FROM contexts c, todos t "+
@ -548,8 +632,8 @@ class StatsController < ApplicationController
# get uncompleted action count per visible context for TOP 5 # get uncompleted action count per visible context for TOP 5
# #
# Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql. Since # Went from GROUP BY c.id to c.id, c.name for compatibility with postgresql.
# the name is forced to be unique, this should work. # Since the name is forced to be unique, this should work.
@running_actions_per_context = @contexts.find_by_sql( @running_actions_per_context = @contexts.find_by_sql(
"SELECT c.id AS id, c.name AS name, count(*) AS total "+ "SELECT c.id AS id, c.name AS name, count(*) AS total "+
"FROM contexts c, todos t "+ "FROM contexts c, todos t "+

View file

@ -103,10 +103,10 @@ module TodosHelper
str += @todo.project.name unless should_suppress_project str += @todo.project.name unless should_suppress_project
str = "(#{str})" unless str.blank? str = "(#{str})" unless str.blank?
else else
if (['project', 'tag'].include?(parent_container_type)) if (['project', 'tag', 'stats'].include?(parent_container_type))
str << item_link_to_context( @todo ) str << item_link_to_context( @todo )
end end
if (['context', 'tickler', 'tag'].include?(parent_container_type)) && @todo.project_id if (['context', 'tickler', 'tag', 'stats'].include?(parent_container_type)) && @todo.project_id
str << item_link_to_project( @todo ) str << item_link_to_project( @todo )
end end
end end
@ -191,6 +191,7 @@ module TodosHelper
def parent_container_type def parent_container_type
return 'tickler' if source_view_is :deferred return 'tickler' if source_view_is :deferred
return 'project' if source_view_is :project return 'project' if source_view_is :project
return 'stats' if source_view_is :stats
return 'context' return 'context'
end end

View file

@ -11,6 +11,9 @@
@sum=0 @sum=0
@count.upto((@max_days/7).to_i) { |i| @sum += @actions_running_time_hash[i] } -%> @count.upto((@max_days/7).to_i) { |i| @sum += @actions_running_time_hash[i] } -%>
<%=@sum%>& <%=@sum%>&
&links=<%
0.upto(@count-1) { |i| %><%= url_for :controller => 'stats', :action => 'show_selected_actions_from_chart', :index => i, :id=> "avrt" %>, <% }
%><%= url_for :controller => 'stats', :action => 'show_selected_actions_from_chart', :index => @count, :id=> "avrt_end" %>&
&line_2=2,0xFF0000& &line_2=2,0xFF0000&
&values_2= &values_2=
<% total=0 <% total=0

View file

@ -0,0 +1,18 @@
<%= render :partial => 'chart', :locals => {:width => @chart_width, :height => @chart_height, :data => url_for(:action => @chart_name)} -%>
<br/>
<p>Click on a bar in the chart to update the actions below. Click <%=link_to "here", {:controller => "stats", :action => "index"} %> to return to the statistics page</p>
<br/>
<div class="container tickler" id="tickler_container">
<h2>
<%= @page_title -%>
</h2>
<div id="tickler" class="items toggle_target">
<div id="tickler-empty-nd" style="display:<%= @selected_actions.empty? ? 'block' : 'none'%>;">
<div class="message"><p>There are no actions selected</p></div>
</div>
<%= render :partial => "todos/todo", :collection => @selected_actions, :locals => { :parent_container_type => 'stats' } %>
</div>
</div>

View file

@ -1,28 +1,39 @@
if @saved if @saved
# show update message
status_message = 'Action saved' status_message = 'Action saved'
status_message += ' to tickler' if @todo.deferred? status_message += ' to tickler' if @todo.deferred?
status_message = 'Added new project / ' + status_message if @new_project_created status_message = 'Added new project / ' + status_message if @new_project_created
status_message = 'Added new context / ' + status_message if @new_context_created status_message = 'Added new context / ' + status_message if @new_context_created
page.notify :notice, status_message, 5.0 page.notify :notice, status_message, 5.0
#update auto completer arrays for context and project
page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}; contextAutoCompleter.changed = true" if @new_context_created page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}; contextAutoCompleter.changed = true" if @new_context_created
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true" if @new_project_created page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true" if @new_project_created
if source_view_is_one_of(:todo, :context) if source_view_is_one_of(:todo, :context)
if @context_changed || @todo.deferred? if @context_changed || @todo.deferred?
page[@todo].remove page[@todo].remove
if (@remaining_in_context == 0) if (@remaining_in_context == 0)
# remove context container from page if empty
source_view do |from| source_view do |from|
from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 } from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 }
from.context { page.show "c#{@original_item_context_id}empty-nd" } from.context { page.show "c#{@original_item_context_id}empty-nd" }
end end
end end
if source_view_is(:todo) && @todo.active? if source_view_is(:todo) && @todo.active?
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}" page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}"
page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
# show all todos in context
page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type } page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
end end
# update badge count
page.replace_html("badge_count", @remaining_in_context) if source_view_is :context page.replace_html("badge_count", @remaining_in_context) if source_view_is :context
page.replace_html("badge_count", @down_count) if source_view_is :todo page.replace_html("badge_count", @down_count) if source_view_is :todo
# show todo in context
page.delay(0.5) do page.delay(0.5) do
page.call "todoItems.ensureContainerHeight", "c#{@original_item_context_id}items" page.call "todoItems.ensureContainerHeight", "c#{@original_item_context_id}items"
if source_view_is(:todo) && @todo.active? if source_view_is(:todo) && @todo.active?
@ -78,6 +89,9 @@ if @saved
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type } page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
page.visual_effect :highlight, dom_id(@todo), :duration => 3 page.visual_effect :highlight, dom_id(@todo), :duration => 3
end end
elsif source_view_is :stats
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :parent_container_type => parent_container_type }
page.visual_effect :highlight, dom_id(@todo), :duration => 3
else else
logger.error "unexpected source_view '#{params[:_source_view]}'" logger.error "unexpected source_view '#{params[:_source_view]}'"
end end