mirror of
https://github.com/redmine/redmine.git
synced 2025-11-11 15:56:03 +01:00
Filters on chained custom fields and custom field attributes (#21249).
git-svn-id: http://svn.redmine.org/redmine/trunk@16191 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
@@ -28,6 +28,8 @@ module QueriesHelper
|
||||
group = :label_relations
|
||||
elsif field_options[:type] == :tree
|
||||
group = query.is_a?(IssueQuery) ? :label_relations : nil
|
||||
elsif field =~ /^cf_\d+\./
|
||||
group = (field_options[:through] || field_options[:field]).try(:name)
|
||||
elsif field =~ /^(.+)\./
|
||||
# association filters
|
||||
group = "field_#{$1}".to_sym
|
||||
@@ -48,7 +50,7 @@ module QueriesHelper
|
||||
end
|
||||
s = options_for_select([[]] + ungrouped)
|
||||
if grouped.present?
|
||||
localized_grouped = grouped.map {|k,v| [l(k), v]}
|
||||
localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]}
|
||||
s << grouped_options_for_select(localized_grouped)
|
||||
end
|
||||
s
|
||||
|
||||
@@ -808,9 +808,13 @@ class Query < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
if field =~ /cf_(\d+)$/
|
||||
if field =~ /^cf_(\d+)\.cf_(\d+)$/
|
||||
filters_clauses << sql_for_chained_custom_field(field, operator, v, $1, $2)
|
||||
elsif field =~ /cf_(\d+)$/
|
||||
# custom field
|
||||
filters_clauses << sql_for_custom_field(field, operator, v, $1)
|
||||
elsif field =~ /^cf_(\d+)\.(.+)$/
|
||||
filters_clauses << sql_for_custom_field_attribute(field, operator, v, $1, $2)
|
||||
elsif respond_to?(method = "sql_for_#{field.gsub('.','_')}_field")
|
||||
# specific statement
|
||||
filters_clauses << send(method, field, operator, v)
|
||||
@@ -951,6 +955,46 @@ class Query < ActiveRecord::Base
|
||||
" WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))"
|
||||
end
|
||||
|
||||
def sql_for_chained_custom_field(field, operator, value, custom_field_id, chained_custom_field_id)
|
||||
not_in = nil
|
||||
if operator == '!'
|
||||
# Makes ! operator work for custom fields with multiple values
|
||||
operator = '='
|
||||
not_in = 'NOT'
|
||||
end
|
||||
|
||||
filter = available_filters[field]
|
||||
target_class = filter[:through].format.target_class
|
||||
|
||||
"#{queried_table_name}.id #{not_in} IN (" +
|
||||
"SELECT customized_id FROM #{CustomValue.table_name}" +
|
||||
" WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" +
|
||||
" AND value <> '' AND CAST(value AS integer) IN (" +
|
||||
" SELECT customized_id FROM #{CustomValue.table_name}" +
|
||||
" WHERE customized_type='#{target_class}' AND custom_field_id=#{chained_custom_field_id}" +
|
||||
" AND #{sql_for_field(field, operator, value, CustomValue.table_name, 'value')}))"
|
||||
|
||||
end
|
||||
|
||||
def sql_for_custom_field_attribute(field, operator, value, custom_field_id, attribute)
|
||||
attribute = 'effective_date' if attribute == 'due_date'
|
||||
not_in = nil
|
||||
if operator == '!'
|
||||
# Makes ! operator work for custom fields with multiple values
|
||||
operator = '='
|
||||
not_in = 'NOT'
|
||||
end
|
||||
|
||||
filter = available_filters[field]
|
||||
target_table_name = filter[:field].format.target_class.table_name
|
||||
|
||||
"#{queried_table_name}.id #{not_in} IN (" +
|
||||
"SELECT customized_id FROM #{CustomValue.table_name}" +
|
||||
" WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" +
|
||||
" AND value <> '' AND CAST(value AS integer) IN (" +
|
||||
" SELECT id FROM #{target_table_name} WHERE #{sql_for_field(field, operator, value, filter[:field].format.target_class.table_name, attribute)}))"
|
||||
end
|
||||
|
||||
# Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
|
||||
def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
|
||||
sql = ''
|
||||
@@ -1124,10 +1168,47 @@ class Query < ActiveRecord::Base
|
||||
})
|
||||
end
|
||||
|
||||
# Adds filters for custom fields associated to the custom field target class
|
||||
# Eg. having a version custom field "Milestone" for issues and a date custom field "Release date"
|
||||
# for versions, it will add an issue filter on Milestone'e Release date.
|
||||
def add_chained_custom_field_filters(field)
|
||||
klass = field.format.target_class
|
||||
if klass
|
||||
CustomField.where(:is_filter => true, :type => "#{klass.name}CustomField").each do |chained|
|
||||
options = chained.query_filter_options(self)
|
||||
|
||||
filter_id = "cf_#{field.id}.cf_#{chained.id}"
|
||||
filter_name = chained.name
|
||||
|
||||
add_available_filter filter_id, options.merge({
|
||||
:name => l(:label_attribute_of_object, :name => chained.name, :object_name => field.name),
|
||||
:field => chained,
|
||||
:through => field
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Adds filters for the given custom fields scope
|
||||
def add_custom_fields_filters(scope, assoc=nil)
|
||||
scope.visible.where(:is_filter => true).sorted.each do |field|
|
||||
add_custom_field_filter(field, assoc)
|
||||
if assoc.nil?
|
||||
add_chained_custom_field_filters(field)
|
||||
|
||||
if field.format.target_class && field.format.target_class == Version
|
||||
add_available_filter "cf_#{field.id}.due_date",
|
||||
:type => :date,
|
||||
:field => field,
|
||||
:name => l(:label_attribute_of_object, :name => l(:field_effective_date), :object_name => field.name)
|
||||
|
||||
add_available_filter "cf_#{field.id}.status",
|
||||
:type => :list,
|
||||
:field => field,
|
||||
:name => l(:label_attribute_of_object, :name => l(:field_status), :object_name => field.name),
|
||||
:values => Version::VERSION_STATUSES.map{|s| [l("version_status_#{s}"), s] }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -948,6 +948,7 @@ en:
|
||||
label_attribute_of_assigned_to: "Assignee's %{name}"
|
||||
label_attribute_of_user: "User's %{name}"
|
||||
label_attribute_of_fixed_version: "Target version's %{name}"
|
||||
label_attribute_of_object: "%{object_name}'s %{name}"
|
||||
label_cross_project_descendants: With subprojects
|
||||
label_cross_project_tree: With project tree
|
||||
label_cross_project_hierarchy: With project hierarchy
|
||||
|
||||
@@ -959,6 +959,7 @@ fr:
|
||||
label_attribute_of_assigned_to: "%{name} de l'assigné"
|
||||
label_attribute_of_user: "%{name} de l'utilisateur"
|
||||
label_attribute_of_fixed_version: "%{name} de la version cible"
|
||||
label_attribute_of_object: "%{name} de \"%{object_name}\""
|
||||
label_cross_project_descendants: Avec les sous-projets
|
||||
label_cross_project_tree: Avec tout l'arbre
|
||||
label_cross_project_hierarchy: Avec toute la hiérarchie
|
||||
|
||||
@@ -877,6 +877,49 @@ class QueryTest < ActiveSupport::TestCase
|
||||
assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort
|
||||
end
|
||||
|
||||
def test_filter_on_version_custom_field
|
||||
field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
|
||||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => '2'})
|
||||
|
||||
query = IssueQuery.new(:name => '_')
|
||||
filter_name = "cf_#{field.id}"
|
||||
assert_include filter_name, query.available_filters.keys
|
||||
|
||||
query.filters = {filter_name => {:operator => '=', :values => ['2']}}
|
||||
issues = find_issues_with_query(query)
|
||||
assert_equal [issue.id], issues.map(&:id).sort
|
||||
end
|
||||
|
||||
def test_filter_on_attribute_of_version_custom_field
|
||||
field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
|
||||
version = Version.generate!(:effective_date => '2017-01-14')
|
||||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
|
||||
|
||||
query = IssueQuery.new(:name => '_')
|
||||
filter_name = "cf_#{field.id}.due_date"
|
||||
assert_include filter_name, query.available_filters.keys
|
||||
|
||||
query.filters = {filter_name => {:operator => '=', :values => ['2017-01-14']}}
|
||||
issues = find_issues_with_query(query)
|
||||
assert_equal [issue.id], issues.map(&:id).sort
|
||||
end
|
||||
|
||||
def test_filter_on_custom_field_of_version_custom_field
|
||||
field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true)
|
||||
attr = VersionCustomField.generate!(:field_format => 'string', :is_filter => true)
|
||||
|
||||
version = Version.generate!(:custom_field_values => {attr.id.to_s => 'ABC'})
|
||||
issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s})
|
||||
|
||||
query = IssueQuery.new(:name => '_')
|
||||
filter_name = "cf_#{field.id}.cf_#{attr.id}"
|
||||
assert_include filter_name, query.available_filters.keys
|
||||
|
||||
query.filters = {filter_name => {:operator => '=', :values => ['ABC']}}
|
||||
issues = find_issues_with_query(query)
|
||||
assert_equal [issue.id], issues.map(&:id).sort
|
||||
end
|
||||
|
||||
def test_filter_on_relations_with_a_specific_issue
|
||||
IssueRelation.delete_all
|
||||
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
|
||||
|
||||
Reference in New Issue
Block a user