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 »