@@ -28,6 +28,8 module QueriesHelper | |||||
28 | group = :label_relations |
|
28 | group = :label_relations | |
29 | elsif field_options[:type] == :tree |
|
29 | elsif field_options[:type] == :tree | |
30 | group = query.is_a?(IssueQuery) ? :label_relations : nil |
|
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 | elsif field =~ /^(.+)\./ |
|
33 | elsif field =~ /^(.+)\./ | |
32 | # association filters |
|
34 | # association filters | |
33 | group = "field_#{$1}".to_sym |
|
35 | group = "field_#{$1}".to_sym | |
@@ -48,7 +50,7 module QueriesHelper | |||||
48 | end |
|
50 | end | |
49 | s = options_for_select([[]] + ungrouped) |
|
51 | s = options_for_select([[]] + ungrouped) | |
50 | if grouped.present? |
|
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 | s << grouped_options_for_select(localized_grouped) |
|
54 | s << grouped_options_for_select(localized_grouped) | |
53 | end |
|
55 | end | |
54 | s |
|
56 | s |
@@ -808,9 +808,13 class Query < ActiveRecord::Base | |||||
808 | end |
|
808 | end | |
809 | end |
|
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 | # custom field |
|
814 | # custom field | |
813 | filters_clauses << sql_for_custom_field(field, operator, v, $1) |
|
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 | elsif respond_to?(method = "sql_for_#{field.gsub('.','_')}_field") |
|
818 | elsif respond_to?(method = "sql_for_#{field.gsub('.','_')}_field") | |
815 | # specific statement |
|
819 | # specific statement | |
816 | filters_clauses << send(method, field, operator, v) |
|
820 | filters_clauses << send(method, field, operator, v) | |
@@ -951,6 +955,46 class Query < ActiveRecord::Base | |||||
951 | " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" |
|
955 | " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))" | |
952 | end |
|
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 | # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ |
|
998 | # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ | |
955 | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) |
|
999 | def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) | |
956 | sql = '' |
|
1000 | sql = '' | |
@@ -1124,10 +1168,47 class Query < ActiveRecord::Base | |||||
1124 | }) |
|
1168 | }) | |
1125 | end |
|
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 | # Adds filters for the given custom fields scope |
|
1192 | # Adds filters for the given custom fields scope | |
1128 | def add_custom_fields_filters(scope, assoc=nil) |
|
1193 | def add_custom_fields_filters(scope, assoc=nil) | |
1129 | scope.visible.where(:is_filter => true).sorted.each do |field| |
|
1194 | scope.visible.where(:is_filter => true).sorted.each do |field| | |
1130 | add_custom_field_filter(field, assoc) |
|
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 | end |
|
1212 | end | |
1132 | end |
|
1213 | end | |
1133 |
|
1214 |
@@ -948,6 +948,7 en: | |||||
948 | label_attribute_of_assigned_to: "Assignee's %{name}" |
|
948 | label_attribute_of_assigned_to: "Assignee's %{name}" | |
949 | label_attribute_of_user: "User's %{name}" |
|
949 | label_attribute_of_user: "User's %{name}" | |
950 | label_attribute_of_fixed_version: "Target version's %{name}" |
|
950 | label_attribute_of_fixed_version: "Target version's %{name}" | |
|
951 | label_attribute_of_object: "%{object_name}'s %{name}" | |||
951 | label_cross_project_descendants: With subprojects |
|
952 | label_cross_project_descendants: With subprojects | |
952 | label_cross_project_tree: With project tree |
|
953 | label_cross_project_tree: With project tree | |
953 | label_cross_project_hierarchy: With project hierarchy |
|
954 | label_cross_project_hierarchy: With project hierarchy |
@@ -959,6 +959,7 fr: | |||||
959 | label_attribute_of_assigned_to: "%{name} de l'assignΓ©" |
|
959 | label_attribute_of_assigned_to: "%{name} de l'assignΓ©" | |
960 | label_attribute_of_user: "%{name} de l'utilisateur" |
|
960 | label_attribute_of_user: "%{name} de l'utilisateur" | |
961 | label_attribute_of_fixed_version: "%{name} de la version cible" |
|
961 | label_attribute_of_fixed_version: "%{name} de la version cible" | |
|
962 | label_attribute_of_object: "%{name} de \"%{object_name}\"" | |||
962 | label_cross_project_descendants: Avec les sous-projets |
|
963 | label_cross_project_descendants: Avec les sous-projets | |
963 | label_cross_project_tree: Avec tout l'arbre |
|
964 | label_cross_project_tree: Avec tout l'arbre | |
964 | label_cross_project_hierarchy: Avec toute la hiΓ©rarchie |
|
965 | label_cross_project_hierarchy: Avec toute la hiΓ©rarchie |
@@ -877,6 +877,49 class QueryTest < ActiveSupport::TestCase | |||||
877 | assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort |
|
877 | assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort | |
878 | end |
|
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 | def test_filter_on_relations_with_a_specific_issue |
|
923 | def test_filter_on_relations_with_a_specific_issue | |
881 | IssueRelation.delete_all |
|
924 | IssueRelation.delete_all | |
882 | IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2)) |
|
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