Index: app/helpers/versions_helper.rb =================================================================== --- app/helpers/versions_helper.rb (wersja 13103) +++ app/helpers/versions_helper.rb (kopia robocza) @@ -54,21 +54,32 @@ def render_issue_status_by(version, criteria) criteria = 'tracker' unless STATUS_BY_CRITERIAS.include?(criteria) - h = Hash.new {|k,v| k[v] = [0, 0]} - begin - # Total issue count - version.fixed_issues.group(criteria).count.each {|c,s| h[c][0] = s} - # Open issues count - version.fixed_issues.open.group(criteria).count.each {|c,s| h[c][1] = s} - rescue ActiveRecord::RecordNotFound - # When grouping by an association, Rails throws this exception if there's no result (bug) + #sort them alphabetically by category name + metrics = version.get_grouped_metrics(criteria).to_a.sort {|x, y| x[0].to_s <=> y[0].to_s} + max = {} + + [{:count => :total}, {:time => :total}].each do |metric_info| + metrics_group, total_metric = metric_info.to_a.flatten + max[metrics_group] = metrics.map{|item| item[1]}.map {|item| item[metrics_group]}.map {|item| item[total_metric]}.max + max[metrics_group] = 1 if max[metrics_group] == 0 end # Sort with nil keys in last position - 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])}} - max = counts.collect {|c| c[:total]}.max - - render :partial => 'issue_counts', :locals => {:version => version, :criteria => criteria, :counts => counts, :max => max} + render :partial => 'issue_counts', :locals => {:version => version, + :criteria => criteria, :grouped_metrics => metrics, :max => max, + :spent_time_allowed => User.current.allowed_to_view_all_time_entries?(@project), + } end + + def time_progress(time_info) + logger.debug "time_info[:spent] = #{time_info[:spent].inspect}" + logger.debug "time_info[:total] = #{time_info[:total].inspect}" + if (time_info[:total] != 0) + time_progress = time_info[:spent].to_f / time_info[:total] + else + time_progress = 0 #no total also means there's no spent time + end + time_progress + end def status_by_options_for_select(value) options_for_select(STATUS_BY_CRITERIAS.collect {|criteria| [l("field_#{criteria}".to_sym), criteria]}, value) Index: app/models/version.rb =================================================================== --- app/models/version.rb (wersja 13103) +++ app/models/version.rb (kopia robocza) @@ -93,7 +93,28 @@ def spent_hours @spent_hours ||= TimeEntry.joins(:issue).where("#{Issue.table_name}.fixed_version_id = ?", id).sum(:hours).to_f end - + + def calc_remaining_and_total_time + @remaining_hours = 0 + @total_hours = 0 + get_grouped_metrics(:category).to_a.map{|item| item[1]}.map {|item| item[:time]}.each do |times| + @remaining_hours += times[:remaining] + @total_hours += times[:total] + end + end + + def remaining_hours + return @remaining_hours if @remaining_hours + calc_remaining_and_total_time + @remaining_hours + end + + def total_hours + return @total_hours if @total_hours + calc_remaining_and_total_time + @total_hours + end + def closed? status == 'closed' end @@ -162,6 +183,12 @@ @closed_issues_count end + def not_estimated_undone_count + @not_estimated_undone_count ||= Issue.where("fixed_version_id = ? AND estimated_hours IS NULL AND " + + "(closed_on is null)", self.id).count + end + + def wiki_page if project.wiki && !wiki_page_title.blank? @wiki_page ||= project.wiki.find_page(wiki_page_title) @@ -234,8 +261,53 @@ fixed_issues.empty? && !referenced_by_a_custom_field? end + def get_grouped_metrics(criteria) + condition = issues_version_condition + + issues = Issue.includes(:status, criteria).where(condition) + + spent_times = {} + TimeEntry.group(:issue_id).sum(:hours, :include => :issue, + :conditions => condition).each do |issue_id, hours| + + spent_times[issue_id] = hours + end + + categories_metrics = {} + issues.each do |issue| + category = issue.send(criteria) + categories_metrics[category] ||= {} + categories_metrics[category][:time] ||= {:estimated => 0, + :spent => 0, :remaining => 0, :total => 0} + metrics = categories_metrics[category][:time] + + estimated = issue.estimated_hours || 0 + metrics[:estimated] += estimated + spent = spent_times[issue.id] || 0 + metrics[:spent] += spent + remaining = issue.closed? ? 0 : estimated - spent + remaining = 0 if remaining < 0 + metrics[:remaining] += remaining + metrics[:total] += (remaining + spent) + + categories_metrics[category][:count] ||= {:open => 0, :closed => 0, :total => 0} + metrics = categories_metrics[category][:count] + metrics[:total] += 1 + if issue.closed? + metrics[:closed] += 1 + else + metrics[:open] += 1 + end + end + categories_metrics + end + + def issues_version_condition + ["#{Issue.table_name}.fixed_version_id = ?", id] + end + private def load_issue_counts unless @issue_count @open_issues_count = 0 Index: app/views/versions/_issue_counts.html.erb =================================================================== --- app/views/versions/_issue_counts.html.erb (wersja 13103) +++ app/views/versions/_issue_counts.html.erb (kopia robocza) @@ -7,24 +7,92 @@ :id => 'status_by_select', :data => {:remote => true, :method => 'post', :url => status_by_version_path(version)})).html_safe %> -<% if counts.empty? %> +<% if grouped_metrics.empty? %>

<%= l(:label_no_data) %>

