]> source.dussan.org Git - redmine.git/commitdiff
Ability to filter issues using project, author, assignee and target version custom...
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 7 Aug 2012 19:17:59 +0000 (19:17 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Tue, 7 Aug 2012 19:17:59 +0000 (19:17 +0000)
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

app/models/query.rb
app/views/custom_fields/_form.html.erb
config/locales/en.yml
config/locales/fr.yml
public/javascripts/application.js
public/stylesheets/application.css
test/functional/issues_controller_test.rb
test/unit/query_test.rb

index 628a1dfeb9f8b5bdda208ae7450f6d3eed0b8e9b..8643b27f29642c5ae6fc9218635019e8b558256f 100644 (file)
@@ -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
 
index dcfcc32ec6a9de34d2248a2f8fd677ff7adcc42b..beaa78b2f906025896271114c28854b8f70be13d 100644 (file)
@@ -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>
index 1c6ed7e4dc6f71cb2d594c18262bc7fad5e6b76f..8038b92c06a9b060c4195ee4ae9316ee8f6cbc11 100644 (file)
@@ -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
index ecaf4470711193e18c10b20d001f0e4ca6322b8c..4802d76055556b76625f56aa46e37bc6149cd9da 100644 (file)
@@ -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
index 20bfb08957adc129565941901b28f9d8170df8c4..5f64c41ba5b634335c0ea1db4101d5e3e4b53427 100644 (file)
@@ -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;
index 3400bd4c07eb8f7c8775b4945c79c71af31378b5..6727d356b3b720800d5fc4f4d71f9c2764aba103 100644 (file)
@@ -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;}
index b554e9e5f433135768477ab53d3bf11489f25e06..ee18dadb27c536ab4ac0b457b8c1ad565bd8d8c8 100644 (file)
@@ -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
index de6765f2c0c39e9097b9f64f665c58e22db92803..7f2073e347c78bee59d86c0d1bbf141d1faa0af4 100644 (file)
@@ -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 = {}