Patch #1671 » version_times_251.diff
app/helpers/versions_helper.rb (kopia robocza) | ||
---|---|---|
32 | 32 |
def render_issue_status_by(version, criteria) |
33 | 33 |
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) |
34 | 34 | |
35 |
h = Hash.new {|k,v| k[v] = [0, 0]}
|
|
36 |
begin
|
|
37 |
# Total issue count
|
|
38 |
Issue.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][0] = s} |
|
39 |
# Open issues count
|
|
40 |
Issue.open.where(:fixed_version_id => version.id).group(criteria).count.each {|c,s| h[c][1] = s}
|
|
41 |
rescue ActiveRecord::RecordNotFound
|
|
42 |
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
|
35 |
#sort them alphabetically by category name
|
|
36 |
metrics = version.get_grouped_metrics(criteria).to_a.sort {|x, y| x[0].to_s <=> y[0].to_s}
|
|
37 |
max = {}
|
|
38 |
|
|
39 |
[{:count => :total}, {:time => :total}].each do |metric_info|
|
|
40 |
metrics_group, total_metric = metric_info.to_a.flatten
|
|
41 |
max[metrics_group] = metrics.map{|item| item[1]}.map {|item| item[metrics_group]}.map {|item| item[total_metric]}.max
|
|
42 |
max[metrics_group] = 1 if max[metrics_group] == 0
|
|
43 | 43 |
end |
44 | 44 |
# Sort with nil keys in last position |
45 |
counts = h.keys.sort {|a,b| a.nil? ? 1 : (b.nil? ? -1 : a <=> b)}.collect {|k| {:group => k, :total => h[k][0], :open => h[k][1], :closed => (h[k][0] - h[k][1])}}
|
|
46 |
max = counts.collect {|c| c[:total]}.max
|
|
47 | ||
48 |
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
|
|
45 |
render :partial => 'issue_counts', :locals => {:version => version,
|
|
46 |
:criteria => criteria, :grouped_metrics => metrics, :max => max,
|
|
47 |
:spent_time_allowed => User.current.allowed_to?(:view_time_entries, @project), |
|
48 |
}
|
|
49 | 49 |
end |
50 |
|
|
51 |
def time_progress(time_info) |
|
52 |
logger.debug "time_info[:spent] = #{time_info[:spent].inspect}" |
|
53 |
logger.debug "time_info[:total] = #{time_info[:total].inspect}" |
|
54 |
if (time_info[:total] != 0) |
|
55 |
time_progress = time_info[:spent].to_f / time_info[:total] |
|
56 |
else |
|
57 |
time_progress = 0 #no total also means there's no spent time |
|
58 |
end |
|
59 |
time_progress |
|
60 |
end |
|
50 | 61 | |
51 | 62 |
def status_by_options_for_select(value) |
52 | 63 |
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) |
app/models/version.rb (kopia robocza) | ||
---|---|---|
82 | 82 |
def spent_hours |
83 | 83 |
@spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f |
84 | 84 |
end |
85 | ||
85 |
|
|
86 |
def calc_remaining_and_total_time |
|
87 |
@remaining_hours = 0 |
|
88 |
@total_hours = 0 |
|
89 |
get_grouped_metrics(:category).to_a.map{|item| item[1]}.map {|item| item[:time]}.each do |times| |
|
90 |
@remaining_hours += times[:remaining] |
|
91 |
@total_hours += times[:total] |
|
92 |
end |
|
93 |
end |
|
94 |
|
|
95 |
def remaining_hours |
|
96 |
return @remaining_hours if @remaining_hours |
|
97 |
calc_remaining_and_total_time |
|
98 |
@remaining_hours |
|
99 |
end |
|
100 |
|
|
101 |
def total_hours |
|
102 |
return @total_hours if @total_hours |
|
103 |
calc_remaining_and_total_time |
|
104 |
@total_hours |
|
105 |
end |
|
106 |
|
|
86 | 107 |
def closed? |
87 | 108 |
status == 'closed' |
88 | 109 |
end |
... | ... | |
163 | 184 |
@closed_issues_count |
164 | 185 |
end |
165 | 186 | |
187 |
def not_estimated_undone_count |
|
188 |
@not_estimated_undone_count ||= Issue.count(:all, :conditions => |
|
189 |
["fixed_version_id = ? AND estimated_hours IS NULL AND " + |
|
190 |
"(closed_on is null)", self.id |
|
191 |
], :include => :status) |
|
192 |
end |
|
193 | ||
194 | ||
166 | 195 |
def wiki_page |
167 | 196 |
if project.wiki && !wiki_page_title.blank? |
168 | 197 |
@wiki_page ||= project.wiki.find_page(wiki_page_title) |
... | ... | |
226 | 255 |
end |
227 | 256 |
end |
228 | 257 | |
258 |
def get_grouped_metrics(criteria) |
|
259 |
condition = issues_version_condition |
|
260 |
|
|
261 |
issues = Issue.find(:all, :include => [:status, criteria], |
|
262 |
:conditions => condition) |
|
263 |
|
|
264 |
spent_times = {} |
|
265 |
TimeEntry.sum(:hours, :group => :issue_id, :include => :issue, |
|
266 |
:conditions => condition).each do |issue_id, hours| |
|
267 |
|
|
268 |
spent_times[issue_id] = hours |
|
269 |
end |
|
270 | ||
271 |
categories_metrics = {} |
|
272 |
issues.each do |issue| |
|
273 |
category = issue.send(criteria) |
|
274 |
categories_metrics[category] ||= {} |
|
275 |
categories_metrics[category][:time] ||= {:estimated => 0, |
|
276 |
:spent => 0, :remaining => 0, :total => 0} |
|
277 |
metrics = categories_metrics[category][:time] |
|
278 |
|
|
279 |
estimated = issue.estimated_hours || 0 |
|
280 |
metrics[:estimated] += estimated |
|
281 |
spent = spent_times[issue.id] || 0 |
|
282 |
metrics[:spent] += spent |
|
283 |
remaining = issue.closed? ? 0 : estimated - spent |
|
284 |
remaining = 0 if remaining < 0 |
|
285 |
metrics[:remaining] += remaining |
|
286 |
metrics[:total] += (remaining + spent) |
|
287 |
|
|
288 |
categories_metrics[category][:count] ||= {:open => 0, :closed => 0, :total => 0} |
|
289 |
metrics = categories_metrics[category][:count] |
|
290 |
metrics[:total] += 1 |
|
291 |
if issue.closed? |
|
292 |
metrics[:closed] += 1 |
|
293 |
else |
|
294 |
metrics[:open] += 1 |
|
295 |
end |
|
296 |
end |
|
297 |
categories_metrics |
|
298 |
end |
|
299 |
|
|
300 |
def issues_version_condition |
|
301 |
["#{Issue.table_name}.fixed_version_id = ?", id] |
|
302 |
end |
|
303 | ||
229 | 304 |
private |
230 | ||
231 | 305 |
def load_issue_counts |
232 | 306 |
unless @issue_count |
233 | 307 |
@open_issues_count = 0 |
app/views/versions/_issue_counts.html.erb (kopia robocza) | ||
---|---|---|
7 | 7 |
:id => 'status_by_select', |
8 | 8 |
:data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %> |
9 | 9 |
</legend> |
10 |
<% if counts.empty? %>
|
|
10 |
<% if grouped_metrics.empty? %>
|
|
11 | 11 |
<p><em><%= l(:label_no_data) %></em></p> |
12 | 12 |
<% else %> |
13 |
<table> |
|
14 |
<% counts.each do |count| %> |
|
15 |
<tr> |
|
16 |
<td style="width:130px; text-align:right;"> |
|
17 |
<% if count[:group] -%> |
|
18 |
<%= link_to(h(count[:group]), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %> |
|
19 |
<% else -%> |
|
20 |
<%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %> |
|
21 |
<% end %> |
|
22 |
</td> |
|
23 |
<td style="width:240px;"> |
|
24 |
<%= progress_bar((count[:closed].to_f / count[:total])*100, |
|
25 |
:legend => "#{count[:closed]}/#{count[:total]}", |
|
26 |
:width => "#{(count[:total].to_f / max * 200).floor}px;") %> |
|
27 |
</td> |
|
28 |
</tr> |
|
13 |
<table class="category_metrics"> |
|
14 |
<% grouped_metrics.each do |metrics_group| |
|
15 |
category, metrics = *metrics_group |
|
16 |
%> |
|
17 |
<% color_class = cycle('odd', 'even')%> |
|
18 |
<tr class="header <%= color_class %>"> |
|
19 |
<td colspan="5"> |
|
20 |
<%= criteria_operator = category ? "=" : "!*" |
|
21 |
link_to category || "[#{l(:text_not_assigned)}]", |
|
22 |
{:controller => 'issues', |
|
23 |
:action => 'index', |
|
24 |
:project_id => version.project, |
|
25 |
:set_filter => 1, |
|
26 |
:fields => ["#{criteria}_id", "fixed_version_id", "status_id"], |
|
27 |
:values => {"#{criteria}_id" => [category], "fixed_version_id" => [version], "status_id" => [1]}, |
|
28 |
:operators => {"#{criteria}_id" => criteria_operator, "fixed_version_id" => "=", "status_id" => "*"} |
|
29 |
} |
|
30 |
%> |
|
31 |
</td> |
|
32 |
</tr> |
|
33 |
<tr class="<%= color_class %>"> |
|
34 |
<td><%= l(:label_issues_count) %> </td> |
|
35 |
<% if spent_time_allowed %> |
|
36 |
<td><%= l(:label_time) %></td> |
|
37 |
<% end %> |
|
38 |
<td class="metric_comment"> |
|
39 |
<span title="<%= l(:field_estimated_hours) %>"> |
|
40 |
<%= l(:label_estimated_time_short) %> |
|
41 |
</span> |
|
42 |
</td> |
|
43 |
<% if spent_time_allowed %> |
|
44 |
<td class="metric_comment"> |
|
45 |
<span title="<%= l(:label_spent_time) %>"> |
|
46 |
<%= l(:label_spent_time_short) %> |
|
47 |
</span> |
|
48 |
</td> |
|
49 |
<td class="metric_comment"> |
|
50 |
<span title="<%= l(:label_remaining_time) %>"> |
|
51 |
<%= l(:label_remaining_time_short) %> |
|
52 |
</span> |
|
53 |
</td> |
|
54 |
<% max_progress_width = 95 %> |
|
55 |
<% else |
|
56 |
max_progress_width = 150 |
|
57 |
end %> |
|
58 |
</tr> |
|
59 |
<tr class="<%= color_class %>"> |
|
60 |
<td class="progress count"> |
|
61 |
<%= count = metrics[:count]; progress_bar((count[:closed].to_f / count[:total])*100, |
|
62 |
:legend => |
|
63 |
"#{count[:closed]}/#{count[:total]}</span>".html_safe, |
|
64 |
:width => "#{(count[:total].to_f / max[:count] * max_progress_width).floor}px;") %> |
|
65 |
</td> |
|
66 |
|
|
67 |
<% |
|
68 |
time = metrics[:time] |
|
69 |
if spent_time_allowed %> |
|
70 |
<td class="progress time"> |
|
71 |
<%= progress_bar(time_progress(time)*100, |
|
72 |
:legend => |
|
73 |
"#{time[:spent].ceil}/#{time[:total].ceil}", |
|
74 |
:width => "#{(time[:total] / max[:time] * max_progress_width).floor}px;") %> |
|
75 |
</td> |
|
76 |
<% end %> |
|
77 |
<% hours = l(:text_hours_short) %> |
|
78 |
<td class="metric_comment"> |
|
79 |
<span title="<%= l(:field_estimated_hours) %>"> |
|
80 |
<%= "#{time[:estimated].ceil}#{hours}" %> |
|
81 |
</span> |
|
82 |
</td> |
|
83 |
<% if spent_time_allowed %> |
|
84 |
<td class="metric_comment"> |
|
85 |
<span title="<%= l(:label_spent_time) %>"> |
|
86 |
<%= "#{time[:spent].ceil}#{hours}" %> |
|
87 |
</span> |
|
88 |
</td> |
|
89 |
<td class="metric_comment"> |
|
90 |
<span title="<%= l(:label_remaining_time) %>"> |
|
91 |
<%= "#{time[:remaining].ceil}#{hours}" %> |
|
92 |
</span> |
|
93 |
</td> |
|
94 |
<% end %> |
|
95 |
</tr> |
|
29 | 96 |
<% end %> |
30 | 97 |
</table> |
31 | 98 |
<% end %> |
app/views/versions/show.html.erb (kopia robocza) | ||
---|---|---|
19 | 19 |
<th><%= l(:field_estimated_hours) %></th> |
20 | 20 |
<td class="total-hours"><%= html_hours(l_hours(@version.estimated_hours)) %></td> |
21 | 21 |
</tr> |
22 |
<% if @version.not_estimated_undone_count > 0 %> |
|
23 |
<tr> |
|
24 |
<th><%= l(:label_not_estimated_and_undone) %></th> |
|
25 |
<td class="not_estimated total-hours"><%= link_to("%s" % |
|
26 |
[@version.not_estimated_undone_count.to_s+" "+l(:label_issue_plural).downcase, @version.not_estimated_undone_count], |
|
27 |
{:controller => 'issues', |
|
28 |
:action => 'index', |
|
29 |
:project_id => @version.project, |
|
30 |
:set_filter => 1, |
|
31 |
:fields => ["estimated_hours", "fixed_version_id", "status_id"], |
|
32 |
:values => {"estimated_hours" => [1], "fixed_version_id" => [@version], "status_id" => [1]}, |
|
33 |
:operators => {"estimated_hours" => "!*", "fixed_version_id" => "=", "status_id" => "o"} |
|
34 |
} |
|
35 |
)%></td> |
|
36 |
</tr> |
|
37 |
<% end %> |
|
38 | ||
22 | 39 |
<% if User.current.allowed_to?(:view_time_entries, @project) %> |
23 | 40 |
<tr> |
24 | 41 |
<th><%= l(:label_spent_time) %></th> |
25 | 42 |
<td class="total-hours"><%= html_hours(l_hours(@version.spent_hours)) %></td> |
26 | 43 |
</tr> |
44 |
<tr> |
|
45 |
<th><%= l(:label_remaining_time) %></th> |
|
46 |
<td class="total-hours"><%= html_hours(l_hours(@version.remaining_hours)) %></td> |
|
47 |
</tr> |
|
48 |
<tr> |
|
49 |
<% title = "#{l(:label_spent_time)} + #{l(:label_remaining_time)}" %> |
|
50 |
<th title="<%= title %>"><%= l(:label_current_total_time) %></th> |
|
51 |
<td class="total-hours" "><%= html_hours(l_hours(@version.total_hours)) %></td> |
|
52 |
</tr> |
|
27 | 53 |
<% end %> |
28 | 54 |
</table> |
29 | 55 |
</fieldset> |
config/locales/en.yml (kopia robocza) | ||
---|---|---|
375 | 375 |
setting_enabled_scm: Enabled SCM |
376 | 376 |
setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" |
377 | 377 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
378 |
label_remaining_time: Remaining time |
|
379 |
label_current_total_time: Total |
|
380 |
label_estimated_time_short: Est. |
|
381 |
label_spent_time_short: Spent |
|
382 |
label_remaining_time_short: Rem. |
|
383 |
label_issues_count: Count |
|
384 |
label_not_estimated_and_undone: Not estimated |
|
385 |
label_time: Time |
|
378 | 386 |
setting_mail_handler_api_key: API key |
379 | 387 |
setting_sequential_project_identifiers: Generate sequential project identifiers |
380 | 388 |
setting_gravatar_enabled: Use Gravatar user icons |
... | ... | |
610 | 618 |
label_open_issues_plural: open |
611 | 619 |
label_closed_issues: closed |
612 | 620 |
label_closed_issues_plural: closed |
621 |
label_done_issues: done |
|
622 |
label_done_issues_plural: done |
|
623 |
label_undone_issues: undone |
|
624 |
label_undone_issues_plural: undone |
|
613 | 625 |
label_x_open_issues_abbr_on_total: |
614 | 626 |
zero: 0 open / %{total} |
615 | 627 |
one: 1 open / %{total} |
config/locales/pl.yml (kopia robocza) | ||
---|---|---|
361 | 361 |
label_closed_issues_plural234: zamknięte |
362 | 362 |
label_closed_issues_plural5: zamknięte |
363 | 363 |
label_closed_issues_plural: zamknięte |
364 |
label_done_issues: done |
|
365 |
label_done_issues_plural: done |
|
366 |
label_undone_issues: undone |
|
367 |
label_undone_issues_plural: undone |
|
364 | 368 |
label_x_open_issues_abbr_on_total: |
365 | 369 |
zero: 0 otwartych / %{total} |
366 | 370 |
one: 1 otwarty / %{total} |
... | ... | |
749 | 753 |
setting_login_required: Wymagane zalogowanie |
750 | 754 |
setting_mail_from: Adres e-mail wysyłki |
751 | 755 |
setting_mail_handler_api_enabled: Uaktywnij usługi sieciowe (WebServices) dla poczty przychodzącej |
756 |
label_remaining_time: Pozostały czas |
|
757 |
label_current_total_time: Suma |
|
758 |
label_estimated_time_short: Szacz. |
|
759 |
label_spent_time_short: Przepr. |
|
760 |
label_remaining_time_short: Pozost. |
|
761 |
label_issues_count: Count |
|
762 |
label_time: Czas |
|
763 |
label_not_estimated_and_undone: Nie oszacowane |
|
764 |
text_hours_short: h |
|
765 |
text_not_assigned: Nie przydzielone |
|
752 | 766 |
setting_mail_handler_api_key: Klucz API |
753 | 767 |
setting_per_page_options: Opcje ilości obiektów na stronie |
754 | 768 |
setting_plain_text_mail: tylko tekst (bez HTML) |
public/stylesheets/application.css (kopia robocza) | ||
---|---|---|
413 | 413 |
div#version-summary fieldset { margin-bottom: 1em; } |
414 | 414 |
div#version-summary fieldset.time-tracking table { width:100%; } |
415 | 415 |
div#version-summary th, div#version-summary td.total-hours { text-align: right; } |
416 |
.not_estimated a { color: red; } |
|
416 | 417 | |
417 | 418 |
table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } |
418 | 419 |
table#time-report tbody tr.subtotal { font-style: italic; color:#777;} |