<% else %> - - <% counts.each do |count| %> - - - - +
- <% if count[:group] -%> - <%= link_to(count[:group], project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => count[:group])) %> - <% else -%> - <%= link_to(l(:label_none), project_issues_path(version.project, :set_filter => 1, :status_id => '*', :fixed_version_id => version, "#{criteria}_id" => "!*")) %> - <% end %> - - <%= progress_bar((count[:closed].to_f / count[:total])*100, - :legend => "#{count[:closed]}/#{count[:total]}") %> -
+ <% grouped_metrics.each do |metrics_group| + category, metrics = *metrics_group + %> + <% color_class = cycle('odd', 'even')%> + + + + + + <% if spent_time_allowed %> + + <% end %> + + <% if spent_time_allowed %> + + + <% max_progress_width = 95 %> + <% else + max_progress_width = 150 + end %> + + + + + <% + time = metrics[:time] + if spent_time_allowed %> + + <% end %> + <% hours = l(:text_hours_short) %> + + <% if spent_time_allowed %> + + + <% end %> + <% end %>
+ <%= criteria_operator = category ? "=" : "!*" + link_to category || "[#{l(:text_not_assigned)}]", + {:controller => 'issues', + :action => 'index', + :project_id => version.project, + :set_filter => 1, + :fields => ["#{criteria}_id", "fixed_version_id", "status_id"], + :values => {"#{criteria}_id" => [category], "fixed_version_id" => [version], "status_id" => [1]}, + :operators => {"#{criteria}_id" => criteria_operator, "fixed_version_id" => "=", "status_id" => "*"} + } + %> +
<%= l(:label_issues_count) %> <%= l(:label_time) %> + + <%= l(:label_estimated_time_short) %> + + + + <%= l(:label_spent_time_short) %> + + + + <%= l(:label_remaining_time_short) %> + +
+ <%= count = metrics[:count]; progress_bar((count[:closed].to_f / count[:total])*100, + :legend => + "#{count[:closed]}/#{count[:total]}".html_safe, + :width => "#{(count[:total].to_f / max[:count] * max_progress_width).floor}px;") %> + + <%= progress_bar(time_progress(time)*100, + :legend => + "#{time[:spent].ceil}/#{time[:total].ceil}", + :width => "#{(time[:total] / max[:time] * max_progress_width).floor}px;") %> + + + <%= "#{time[:estimated].ceil}#{hours}" %> + + + + <%= "#{time[:spent].ceil}#{hours}" %> + + + + <%= "#{time[:remaining].ceil}#{hours}" %> + +
<% end %> Index: app/views/versions/show.html.erb =================================================================== --- app/views/versions/show.html.erb (wersja 13103) +++ app/views/versions/show.html.erb (kopia robocza) @@ -19,11 +19,37 @@ <%= l(:field_estimated_hours) %> <%= html_hours(l_hours(@version.estimated_hours)) %> +<% if @version.not_estimated_undone_count > 0 %> + + <%= l(:label_not_estimated_and_undone) %> + <%= link_to("%s" % + [@version.not_estimated_undone_count.to_s+" "+l(:label_issue_plural).downcase, @version.not_estimated_undone_count], + {:controller => 'issues', + :action => 'index', + :project_id => @version.project, + :set_filter => 1, + :fields => ["estimated_hours", "fixed_version_id", "status_id"], + :values => {"estimated_hours" => [1], "fixed_version_id" => [@version], "status_id" => [1]}, + :operators => {"estimated_hours" => "!*", "fixed_version_id" => "=", "status_id" => "o"} + } +)%> + +<% end %> + <% if User.current.allowed_to_view_all_time_entries?(@project) %> <%= l(:label_spent_time) %> <%= html_hours(l_hours(@version.spent_hours)) %> + + <%= l(:label_remaining_time) %> + <%= html_hours(l_hours(@version.remaining_hours)) %> + + + <% title = "#{l(:label_spent_time)} + #{l(:label_remaining_time)}" %> + <%= l(:label_current_total_time) %> + <%= html_hours(l_hours(@version.total_hours)) %> + <% end %> Index: config/locales/en.yml =================================================================== --- config/locales/en.yml (wersja 13103) +++ config/locales/en.yml (kopia robocza) @@ -389,6 +389,16 @@ setting_enabled_scm: Enabled SCM setting_mail_handler_body_delimiters: "Truncate emails after one of these lines" setting_mail_handler_api_enabled: Enable WS for incoming emails + label_remaining_time: Remaining time + label_current_total_time: Total + label_estimated_time_short: Est. + label_spent_time_short: Spent + label_remaining_time_short: Rem. + label_issues_count: Count + label_not_estimated_and_undone: Not estimated + label_time: Time + text_hours_short: h + text_not_assigned: Not Assigned setting_mail_handler_api_key: API key setting_sequential_project_identifiers: Generate sequential project identifiers setting_gravatar_enabled: Use Gravatar user icons @@ -635,6 +645,10 @@ label_open_issues_plural: open label_closed_issues: closed label_closed_issues_plural: closed + label_done_issues: done + label_done_issues_plural: done + label_undone_issues: undone + label_undone_issues_plural: undone label_x_open_issues_abbr: zero: 0 open one: 1 open Index: public/stylesheets/application.css =================================================================== --- public/stylesheets/application.css (wersja 13103) +++ public/stylesheets/application.css (kopia robocza) @@ -444,6 +444,7 @@ div#version-summary fieldset { margin-bottom: 1em; } div#version-summary fieldset.time-tracking table { width:100%; } div#version-summary th, div#version-summary td.total-hours { text-align: right; } +.not_estimated a { color: red; } table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } table#time-report tbody tr.subtotal { font-style: italic; color:#777;}