Patch #1028 ยป cleanup_timelog.diff
app/controllers/timelog_controller.rb (working copy) | ||
---|---|---|
30 | 30 |
include CustomFieldsHelper |
31 | 31 |
|
32 | 32 |
def report |
33 |
@available_criterias = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id",
|
|
33 |
@available_criteria = { 'project' => {:sql => "#{TimeEntry.table_name}.project_id", |
|
34 | 34 |
:klass => Project, |
35 | 35 |
:label => :label_project}, |
36 | 36 |
'version' => {:sql => "#{Issue.table_name}.fixed_version_id", |
... | ... | |
53 | 53 |
:label => :label_issue} |
54 | 54 |
} |
55 | 55 |
|
56 |
# Add list and boolean custom fields as available criterias
|
|
56 |
# Add list and boolean custom fields as available criteria |
|
57 | 57 |
@project.all_custom_fields.select {|cf| %w(list bool).include? cf.field_format }.each do |cf| |
58 |
@available_criterias["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)",
|
|
58 |
@available_criteria["cf_#{cf.id}"] = {:sql => "(SELECT c.value FROM custom_values c WHERE c.custom_field_id = #{cf.id} AND c.customized_type = 'Issue' AND c.customized_id = issues.id)", |
|
59 | 59 |
:format => cf.field_format, |
60 | 60 |
:label => cf.name} |
61 | 61 |
end |
62 | 62 |
|
63 |
@criterias = params[:criterias] || []
|
|
64 |
@criterias = @criterias.select{|criteria| @available_criterias.has_key? criteria}
|
|
65 |
@criterias.uniq!
|
|
66 |
@criterias = @criterias[0,3]
|
|
63 |
@criteria = params[:criteria] || []
|
|
64 |
@criteria = @criteria.select{|criterion| @available_criteria.has_key? criterion}
|
|
65 |
@criteria.uniq! |
|
66 |
@criteria = @criteria[0,3]
|
|
67 | 67 |
|
68 | 68 |
@columns = (params[:columns] && %w(year month week day).include?(params[:columns])) ? params[:columns] : 'month' |
69 | 69 |
|
70 | 70 |
retrieve_date_range |
71 | 71 |
|
72 |
unless @criterias.empty?
|
|
73 |
sql_select = @criterias.collect{|criteria| @available_criterias[criteria][:sql] + " AS " + criteria}.join(', ')
|
|
74 |
sql_group_by = @criterias.collect{|criteria| @available_criterias[criteria][:sql]}.join(', ')
|
|
72 |
unless @criteria.empty? |
|
73 |
sql_select = @criteria.collect{|criterion| @available_criteria[criterion][:sql] + " AS " + criterion}.join(', ')
|
|
74 |
sql_group_by = @criteria.collect{|criterion| @available_criteria[criterion][:sql]}.join(', ')
|
|
75 | 75 |
|
76 | 76 |
sql = "SELECT #{sql_select}, tyear, tmonth, tweek, spent_on, SUM(hours) AS hours" |
77 | 77 |
sql << " FROM #{TimeEntry.table_name}" |
... | ... | |
123 | 123 |
|
124 | 124 |
respond_to do |format| |
125 | 125 |
format.html { render :layout => !request.xhr? } |
126 |
format.csv { send_data(report_to_csv(@criterias, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') }
|
|
126 |
format.csv { send_data(report_to_csv(@criteria, @periods, @hours).read, :type => 'text/csv; header=present', :filename => 'timelog.csv') } |
|
127 | 127 |
end |
128 | 128 |
end |
129 | 129 |
|
app/helpers/timelog_helper.rb (working copy) | ||
---|---|---|
16 | 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 | |
18 | 18 |
module TimelogHelper |
19 |
def select_hours(data, criteria, value)
|
|
20 |
data.select {|row| row[criteria] == value}
|
|
19 |
def select_hours(data, criterion, value)
|
|
20 |
data.select {|row| row[criterion] == value}
|
|
21 | 21 |
end |
22 | 22 |
|
23 | 23 |
def sum_hours(data) |
... | ... | |
77 | 77 |
export |
78 | 78 |
end |
79 | 79 |
|
80 |
def format_criteria_value(criteria, value)
|
|
81 |
value.blank? ? l(:label_none) : ((k = @available_criterias[criteria][:klass]) ? k.find_by_id(value.to_i) : format_value(value, @available_criterias[criteria][:format]))
|
|
80 |
def format_criterion_value(criterion, value)
|
|
81 |
value.blank? ? l(:label_none) : ((k = @available_criteria[criterion][:klass]) ? k.find_by_id(value.to_i) : format_value(value, @available_criteria[criterion][:format]))
|
|
82 | 82 |
end |
83 | 83 |
|
84 |
def report_to_csv(criterias, periods, hours)
|
|
84 |
def report_to_csv(criteria, periods, hours) |
|
85 | 85 |
export = StringIO.new |
86 | 86 |
CSV::Writer.generate(export, l(:general_csv_separator)) do |csv| |
87 | 87 |
# Column headers |
88 |
headers = criterias.collect {|criteria| l(@available_criterias[criteria][:label]) }
|
|
88 |
headers = criteria.collect {|criterion| l(@available_criteria[criterion][:label]) }
|
|
89 | 89 |
headers += periods |
90 | 90 |
headers << l(:label_total) |
91 | 91 |
csv << headers.collect {|c| to_utf8(c) } |
92 | 92 |
# Content |
93 |
report_criteria_to_csv(csv, criterias, periods, hours)
|
|
93 |
report_criteria_to_csv(csv, criteria, periods, hours) |
|
94 | 94 |
# Total row |
95 |
row = [ l(:label_total) ] + [''] * (criterias.size - 1)
|
|
95 |
row = [ l(:label_total) ] + [''] * (criteria.size - 1) |
|
96 | 96 |
total = 0 |
97 | 97 |
periods.each do |period| |
98 | 98 |
sum = sum_hours(select_hours(hours, @columns, period.to_s)) |
... | ... | |
106 | 106 |
export |
107 | 107 |
end |
108 | 108 |
|
109 |
def report_criteria_to_csv(csv, criterias, periods, hours, level=0)
|
|
110 |
hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value|
|
|
111 |
hours_for_value = select_hours(hours, criterias[level], value)
|
|
109 |
def report_criteria_to_csv(csv, criteria, periods, hours, level=0) |
|
110 |
hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| |
|
111 |
hours_for_value = select_hours(hours, criteria[level], value) |
|
112 | 112 |
next if hours_for_value.empty? |
113 | 113 |
row = [''] * level |
114 |
row << to_utf8(format_criteria_value(criterias[level], value))
|
|
115 |
row += [''] * (criterias.length - level - 1)
|
|
114 |
row << to_utf8(format_criterion_value(criteria[level], value))
|
|
115 |
row += [''] * (criteria.length - level - 1) |
|
116 | 116 |
total = 0 |
117 | 117 |
periods.each do |period| |
118 | 118 |
sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)) |
... | ... | |
122 | 122 |
row << "%.2f" %total |
123 | 123 |
csv << row |
124 | 124 |
|
125 |
if criterias.length > level + 1
|
|
126 |
report_criteria_to_csv(csv, criterias, periods, hours_for_value, level + 1)
|
|
125 |
if criteria.length > level + 1 |
|
126 |
report_criteria_to_csv(csv, criteria, periods, hours_for_value, level + 1) |
|
127 | 127 |
end |
128 | 128 |
end |
129 | 129 |
end |
app/views/timelog/report.rhtml (working copy) | ||
---|---|---|
5 | 5 |
<h2><%= l(:label_spent_time) %></h2> |
6 | 6 | |
7 | 7 |
<% form_remote_tag(:url => {}, :update => 'content') do %> |
8 |
<% @criterias.each do |criteria| %>
|
|
9 |
<%= hidden_field_tag 'criterias[]', criteria, :id => nil %>
|
|
8 |
<% @criteria.each do |criterion| %>
|
|
9 |
<%= hidden_field_tag 'criteria[]', criterion, :id => nil %>
|
|
10 | 10 |
<% end %> |
11 | 11 |
<%= hidden_field_tag 'project_id', params[:project_id] %> |
12 | 12 |
<%= render :partial => 'date_range' %> |
... | ... | |
17 | 17 |
[l(:label_day_plural).titleize, 'day']], @columns), |
18 | 18 |
:onchange => "this.form.onsubmit();" %> |
19 | 19 | |
20 |
<%= l(:button_add) %>: <%= select_tag('criterias[]', options_for_select([[]] + (@available_criterias.keys - @criterias).collect{|k| [l(@available_criterias[k][:label]), k]}),
|
|
20 |
<%= l(:button_add) %>: <%= select_tag('criteria[]', options_for_select([[]] + (@available_criteria.keys - @criteria).collect{|k| [l(@available_criteria[k][:label]), k]}),
|
|
21 | 21 |
:onchange => "this.form.onsubmit();", |
22 | 22 |
:style => 'width: 200px', |
23 | 23 |
:id => nil, |
24 |
:disabled => (@criterias.length >= 3)) %>
|
|
24 |
:disabled => (@criteria.length >= 3)) %> |
|
25 | 25 |
<%= link_to_remote l(:button_clear), {:url => {:project_id => @project, :period_type => params[:period_type], :period => params[:period], :from => @from, :to => @to, :columns => @columns}, |
26 | 26 |
:update => 'content' |
27 | 27 |
}, :class => 'icon icon-reload' %></p> |
28 | 28 |
<% end %> |
29 | 29 | |
30 |
<% unless @criterias.empty? %>
|
|
30 |
<% unless @criteria.empty? %> |
|
31 | 31 |
<div class="total-hours"> |
32 | 32 |
<p><%= l(:label_total) %>: <%= html_hours(lwr(:label_f_hour, @total_hours)) %></p> |
33 | 33 |
</div> |
... | ... | |
36 | 36 |
<table class="list" id="time-report"> |
37 | 37 |
<thead> |
38 | 38 |
<tr> |
39 |
<% @criterias.each do |criteria| %>
|
|
40 |
<th><%= l(@available_criterias[criteria][:label]) %></th>
|
|
39 |
<% @criteria.each do |criterion| %>
|
|
40 |
<th><%= l(@available_criteria[criterion][:label]) %></th>
|
|
41 | 41 |
<% end %> |
42 | 42 |
<% columns_width = (40 / (@periods.length+1)).to_i %> |
43 | 43 |
<% @periods.each do |period| %> |
... | ... | |
47 | 47 |
</tr> |
48 | 48 |
</thead> |
49 | 49 |
<tbody> |
50 |
<%= render :partial => 'report_criteria', :locals => {:criterias => @criterias, :hours => @hours, :level => 0} %>
|
|
50 |
<%= render :partial => 'report_criteria', :locals => {:criteria => @criteria, :hours => @hours, :level => 0} %>
|
|
51 | 51 |
<tr class="total"> |
52 | 52 |
<td><%= l(:label_total) %></td> |
53 |
<%= '<td></td>' * (@criterias.size - 1) %>
|
|
53 |
<%= '<td></td>' * (@criteria.size - 1) %> |
|
54 | 54 |
<% total = 0 -%> |
55 | 55 |
<% @periods.each do |period| -%> |
56 | 56 |
<% sum = sum_hours(select_hours(@hours, @columns, period.to_s)); total += sum -%> |
app/views/timelog/_report_criteria.rhtml (working copy) | ||
---|---|---|
1 |
<% @hours.collect {|h| h[criterias[level]].to_s}.uniq.each do |value| %>
|
|
2 |
<% hours_for_value = select_hours(hours, criterias[level], value) -%>
|
|
1 |
<% @hours.collect {|h| h[criteria[level]].to_s}.uniq.each do |value| %> |
|
2 |
<% hours_for_value = select_hours(hours, criteria[level], value) -%> |
|
3 | 3 |
<% next if hours_for_value.empty? -%> |
4 |
<tr class="<%= cycle('odd', 'even') %> <%= 'last-level' unless criterias.length > level+1 %>">
|
|
4 |
<tr class="<%= cycle('odd', 'even') %> <%= 'last-level' unless criteria.length > level+1 %>"> |
|
5 | 5 |
<%= '<td></td>' * level %> |
6 |
<td><%= format_criteria_value(criterias[level], value) %></td>
|
|
7 |
<%= '<td></td>' * (criterias.length - level - 1) -%>
|
|
6 |
<td><%= format_criterion_value(criteria[level], value) %></td>
|
|
7 |
<%= '<td></td>' * (criteria.length - level - 1) -%> |
|
8 | 8 |
<% total = 0 -%> |
9 | 9 |
<% @periods.each do |period| -%> |
10 | 10 |
<% sum = sum_hours(select_hours(hours_for_value, @columns, period.to_s)); total += sum -%> |
... | ... | |
12 | 12 |
<% end -%> |
13 | 13 |
<td class="hours"><%= html_hours("%.2f" % total) if total > 0 %></td> |
14 | 14 |
</tr> |
15 |
<% if criterias.length > level+1 -%>
|
|
16 |
<%= render(:partial => 'report_criteria', :locals => {:criterias => criterias, :hours => hours_for_value, :level => (level + 1)}) %>
|
|
15 |
<% if criteria.length > level+1 -%> |
|
16 |
<%= render(:partial => 'report_criteria', :locals => {:criteria => criteria, :hours => hours_for_value, :level => (level + 1)}) %>
|
|
17 | 17 |
<% end -%> |
18 | 18 | |
19 | 19 |
<% end %> |
test/functional/timelog_controller_test.rb (working copy) | ||
---|---|---|
80 | 80 |
end |
81 | 81 | |
82 | 82 |
def test_report_all_time |
83 |
get :report, :project_id => 1, :criterias => ['project', 'issue']
|
|
83 |
get :report, :project_id => 1, :criteria => ['project', 'issue'] |
|
84 | 84 |
assert_response :success |
85 | 85 |
assert_template 'report' |
86 | 86 |
assert_not_nil assigns(:total_hours) |
... | ... | |
88 | 88 |
end |
89 | 89 | |
90 | 90 |
def test_report_all_time_by_day |
91 |
get :report, :project_id => 1, :criterias => ['project', 'issue'], :columns => 'day'
|
|
91 |
get :report, :project_id => 1, :criteria => ['project', 'issue'], :columns => 'day' |
|
92 | 92 |
assert_response :success |
93 | 93 |
assert_template 'report' |
94 | 94 |
assert_not_nil assigns(:total_hours) |
... | ... | |
96 | 96 |
assert_tag :tag => 'th', :content => '2007-03-12' |
97 | 97 |
end |
98 | 98 |
|
99 |
def test_report_one_criteria
|
|
100 |
get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criterias => ['project']
|
|
99 |
def test_report_one_criterion
|
|
100 |
get :report, :project_id => 1, :columns => 'week', :from => "2007-04-01", :to => "2007-04-30", :criteria => ['project'] |
|
101 | 101 |
assert_response :success |
102 | 102 |
assert_template 'report' |
103 | 103 |
assert_not_nil assigns(:total_hours) |
104 | 104 |
assert_equal "8.65", "%.2f" % assigns(:total_hours) |
105 | 105 |
end |
106 | 106 |
|
107 |
def test_report_two_criterias
|
|
108 |
get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criterias => ["member", "activity"]
|
|
107 |
def test_report_two_criteria |
|
108 |
get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-12-31", :criteria => ["member", "activity"] |
|
109 | 109 |
assert_response :success |
110 | 110 |
assert_template 'report' |
111 | 111 |
assert_not_nil assigns(:total_hours) |
112 | 112 |
assert_equal "162.90", "%.2f" % assigns(:total_hours) |
113 | 113 |
end |
114 | 114 |
|
115 |
def test_report_custom_field_criteria
|
|
116 |
get :report, :project_id => 1, :criterias => ['project', 'cf_1']
|
|
115 |
def test_report_custom_field_criterion
|
|
116 |
get :report, :project_id => 1, :criteria => ['project', 'cf_1'] |
|
117 | 117 |
assert_response :success |
118 | 118 |
assert_template 'report' |
119 | 119 |
assert_not_nil assigns(:total_hours) |
120 |
assert_not_nil assigns(:criterias)
|
|
121 |
assert_equal 2, assigns(:criterias).size
|
|
120 |
assert_not_nil assigns(:criteria) |
|
121 |
assert_equal 2, assigns(:criteria).size |
|
122 | 122 |
assert_equal "162.90", "%.2f" % assigns(:total_hours) |
123 | 123 |
# Custom field column |
124 | 124 |
assert_tag :tag => 'th', :content => 'Database' |
... | ... | |
129 | 129 |
:content => '1' }} |
130 | 130 |
end |
131 | 131 |
|
132 |
def test_report_one_criteria_no_result
|
|
133 |
get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criterias => ['project']
|
|
132 |
def test_report_one_criterion_no_result
|
|
133 |
get :report, :project_id => 1, :columns => 'week', :from => "1998-04-01", :to => "1998-04-30", :criteria => ['project'] |
|
134 | 134 |
assert_response :success |
135 | 135 |
assert_template 'report' |
136 | 136 |
assert_not_nil assigns(:total_hours) |
... | ... | |
138 | 138 |
end |
139 | 139 |
|
140 | 140 |
def test_report_csv_export |
141 |
get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criterias => ["project", "member", "activity"], :format => "csv"
|
|
141 |
get :report, :project_id => 1, :columns => 'month', :from => "2007-01-01", :to => "2007-06-30", :criteria => ["project", "member", "activity"], :format => "csv" |
|
142 | 142 |
assert_response :success |
143 | 143 |
assert_equal 'text/csv', @response.content_type |
144 | 144 |
lines = @response.body.chomp.split("\n") |