Patch #1671 » version_times_320.diff
app/helpers/versions_helper.rb (kopia robocza) | ||
---|---|---|
54 | 54 |
def render_issue_status_by(version, criteria) |
55 | 55 |
criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) |
56 | 56 | |
57 |
h = Hash.new {|k,v| k[v] = [0, 0]}
|
|
58 |
begin
|
|
59 |
# Total issue count
|
|
60 |
version.fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s} |
|
61 |
# Open issues count
|
|
62 |
version.fixed_issues.open.group(criteria).count.each {|c,s| h[c][1] = s}
|
|
63 |
rescue ActiveRecord::RecordNotFound
|
|
64 |
# When grouping by an association, Rails throws this exception if there's no result (bug)
|
|
57 |
#sort them alphabetically by category name
|
|
58 |
metrics = version.get_grouped_metrics(criteria).to_a.sort {|x, y| x[0].to_s <=> y[0].to_s}
|
|
59 |
max = {}
|
|
60 |
|
|
61 |
[{:count => :total}, {:time => :total}].each do |metric_info|
|
|
62 |
metrics_group, total_metric = metric_info.to_a.flatten
|
|
63 |
max[metrics_group] = metrics.map{|item| item[1]}.map {|item| item[metrics_group]}.map {|item| item[total_metric]}.max
|
|
64 |
max[metrics_group] = 1 if max[metrics_group] == 0
|
|
65 | 65 |
end |
66 | 66 |
# Sort with nil keys in last position |
67 |
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])}}
|
|
68 |
max = counts.collect {|c| c[:total]}.max
|
|
69 | ||
70 |
render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max}
|
|
67 |
render :partial => 'issue_counts', :locals => {:version => version,
|
|
68 |
:criteria => criteria, :grouped_metrics => metrics, :max => max,
|
|
69 |
:spent_time_allowed => User.current.allowed_to_view_all_time_entries?(@project), |
|
70 |
}
|
|
71 | 71 |
end |
72 |
|
|
73 |
def time_progress(time_info) |
|
74 |
logger.debug "time_info[:spent] = #{time_info[:spent].inspect}" |
|
75 |
logger.debug "time_info[:total] = #{time_info[:total].inspect}" |
|
76 |
if (time_info[:total] != 0) |
|
77 |
time_progress = time_info[:spent].to_f / time_info[:total] |
|
78 |
else |
|
79 |
time_progress = 0 #no total also means there's no spent time |
|
80 |
end |
|
81 |
time_progress |
|
82 |
end |
|
72 | 83 | |
73 | 84 |
def status_by_options_for_select(value) |
74 | 85 |
options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) |
app/models/version.rb (kopia robocza) | ||
---|---|---|
93 | 93 |
def spent_hours |
94 | 94 |
@spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f |
95 | 95 |
end |
96 | ||
96 |
|
|
97 |
def calc_remaining_and_total_time |
|
98 |
@remaining_hours = 0 |
|
99 |
@total_hours = 0 |
|
100 |
get_grouped_metrics(:category).to_a.map{|item| item[1]}.map {|item| item[:time]}.each do |times| |
|
101 |
@remaining_hours += times[:remaining] |
|
102 |
@total_hours += times[:total] |
|
103 |
end |
|
104 |
end |
|
105 |
|
|
106 |
def remaining_hours |
|
107 |
return @remaining_hours if @remaining_hours |
|
108 |
calc_remaining_and_total_time |
|
109 |
@remaining_hours |
|
110 |
end |
|
111 |
|
|
112 |
def total_hours |
|
113 |
return @total_hours if @total_hours |
|
114 |
calc_remaining_and_total_time |
|
115 |
@total_hours |
|
116 |
end |
|
117 |
|
|
97 | 118 |
def closed? |
98 | 119 |
status == 'closed' |
99 | 120 |
end |
... | ... | |
162 | 183 |
@closed_issues_count |
163 | 184 |
end |
164 | 185 | |
186 |
def not_estimated_undone_count |
|
187 |
@not_estimated_undone_count ||= Issue.where("fixed_version_id = ? AND estimated_hours IS NULL AND " + |
|
188 |
"(closed_on is null)", self.id).count |
|
189 |
end |
|
190 | ||
191 | ||
165 | 192 |
def wiki_page |
166 | 193 |
if project.wiki && !wiki_page_title.blank? |
167 | 194 |
@wiki_page ||= project.wiki.find_page(wiki_page_title) |
... | ... | |
234 | 261 |
fixed_issues.empty? && !referenced_by_a_custom_field? |
235 | 262 |
end |
236 | 263 | |
264 |
def get_grouped_metrics(criteria) |
|
265 |
condition = issues_version_condition |
|
266 |
|
|
267 |
issues = Issue.includes(:status, criteria).where(condition) |
|
268 |
|
|
269 |
spent_times = {} |
|
270 |
TimeEntry.group(:issue_id).sum(:hours, :include => :issue, |
|
271 |
:conditions => condition).each do |issue_id, hours| |
|
272 |
|
|
273 |
spent_times[issue_id] = hours |
|
274 |
end |
|
275 | ||
276 |
categories_metrics = {} |
|
277 |
issues.each do |issue| |
|
278 |
category = issue.send(criteria) |
|
279 |
categories_metrics[category] ||= {} |
|
280 |
categories_metrics[category][:time] ||= {:estimated => 0, |
|
281 |
:spent => 0, :remaining => 0, :total => 0} |
|
282 |
metrics = categories_metrics[category][:time] |
|
283 |
|
|
284 |
estimated = issue.estimated_hours || 0 |
|
285 |
metrics[:estimated] += estimated |
|
286 |
spent = spent_times[issue.id] || 0 |
|
287 |
metrics[:spent] += spent |
|
288 |
remaining = issue.closed? ? 0 : estimated - spent |
|
289 |
remaining = 0 if remaining < 0 |
|
290 |
metrics[:remaining] += remaining |
|
291 |
metrics[:total] += (remaining + spent) |
|
292 |
|
|
293 |
categories_metrics[category][:count] ||= {:open => 0, :closed => 0, :total => 0} |
|
294 |
metrics = categories_metrics[category][:count] |
|
295 |
metrics[:total] += 1 |
|
296 |
if issue.closed? |
|
297 |
metrics[:closed] += 1 |
|
298 |
else |
|
299 |
metrics[:open] += 1 |
|
300 |
end |
|
301 |
end |
|
302 |
categories_metrics |
|
303 |
end |
|
304 |
|
|
305 |
def issues_version_condition |
|
306 |
["#{Issue.table_name}.fixed_version_id = ?", id] |
|
307 |
end |
|
308 | ||
237 | 309 |
private |
238 | 310 | |
239 | 311 |
def load_issue_counts |
240 | 312 |
unless @issue_count |
241 | 313 |
@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(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 |
</td> |
|
27 |
</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> |
|
28 | 96 |
<% end %> |
29 | 97 |
</table> |
30 | 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_all_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) | ||
---|---|---|
389 | 389 |
setting_enabled_scm: Enabled SCM |
390 | 390 |
setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" |
391 | 391 |
setting_mail_handler_api_enabled: Enable WS for incoming emails |
392 |
label_remaining_time: Remaining time |
|
393 |
label_current_total_time: Total |
|
394 |
label_estimated_time_short: Est. |
|
395 |
label_spent_time_short: Spent |
|
396 |
label_remaining_time_short: Rem. |
|
397 |
label_issues_count: Count |
|
398 |
label_not_estimated_and_undone: Not estimated |
|
399 |
label_time: Time |
|
400 |
text_hours_short: h |
|
401 |
text_not_assigned: Not Assigned |
|
392 | 402 |
setting_mail_handler_api_key: API key |
393 | 403 |
setting_sequential_project_identifiers: Generate sequential project identifiers |
394 | 404 |
setting_gravatar_enabled: Use Gravatar user icons |
... | ... | |
635 | 645 |
label_open_issues_plural: open |
636 | 646 |
label_closed_issues: closed |
637 | 647 |
label_closed_issues_plural: closed |
648 |
label_done_issues: done |
|
649 |
label_done_issues_plural: done |
|
650 |
label_undone_issues: undone |
|
651 |
label_undone_issues_plural: undone |
|
638 | 652 |
label_x_open_issues_abbr: |
639 | 653 |
zero: 0 open |
640 | 654 |
one: 1 open |
public/stylesheets/application.css (kopia robocza) | ||
---|---|---|
444 | 444 |
div#version-summary fieldset { margin-bottom: 1em; } |
445 | 445 |
div#version-summary fieldset.time-tracking table { width:100%; } |
446 | 446 |
div#version-summary th, div#version-summary td.total-hours { text-align: right; } |
447 |
.not_estimated a { color: red; } |
|
447 | 448 | |
448 | 449 |
table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } |
449 | 450 |
table#time-report tbody tr.subtotal { font-style: italic; color:#777;} |
- « Previous
- 1
- …
- 4
- 5
- 6
- Next »