git-svn-id: http://svn.redmine.org/redmine/trunk@16191 e93f8b46-1217-0410-a6f0-8f06a7374b81tags/3.4.0
group = :label_relations | group = :label_relations | ||||
elsif field_options[:type] == :tree | elsif field_options[:type] == :tree | ||||
group = query.is_a?(IssueQuery) ? :label_relations : nil | group = query.is_a?(IssueQuery) ? :label_relations : nil | ||||
elsif field =~ /^cf_\d+\./ | |||||
group = (field_options[:through] || field_options[:field]).try(:name) | |||||
elsif field =~ /^(.+)\./ | elsif field =~ /^(.+)\./ | ||||
# association filters | # association filters | ||||
group = "field_#{$1}".to_sym | group = "field_#{$1}".to_sym | ||||
end | end | ||||
s = options_for_select([[]] + ungrouped) | s = options_for_select([[]] + ungrouped) | ||||
if grouped.present? | 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) | s << grouped_options_for_select(localized_grouped) | ||||
end | end | ||||
s | s |
end | end | ||||
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 | # custom field | ||||
filters_clauses << sql_for_custom_field(field, operator, v, $1) | 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") | elsif respond_to?(method = "sql_for_#{field.gsub('.','_')}_field") | ||||
# specific statement | # specific statement | ||||
filters_clauses << send(method, field, operator, v) | filters_clauses << send(method, field, operator, v) | ||||
" WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" | " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" | ||||
end | 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+ | # 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) | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) | ||||
sql = '' | sql = '' | ||||
}) | }) | ||||
end | 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 | # Adds filters for the given custom fields scope | ||||
def add_custom_fields_filters(scope, assoc=nil) | def add_custom_fields_filters(scope, assoc=nil) | ||||
scope.visible.where(:is_filter => true).sorted.each do |field| | scope.visible.where(:is_filter => true).sorted.each do |field| | ||||
add_custom_field_filter(field, assoc) | 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 | ||||
end | end | ||||
label_attribute_of_assigned_to: "Assignee's %{name}" | label_attribute_of_assigned_to: "Assignee's %{name}" | ||||
label_attribute_of_user: "User's %{name}" | label_attribute_of_user: "User's %{name}" | ||||
label_attribute_of_fixed_version: "Target version'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_descendants: With subprojects | ||||
label_cross_project_tree: With project tree | label_cross_project_tree: With project tree | ||||
label_cross_project_hierarchy: With project hierarchy | label_cross_project_hierarchy: With project hierarchy |
label_attribute_of_assigned_to: "%{name} de l'assigné" | label_attribute_of_assigned_to: "%{name} de l'assigné" | ||||
label_attribute_of_user: "%{name} de l'utilisateur" | label_attribute_of_user: "%{name} de l'utilisateur" | ||||
label_attribute_of_fixed_version: "%{name} de la version cible" | 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_descendants: Avec les sous-projets | ||||
label_cross_project_tree: Avec tout l'arbre | label_cross_project_tree: Avec tout l'arbre | ||||
label_cross_project_hierarchy: Avec toute la hiérarchie | label_cross_project_hierarchy: Avec toute la hiérarchie |
assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort | assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort | ||||
end | 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 | def test_filter_on_relations_with_a_specific_issue | ||||
IssueRelation.delete_all | IssueRelation.delete_all | ||||
IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |