diff options
author | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2012-08-07 19:17:59 +0000 |
---|---|---|
committer | Jean-Philippe Lang <jp_lang@yahoo.fr> | 2012-08-07 19:17:59 +0000 |
commit | 3676783052fed523c23247fb8181124e918dd4e1 (patch) | |
tree | ffcddde17e6c732a97ab0b8c25b66e2c9e3eed1e | |
parent | 599736aca7b9b5bd47d39fdf72c9fc1127730497 (diff) | |
download | redmine-3676783052fed523c23247fb8181124e918dd4e1.tar.gz redmine-3676783052fed523c23247fb8181124e918dd4e1.zip |
Ability to filter issues using project, author, assignee and target version custom fields (#8161).
Custom fields must be marked as "Used as filter" to show up in the filters list.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10164 e93f8b46-1217-0410-a6f0-8f06a7374b81
-rw-r--r-- | app/models/query.rb | 40 | ||||
-rw-r--r-- | app/views/custom_fields/_form.html.erb | 10 | ||||
-rw-r--r-- | config/locales/en.yml | 4 | ||||
-rw-r--r-- | config/locales/fr.yml | 4 | ||||
-rw-r--r-- | public/javascripts/application.js | 3 | ||||
-rw-r--r-- | public/stylesheets/application.css | 2 | ||||
-rw-r--r-- | test/functional/issues_controller_test.rb | 16 | ||||
-rw-r--r-- | test/unit/query_test.rb | 45 |
8 files changed, 117 insertions, 7 deletions
diff --git a/app/models/query.rb b/app/models/query.rb index 628a1dfeb..8643b27f2 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -305,6 +305,8 @@ class Query < ActiveRecord::Base add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) end + add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version + if User.current.allowed_to?(:set_issues_private, nil, :global => true) || User.current.allowed_to?(:set_own_issues_private, nil, :global => true) @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 end end - if field =~ /^cf_(\d+)$/ + if field =~ /cf_(\d+)$/ # custom field filters_clauses << sql_for_custom_field(field, operator, v, $1) elsif respond_to?("sql_for_#{field}_field") @@ -733,7 +735,8 @@ class Query < ActiveRecord::Base db_table = CustomValue.table_name db_field = 'value' filter = @available_filters[field] - if filter && filter[:format] == 'user' + return nil unless filter + if filter[:format] == 'user' if value.delete('me') value.push User.current.id.to_s end @@ -744,7 +747,15 @@ class Query < ActiveRecord::Base operator = '=' not_in = 'NOT' end - "#{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 " + + customized_key = "id" + customized_class = Issue + if field =~ /^(.+)\.cf_/ + assoc = $1 + customized_key = "#{assoc}_id" + customized_class = Issue.reflect_on_association(assoc.to_sym).klass.base_class rescue nil + raise "Unknown Issue association #{assoc}" unless customized_class + end + "#{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 " + sql_for_field(field, operator, value, db_table, db_field, true) + ')' end @@ -853,7 +864,8 @@ class Query < ActiveRecord::Base return sql end - def add_custom_fields_filters(custom_fields) + def add_custom_fields_filters(custom_fields, assoc=nil) + return unless custom_fields.present? @available_filters ||= {} custom_fields.select(&:is_filter?).each do |field| @@ -880,7 +892,25 @@ class Query < ActiveRecord::Base else options = { :type => :string, :order => 20 } end - @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name, :format => field.field_format }) + filter_id = "cf_#{field.id}" + filter_name = field.name + if assoc.present? + filter_id = "#{assoc}.#{filter_id}" + filter_name = l("label_attribute_of_#{assoc}", :name => filter_name) + end + @available_filters[filter_id] = options.merge({ :name => filter_name, :format => field.field_format }) + end + end + + def add_associations_custom_fields_filters(*associations) + fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) + associations.each do |assoc| + association_klass = Issue.reflect_on_association(assoc).klass + fields_by_class.each do |field_class, fields| + if field_class.customized_class <= association_klass + add_custom_fields_filters(fields, assoc) + end + end end end diff --git a/app/views/custom_fields/_form.html.erb b/app/views/custom_fields/_form.html.erb index dcfcc32ec..beaa78b2f 100644 --- a/app/views/custom_fields/_form.html.erb +++ b/app/views/custom_fields/_form.html.erb @@ -55,11 +55,21 @@ when "IssueCustomField" %> <p><%= f.check_box :is_required %></p> <p><%= f.check_box :visible %></p> <p><%= f.check_box :editable %></p> + <p><%= f.check_box :is_filter %></p> <% when "ProjectCustomField" %> <p><%= f.check_box :is_required %></p> <p><%= f.check_box :visible %></p> <p><%= f.check_box :searchable %></p> + <p><%= f.check_box :is_filter %></p> + +<% when "VersionCustomField" %> + <p><%= f.check_box :is_required %></p> + <p><%= f.check_box :is_filter %></p> + +<% when "GroupCustomField" %> + <p><%= f.check_box :is_required %></p> + <p><%= f.check_box :is_filter %></p> <% when "TimeEntryCustomField" %> <p><%= f.check_box :is_required %></p> diff --git a/config/locales/en.yml b/config/locales/en.yml index 1c6ed7e4d..8038b92c0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -866,6 +866,10 @@ en: label_fields_permissions: Fields permissions label_readonly: Read-only label_required: Required + label_attribute_of_project: "Project's %{name}" + label_attribute_of_author: "Author's %{name}" + label_attribute_of_assigned_to: "Assignee's %{name}" + label_attribute_of_fixed_version: "Target version's %{name}" button_login: Login button_submit: Submit diff --git a/config/locales/fr.yml b/config/locales/fr.yml index ecaf44707..4802d7605 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -841,6 +841,10 @@ fr: label_fields_permissions: Permissions sur les champs label_readonly: Lecture label_required: Obligatoire + label_attribute_of_project: "%{name} du projet" + label_attribute_of_author: "%{name} de l'auteur" + label_attribute_of_assigned_to: "%{name} de l'assigné" + label_attribute_of_fixed_version: "%{name} de la version cible" button_login: Connexion button_submit: Soumettre diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 20bfb0895..5f64c41ba 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -152,10 +152,11 @@ function buildFilterRow(field, operator, values) { var option = $('<option>'); if ($.isArray(filterValue)) { option.val(filterValue[1]).html(filterValue[0]); + if (values.indexOf(filterValue[1]) > -1) {option.attr('selected', true)}; } else { option.val(filterValue).html(filterValue); + if (values.indexOf(filterValue) > -1) {option.attr('selected', true)}; } - if (values.indexOf(filterValues[i][1]) > -1) {option.attr('selected', true)}; select.append(option); } break; diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index 3400bd4c0..6727d356b 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -339,7 +339,7 @@ fieldset#date-range p { margin: 2px 0 2px 0; } fieldset#filters table { border-collapse: collapse; } fieldset#filters table td { padding: 0; vertical-align: middle; } fieldset#filters tr.filter { height: 2.1em; } -fieldset#filters td.field { width:200px; } +fieldset#filters td.field { width:250px; } fieldset#filters td.operator { width:170px; } fieldset#filters td.values { white-space:nowrap; } fieldset#filters td.values select {min-width:130px;} diff --git a/test/functional/issues_controller_test.rb b/test/functional/issues_controller_test.rb index b554e9e5f..ee18dadb2 100644 --- a/test/functional/issues_controller_test.rb +++ b/test/functional/issues_controller_test.rb @@ -230,6 +230,22 @@ class IssuesControllerTest < ActionController::TestCase assert_equal({}, query.filters) end + def test_index_with_project_custom_field_filter + field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') + CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') + filter_name = "project.cf_#{field.id}" + @request.session[:user_id] = 1 + + get :index, :set_filter => 1, + :f => [filter_name], + :op => {filter_name => '='}, + :v => {filter_name => ['Foo']} + assert_response :success + assert_template 'index' + assert_equal [3, 5], assigns(:issues).map(&:project_id).uniq.sort + end + def test_index_with_query get :index, :project_id => 1, :query_id => 5 assert_response :success diff --git a/test/unit/query_test.rb b/test/unit/query_test.rb index de6765f2c..7f2073e34 100644 --- a/test/unit/query_test.rb +++ b/test/unit/query_test.rb @@ -577,6 +577,51 @@ class QueryTest < ActiveSupport::TestCase User.current = nil end + def test_filter_on_project_custom_field + field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo') + CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo') + + query = Query.new(:name => '_') + filter_name = "project.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort + end + + def test_filter_on_author_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = Query.new(:name => '_') + filter_name = "author.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort + end + + def test_filter_on_assigned_to_custom_field + field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo') + + query = Query.new(:name => '_') + filter_name = "assigned_to.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort + end + + def test_filter_on_fixed_version_custom_field + field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string') + CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo') + + query = Query.new(:name => '_') + filter_name = "fixed_version.cf_#{field.id}" + assert_include filter_name, query.available_filters.keys + query.filters = {filter_name => {:operator => '=', :values => ['Foo']}} + assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort + end + def test_statement_should_be_nil_with_no_filters q = Query.new(:name => '_') q.filters = {} |