##// END OF EJS Templates
Ability to group and sort the issue list by user/version custom field (#9419)....
Jean-Philippe Lang -
r9890:faab8678d440
parent child
Show More
@@ -146,6 +146,8 class CustomField < ActiveRecord::Base
146 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
146 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
147 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
147 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
148 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
148 " AND cv_sort.custom_field_id=#{id} AND cv_sort.value <> '' AND cv_sort.value IS NOT NULL LIMIT 1)"
149 when 'user', 'version'
150 value_class.fields_for_order_statement(value_join_alias)
149 else
151 else
150 nil
152 nil
151 end
153 end
@@ -158,15 +160,57 class CustomField < ActiveRecord::Base
158 case field_format
160 case field_format
159 when 'list', 'date', 'bool', 'int'
161 when 'list', 'date', 'bool', 'int'
160 order_statement
162 order_statement
163 when 'user', 'version'
164 "COALESCE((SELECT cv_sort.value FROM #{CustomValue.table_name} cv_sort" +
165 " WHERE cv_sort.customized_type='#{self.class.customized_class.base_class.name}'" +
166 " AND cv_sort.customized_id=#{self.class.customized_class.table_name}.id" +
167 " AND cv_sort.custom_field_id=#{id} LIMIT 1), '')"
168 else
169 nil
170 end
171 end
172
173 def join_for_order_statement
174 case field_format
175 when 'user', 'version'
176 "LEFT OUTER JOIN #{CustomValue.table_name} #{join_alias}" +
177 " ON #{join_alias}.customized_type = '#{self.class.customized_class.base_class.name}'" +
178 " AND #{join_alias}.customized_id = #{self.class.customized_class.table_name}.id" +
179 " AND #{join_alias}.custom_field_id = #{id}" +
180 " AND #{join_alias}.value <> ''" +
181 " AND #{join_alias}.id = (SELECT max(#{join_alias}_2.id) FROM #{CustomValue.table_name} #{join_alias}_2" +
182 " WHERE #{join_alias}_2.customized_type = #{join_alias}.customized_type" +
183 " AND #{join_alias}_2.customized_id = #{join_alias}.customized_id" +
184 " AND #{join_alias}_2.custom_field_id = #{join_alias}.custom_field_id)" +
185 " LEFT OUTER JOIN #{value_class.table_name} #{value_join_alias}" +
186 " ON CAST(#{join_alias}.value as decimal(60,0)) = #{value_join_alias}.id"
161 else
187 else
162 nil
188 nil
163 end
189 end
164 end
190 end
165
191
192 def join_alias
193 "cf_#{id}"
194 end
195
196 def value_join_alias
197 join_alias + "_" + field_format
198 end
199
166 def <=>(field)
200 def <=>(field)
167 position <=> field.position
201 position <=> field.position
168 end
202 end
169
203
204 # Returns the class that values represent
205 def value_class
206 case field_format
207 when 'user', 'version'
208 field_format.classify.constantize
209 else
210 nil
211 end
212 end
213
170 def self.customized_class
214 def self.customized_class
171 self.name =~ /^(.+)CustomField$/
215 self.name =~ /^(.+)CustomField$/
172 begin; $1.constantize; rescue nil; end
216 begin; $1.constantize; rescue nil; end
@@ -604,13 +604,11 class Query < ActiveRecord::Base
604 def issues(options={})
604 def issues(options={})
605 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
605 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
606 order_option = nil if order_option.blank?
606 order_option = nil if order_option.blank?
607
608 joins = (order_option && order_option.include?('authors')) ? "LEFT OUTER JOIN users authors ON authors.id = #{Issue.table_name}.author_id" : nil
609
607
610 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
608 issues = Issue.visible.scoped(:conditions => options[:conditions]).find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
611 :conditions => statement,
609 :conditions => statement,
612 :order => order_option,
610 :order => order_option,
613 :joins => joins,
611 :joins => joins_for_order_statement(order_option),
614 :limit => options[:limit],
612 :limit => options[:limit],
615 :offset => options[:offset]
613 :offset => options[:offset]
616
614
@@ -626,13 +624,11 class Query < ActiveRecord::Base
626 def issue_ids(options={})
624 def issue_ids(options={})
627 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
625 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
628 order_option = nil if order_option.blank?
626 order_option = nil if order_option.blank?
629
630 joins = (order_option && order_option.include?('authors')) ? "LEFT OUTER JOIN users authors ON authors.id = #{Issue.table_name}.author_id" : nil
631
627
632 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
628 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
633 :conditions => statement,
629 :conditions => statement,
634 :order => order_option,
630 :order => order_option,
635 :joins => joins,
631 :joins => joins_for_order_statement(order_option),
636 :limit => options[:limit],
632 :limit => options[:limit],
637 :offset => options[:offset]).find_ids
633 :offset => options[:offset]).find_ids
638 rescue ::ActiveRecord::StatementInvalid => e
634 rescue ::ActiveRecord::StatementInvalid => e
@@ -895,4 +891,24 class Query < ActiveRecord::Base
895 def relative_date_clause(table, field, days_from, days_to)
891 def relative_date_clause(table, field, days_from, days_to)
896 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
892 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
897 end
893 end
894
895 # Additional joins required for the given sort options
896 def joins_for_order_statement(order_options)
897 joins = []
898
899 if order_options
900 if order_options.include?('authors')
901 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{Issue.table_name}.author_id"
902 end
903 order_options.scan(/cf_\d+/).uniq.each do |name|
904 column = available_columns.detect {|c| c.name.to_s == name}
905 join = column.custom_field.join_for_order_statement
906 if join
907 joins << join
908 end
909 end
910 end
911
912 joins.any? ? joins.join(' ') : nil
913 end
898 end
914 end
@@ -254,6 +254,27 class IssuesControllerTest < ActionController::TestCase
254 assert_not_nil assigns(:issue_count_by_group)
254 assert_not_nil assigns(:issue_count_by_group)
255 end
255 end
256
256
257 def test_index_with_query_grouped_by_user_custom_field
258 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
259 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
260 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
261 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
262 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
263
264 get :index, :project_id => 1, :set_filter => 1, :group_by => "cf_#{cf.id}"
265 assert_response :success
266
267 assert_select 'tr.group', 3
268 assert_select 'tr.group' do
269 assert_select 'a', :text => 'John Smith'
270 assert_select 'span.count', :text => '(1)'
271 end
272 assert_select 'tr.group' do
273 assert_select 'a', :text => 'Dave Lopper'
274 assert_select 'span.count', :text => '(2)'
275 end
276 end
277
257 def test_index_with_query_id_and_project_id_should_set_session_query
278 def test_index_with_query_id_and_project_id_should_set_session_query
258 get :index, :project_id => 1, :query_id => 4
279 get :index, :project_id => 1, :query_id => 4
259 assert_response :success
280 assert_response :success
@@ -619,6 +640,19 class IssuesControllerTest < ActionController::TestCase
619 assert_equal hours.sort.reverse, hours
640 assert_equal hours.sort.reverse, hours
620 end
641 end
621
642
643 def test_index_sort_by_user_custom_field
644 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
645 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
646 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
647 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
648 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
649
650 get :index, :project_id => 1, :set_filter => 1, :sort => "cf_#{cf.id},id"
651 assert_response :success
652
653 assert_equal [2, 3, 1], assigns(:issues).select {|issue| issue.custom_field_value(cf).present?}.map(&:id)
654 end
655
622 def test_index_with_columns
656 def test_index_with_columns
623 columns = ['tracker', 'subject', 'assigned_to']
657 columns = ['tracker', 'subject', 'assigned_to']
624 get :index, :set_filter => 1, :c => columns
658 get :index, :set_filter => 1, :c => columns
@@ -1114,6 +1148,24 class IssuesControllerTest < ActionController::TestCase
1114 assert_no_tag 'a', :content => /Next/
1148 assert_no_tag 'a', :content => /Next/
1115 end
1149 end
1116
1150
1151 def test_show_show_should_display_prev_next_links_with_query_sort_by_user_custom_field
1152 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1,2,3], :field_format => 'user')
1153 CustomValue.create!(:custom_field => cf, :customized => Issue.find(1), :value => '2')
1154 CustomValue.create!(:custom_field => cf, :customized => Issue.find(2), :value => '3')
1155 CustomValue.create!(:custom_field => cf, :customized => Issue.find(3), :value => '3')
1156 CustomValue.create!(:custom_field => cf, :customized => Issue.find(5), :value => '')
1157
1158 query = Query.create!(:name => 'test', :is_public => true, :user_id => 1, :filters => {},
1159 :sort_criteria => [["cf_#{cf.id}", 'asc'], ['id', 'asc']])
1160 @request.session[:query] = {:id => query.id, :project_id => nil}
1161
1162 get :show, :id => 3
1163 assert_response :success
1164
1165 assert_equal 2, assigns(:prev_issue_id)
1166 assert_equal 1, assigns(:next_issue_id)
1167 end
1168
1117 def test_show_should_display_link_to_the_assignee
1169 def test_show_should_display_link_to_the_assignee
1118 get :show, :id => 2
1170 get :show, :id => 2
1119 assert_response :success
1171 assert_response :success
@@ -202,4 +202,14 class CustomFieldTest < ActiveSupport::TestCase
202 assert f.valid_field_value?(['value1', 'value2'])
202 assert f.valid_field_value?(['value1', 'value2'])
203 assert !f.valid_field_value?(['value1', 'abc'])
203 assert !f.valid_field_value?(['value1', 'abc'])
204 end
204 end
205
206 def test_value_class_should_return_the_class_used_for_fields_values
207 assert_equal User, CustomField.new(:field_format => 'user')
208 assert_equal Version, CustomField.new(:field_format => 'version')
209 end
210
211 def test_value_class_should_return_nil_for_other_fields
212 assert_nil CustomField.new(:field_format => 'text')
213 assert_nil CustomField.new
214 end
205 end
215 end
@@ -621,6 +621,20 class QueryTest < ActiveSupport::TestCase
621 assert_nil column
621 assert_nil column
622 end
622 end
623
623
624 def test_groupable_columns_should_include_user_custom_fields
625 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
626
627 q = Query.new
628 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
629 end
630
631 def test_groupable_columns_should_include_version_custom_fields
632 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
633
634 q = Query.new
635 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
636 end
637
624 def test_grouped_with_valid_column
638 def test_grouped_with_valid_column
625 q = Query.new(:group_by => 'status')
639 q = Query.new(:group_by => 'status')
626 assert q.grouped?
640 assert q.grouped?
General Comments 0
You need to be logged in to leave comments. Login now