@@ -84,6 +84,14 module QueriesHelper | |||||
84 | tags |
|
84 | tags | |
85 | end |
|
85 | end | |
86 |
|
86 | |||
|
87 | def available_totalable_columns_tags(query) | |||
|
88 | tags = ''.html_safe | |||
|
89 | query.available_totalable_columns.each do |column| | |||
|
90 | tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline') | |||
|
91 | end | |||
|
92 | tags | |||
|
93 | end | |||
|
94 | ||||
87 | def query_available_inline_columns_options(query) |
|
95 | def query_available_inline_columns_options(query) | |
88 | (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} |
|
96 | (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]} | |
89 | end |
|
97 | end | |
@@ -97,6 +105,16 module QueriesHelper | |||||
97 | render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} |
|
105 | render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name} | |
98 | end |
|
106 | end | |
99 |
|
107 | |||
|
108 | def render_query_totals(query) | |||
|
109 | return unless query.totalable_columns.present? | |||
|
110 | totals = query.totalable_columns.map do |column| | |||
|
111 | label = content_tag('span', "#{column.caption}:") | |||
|
112 | value = content_tag('span', " #{query.total_for(column)}", :class => 'value') | |||
|
113 | content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}") | |||
|
114 | end | |||
|
115 | content_tag('p', totals.join(" ").html_safe, :class => "query-totals") | |||
|
116 | end | |||
|
117 | ||||
100 | def column_header(column) |
|
118 | def column_header(column) | |
101 | column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, |
|
119 | column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption, | |
102 | :default_order => column.default_order) : |
|
120 | :default_order => column.default_order) : | |
@@ -194,12 +212,12 module QueriesHelper | |||||
194 | @query = IssueQuery.new(:name => "_") |
|
212 | @query = IssueQuery.new(:name => "_") | |
195 | @query.project = @project |
|
213 | @query.project = @project | |
196 | @query.build_from_params(params) |
|
214 | @query.build_from_params(params) | |
197 | session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names} |
|
215 | session[:query] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names} | |
198 | else |
|
216 | else | |
199 | # retrieve from session |
|
217 | # retrieve from session | |
200 | @query = nil |
|
218 | @query = nil | |
201 | @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] |
|
219 | @query = IssueQuery.find_by_id(session[:query][:id]) if session[:query][:id] | |
202 | @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) |
|
220 | @query ||= IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names]) | |
203 | @query.project = @project |
|
221 | @query.project = @project | |
204 | end |
|
222 | end | |
205 | end |
|
223 | end | |
@@ -210,7 +228,7 module QueriesHelper | |||||
210 | @query = IssueQuery.find_by_id(session[:query][:id]) |
|
228 | @query = IssueQuery.find_by_id(session[:query][:id]) | |
211 | return unless @query |
|
229 | return unless @query | |
212 | else |
|
230 | else | |
213 | @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names]) |
|
231 | @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names]) | |
214 | end |
|
232 | end | |
215 | if session[:query].has_key?(:project_id) |
|
233 | if session[:query].has_key?(:project_id) | |
216 | @query.project_id = session[:query][:project_id] |
|
234 | @query.project_id = session[:query][:project_id] |
@@ -34,7 +34,7 class IssueQuery < Query | |||||
34 | QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), |
|
34 | QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true), | |
35 | QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), |
|
35 | QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"), | |
36 | QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), |
|
36 | QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"), | |
37 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), |
|
37 | QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true), | |
38 | QueryColumn.new(:total_estimated_hours, |
|
38 | QueryColumn.new(:total_estimated_hours, | |
39 | :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" + |
|
39 | :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" + | |
40 | " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)", |
|
40 | " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)", | |
@@ -268,7 +268,8 class IssueQuery < Query | |||||
268 | @available_columns.insert index, QueryColumn.new(:spent_hours, |
|
268 | @available_columns.insert index, QueryColumn.new(:spent_hours, | |
269 | :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", |
|
269 | :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)", | |
270 | :default_order => 'desc', |
|
270 | :default_order => 'desc', | |
271 | :caption => :label_spent_time |
|
271 | :caption => :label_spent_time, | |
|
272 | :totalable => true | |||
272 | ) |
|
273 | ) | |
273 | @available_columns.insert index+1, QueryColumn.new(:total_spent_hours, |
|
274 | @available_columns.insert index+1, QueryColumn.new(:total_spent_hours, | |
274 | :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" + |
|
275 | :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" + | |
@@ -299,13 +300,44 class IssueQuery < Query | |||||
299 | end |
|
300 | end | |
300 | end |
|
301 | end | |
301 |
|
302 | |||
|
303 | def base_scope | |||
|
304 | Issue.visible.joins(:status, :project).where(statement) | |||
|
305 | end | |||
|
306 | private :base_scope | |||
|
307 | ||||
302 | # Returns the issue count |
|
308 | # Returns the issue count | |
303 | def issue_count |
|
309 | def issue_count | |
304 | Issue.visible.joins(:status, :project).where(statement).count |
|
310 | base_scope.count | |
305 | rescue ::ActiveRecord::StatementInvalid => e |
|
311 | rescue ::ActiveRecord::StatementInvalid => e | |
306 | raise StatementInvalid.new(e.message) |
|
312 | raise StatementInvalid.new(e.message) | |
307 | end |
|
313 | end | |
308 |
|
314 | |||
|
315 | # Returns sum of all the issue's estimated_hours | |||
|
316 | def total_for_estimated_hours | |||
|
317 | base_scope.sum(:estimated_hours) | |||
|
318 | end | |||
|
319 | ||||
|
320 | # Returns sum of all the issue's time entries hours | |||
|
321 | def total_for_spent_hours | |||
|
322 | base_scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours") | |||
|
323 | end | |||
|
324 | ||||
|
325 | def total_for_custom_field(custom_field) | |||
|
326 | base_scope.joins(:custom_values). | |||
|
327 | where(:custom_values => {:custom_field_id => custom_field.id}). | |||
|
328 | where.not(:custom_values => {:value => ''}). | |||
|
329 | sum("CAST(#{CustomValue.table_name}.value AS decimal(30,3))") | |||
|
330 | end | |||
|
331 | private :total_for_custom_field | |||
|
332 | ||||
|
333 | def total_for_float_custom_field(custom_field) | |||
|
334 | total_for_custom_field(custom_field).to_f | |||
|
335 | end | |||
|
336 | ||||
|
337 | def total_for_int_custom_field(custom_field) | |||
|
338 | total_for_custom_field(custom_field).to_i | |||
|
339 | end | |||
|
340 | ||||
309 | # Returns the issue count by group or nil if query is not grouped |
|
341 | # Returns the issue count by group or nil if query is not grouped | |
310 | def issue_count_by_group |
|
342 | def issue_count_by_group | |
311 | r = nil |
|
343 | r = nil |
@@ -16,7 +16,7 | |||||
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |
17 |
|
17 | |||
18 | class QueryColumn |
|
18 | class QueryColumn | |
19 | attr_accessor :name, :sortable, :groupable, :default_order |
|
19 | attr_accessor :name, :sortable, :groupable, :totalable, :default_order | |
20 | include Redmine::I18n |
|
20 | include Redmine::I18n | |
21 |
|
21 | |||
22 | def initialize(name, options={}) |
|
22 | def initialize(name, options={}) | |
@@ -26,6 +26,7 class QueryColumn | |||||
26 | if groupable == true |
|
26 | if groupable == true | |
27 | self.groupable = name.to_s |
|
27 | self.groupable = name.to_s | |
28 | end |
|
28 | end | |
|
29 | self.totalable = options[:totalable] || false | |||
29 | self.default_order = options[:default_order] |
|
30 | self.default_order = options[:default_order] | |
30 | @inline = options.key?(:inline) ? options[:inline] : true |
|
31 | @inline = options.key?(:inline) ? options[:inline] : true | |
31 | @caption_key = options[:caption] || "field_#{name}".to_sym |
|
32 | @caption_key = options[:caption] || "field_#{name}".to_sym | |
@@ -79,6 +80,7 class QueryCustomFieldColumn < QueryColumn | |||||
79 | self.name = "cf_#{custom_field.id}".to_sym |
|
80 | self.name = "cf_#{custom_field.id}".to_sym | |
80 | self.sortable = custom_field.order_statement || false |
|
81 | self.sortable = custom_field.order_statement || false | |
81 | self.groupable = custom_field.group_statement || false |
|
82 | self.groupable = custom_field.group_statement || false | |
|
83 | self.totalable = ['int', 'float'].include?(custom_field.field_format) | |||
82 | @inline = true |
|
84 | @inline = true | |
83 | @cf = custom_field |
|
85 | @cf = custom_field | |
84 | end |
|
86 | end | |
@@ -246,6 +248,7 class Query < ActiveRecord::Base | |||||
246 | end |
|
248 | end | |
247 | self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) |
|
249 | self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by]) | |
248 | self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) |
|
250 | self.column_names = params[:c] || (params[:query] && params[:query][:column_names]) | |
|
251 | self.totalable_names = params[:t] || (params[:query] && params[:query][:totalable_names]) | |||
249 | self |
|
252 | self | |
250 | end |
|
253 | end | |
251 |
|
254 | |||
@@ -454,6 +457,10 class Query < ActiveRecord::Base | |||||
454 | available_columns.reject(&:inline?) |
|
457 | available_columns.reject(&:inline?) | |
455 | end |
|
458 | end | |
456 |
|
459 | |||
|
460 | def available_totalable_columns | |||
|
461 | available_columns.select(&:totalable) | |||
|
462 | end | |||
|
463 | ||||
457 | def default_columns_names |
|
464 | def default_columns_names | |
458 | [] |
|
465 | [] | |
459 | end |
|
466 | end | |
@@ -482,6 +489,22 class Query < ActiveRecord::Base | |||||
482 | column_names.nil? || column_names.empty? |
|
489 | column_names.nil? || column_names.empty? | |
483 | end |
|
490 | end | |
484 |
|
491 | |||
|
492 | def totalable_columns | |||
|
493 | names = totalable_names | |||
|
494 | available_totalable_columns.select {|column| names.include?(column.name)} | |||
|
495 | end | |||
|
496 | ||||
|
497 | def totalable_names=(names) | |||
|
498 | if names | |||
|
499 | names = names.select(&:present?).map {|n| n.is_a?(Symbol) ? n : n.to_sym} | |||
|
500 | end | |||
|
501 | options[:totalable_names] = names | |||
|
502 | end | |||
|
503 | ||||
|
504 | def totalable_names | |||
|
505 | options[:totalable_names] || Setting.issue_list_default_totals.map(&:to_sym) || [] | |||
|
506 | end | |||
|
507 | ||||
485 | def sort_criteria=(arg) |
|
508 | def sort_criteria=(arg) | |
486 | c = [] |
|
509 | c = [] | |
487 | if arg.is_a?(Hash) |
|
510 | if arg.is_a?(Hash) | |
@@ -607,6 +630,22 class Query < ActiveRecord::Base | |||||
607 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil |
|
630 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil | |
608 | end |
|
631 | end | |
609 |
|
632 | |||
|
633 | # Returns the sum of values for the given column | |||
|
634 | def total_for(column) | |||
|
635 | unless column.is_a?(QueryColumn) | |||
|
636 | column = column.to_sym | |||
|
637 | column = available_totalable_columns.detect {|c| c.name == column} | |||
|
638 | end | |||
|
639 | if column.is_a?(QueryCustomFieldColumn) | |||
|
640 | custom_field = column.custom_field | |||
|
641 | send "total_for_#{custom_field.field_format}_custom_field", custom_field | |||
|
642 | else | |||
|
643 | send "total_for_#{column.name}" | |||
|
644 | end | |||
|
645 | rescue ::ActiveRecord::StatementInvalid => e | |||
|
646 | raise StatementInvalid.new(e.message) | |||
|
647 | end | |||
|
648 | ||||
610 | private |
|
649 | private | |
611 |
|
650 | |||
612 | def sql_for_custom_field(field, operator, value, custom_field_id) |
|
651 | def sql_for_custom_field(field, operator, value, custom_field_id) |
@@ -39,6 +39,10 | |||||
39 | <td><%= l(:button_show) %></td> |
|
39 | <td><%= l(:button_show) %></td> | |
40 | <td><%= available_block_columns_tags(@query) %></td> |
|
40 | <td><%= available_block_columns_tags(@query) %></td> | |
41 | </tr> |
|
41 | </tr> | |
|
42 | <tr> | |||
|
43 | <td><%= l(:label_total_plural) %></td> | |||
|
44 | <td><%= available_totalable_columns_tags(@query) %></td> | |||
|
45 | </tr> | |||
42 | </table> |
|
46 | </table> | |
43 | </div> |
|
47 | </div> | |
44 | </fieldset> |
|
48 | </fieldset> | |
@@ -60,6 +64,7 | |||||
60 | <% if @issues.empty? %> |
|
64 | <% if @issues.empty? %> | |
61 | <p class="nodata"><%= l(:label_no_data) %></p> |
|
65 | <p class="nodata"><%= l(:label_no_data) %></p> | |
62 | <% else %> |
|
66 | <% else %> | |
|
67 | <%= render_query_totals(@query) %> | |||
63 | <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> |
|
68 | <%= render :partial => 'issues/list', :locals => {:issues => @issues, :query => @query} %> | |
64 | <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p> |
|
69 | <p class="pagination"><%= pagination_links_full @issue_pages, @issue_count %></p> | |
65 | <% end %> |
|
70 | <% end %> |
@@ -33,6 +33,9 | |||||
33 |
|
33 | |||
34 | <p><label><%= l(:button_show) %></label> |
|
34 | <p><label><%= l(:button_show) %></label> | |
35 | <%= available_block_columns_tags(@query) %></p> |
|
35 | <%= available_block_columns_tags(@query) %></p> | |
|
36 | ||||
|
37 | <p><label><%= l(:label_total_plural) %></label> | |||
|
38 | <%= available_totalable_columns_tags(@query) %></p> | |||
36 | </fieldset> |
|
39 | </fieldset> | |
37 | <% else %> |
|
40 | <% else %> | |
38 | <fieldset><legend><%= l(:label_options) %></legend> |
|
41 | <fieldset><legend><%= l(:label_options) %></legend> |
@@ -38,6 +38,11 | |||||
38 | <%= render_query_columns_selection( |
|
38 | <%= render_query_columns_selection( | |
39 | IssueQuery.new(:column_names => Setting.issue_list_default_columns), |
|
39 | IssueQuery.new(:column_names => Setting.issue_list_default_columns), | |
40 | :name => 'settings[issue_list_default_columns]') %> |
|
40 | :name => 'settings[issue_list_default_columns]') %> | |
|
41 | ||||
|
42 | <p><%= setting_multiselect :issue_list_default_totals, | |||
|
43 | IssueQuery.new(:totalable_names => Setting.issue_list_default_totals).available_totalable_columns.map {|c| [c.caption, c.name.to_s]}, | |||
|
44 | :inline => true, | |||
|
45 | :label => :label_total_plural %></p> | |||
41 | </fieldset> |
|
46 | </fieldset> | |
42 |
|
47 | |||
43 | <%= submit_tag l(:button_save) %> |
|
48 | <%= submit_tag l(:button_save) %> |
@@ -646,6 +646,7 en: | |||||
646 | one: 1 issue |
|
646 | one: 1 issue | |
647 | other: "%{count} issues" |
|
647 | other: "%{count} issues" | |
648 | label_total: Total |
|
648 | label_total: Total | |
|
649 | label_total_plural: Totals | |||
649 | label_total_time: Total time |
|
650 | label_total_time: Total time | |
650 | label_permissions: Permissions |
|
651 | label_permissions: Permissions | |
651 | label_current_status: Current status |
|
652 | label_current_status: Current status |
@@ -666,6 +666,7 fr: | |||||
666 | one: 1 demande |
|
666 | one: 1 demande | |
667 | other: "%{count} demandes" |
|
667 | other: "%{count} demandes" | |
668 | label_total: Total |
|
668 | label_total: Total | |
|
669 | label_total_plural: Totaux | |||
669 | label_total_time: Temps total |
|
670 | label_total_time: Temps total | |
670 | label_permissions: Permissions |
|
671 | label_permissions: Permissions | |
671 | label_current_status: Statut actuel |
|
672 | label_current_status: Statut actuel |
@@ -180,6 +180,9 issue_list_default_columns: | |||||
180 | - subject |
|
180 | - subject | |
181 | - assigned_to |
|
181 | - assigned_to | |
182 | - updated_on |
|
182 | - updated_on | |
|
183 | issue_list_default_totals: | |||
|
184 | serialized: true | |||
|
185 | default: [] | |||
183 | display_subprojects_issues: |
|
186 | display_subprojects_issues: | |
184 | default: 1 |
|
187 | default: 1 | |
185 | issue_done_ratio: |
|
188 | issue_done_ratio: |
@@ -271,6 +271,9 table.query-columns td.buttons { | |||||
271 | text-align: center; |
|
271 | text-align: center; | |
272 | } |
|
272 | } | |
273 | table.query-columns td.buttons input[type=button] {width:35px;} |
|
273 | table.query-columns td.buttons input[type=button] {width:35px;} | |
|
274 | .query-totals {text-align:right; margin-top:-2.3em;} | |||
|
275 | .query-totals>span {margin-left:0.6em;} | |||
|
276 | .query-totals .value {font-weight:bold;} | |||
274 |
|
277 | |||
275 | td.center {text-align:center;} |
|
278 | td.center {text-align:center;} | |
276 |
|
279 |
@@ -945,6 +945,38 class IssuesControllerTest < ActionController::TestCase | |||||
945 | assert_select 'td.parent a[title=?]', parent.subject |
|
945 | assert_select 'td.parent a[title=?]', parent.subject | |
946 | end |
|
946 | end | |
947 |
|
947 | |||
|
948 | def test_index_with_estimated_hours_total | |||
|
949 | Issue.delete_all | |||
|
950 | Issue.generate!(:estimated_hours => 5.5) | |||
|
951 | Issue.generate!(:estimated_hours => 1.1) | |||
|
952 | ||||
|
953 | get :index, :t => %w(estimated_hours) | |||
|
954 | assert_response :success | |||
|
955 | assert_select '.query-totals' | |||
|
956 | assert_select '.total-for-estimated-hours span.value', :text => '6.6' | |||
|
957 | assert_select 'input[type=checkbox][name=?][value=estimated_hours][checked=checked]', 't[]' | |||
|
958 | end | |||
|
959 | ||||
|
960 | def test_index_with_int_custom_field_total | |||
|
961 | field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true) | |||
|
962 | CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2') | |||
|
963 | CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7') | |||
|
964 | ||||
|
965 | get :index, :t => ["cf_#{field.id}"] | |||
|
966 | assert_response :success | |||
|
967 | assert_select '.query-totals' | |||
|
968 | assert_select ".total-for-cf-#{field.id} span.value", :text => '9' | |||
|
969 | end | |||
|
970 | ||||
|
971 | def test_index_totals_should_default_to_settings | |||
|
972 | with_settings :issue_list_default_totals => ['estimated_hours'] do | |||
|
973 | get :index | |||
|
974 | assert_response :success | |||
|
975 | assert_select '.total-for-estimated-hours span.value' | |||
|
976 | assert_select '.query-totals>span', 1 | |||
|
977 | end | |||
|
978 | end | |||
|
979 | ||||
948 | def test_index_send_html_if_query_is_invalid |
|
980 | def test_index_send_html_if_query_is_invalid | |
949 | get :index, :f => ['start_date'], :op => {:start_date => '='} |
|
981 | get :index, :f => ['start_date'], :op => {:start_date => '='} | |
950 | assert_equal 'text/html', @response.content_type |
|
982 | assert_equal 'text/html', @response.content_type |
@@ -1136,6 +1136,82 class QueryTest < ActiveSupport::TestCase | |||||
1136 | assert_equal values.sort, values |
|
1136 | assert_equal values.sort, values | |
1137 | end |
|
1137 | end | |
1138 |
|
1138 | |||
|
1139 | def test_set_totalable_names | |||
|
1140 | q = IssueQuery.new | |||
|
1141 | q.totalable_names = ['estimated_hours', :spent_hours, ''] | |||
|
1142 | assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name) | |||
|
1143 | end | |||
|
1144 | ||||
|
1145 | def test_totalable_columns_should_default_to_settings | |||
|
1146 | with_settings :issue_list_default_totals => ['estimated_hours'] do | |||
|
1147 | q = IssueQuery.new | |||
|
1148 | assert_equal [:estimated_hours], q.totalable_columns.map(&:name) | |||
|
1149 | end | |||
|
1150 | end | |||
|
1151 | ||||
|
1152 | def test_available_totalable_columns_should_include_estimated_hours | |||
|
1153 | q = IssueQuery.new | |||
|
1154 | assert_include :estimated_hours, q.available_totalable_columns.map(&:name) | |||
|
1155 | end | |||
|
1156 | ||||
|
1157 | def test_available_totalable_columns_should_include_spent_hours | |||
|
1158 | User.current = User.find(1) | |||
|
1159 | ||||
|
1160 | q = IssueQuery.new | |||
|
1161 | assert_include :spent_hours, q.available_totalable_columns.map(&:name) | |||
|
1162 | end | |||
|
1163 | ||||
|
1164 | def test_available_totalable_columns_should_include_int_custom_field | |||
|
1165 | field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true) | |||
|
1166 | q = IssueQuery.new | |||
|
1167 | assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name) | |||
|
1168 | end | |||
|
1169 | ||||
|
1170 | def test_available_totalable_columns_should_include_float_custom_field | |||
|
1171 | field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true) | |||
|
1172 | q = IssueQuery.new | |||
|
1173 | assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name) | |||
|
1174 | end | |||
|
1175 | ||||
|
1176 | def test_total_for_estimated_hours | |||
|
1177 | Issue.delete_all | |||
|
1178 | Issue.generate!(:estimated_hours => 5.5) | |||
|
1179 | Issue.generate!(:estimated_hours => 1.1) | |||
|
1180 | Issue.generate! | |||
|
1181 | ||||
|
1182 | q = IssueQuery.new | |||
|
1183 | assert_equal 6.6, q.total_for(:estimated_hours) | |||
|
1184 | end | |||
|
1185 | ||||
|
1186 | def test_total_for_spent_hours | |||
|
1187 | TimeEntry.delete_all | |||
|
1188 | TimeEntry.generate!(:hours => 5.5) | |||
|
1189 | TimeEntry.generate!(:hours => 1.1) | |||
|
1190 | ||||
|
1191 | q = IssueQuery.new | |||
|
1192 | assert_equal 6.6, q.total_for(:spent_hours) | |||
|
1193 | end | |||
|
1194 | ||||
|
1195 | def test_total_for_int_custom_field | |||
|
1196 | field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true) | |||
|
1197 | CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2') | |||
|
1198 | CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7') | |||
|
1199 | CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '') | |||
|
1200 | ||||
|
1201 | q = IssueQuery.new | |||
|
1202 | assert_equal 9, q.total_for("cf_#{field.id}") | |||
|
1203 | end | |||
|
1204 | ||||
|
1205 | def test_total_for_float_custom_field | |||
|
1206 | field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true) | |||
|
1207 | CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3') | |||
|
1208 | CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7') | |||
|
1209 | CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '') | |||
|
1210 | ||||
|
1211 | q = IssueQuery.new | |||
|
1212 | assert_equal 9.3, q.total_for("cf_#{field.id}") | |||
|
1213 | end | |||
|
1214 | ||||
1139 | def test_invalid_query_should_raise_query_statement_invalid_error |
|
1215 | def test_invalid_query_should_raise_query_statement_invalid_error | |
1140 | q = IssueQuery.new |
|
1216 | q = IssueQuery.new | |
1141 | assert_raise Query::StatementInvalid do |
|
1217 | assert_raise Query::StatementInvalid do |
General Comments 0
You need to be logged in to leave comments.
Login now