diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index d803df0cc..06e5f87bc 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -34,6 +34,10 @@ module IssuesHelper def grouped_issue_list(issues, query, issue_count_by_group, &block) previous_group, first = false, true + totals_by_group = query.totalable_columns.inject({}) do |h, column| + h[column] = query.total_by_group_for(column) + h + end issue_list(issues) do |issue, level| group_name = group_count = nil if query.grouped? && ((group = query.group_by_column.value(issue)) != previous_group || first) @@ -44,8 +48,9 @@ module IssuesHelper end group_name ||= "" group_count = issue_count_by_group[group] + group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe end - yield issue, level, group_name, group_count + yield issue, level, group_name, group_count, group_totals previous_group, first = group, false end end diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb index 3c6049acd..f817265dd 100644 --- a/app/helpers/queries_helper.rb +++ b/app/helpers/queries_helper.rb @@ -108,13 +108,17 @@ module QueriesHelper def render_query_totals(query) return unless query.totalable_columns.present? totals = query.totalable_columns.map do |column| - label = content_tag('span', "#{column.caption}:") - value = content_tag('span', " #{query.total_for(column)}", :class => 'value') - content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}") + total_tag(column, query.total_for(column)) end content_tag('p', totals.join(" ").html_safe, :class => "query-totals") end + def total_tag(column, value) + label = content_tag('span', "#{column.caption}:") + value = content_tag('span', format_object(value), :class => 'value') + content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}") + end + def column_header(column) column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, :default_order => column.default_order) : diff --git a/app/models/issue_query.rb b/app/models/issue_query.rb index 36f47ef0e..45aacdffa 100644 --- a/app/models/issue_query.rb +++ b/app/models/issue_query.rb @@ -303,7 +303,6 @@ class IssueQuery < Query def base_scope Issue.visible.joins(:status, :project).where(statement) end - private :base_scope # Returns the issue count def issue_count @@ -312,55 +311,21 @@ class IssueQuery < Query raise StatementInvalid.new(e.message) end + # Returns the issue count by group or nil if query is not grouped + def issue_count_by_group + grouped_query do |scope| + scope.count + end + end + # Returns sum of all the issue's estimated_hours - def total_for_estimated_hours - base_scope.sum(:estimated_hours).to_f.round(2) + def total_for_estimated_hours(scope) + scope.sum(:estimated_hours) end # Returns sum of all the issue's time entries hours - def total_for_spent_hours - base_scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours").to_f.round(2) - end - - def total_for_custom_field(custom_field) - base_scope.joins(:custom_values). - where(:custom_values => {:custom_field_id => custom_field.id}). - where.not(:custom_values => {:value => ''}). - sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))") - end - private :total_for_custom_field - - def total_for_float_custom_field(custom_field) - total_for_custom_field(custom_field).to_f - end - - def total_for_int_custom_field(custom_field) - total_for_custom_field(custom_field).to_i - end - - # Returns the issue count by group or nil if query is not grouped - def issue_count_by_group - r = nil - if grouped? - begin - # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value - r = Issue.visible. - joins(:status, :project). - where(statement). - joins(joins_for_order_statement(group_by_statement)). - group(group_by_statement). - count - rescue ActiveRecord::RecordNotFound - r = {nil => issue_count} - end - c = group_by_column - if c.is_a?(QueryCustomFieldColumn) - r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} - end - end - r - rescue ::ActiveRecord::StatementInvalid => e - raise StatementInvalid.new(e.message) + def total_for_spent_hours(scope) + scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours") end # Returns the issues diff --git a/app/models/query.rb b/app/models/query.rb index ac7b7cd1c..452b6d00c 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -632,21 +632,99 @@ class Query < ActiveRecord::Base # Returns the sum of values for the given column def total_for(column) + total_with_scope(column, base_scope) + end + + # Returns a hash of the sum of the given column for each group, + # or nil if the query is not grouped + def total_by_group_for(column) + grouped_query do |scope| + total_with_scope(column, scope) + end + end + + def totals + totals = totalable_columns.map {|column| [column, total_for(column)]} + yield totals if block_given? + totals + end + + def totals_by_group + totals = totalable_columns.map {|column| [column, total_by_group_for(column)]} + yield totals if block_given? + totals + end + + private + + def grouped_query(&block) + r = nil + if grouped? + begin + # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value + r = yield base_group_scope + rescue ActiveRecord::RecordNotFound + r = {nil => yield(base_scope)} + end + c = group_by_column + if c.is_a?(QueryCustomFieldColumn) + r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h} + end + end + r + rescue ::ActiveRecord::StatementInvalid => e + raise StatementInvalid.new(e.message) + end + + def total_with_scope(column, scope) unless column.is_a?(QueryColumn) column = column.to_sym column = available_totalable_columns.detect {|c| c.name == column} end if column.is_a?(QueryCustomFieldColumn) custom_field = column.custom_field - send "total_for_#{custom_field.field_format}_custom_field", custom_field + send "total_for_#{custom_field.field_format}_custom_field", custom_field, scope else - send "total_for_#{column.name}" + send "total_for_#{column.name}", scope end rescue ::ActiveRecord::StatementInvalid => e raise StatementInvalid.new(e.message) end - private + def base_scope + raise "unimplemented" + end + + def base_group_scope + base_scope. + joins(joins_for_order_statement(group_by_statement)). + group(group_by_statement) + end + + def total_for_float_custom_field(custom_field, scope) + total_for_custom_field(custom_field, scope) {|t| t.to_f.round(2)} + end + + def total_for_int_custom_field(custom_field, scope) + total_for_custom_field(custom_field, scope) {|t| t.to_i} + end + + def total_for_custom_field(custom_field, scope) + total = scope.joins(:custom_values). + where(:custom_values => {:custom_field_id => custom_field.id}). + where.not(:custom_values => {:value => ''}). + sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))") + + if block_given? + if total.is_a?(Hash) + total.keys.each {|k| total[k] = yield total[k]} + else + total = yield total + end + end + + total + end def sql_for_custom_field(field, operator, value, custom_field_id) db_table = CustomValue.table_name diff --git a/app/views/issues/_list.html.erb b/app/views/issues/_list.html.erb index c6c7613b4..57ae6b2bc 100644 --- a/app/views/issues/_list.html.erb +++ b/app/views/issues/_list.html.erb @@ -15,13 +15,13 @@
- <% grouped_issue_list(issues, @query, @issue_count_by_group) do |issue, level, group_name, group_count| -%> + <% grouped_issue_list(issues, @query, @issue_count_by_group) do |issue, level, group_name, group_count, group_totals| -%> <% if group_name %> <% reset_cycle %>