##// END OF EJS Templates
Ability to filter issues using project, author, assignee and target version custom fields (#8161)....
Jean-Philippe Lang -
r9981:3676783052fe
parent child
Show More
@@ -305,6 +305,8 class Query < ActiveRecord::Base
305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
305 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
306 end
306 end
307
307
308 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
309
308 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
310 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
309 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
311 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
310 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
312 @available_filters["is_private"] = { :type => :list, :order => 15, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]] }
@@ -572,7 +574,7 class Query < ActiveRecord::Base
572 end
574 end
573 end
575 end
574
576
575 if field =~ /^cf_(\d+)$/
577 if field =~ /cf_(\d+)$/
576 # custom field
578 # custom field
577 filters_clauses << sql_for_custom_field(field, operator, v, $1)
579 filters_clauses << sql_for_custom_field(field, operator, v, $1)
578 elsif respond_to?("sql_for_#{field}_field")
580 elsif respond_to?("sql_for_#{field}_field")
@@ -733,7 +735,8 class Query < ActiveRecord::Base
733 db_table = CustomValue.table_name
735 db_table = CustomValue.table_name
734 db_field = 'value'
736 db_field = 'value'
735 filter = @available_filters[field]
737 filter = @available_filters[field]
736 if filter && filter[:format] == 'user'
738 return nil unless filter
739 if filter[:format] == 'user'
737 if value.delete('me')
740 if value.delete('me')
738 value.push User.current.id.to_s
741 value.push User.current.id.to_s
739 end
742 end
@@ -744,7 +747,15 class Query < ActiveRecord::Base
744 operator = '='
747 operator = '='
745 not_in = 'NOT'
748 not_in = 'NOT'
746 end
749 end
747 "#{Issue.table_name}.id #{not_in} IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
750 customized_key = "id"
751 customized_class = Issue
752 if field =~ /^(.+)\.cf_/
753 assoc = $1
754 customized_key = "#{assoc}_id"
755 customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
756 raise "Unknown Issue association #{assoc}" unless customized_class
757 end
758 "#{Issue.table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
748 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
759 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
749 end
760 end
750
761
@@ -853,7 +864,8 class Query < ActiveRecord::Base
853 return sql
864 return sql
854 end
865 end
855
866
856 def add_custom_fields_filters(custom_fields)
867 def add_custom_fields_filters(custom_fields, assoc=nil)
868 return unless custom_fields.present?
857 @available_filters ||= {}
869 @available_filters ||= {}
858
870
859 custom_fields.select(&:is_filter?).each do |field|
871 custom_fields.select(&:is_filter?).each do |field|
@@ -880,7 +892,25 class Query < ActiveRecord::Base
880 else
892 else
881 options = { :type => :string, :order => 20 }
893 options = { :type => :string, :order => 20 }
882 end
894 end
883 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format })
895 filter_id = "cf_#{field.id}"
896 filter_name = field.name
897 if assoc.present?
898 filter_id = "#{assoc}.#{filter_id}"
899 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
900 end
901 @available_filters[filter_id] = options.merge({ :name => filter_name, :format => field.field_format })
902 end
903 end
904
905 def add_associations_custom_fields_filters(*associations)
906 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
907 associations.each do |assoc|
908 association_klass = Issue.reflect_on_association(assoc).klass
909 fields_by_class.each do |field_class, fields|
910 if field_class.customized_class <= association_klass
911 add_custom_fields_filters(fields, assoc)
912 end
913 end
884 end
914 end
885 end
915 end
886
916
@@ -55,11 +55,21 when "IssueCustomField" %>
55 <p><%= f.check_box :is_required %></p>
55 <p><%= f.check_box :is_required %></p>
56 <p><%= f.check_box :visible %></p>
56 <p><%= f.check_box :visible %></p>
57 <p><%= f.check_box :editable %></p>
57 <p><%= f.check_box :editable %></p>
58 <p><%= f.check_box :is_filter %></p>
58
59
59 <% when "ProjectCustomField" %>
60 <% when "ProjectCustomField" %>
60 <p><%= f.check_box :is_required %></p>
61 <p><%= f.check_box :is_required %></p>
61 <p><%= f.check_box :visible %></p>
62 <p><%= f.check_box :visible %></p>
62 <p><%= f.check_box :searchable %></p>
63 <p><%= f.check_box :searchable %></p>
64 <p><%= f.check_box :is_filter %></p>
65
66 <% when "VersionCustomField" %>
67 <p><%= f.check_box :is_required %></p>
68 <p><%= f.check_box :is_filter %></p>
69
70 <% when "GroupCustomField" %>
71 <p><%= f.check_box :is_required %></p>
72 <p><%= f.check_box :is_filter %></p>
63
73
64 <% when "TimeEntryCustomField" %>
74 <% when "TimeEntryCustomField" %>
65 <p><%= f.check_box :is_required %></p>
75 <p><%= f.check_box :is_required %></p>
@@ -866,6 +866,10 en:
866 label_fields_permissions: Fields permissions
866 label_fields_permissions: Fields permissions
867 label_readonly: Read-only
867 label_readonly: Read-only
868 label_required: Required
868 label_required: Required
869 label_attribute_of_project: "Project's %{name}"
870 label_attribute_of_author: "Author's %{name}"
871 label_attribute_of_assigned_to: "Assignee's %{name}"
872 label_attribute_of_fixed_version: "Target version's %{name}"
869
873
870 button_login: Login
874 button_login: Login
871 button_submit: Submit
875 button_submit: Submit
@@ -841,6 +841,10 fr:
841 label_fields_permissions: Permissions sur les champs
841 label_fields_permissions: Permissions sur les champs
842 label_readonly: Lecture
842 label_readonly: Lecture
843 label_required: Obligatoire
843 label_required: Obligatoire
844 label_attribute_of_project: "%{name} du projet"
845 label_attribute_of_author: "%{name} de l'auteur"
846 label_attribute_of_assigned_to: "%{name} de l'assignΓ©"
847 label_attribute_of_fixed_version: "%{name} de la version cible"
844
848
845 button_login: Connexion
849 button_login: Connexion
846 button_submit: Soumettre
850 button_submit: Soumettre
@@ -152,10 +152,11 function buildFilterRow(field, operator, values) {
152 var option = $('<option>');
152 var option = $('<option>');
153 if ($.isArray(filterValue)) {
153 if ($.isArray(filterValue)) {
154 option.val(filterValue[1]).html(filterValue[0]);
154 option.val(filterValue[1]).html(filterValue[0]);
155 if (values.indexOf(filterValue[1]) > -1) {option.attr('selected', true)};
155 } else {
156 } else {
156 option.val(filterValue).html(filterValue);
157 option.val(filterValue).html(filterValue);
158 if (values.indexOf(filterValue) > -1) {option.attr('selected', true)};
157 }
159 }
158 if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)};
159 select.append(option);
160 select.append(option);
160 }
161 }
161 break;
162 break;
@@ -339,7 +339,7 fieldset#date-range p { margin: 2px 0 2px 0; }
339 fieldset#filters table { border-collapse: collapse; }
339 fieldset#filters table { border-collapse: collapse; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
340 fieldset#filters table td { padding: 0; vertical-align: middle; }
341 fieldset#filters tr.filter { height: 2.1em; }
341 fieldset#filters tr.filter { height: 2.1em; }
342 fieldset#filters td.field { width:200px; }
342 fieldset#filters td.field { width:250px; }
343 fieldset#filters td.operator { width:170px; }
343 fieldset#filters td.operator { width:170px; }
344 fieldset#filters td.values { white-space:nowrap; }
344 fieldset#filters td.values { white-space:nowrap; }
345 fieldset#filters td.values select {min-width:130px;}
345 fieldset#filters td.values select {min-width:130px;}
@@ -230,6 +230,22 class IssuesControllerTest < ActionController::TestCase
230 assert_equal({}, query.filters)
230 assert_equal({}, query.filters)
231 end
231 end
232
232
233 def test_index_with_project_custom_field_filter
234 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
235 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
236 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
237 filter_name = "project.cf_#{field.id}"
238 @request.session[:user_id] = 1
239
240 get :index, :set_filter => 1,
241 :f => [filter_name],
242 :op => {filter_name => '='},
243 :v => {filter_name => ['Foo']}
244 assert_response :success
245 assert_template 'index'
246 assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort
247 end
248
233 def test_index_with_query
249 def test_index_with_query
234 get :index, :project_id => 1, :query_id => 5
250 get :index, :project_id => 1, :query_id => 5
235 assert_response :success
251 assert_response :success
@@ -577,6 +577,51 class QueryTest < ActiveSupport::TestCase
577 User.current = nil
577 User.current = nil
578 end
578 end
579
579
580 def test_filter_on_project_custom_field
581 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
582 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
583 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
584
585 query = Query.new(:name => '_')
586 filter_name = "project.cf_#{field.id}"
587 assert_include filter_name, query.available_filters.keys
588 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
589 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
590 end
591
592 def test_filter_on_author_custom_field
593 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
594 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
595
596 query = Query.new(:name => '_')
597 filter_name = "author.cf_#{field.id}"
598 assert_include filter_name, query.available_filters.keys
599 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
600 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
601 end
602
603 def test_filter_on_assigned_to_custom_field
604 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
605 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
606
607 query = Query.new(:name => '_')
608 filter_name = "assigned_to.cf_#{field.id}"
609 assert_include filter_name, query.available_filters.keys
610 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
611 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
612 end
613
614 def test_filter_on_fixed_version_custom_field
615 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
616 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
617
618 query = Query.new(:name => '_')
619 filter_name = "fixed_version.cf_#{field.id}"
620 assert_include filter_name, query.available_filters.keys
621 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
622 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
623 end
624
580 def test_statement_should_be_nil_with_no_filters
625 def test_statement_should_be_nil_with_no_filters
581 q = Query.new(:name => '_')
626 q = Query.new(:name => '_')
582 q.filters = {}
627 q.filters = {}
General Comments 0
You need to be logged in to leave comments. Login now