@@ -28,6 +28,8 module QueriesHelper | |||
|
28 | 28 | group = :label_relations |
|
29 | 29 | elsif field_options[:type] == :tree |
|
30 | 30 | group = query.is_a?(IssueQuery) ? :label_relations : nil |
|
31 | elsif field =~ /^cf_\d+\./ | |
|
32 | group = (field_options[:through] || field_options[:field]).try(:name) | |
|
31 | 33 | elsif field =~ /^(.+)\./ |
|
32 | 34 | # association filters |
|
33 | 35 | group = "field_#{$1}".to_sym |
@@ -48,7 +50,7 module QueriesHelper | |||
|
48 | 50 | end |
|
49 | 51 | s = options_for_select([[]] + ungrouped) |
|
50 | 52 | if grouped.present? |
|
51 | localized_grouped = grouped.map {|k,v| [l(k), v]} | |
|
53 | localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]} | |
|
52 | 54 | s << grouped_options_for_select(localized_grouped) |
|
53 | 55 | end |
|
54 | 56 | s |
@@ -808,9 +808,13 class Query < ActiveRecord::Base | |||
|
808 | 808 | end |
|
809 | 809 | end |
|
810 | 810 | |
|
811 | if field =~ /cf_(\d+)$/ | |
|
811 | if field =~ /^cf_(\d+)\.cf_(\d+)$/ | |
|
812 | filters_clauses << sql_for_chained_custom_field(field, operator, v, $1, $2) | |
|
813 | elsif field =~ /cf_(\d+)$/ | |
|
812 | 814 | # custom field |
|
813 | 815 | filters_clauses << sql_for_custom_field(field, operator, v, $1) |
|
816 | elsif field =~ /^cf_(\d+)\.(.+)$/ | |
|
817 | filters_clauses << sql_for_custom_field_attribute(field, operator, v, $1, $2) | |
|
814 | 818 | elsif respond_to?(method = "sql_for_#{field.gsub('.','_')}_field") |
|
815 | 819 | # specific statement |
|
816 | 820 | filters_clauses << send(method, field, operator, v) |
@@ -951,6 +955,46 class Query < ActiveRecord::Base | |||
|
951 | 955 | " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" |
|
952 | 956 | end |
|
953 | 957 | |
|
958 | def sql_for_chained_custom_field(field, operator, value, custom_field_id, chained_custom_field_id) | |
|
959 | not_in = nil | |
|
960 | if operator == '!' | |
|
961 | # Makes ! operator work for custom fields with multiple values | |
|
962 | operator = '=' | |
|
963 | not_in = 'NOT' | |
|
964 | end | |
|
965 | ||
|
966 | filter = available_filters[field] | |
|
967 | target_class = filter[:through].format.target_class | |
|
968 | ||
|
969 | "#{queried_table_name}.id #{not_in} IN (" + | |
|
970 | "SELECT customized_id FROM #{CustomValue.table_name}" + | |
|
971 | " WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" + | |
|
972 | " AND value <> '' AND CAST(value AS integer) IN (" + | |
|
973 | " SELECT customized_id FROM #{CustomValue.table_name}" + | |
|
974 | " WHERE customized_type='#{target_class}' AND custom_field_id=#{chained_custom_field_id}" + | |
|
975 | " AND #{sql_for_field(field, operator, value, CustomValue.table_name, 'value')}))" | |
|
976 | ||
|
977 | end | |
|
978 | ||
|
979 | def sql_for_custom_field_attribute(field, operator, value, custom_field_id, attribute) | |
|
980 | attribute = 'effective_date' if attribute == 'due_date' | |
|
981 | not_in = nil | |
|
982 | if operator == '!' | |
|
983 | # Makes ! operator work for custom fields with multiple values | |
|
984 | operator = '=' | |
|
985 | not_in = 'NOT' | |
|
986 | end | |
|
987 | ||
|
988 | filter = available_filters[field] | |
|
989 | target_table_name = filter[:field].format.target_class.table_name | |
|
990 | ||
|
991 | "#{queried_table_name}.id #{not_in} IN (" + | |
|
992 | "SELECT customized_id FROM #{CustomValue.table_name}" + | |
|
993 | " WHERE customized_type='#{queried_class}' AND custom_field_id=#{custom_field_id}" + | |
|
994 | " AND value <> '' AND CAST(value AS integer) IN (" + | |
|
995 | " SELECT id FROM #{target_table_name} WHERE #{sql_for_field(field, operator, value, filter[:field].format.target_class.table_name, attribute)}))" | |
|
996 | end | |
|
997 | ||
|
954 | 998 | # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ |
|
955 | 999 | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) |
|
956 | 1000 | sql = '' |
@@ -1124,10 +1168,47 class Query < ActiveRecord::Base | |||
|
1124 | 1168 | }) |
|
1125 | 1169 | end |
|
1126 | 1170 | |
|
1171 | # Adds filters for custom fields associated to the custom field target class | |
|
1172 | # Eg. having a version custom field "Milestone" for issues and a date custom field "Release date" | |
|
1173 | # for versions, it will add an issue filter on Milestone'e Release date. | |
|
1174 | def add_chained_custom_field_filters(field) | |
|
1175 | klass = field.format.target_class | |
|
1176 | if klass | |
|
1177 | CustomField.where(:is_filter => true, :type => "#{klass.name}CustomField").each do |chained| | |
|
1178 | options = chained.query_filter_options(self) | |
|
1179 | ||
|
1180 | filter_id = "cf_#{field.id}.cf_#{chained.id}" | |
|
1181 | filter_name = chained.name | |
|
1182 | ||
|
1183 | add_available_filter filter_id, options.merge({ | |
|
1184 | :name => l(:label_attribute_of_object, :name => chained.name, :object_name => field.name), | |
|
1185 | :field => chained, | |
|
1186 | :through => field | |
|
1187 | }) | |
|
1188 | end | |
|
1189 | end | |
|
1190 | end | |
|
1191 | ||
|
1127 | 1192 | # Adds filters for the given custom fields scope |
|
1128 | 1193 | def add_custom_fields_filters(scope, assoc=nil) |
|
1129 | 1194 | scope.visible.where(:is_filter => true).sorted.each do |field| |
|
1130 | 1195 | add_custom_field_filter(field, assoc) |
|
1196 | if assoc.nil? | |
|
1197 | add_chained_custom_field_filters(field) | |
|
1198 | ||
|
1199 | if field.format.target_class && field.format.target_class == Version | |
|
1200 | add_available_filter "cf_#{field.id}.due_date", | |
|
1201 | :type => :date, | |
|
1202 | :field => field, | |
|
1203 | :name => l(:label_attribute_of_object, :name => l(:field_effective_date), :object_name => field.name) | |
|
1204 | ||
|
1205 | add_available_filter "cf_#{field.id}.status", | |
|
1206 | :type => :list, | |
|
1207 | :field => field, | |
|
1208 | :name => l(:label_attribute_of_object, :name => l(:field_status), :object_name => field.name), | |
|
1209 | :values => Version::VERSION_STATUSES.map{|s| [l("version_status_#{s}"), s] } | |
|
1210 | end | |
|
1211 | end | |
|
1131 | 1212 | end |
|
1132 | 1213 | end |
|
1133 | 1214 |
@@ -948,6 +948,7 en: | |||
|
948 | 948 | label_attribute_of_assigned_to: "Assignee's %{name}" |
|
949 | 949 | label_attribute_of_user: "User's %{name}" |
|
950 | 950 | label_attribute_of_fixed_version: "Target version's %{name}" |
|
951 | label_attribute_of_object: "%{object_name}'s %{name}" | |
|
951 | 952 | label_cross_project_descendants: With subprojects |
|
952 | 953 | label_cross_project_tree: With project tree |
|
953 | 954 | label_cross_project_hierarchy: With project hierarchy |
@@ -959,6 +959,7 fr: | |||
|
959 | 959 | label_attribute_of_assigned_to: "%{name} de l'assignΓ©" |
|
960 | 960 | label_attribute_of_user: "%{name} de l'utilisateur" |
|
961 | 961 | label_attribute_of_fixed_version: "%{name} de la version cible" |
|
962 | label_attribute_of_object: "%{name} de \"%{object_name}\"" | |
|
962 | 963 | label_cross_project_descendants: Avec les sous-projets |
|
963 | 964 | label_cross_project_tree: Avec tout l'arbre |
|
964 | 965 | label_cross_project_hierarchy: Avec toute la hiΓ©rarchie |
@@ -877,6 +877,49 class QueryTest < ActiveSupport::TestCase | |||
|
877 | 877 | assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort |
|
878 | 878 | end |
|
879 | 879 | |
|
880 | def test_filter_on_version_custom_field | |
|
881 | field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true) | |
|
882 | issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => '2'}) | |
|
883 | ||
|
884 | query = IssueQuery.new(:name => '_') | |
|
885 | filter_name = "cf_#{field.id}" | |
|
886 | assert_include filter_name, query.available_filters.keys | |
|
887 | ||
|
888 | query.filters = {filter_name => {:operator => '=', :values => ['2']}} | |
|
889 | issues = find_issues_with_query(query) | |
|
890 | assert_equal [issue.id], issues.map(&:id).sort | |
|
891 | end | |
|
892 | ||
|
893 | def test_filter_on_attribute_of_version_custom_field | |
|
894 | field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true) | |
|
895 | version = Version.generate!(:effective_date => '2017-01-14') | |
|
896 | issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s}) | |
|
897 | ||
|
898 | query = IssueQuery.new(:name => '_') | |
|
899 | filter_name = "cf_#{field.id}.due_date" | |
|
900 | assert_include filter_name, query.available_filters.keys | |
|
901 | ||
|
902 | query.filters = {filter_name => {:operator => '=', :values => ['2017-01-14']}} | |
|
903 | issues = find_issues_with_query(query) | |
|
904 | assert_equal [issue.id], issues.map(&:id).sort | |
|
905 | end | |
|
906 | ||
|
907 | def test_filter_on_custom_field_of_version_custom_field | |
|
908 | field = IssueCustomField.generate!(:field_format => 'version', :is_filter => true) | |
|
909 | attr = VersionCustomField.generate!(:field_format => 'string', :is_filter => true) | |
|
910 | ||
|
911 | version = Version.generate!(:custom_field_values => {attr.id.to_s => 'ABC'}) | |
|
912 | issue = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => version.id.to_s}) | |
|
913 | ||
|
914 | query = IssueQuery.new(:name => '_') | |
|
915 | filter_name = "cf_#{field.id}.cf_#{attr.id}" | |
|
916 | assert_include filter_name, query.available_filters.keys | |
|
917 | ||
|
918 | query.filters = {filter_name => {:operator => '=', :values => ['ABC']}} | |
|
919 | issues = find_issues_with_query(query) | |
|
920 | assert_equal [issue.id], issues.map(&:id).sort | |
|
921 | end | |
|
922 | ||
|
880 | 923 | def test_filter_on_relations_with_a_specific_issue |
|
881 | 924 | IssueRelation.delete_all |
|
882 | 925 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
General Comments 0
You need to be logged in to leave comments.
Login now