mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-17 07:40:12 +01:00
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:
parent
52bade4b0d
commit
1abacfbded
5 changed files with 155 additions and 35 deletions
|
|
@ -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 "+
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
18
tracks/app/views/stats/show_selection_from_chart.rhtml
Normal file
18
tracks/app/views/stats/show_selection_from_chart.rhtml
Normal 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>
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue