##// END OF EJS Templates
Let QueryColumn accept a Proc as caption (#18276)....
Jean-Philippe Lang -
r13176:bc3bd8aff2a7
parent child
Show More
@@ -1,884 +1,891
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 class QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @inline = options.key?(:inline) ? options[:inline] : true
30 @inline = options.key?(:inline) ? options[:inline] : true
31 @caption_key = options[:caption] || "field_#{name}".to_sym
31 @caption_key = options[:caption] || "field_#{name}".to_sym
32 @frozen = options[:frozen]
32 @frozen = options[:frozen]
33 end
33 end
34
34
35 def caption
35 def caption
36 @caption_key.is_a?(Symbol) ? l(@caption_key) : @caption_key
36 case @caption_key
37 when Symbol
38 l(@caption_key)
39 when Proc
40 @caption_key.call
41 else
42 @caption_key
43 end
37 end
44 end
38
45
39 # Returns true if the column is sortable, otherwise false
46 # Returns true if the column is sortable, otherwise false
40 def sortable?
47 def sortable?
41 !@sortable.nil?
48 !@sortable.nil?
42 end
49 end
43
50
44 def sortable
51 def sortable
45 @sortable.is_a?(Proc) ? @sortable.call : @sortable
52 @sortable.is_a?(Proc) ? @sortable.call : @sortable
46 end
53 end
47
54
48 def inline?
55 def inline?
49 @inline
56 @inline
50 end
57 end
51
58
52 def frozen?
59 def frozen?
53 @frozen
60 @frozen
54 end
61 end
55
62
56 def value(object)
63 def value(object)
57 object.send name
64 object.send name
58 end
65 end
59
66
60 def value_object(object)
67 def value_object(object)
61 object.send name
68 object.send name
62 end
69 end
63
70
64 def css_classes
71 def css_classes
65 name
72 name
66 end
73 end
67 end
74 end
68
75
69 class QueryCustomFieldColumn < QueryColumn
76 class QueryCustomFieldColumn < QueryColumn
70
77
71 def initialize(custom_field)
78 def initialize(custom_field)
72 self.name = "cf_#{custom_field.id}".to_sym
79 self.name = "cf_#{custom_field.id}".to_sym
73 self.sortable = custom_field.order_statement || false
80 self.sortable = custom_field.order_statement || false
74 self.groupable = custom_field.group_statement || false
81 self.groupable = custom_field.group_statement || false
75 @inline = true
82 @inline = true
76 @cf = custom_field
83 @cf = custom_field
77 end
84 end
78
85
79 def caption
86 def caption
80 @cf.name
87 @cf.name
81 end
88 end
82
89
83 def custom_field
90 def custom_field
84 @cf
91 @cf
85 end
92 end
86
93
87 def value_object(object)
94 def value_object(object)
88 if custom_field.visible_by?(object.project, User.current)
95 if custom_field.visible_by?(object.project, User.current)
89 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
96 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}
90 cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first
97 cv.size > 1 ? cv.sort {|a,b| a.value.to_s <=> b.value.to_s} : cv.first
91 else
98 else
92 nil
99 nil
93 end
100 end
94 end
101 end
95
102
96 def value(object)
103 def value(object)
97 raw = value_object(object)
104 raw = value_object(object)
98 if raw.is_a?(Array)
105 if raw.is_a?(Array)
99 raw.map {|r| @cf.cast_value(r.value)}
106 raw.map {|r| @cf.cast_value(r.value)}
100 elsif raw
107 elsif raw
101 @cf.cast_value(raw.value)
108 @cf.cast_value(raw.value)
102 else
109 else
103 nil
110 nil
104 end
111 end
105 end
112 end
106
113
107 def css_classes
114 def css_classes
108 @css_classes ||= "#{name} #{@cf.field_format}"
115 @css_classes ||= "#{name} #{@cf.field_format}"
109 end
116 end
110 end
117 end
111
118
112 class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
119 class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
113
120
114 def initialize(association, custom_field)
121 def initialize(association, custom_field)
115 super(custom_field)
122 super(custom_field)
116 self.name = "#{association}.cf_#{custom_field.id}".to_sym
123 self.name = "#{association}.cf_#{custom_field.id}".to_sym
117 # TODO: support sorting/grouping by association custom field
124 # TODO: support sorting/grouping by association custom field
118 self.sortable = false
125 self.sortable = false
119 self.groupable = false
126 self.groupable = false
120 @association = association
127 @association = association
121 end
128 end
122
129
123 def value_object(object)
130 def value_object(object)
124 if assoc = object.send(@association)
131 if assoc = object.send(@association)
125 super(assoc)
132 super(assoc)
126 end
133 end
127 end
134 end
128
135
129 def css_classes
136 def css_classes
130 @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
137 @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
131 end
138 end
132 end
139 end
133
140
134 class Query < ActiveRecord::Base
141 class Query < ActiveRecord::Base
135 class StatementInvalid < ::ActiveRecord::StatementInvalid
142 class StatementInvalid < ::ActiveRecord::StatementInvalid
136 end
143 end
137
144
138 VISIBILITY_PRIVATE = 0
145 VISIBILITY_PRIVATE = 0
139 VISIBILITY_ROLES = 1
146 VISIBILITY_ROLES = 1
140 VISIBILITY_PUBLIC = 2
147 VISIBILITY_PUBLIC = 2
141
148
142 belongs_to :project
149 belongs_to :project
143 belongs_to :user
150 belongs_to :user
144 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
151 has_and_belongs_to_many :roles, :join_table => "#{table_name_prefix}queries_roles#{table_name_suffix}", :foreign_key => "query_id"
145 serialize :filters
152 serialize :filters
146 serialize :column_names
153 serialize :column_names
147 serialize :sort_criteria, Array
154 serialize :sort_criteria, Array
148 serialize :options, Hash
155 serialize :options, Hash
149
156
150 attr_protected :project_id, :user_id
157 attr_protected :project_id, :user_id
151
158
152 validates_presence_of :name
159 validates_presence_of :name
153 validates_length_of :name, :maximum => 255
160 validates_length_of :name, :maximum => 255
154 validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] }
161 validates :visibility, :inclusion => { :in => [VISIBILITY_PUBLIC, VISIBILITY_ROLES, VISIBILITY_PRIVATE] }
155 validate :validate_query_filters
162 validate :validate_query_filters
156 validate do |query|
163 validate do |query|
157 errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
164 errors.add(:base, l(:label_role_plural) + ' ' + l('activerecord.errors.messages.blank')) if query.visibility == VISIBILITY_ROLES && roles.blank?
158 end
165 end
159
166
160 after_save do |query|
167 after_save do |query|
161 if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
168 if query.visibility_changed? && query.visibility != VISIBILITY_ROLES
162 query.roles.clear
169 query.roles.clear
163 end
170 end
164 end
171 end
165
172
166 class_attribute :operators
173 class_attribute :operators
167 self.operators = {
174 self.operators = {
168 "=" => :label_equals,
175 "=" => :label_equals,
169 "!" => :label_not_equals,
176 "!" => :label_not_equals,
170 "o" => :label_open_issues,
177 "o" => :label_open_issues,
171 "c" => :label_closed_issues,
178 "c" => :label_closed_issues,
172 "!*" => :label_none,
179 "!*" => :label_none,
173 "*" => :label_any,
180 "*" => :label_any,
174 ">=" => :label_greater_or_equal,
181 ">=" => :label_greater_or_equal,
175 "<=" => :label_less_or_equal,
182 "<=" => :label_less_or_equal,
176 "><" => :label_between,
183 "><" => :label_between,
177 "<t+" => :label_in_less_than,
184 "<t+" => :label_in_less_than,
178 ">t+" => :label_in_more_than,
185 ">t+" => :label_in_more_than,
179 "><t+"=> :label_in_the_next_days,
186 "><t+"=> :label_in_the_next_days,
180 "t+" => :label_in,
187 "t+" => :label_in,
181 "t" => :label_today,
188 "t" => :label_today,
182 "ld" => :label_yesterday,
189 "ld" => :label_yesterday,
183 "w" => :label_this_week,
190 "w" => :label_this_week,
184 "lw" => :label_last_week,
191 "lw" => :label_last_week,
185 "l2w" => [:label_last_n_weeks, {:count => 2}],
192 "l2w" => [:label_last_n_weeks, {:count => 2}],
186 "m" => :label_this_month,
193 "m" => :label_this_month,
187 "lm" => :label_last_month,
194 "lm" => :label_last_month,
188 "y" => :label_this_year,
195 "y" => :label_this_year,
189 ">t-" => :label_less_than_ago,
196 ">t-" => :label_less_than_ago,
190 "<t-" => :label_more_than_ago,
197 "<t-" => :label_more_than_ago,
191 "><t-"=> :label_in_the_past_days,
198 "><t-"=> :label_in_the_past_days,
192 "t-" => :label_ago,
199 "t-" => :label_ago,
193 "~" => :label_contains,
200 "~" => :label_contains,
194 "!~" => :label_not_contains,
201 "!~" => :label_not_contains,
195 "=p" => :label_any_issues_in_project,
202 "=p" => :label_any_issues_in_project,
196 "=!p" => :label_any_issues_not_in_project,
203 "=!p" => :label_any_issues_not_in_project,
197 "!p" => :label_no_issues_in_project
204 "!p" => :label_no_issues_in_project
198 }
205 }
199
206
200 class_attribute :operators_by_filter_type
207 class_attribute :operators_by_filter_type
201 self.operators_by_filter_type = {
208 self.operators_by_filter_type = {
202 :list => [ "=", "!" ],
209 :list => [ "=", "!" ],
203 :list_status => [ "o", "=", "!", "c", "*" ],
210 :list_status => [ "o", "=", "!", "c", "*" ],
204 :list_optional => [ "=", "!", "!*", "*" ],
211 :list_optional => [ "=", "!", "!*", "*" ],
205 :list_subprojects => [ "*", "!*", "=" ],
212 :list_subprojects => [ "*", "!*", "=" ],
206 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
213 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
207 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
214 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
208 :string => [ "=", "~", "!", "!~", "!*", "*" ],
215 :string => [ "=", "~", "!", "!~", "!*", "*" ],
209 :text => [ "~", "!~", "!*", "*" ],
216 :text => [ "~", "!~", "!*", "*" ],
210 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
217 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
211 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
218 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
212 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
219 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
213 }
220 }
214
221
215 class_attribute :available_columns
222 class_attribute :available_columns
216 self.available_columns = []
223 self.available_columns = []
217
224
218 class_attribute :queried_class
225 class_attribute :queried_class
219
226
220 def queried_table_name
227 def queried_table_name
221 @queried_table_name ||= self.class.queried_class.table_name
228 @queried_table_name ||= self.class.queried_class.table_name
222 end
229 end
223
230
224 def initialize(attributes=nil, *args)
231 def initialize(attributes=nil, *args)
225 super attributes
232 super attributes
226 @is_for_all = project.nil?
233 @is_for_all = project.nil?
227 end
234 end
228
235
229 # Builds the query from the given params
236 # Builds the query from the given params
230 def build_from_params(params)
237 def build_from_params(params)
231 if params[:fields] || params[:f]
238 if params[:fields] || params[:f]
232 self.filters = {}
239 self.filters = {}
233 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
240 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
234 else
241 else
235 available_filters.keys.each do |field|
242 available_filters.keys.each do |field|
236 add_short_filter(field, params[field]) if params[field]
243 add_short_filter(field, params[field]) if params[field]
237 end
244 end
238 end
245 end
239 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
246 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
240 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
247 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
241 self
248 self
242 end
249 end
243
250
244 # Builds a new query from the given params and attributes
251 # Builds a new query from the given params and attributes
245 def self.build_from_params(params, attributes={})
252 def self.build_from_params(params, attributes={})
246 new(attributes).build_from_params(params)
253 new(attributes).build_from_params(params)
247 end
254 end
248
255
249 def validate_query_filters
256 def validate_query_filters
250 filters.each_key do |field|
257 filters.each_key do |field|
251 if values_for(field)
258 if values_for(field)
252 case type_for(field)
259 case type_for(field)
253 when :integer
260 when :integer
254 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
261 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
255 when :float
262 when :float
256 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
263 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
257 when :date, :date_past
264 when :date, :date_past
258 case operator_for(field)
265 case operator_for(field)
259 when "=", ">=", "<=", "><"
266 when "=", ">=", "<=", "><"
260 add_filter_error(field, :invalid) if values_for(field).detect {|v|
267 add_filter_error(field, :invalid) if values_for(field).detect {|v|
261 v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?)
268 v.present? && (!v.match(/\A\d{4}-\d{2}-\d{2}(T\d{2}((:)?\d{2}){0,2}(Z|\d{2}:?\d{2})?)?\z/) || parse_date(v).nil?)
262 }
269 }
263 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
270 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
264 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
271 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
265 end
272 end
266 end
273 end
267 end
274 end
268
275
269 add_filter_error(field, :blank) unless
276 add_filter_error(field, :blank) unless
270 # filter requires one or more values
277 # filter requires one or more values
271 (values_for(field) and !values_for(field).first.blank?) or
278 (values_for(field) and !values_for(field).first.blank?) or
272 # filter doesn't require any value
279 # filter doesn't require any value
273 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
280 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
274 end if filters
281 end if filters
275 end
282 end
276
283
277 def add_filter_error(field, message)
284 def add_filter_error(field, message)
278 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
285 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
279 errors.add(:base, m)
286 errors.add(:base, m)
280 end
287 end
281
288
282 def editable_by?(user)
289 def editable_by?(user)
283 return false unless user
290 return false unless user
284 # Admin can edit them all and regular users can edit their private queries
291 # Admin can edit them all and regular users can edit their private queries
285 return true if user.admin? || (is_private? && self.user_id == user.id)
292 return true if user.admin? || (is_private? && self.user_id == user.id)
286 # Members can not edit public queries that are for all project (only admin is allowed to)
293 # Members can not edit public queries that are for all project (only admin is allowed to)
287 is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
294 is_public? && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
288 end
295 end
289
296
290 def trackers
297 def trackers
291 @trackers ||= project.nil? ? Tracker.sorted.to_a : project.rolled_up_trackers
298 @trackers ||= project.nil? ? Tracker.sorted.to_a : project.rolled_up_trackers
292 end
299 end
293
300
294 # Returns a hash of localized labels for all filter operators
301 # Returns a hash of localized labels for all filter operators
295 def self.operators_labels
302 def self.operators_labels
296 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
303 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
297 end
304 end
298
305
299 # Returns a representation of the available filters for JSON serialization
306 # Returns a representation of the available filters for JSON serialization
300 def available_filters_as_json
307 def available_filters_as_json
301 json = {}
308 json = {}
302 available_filters.each do |field, options|
309 available_filters.each do |field, options|
303 json[field] = options.slice(:type, :name, :values).stringify_keys
310 json[field] = options.slice(:type, :name, :values).stringify_keys
304 end
311 end
305 json
312 json
306 end
313 end
307
314
308 def all_projects
315 def all_projects
309 @all_projects ||= Project.visible.to_a
316 @all_projects ||= Project.visible.to_a
310 end
317 end
311
318
312 def all_projects_values
319 def all_projects_values
313 return @all_projects_values if @all_projects_values
320 return @all_projects_values if @all_projects_values
314
321
315 values = []
322 values = []
316 Project.project_tree(all_projects) do |p, level|
323 Project.project_tree(all_projects) do |p, level|
317 prefix = (level > 0 ? ('--' * level + ' ') : '')
324 prefix = (level > 0 ? ('--' * level + ' ') : '')
318 values << ["#{prefix}#{p.name}", p.id.to_s]
325 values << ["#{prefix}#{p.name}", p.id.to_s]
319 end
326 end
320 @all_projects_values = values
327 @all_projects_values = values
321 end
328 end
322
329
323 # Adds available filters
330 # Adds available filters
324 def initialize_available_filters
331 def initialize_available_filters
325 # implemented by sub-classes
332 # implemented by sub-classes
326 end
333 end
327 protected :initialize_available_filters
334 protected :initialize_available_filters
328
335
329 # Adds an available filter
336 # Adds an available filter
330 def add_available_filter(field, options)
337 def add_available_filter(field, options)
331 @available_filters ||= ActiveSupport::OrderedHash.new
338 @available_filters ||= ActiveSupport::OrderedHash.new
332 @available_filters[field] = options
339 @available_filters[field] = options
333 @available_filters
340 @available_filters
334 end
341 end
335
342
336 # Removes an available filter
343 # Removes an available filter
337 def delete_available_filter(field)
344 def delete_available_filter(field)
338 if @available_filters
345 if @available_filters
339 @available_filters.delete(field)
346 @available_filters.delete(field)
340 end
347 end
341 end
348 end
342
349
343 # Return a hash of available filters
350 # Return a hash of available filters
344 def available_filters
351 def available_filters
345 unless @available_filters
352 unless @available_filters
346 initialize_available_filters
353 initialize_available_filters
347 @available_filters.each do |field, options|
354 @available_filters.each do |field, options|
348 options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
355 options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
349 end
356 end
350 end
357 end
351 @available_filters
358 @available_filters
352 end
359 end
353
360
354 def add_filter(field, operator, values=nil)
361 def add_filter(field, operator, values=nil)
355 # values must be an array
362 # values must be an array
356 return unless values.nil? || values.is_a?(Array)
363 return unless values.nil? || values.is_a?(Array)
357 # check if field is defined as an available filter
364 # check if field is defined as an available filter
358 if available_filters.has_key? field
365 if available_filters.has_key? field
359 filter_options = available_filters[field]
366 filter_options = available_filters[field]
360 filters[field] = {:operator => operator, :values => (values || [''])}
367 filters[field] = {:operator => operator, :values => (values || [''])}
361 end
368 end
362 end
369 end
363
370
364 def add_short_filter(field, expression)
371 def add_short_filter(field, expression)
365 return unless expression && available_filters.has_key?(field)
372 return unless expression && available_filters.has_key?(field)
366 field_type = available_filters[field][:type]
373 field_type = available_filters[field][:type]
367 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
374 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
368 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
375 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
369 values = $1
376 values = $1
370 add_filter field, operator, values.present? ? values.split('|') : ['']
377 add_filter field, operator, values.present? ? values.split('|') : ['']
371 end || add_filter(field, '=', expression.split('|'))
378 end || add_filter(field, '=', expression.split('|'))
372 end
379 end
373
380
374 # Add multiple filters using +add_filter+
381 # Add multiple filters using +add_filter+
375 def add_filters(fields, operators, values)
382 def add_filters(fields, operators, values)
376 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
383 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
377 fields.each do |field|
384 fields.each do |field|
378 add_filter(field, operators[field], values && values[field])
385 add_filter(field, operators[field], values && values[field])
379 end
386 end
380 end
387 end
381 end
388 end
382
389
383 def has_filter?(field)
390 def has_filter?(field)
384 filters and filters[field]
391 filters and filters[field]
385 end
392 end
386
393
387 def type_for(field)
394 def type_for(field)
388 available_filters[field][:type] if available_filters.has_key?(field)
395 available_filters[field][:type] if available_filters.has_key?(field)
389 end
396 end
390
397
391 def operator_for(field)
398 def operator_for(field)
392 has_filter?(field) ? filters[field][:operator] : nil
399 has_filter?(field) ? filters[field][:operator] : nil
393 end
400 end
394
401
395 def values_for(field)
402 def values_for(field)
396 has_filter?(field) ? filters[field][:values] : nil
403 has_filter?(field) ? filters[field][:values] : nil
397 end
404 end
398
405
399 def value_for(field, index=0)
406 def value_for(field, index=0)
400 (values_for(field) || [])[index]
407 (values_for(field) || [])[index]
401 end
408 end
402
409
403 def label_for(field)
410 def label_for(field)
404 label = available_filters[field][:name] if available_filters.has_key?(field)
411 label = available_filters[field][:name] if available_filters.has_key?(field)
405 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
412 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
406 end
413 end
407
414
408 def self.add_available_column(column)
415 def self.add_available_column(column)
409 self.available_columns << (column) if column.is_a?(QueryColumn)
416 self.available_columns << (column) if column.is_a?(QueryColumn)
410 end
417 end
411
418
412 # Returns an array of columns that can be used to group the results
419 # Returns an array of columns that can be used to group the results
413 def groupable_columns
420 def groupable_columns
414 available_columns.select {|c| c.groupable}
421 available_columns.select {|c| c.groupable}
415 end
422 end
416
423
417 # Returns a Hash of columns and the key for sorting
424 # Returns a Hash of columns and the key for sorting
418 def sortable_columns
425 def sortable_columns
419 available_columns.inject({}) {|h, column|
426 available_columns.inject({}) {|h, column|
420 h[column.name.to_s] = column.sortable
427 h[column.name.to_s] = column.sortable
421 h
428 h
422 }
429 }
423 end
430 end
424
431
425 def columns
432 def columns
426 # preserve the column_names order
433 # preserve the column_names order
427 cols = (has_default_columns? ? default_columns_names : column_names).collect do |name|
434 cols = (has_default_columns? ? default_columns_names : column_names).collect do |name|
428 available_columns.find { |col| col.name == name }
435 available_columns.find { |col| col.name == name }
429 end.compact
436 end.compact
430 available_columns.select(&:frozen?) | cols
437 available_columns.select(&:frozen?) | cols
431 end
438 end
432
439
433 def inline_columns
440 def inline_columns
434 columns.select(&:inline?)
441 columns.select(&:inline?)
435 end
442 end
436
443
437 def block_columns
444 def block_columns
438 columns.reject(&:inline?)
445 columns.reject(&:inline?)
439 end
446 end
440
447
441 def available_inline_columns
448 def available_inline_columns
442 available_columns.select(&:inline?)
449 available_columns.select(&:inline?)
443 end
450 end
444
451
445 def available_block_columns
452 def available_block_columns
446 available_columns.reject(&:inline?)
453 available_columns.reject(&:inline?)
447 end
454 end
448
455
449 def default_columns_names
456 def default_columns_names
450 []
457 []
451 end
458 end
452
459
453 def column_names=(names)
460 def column_names=(names)
454 if names
461 if names
455 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
462 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
456 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
463 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
457 # Set column_names to nil if default columns
464 # Set column_names to nil if default columns
458 if names == default_columns_names
465 if names == default_columns_names
459 names = nil
466 names = nil
460 end
467 end
461 end
468 end
462 write_attribute(:column_names, names)
469 write_attribute(:column_names, names)
463 end
470 end
464
471
465 def has_column?(column)
472 def has_column?(column)
466 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
473 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
467 end
474 end
468
475
469 def has_custom_field_column?
476 def has_custom_field_column?
470 columns.any? {|column| column.is_a? QueryCustomFieldColumn}
477 columns.any? {|column| column.is_a? QueryCustomFieldColumn}
471 end
478 end
472
479
473 def has_default_columns?
480 def has_default_columns?
474 column_names.nil? || column_names.empty?
481 column_names.nil? || column_names.empty?
475 end
482 end
476
483
477 def sort_criteria=(arg)
484 def sort_criteria=(arg)
478 c = []
485 c = []
479 if arg.is_a?(Hash)
486 if arg.is_a?(Hash)
480 arg = arg.keys.sort.collect {|k| arg[k]}
487 arg = arg.keys.sort.collect {|k| arg[k]}
481 end
488 end
482 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
489 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
483 write_attribute(:sort_criteria, c)
490 write_attribute(:sort_criteria, c)
484 end
491 end
485
492
486 def sort_criteria
493 def sort_criteria
487 read_attribute(:sort_criteria) || []
494 read_attribute(:sort_criteria) || []
488 end
495 end
489
496
490 def sort_criteria_key(arg)
497 def sort_criteria_key(arg)
491 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
498 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
492 end
499 end
493
500
494 def sort_criteria_order(arg)
501 def sort_criteria_order(arg)
495 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
502 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
496 end
503 end
497
504
498 def sort_criteria_order_for(key)
505 def sort_criteria_order_for(key)
499 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
506 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
500 end
507 end
501
508
502 # Returns the SQL sort order that should be prepended for grouping
509 # Returns the SQL sort order that should be prepended for grouping
503 def group_by_sort_order
510 def group_by_sort_order
504 if grouped? && (column = group_by_column)
511 if grouped? && (column = group_by_column)
505 order = sort_criteria_order_for(column.name) || column.default_order
512 order = sort_criteria_order_for(column.name) || column.default_order
506 column.sortable.is_a?(Array) ?
513 column.sortable.is_a?(Array) ?
507 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
514 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
508 "#{column.sortable} #{order}"
515 "#{column.sortable} #{order}"
509 end
516 end
510 end
517 end
511
518
512 # Returns true if the query is a grouped query
519 # Returns true if the query is a grouped query
513 def grouped?
520 def grouped?
514 !group_by_column.nil?
521 !group_by_column.nil?
515 end
522 end
516
523
517 def group_by_column
524 def group_by_column
518 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
525 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
519 end
526 end
520
527
521 def group_by_statement
528 def group_by_statement
522 group_by_column.try(:groupable)
529 group_by_column.try(:groupable)
523 end
530 end
524
531
525 def project_statement
532 def project_statement
526 project_clauses = []
533 project_clauses = []
527 if project && !project.descendants.active.empty?
534 if project && !project.descendants.active.empty?
528 ids = [project.id]
535 ids = [project.id]
529 if has_filter?("subproject_id")
536 if has_filter?("subproject_id")
530 case operator_for("subproject_id")
537 case operator_for("subproject_id")
531 when '='
538 when '='
532 # include the selected subprojects
539 # include the selected subprojects
533 ids += values_for("subproject_id").each(&:to_i)
540 ids += values_for("subproject_id").each(&:to_i)
534 when '!*'
541 when '!*'
535 # main project only
542 # main project only
536 else
543 else
537 # all subprojects
544 # all subprojects
538 ids += project.descendants.collect(&:id)
545 ids += project.descendants.collect(&:id)
539 end
546 end
540 elsif Setting.display_subprojects_issues?
547 elsif Setting.display_subprojects_issues?
541 ids += project.descendants.collect(&:id)
548 ids += project.descendants.collect(&:id)
542 end
549 end
543 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
550 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
544 elsif project
551 elsif project
545 project_clauses << "#{Project.table_name}.id = %d" % project.id
552 project_clauses << "#{Project.table_name}.id = %d" % project.id
546 end
553 end
547 project_clauses.any? ? project_clauses.join(' AND ') : nil
554 project_clauses.any? ? project_clauses.join(' AND ') : nil
548 end
555 end
549
556
550 def statement
557 def statement
551 # filters clauses
558 # filters clauses
552 filters_clauses = []
559 filters_clauses = []
553 filters.each_key do |field|
560 filters.each_key do |field|
554 next if field == "subproject_id"
561 next if field == "subproject_id"
555 v = values_for(field).clone
562 v = values_for(field).clone
556 next unless v and !v.empty?
563 next unless v and !v.empty?
557 operator = operator_for(field)
564 operator = operator_for(field)
558
565
559 # "me" value substitution
566 # "me" value substitution
560 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
567 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
561 if v.delete("me")
568 if v.delete("me")
562 if User.current.logged?
569 if User.current.logged?
563 v.push(User.current.id.to_s)
570 v.push(User.current.id.to_s)
564 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
571 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
565 else
572 else
566 v.push("0")
573 v.push("0")
567 end
574 end
568 end
575 end
569 end
576 end
570
577
571 if field == 'project_id'
578 if field == 'project_id'
572 if v.delete('mine')
579 if v.delete('mine')
573 v += User.current.memberships.map(&:project_id).map(&:to_s)
580 v += User.current.memberships.map(&:project_id).map(&:to_s)
574 end
581 end
575 end
582 end
576
583
577 if field =~ /cf_(\d+)$/
584 if field =~ /cf_(\d+)$/
578 # custom field
585 # custom field
579 filters_clauses << sql_for_custom_field(field, operator, v, $1)
586 filters_clauses << sql_for_custom_field(field, operator, v, $1)
580 elsif respond_to?("sql_for_#{field}_field")
587 elsif respond_to?("sql_for_#{field}_field")
581 # specific statement
588 # specific statement
582 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
589 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
583 else
590 else
584 # regular field
591 # regular field
585 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
592 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
586 end
593 end
587 end if filters and valid?
594 end if filters and valid?
588
595
589 if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
596 if (c = group_by_column) && c.is_a?(QueryCustomFieldColumn)
590 # Excludes results for which the grouped custom field is not visible
597 # Excludes results for which the grouped custom field is not visible
591 filters_clauses << c.custom_field.visibility_by_project_condition
598 filters_clauses << c.custom_field.visibility_by_project_condition
592 end
599 end
593
600
594 filters_clauses << project_statement
601 filters_clauses << project_statement
595 filters_clauses.reject!(&:blank?)
602 filters_clauses.reject!(&:blank?)
596
603
597 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
604 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
598 end
605 end
599
606
600 private
607 private
601
608
602 def sql_for_custom_field(field, operator, value, custom_field_id)
609 def sql_for_custom_field(field, operator, value, custom_field_id)
603 db_table = CustomValue.table_name
610 db_table = CustomValue.table_name
604 db_field = 'value'
611 db_field = 'value'
605 filter = @available_filters[field]
612 filter = @available_filters[field]
606 return nil unless filter
613 return nil unless filter
607 if filter[:field].format.target_class && filter[:field].format.target_class <= User
614 if filter[:field].format.target_class && filter[:field].format.target_class <= User
608 if value.delete('me')
615 if value.delete('me')
609 value.push User.current.id.to_s
616 value.push User.current.id.to_s
610 end
617 end
611 end
618 end
612 not_in = nil
619 not_in = nil
613 if operator == '!'
620 if operator == '!'
614 # Makes ! operator work for custom fields with multiple values
621 # Makes ! operator work for custom fields with multiple values
615 operator = '='
622 operator = '='
616 not_in = 'NOT'
623 not_in = 'NOT'
617 end
624 end
618 customized_key = "id"
625 customized_key = "id"
619 customized_class = queried_class
626 customized_class = queried_class
620 if field =~ /^(.+)\.cf_/
627 if field =~ /^(.+)\.cf_/
621 assoc = $1
628 assoc = $1
622 customized_key = "#{assoc}_id"
629 customized_key = "#{assoc}_id"
623 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
630 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
624 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
631 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
625 end
632 end
626 where = sql_for_field(field, operator, value, db_table, db_field, true)
633 where = sql_for_field(field, operator, value, db_table, db_field, true)
627 if operator =~ /[<>]/
634 if operator =~ /[<>]/
628 where = "(#{where}) AND #{db_table}.#{db_field} <> ''"
635 where = "(#{where}) AND #{db_table}.#{db_field} <> ''"
629 end
636 end
630 "#{queried_table_name}.#{customized_key} #{not_in} IN (" +
637 "#{queried_table_name}.#{customized_key} #{not_in} IN (" +
631 "SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name}" +
638 "SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name}" +
632 " 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}" +
639 " 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}" +
633 " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))"
640 " WHERE (#{where}) AND (#{filter[:field].visibility_by_project_condition}))"
634 end
641 end
635
642
636 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
643 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
637 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
644 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
638 sql = ''
645 sql = ''
639 case operator
646 case operator
640 when "="
647 when "="
641 if value.any?
648 if value.any?
642 case type_for(field)
649 case type_for(field)
643 when :date, :date_past
650 when :date, :date_past
644 sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
651 sql = date_clause(db_table, db_field, parse_date(value.first), parse_date(value.first))
645 when :integer
652 when :integer
646 if is_custom_filter
653 if is_custom_filter
647 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})"
654 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) = #{value.first.to_i})"
648 else
655 else
649 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
656 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
650 end
657 end
651 when :float
658 when :float
652 if is_custom_filter
659 if is_custom_filter
653 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
660 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5})"
654 else
661 else
655 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
662 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
656 end
663 end
657 else
664 else
658 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")"
665 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")"
659 end
666 end
660 else
667 else
661 # IN an empty set
668 # IN an empty set
662 sql = "1=0"
669 sql = "1=0"
663 end
670 end
664 when "!"
671 when "!"
665 if value.any?
672 if value.any?
666 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + "))"
673 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + "))"
667 else
674 else
668 # NOT IN an empty set
675 # NOT IN an empty set
669 sql = "1=1"
676 sql = "1=1"
670 end
677 end
671 when "!*"
678 when "!*"
672 sql = "#{db_table}.#{db_field} IS NULL"
679 sql = "#{db_table}.#{db_field} IS NULL"
673 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
680 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
674 when "*"
681 when "*"
675 sql = "#{db_table}.#{db_field} IS NOT NULL"
682 sql = "#{db_table}.#{db_field} IS NOT NULL"
676 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
683 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
677 when ">="
684 when ">="
678 if [:date, :date_past].include?(type_for(field))
685 if [:date, :date_past].include?(type_for(field))
679 sql = date_clause(db_table, db_field, parse_date(value.first), nil)
686 sql = date_clause(db_table, db_field, parse_date(value.first), nil)
680 else
687 else
681 if is_custom_filter
688 if is_custom_filter
682 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
689 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) >= #{value.first.to_f})"
683 else
690 else
684 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
691 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
685 end
692 end
686 end
693 end
687 when "<="
694 when "<="
688 if [:date, :date_past].include?(type_for(field))
695 if [:date, :date_past].include?(type_for(field))
689 sql = date_clause(db_table, db_field, nil, parse_date(value.first))
696 sql = date_clause(db_table, db_field, nil, parse_date(value.first))
690 else
697 else
691 if is_custom_filter
698 if is_custom_filter
692 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
699 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) <= #{value.first.to_f})"
693 else
700 else
694 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
701 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
695 end
702 end
696 end
703 end
697 when "><"
704 when "><"
698 if [:date, :date_past].include?(type_for(field))
705 if [:date, :date_past].include?(type_for(field))
699 sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
706 sql = date_clause(db_table, db_field, parse_date(value[0]), parse_date(value[1]))
700 else
707 else
701 if is_custom_filter
708 if is_custom_filter
702 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
709 sql = "(#{db_table}.#{db_field} <> '' AND CAST(CASE #{db_table}.#{db_field} WHEN '' THEN '0' ELSE #{db_table}.#{db_field} END AS decimal(30,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f})"
703 else
710 else
704 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
711 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
705 end
712 end
706 end
713 end
707 when "o"
714 when "o"
708 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id"
715 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false})" if field == "status_id"
709 when "c"
716 when "c"
710 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id"
717 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_true})" if field == "status_id"
711 when "><t-"
718 when "><t-"
712 # between today - n days and today
719 # between today - n days and today
713 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
720 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
714 when ">t-"
721 when ">t-"
715 # >= today - n days
722 # >= today - n days
716 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
723 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
717 when "<t-"
724 when "<t-"
718 # <= today - n days
725 # <= today - n days
719 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
726 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
720 when "t-"
727 when "t-"
721 # = n days in past
728 # = n days in past
722 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
729 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
723 when "><t+"
730 when "><t+"
724 # between today and today + n days
731 # between today and today + n days
725 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
732 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
726 when ">t+"
733 when ">t+"
727 # >= today + n days
734 # >= today + n days
728 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
735 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
729 when "<t+"
736 when "<t+"
730 # <= today + n days
737 # <= today + n days
731 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
738 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
732 when "t+"
739 when "t+"
733 # = today + n days
740 # = today + n days
734 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
741 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
735 when "t"
742 when "t"
736 # = today
743 # = today
737 sql = relative_date_clause(db_table, db_field, 0, 0)
744 sql = relative_date_clause(db_table, db_field, 0, 0)
738 when "ld"
745 when "ld"
739 # = yesterday
746 # = yesterday
740 sql = relative_date_clause(db_table, db_field, -1, -1)
747 sql = relative_date_clause(db_table, db_field, -1, -1)
741 when "w"
748 when "w"
742 # = this week
749 # = this week
743 first_day_of_week = l(:general_first_day_of_week).to_i
750 first_day_of_week = l(:general_first_day_of_week).to_i
744 day_of_week = Date.today.cwday
751 day_of_week = Date.today.cwday
745 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
752 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
746 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
753 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
747 when "lw"
754 when "lw"
748 # = last week
755 # = last week
749 first_day_of_week = l(:general_first_day_of_week).to_i
756 first_day_of_week = l(:general_first_day_of_week).to_i
750 day_of_week = Date.today.cwday
757 day_of_week = Date.today.cwday
751 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
758 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
752 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
759 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
753 when "l2w"
760 when "l2w"
754 # = last 2 weeks
761 # = last 2 weeks
755 first_day_of_week = l(:general_first_day_of_week).to_i
762 first_day_of_week = l(:general_first_day_of_week).to_i
756 day_of_week = Date.today.cwday
763 day_of_week = Date.today.cwday
757 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
764 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
758 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
765 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
759 when "m"
766 when "m"
760 # = this month
767 # = this month
761 date = Date.today
768 date = Date.today
762 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
769 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
763 when "lm"
770 when "lm"
764 # = last month
771 # = last month
765 date = Date.today.prev_month
772 date = Date.today.prev_month
766 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
773 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
767 when "y"
774 when "y"
768 # = this year
775 # = this year
769 date = Date.today
776 date = Date.today
770 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
777 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
771 when "~"
778 when "~"
772 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
779 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
773 when "!~"
780 when "!~"
774 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
781 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{self.class.connection.quote_string(value.first.to_s.downcase)}%'"
775 else
782 else
776 raise "Unknown query operator #{operator}"
783 raise "Unknown query operator #{operator}"
777 end
784 end
778
785
779 return sql
786 return sql
780 end
787 end
781
788
782 # Adds a filter for the given custom field
789 # Adds a filter for the given custom field
783 def add_custom_field_filter(field, assoc=nil)
790 def add_custom_field_filter(field, assoc=nil)
784 options = field.format.query_filter_options(field, self)
791 options = field.format.query_filter_options(field, self)
785 if field.format.target_class && field.format.target_class <= User
792 if field.format.target_class && field.format.target_class <= User
786 if options[:values].is_a?(Array) && User.current.logged?
793 if options[:values].is_a?(Array) && User.current.logged?
787 options[:values].unshift ["<< #{l(:label_me)} >>", "me"]
794 options[:values].unshift ["<< #{l(:label_me)} >>", "me"]
788 end
795 end
789 end
796 end
790
797
791 filter_id = "cf_#{field.id}"
798 filter_id = "cf_#{field.id}"
792 filter_name = field.name
799 filter_name = field.name
793 if assoc.present?
800 if assoc.present?
794 filter_id = "#{assoc}.#{filter_id}"
801 filter_id = "#{assoc}.#{filter_id}"
795 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
802 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
796 end
803 end
797 add_available_filter filter_id, options.merge({
804 add_available_filter filter_id, options.merge({
798 :name => filter_name,
805 :name => filter_name,
799 :field => field
806 :field => field
800 })
807 })
801 end
808 end
802
809
803 # Adds filters for the given custom fields scope
810 # Adds filters for the given custom fields scope
804 def add_custom_fields_filters(scope, assoc=nil)
811 def add_custom_fields_filters(scope, assoc=nil)
805 scope.visible.where(:is_filter => true).sorted.each do |field|
812 scope.visible.where(:is_filter => true).sorted.each do |field|
806 add_custom_field_filter(field, assoc)
813 add_custom_field_filter(field, assoc)
807 end
814 end
808 end
815 end
809
816
810 # Adds filters for the given associations custom fields
817 # Adds filters for the given associations custom fields
811 def add_associations_custom_fields_filters(*associations)
818 def add_associations_custom_fields_filters(*associations)
812 fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class)
819 fields_by_class = CustomField.visible.where(:is_filter => true).group_by(&:class)
813 associations.each do |assoc|
820 associations.each do |assoc|
814 association_klass = queried_class.reflect_on_association(assoc).klass
821 association_klass = queried_class.reflect_on_association(assoc).klass
815 fields_by_class.each do |field_class, fields|
822 fields_by_class.each do |field_class, fields|
816 if field_class.customized_class <= association_klass
823 if field_class.customized_class <= association_klass
817 fields.sort.each do |field|
824 fields.sort.each do |field|
818 add_custom_field_filter(field, assoc)
825 add_custom_field_filter(field, assoc)
819 end
826 end
820 end
827 end
821 end
828 end
822 end
829 end
823 end
830 end
824
831
825 # Returns a SQL clause for a date or datetime field.
832 # Returns a SQL clause for a date or datetime field.
826 def date_clause(table, field, from, to)
833 def date_clause(table, field, from, to)
827 s = []
834 s = []
828 if from
835 if from
829 if from.is_a?(Date)
836 if from.is_a?(Date)
830 from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
837 from = Time.local(from.year, from.month, from.day).yesterday.end_of_day
831 else
838 else
832 from = from - 1 # second
839 from = from - 1 # second
833 end
840 end
834 if self.class.default_timezone == :utc
841 if self.class.default_timezone == :utc
835 from = from.utc
842 from = from.utc
836 end
843 end
837 s << ("#{table}.#{field} > '%s'" % [self.class.connection.quoted_date(from)])
844 s << ("#{table}.#{field} > '%s'" % [self.class.connection.quoted_date(from)])
838 end
845 end
839 if to
846 if to
840 if to.is_a?(Date)
847 if to.is_a?(Date)
841 to = Time.local(to.year, to.month, to.day).end_of_day
848 to = Time.local(to.year, to.month, to.day).end_of_day
842 end
849 end
843 if self.class.default_timezone == :utc
850 if self.class.default_timezone == :utc
844 to = to.utc
851 to = to.utc
845 end
852 end
846 s << ("#{table}.#{field} <= '%s'" % [self.class.connection.quoted_date(to)])
853 s << ("#{table}.#{field} <= '%s'" % [self.class.connection.quoted_date(to)])
847 end
854 end
848 s.join(' AND ')
855 s.join(' AND ')
849 end
856 end
850
857
851 # Returns a SQL clause for a date or datetime field using relative dates.
858 # Returns a SQL clause for a date or datetime field using relative dates.
852 def relative_date_clause(table, field, days_from, days_to)
859 def relative_date_clause(table, field, days_from, days_to)
853 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
860 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
854 end
861 end
855
862
856 # Returns a Date or Time from the given filter value
863 # Returns a Date or Time from the given filter value
857 def parse_date(arg)
864 def parse_date(arg)
858 if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
865 if arg.to_s =~ /\A\d{4}-\d{2}-\d{2}T/
859 Time.parse(arg) rescue nil
866 Time.parse(arg) rescue nil
860 else
867 else
861 Date.parse(arg) rescue nil
868 Date.parse(arg) rescue nil
862 end
869 end
863 end
870 end
864
871
865 # Additional joins required for the given sort options
872 # Additional joins required for the given sort options
866 def joins_for_order_statement(order_options)
873 def joins_for_order_statement(order_options)
867 joins = []
874 joins = []
868
875
869 if order_options
876 if order_options
870 if order_options.include?('authors')
877 if order_options.include?('authors')
871 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
878 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
872 end
879 end
873 order_options.scan(/cf_\d+/).uniq.each do |name|
880 order_options.scan(/cf_\d+/).uniq.each do |name|
874 column = available_columns.detect {|c| c.name.to_s == name}
881 column = available_columns.detect {|c| c.name.to_s == name}
875 join = column && column.custom_field.join_for_order_statement
882 join = column && column.custom_field.join_for_order_statement
876 if join
883 if join
877 joins << join
884 joins << join
878 end
885 end
879 end
886 end
880 end
887 end
881
888
882 joins.any? ? joins.join(' ') : nil
889 joins.any? ? joins.join(' ') : nil
883 end
890 end
884 end
891 end
@@ -1,1418 +1,1428
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
2 # Copyright (C) 2006-2014 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 fixtures :projects, :enabled_modules, :users, :members,
23 fixtures :projects, :enabled_modules, :users, :members,
24 :member_roles, :roles, :trackers, :issue_statuses,
24 :member_roles, :roles, :trackers, :issue_statuses,
25 :issue_categories, :enumerations, :issues,
25 :issue_categories, :enumerations, :issues,
26 :watchers, :custom_fields, :custom_values, :versions,
26 :watchers, :custom_fields, :custom_values, :versions,
27 :queries,
27 :queries,
28 :projects_trackers,
28 :projects_trackers,
29 :custom_fields_trackers,
29 :custom_fields_trackers,
30 :workflows
30 :workflows
31
31
32 def test_query_with_roles_visibility_should_validate_roles
32 def test_query_with_roles_visibility_should_validate_roles
33 set_language_if_valid 'en'
33 set_language_if_valid 'en'
34 query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
34 query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
35 assert !query.save
35 assert !query.save
36 assert_include "Roles can't be blank", query.errors.full_messages
36 assert_include "Roles can't be blank", query.errors.full_messages
37 query.role_ids = [1, 2]
37 query.role_ids = [1, 2]
38 assert query.save
38 assert query.save
39 end
39 end
40
40
41 def test_changing_roles_visibility_should_clear_roles
41 def test_changing_roles_visibility_should_clear_roles
42 query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
42 query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
43 assert_equal 2, query.roles.count
43 assert_equal 2, query.roles.count
44
44
45 query.visibility = IssueQuery::VISIBILITY_PUBLIC
45 query.visibility = IssueQuery::VISIBILITY_PUBLIC
46 query.save!
46 query.save!
47 assert_equal 0, query.roles.count
47 assert_equal 0, query.roles.count
48 end
48 end
49
49
50 def test_available_filters_should_be_ordered
50 def test_available_filters_should_be_ordered
51 set_language_if_valid 'en'
51 set_language_if_valid 'en'
52 query = IssueQuery.new
52 query = IssueQuery.new
53 assert_equal 0, query.available_filters.keys.index('status_id')
53 assert_equal 0, query.available_filters.keys.index('status_id')
54 expected_order = [
54 expected_order = [
55 "Status",
55 "Status",
56 "Project",
56 "Project",
57 "Tracker",
57 "Tracker",
58 "Priority"
58 "Priority"
59 ]
59 ]
60 assert_equal expected_order,
60 assert_equal expected_order,
61 (query.available_filters.values.map{|v| v[:name]} & expected_order)
61 (query.available_filters.values.map{|v| v[:name]} & expected_order)
62 end
62 end
63
63
64 def test_available_filters_with_custom_fields_should_be_ordered
64 def test_available_filters_with_custom_fields_should_be_ordered
65 set_language_if_valid 'en'
65 set_language_if_valid 'en'
66 UserCustomField.create!(
66 UserCustomField.create!(
67 :name => 'order test', :field_format => 'string',
67 :name => 'order test', :field_format => 'string',
68 :is_for_all => true, :is_filter => true
68 :is_for_all => true, :is_filter => true
69 )
69 )
70 query = IssueQuery.new
70 query = IssueQuery.new
71 expected_order = [
71 expected_order = [
72 "Searchable field",
72 "Searchable field",
73 "Database",
73 "Database",
74 "Project's Development status",
74 "Project's Development status",
75 "Author's order test",
75 "Author's order test",
76 "Assignee's order test"
76 "Assignee's order test"
77 ]
77 ]
78 assert_equal expected_order,
78 assert_equal expected_order,
79 (query.available_filters.values.map{|v| v[:name]} & expected_order)
79 (query.available_filters.values.map{|v| v[:name]} & expected_order)
80 end
80 end
81
81
82 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
82 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
83 query = IssueQuery.new(:project => nil, :name => '_')
83 query = IssueQuery.new(:project => nil, :name => '_')
84 assert query.available_filters.has_key?('cf_1')
84 assert query.available_filters.has_key?('cf_1')
85 assert !query.available_filters.has_key?('cf_3')
85 assert !query.available_filters.has_key?('cf_3')
86 end
86 end
87
87
88 def test_system_shared_versions_should_be_available_in_global_queries
88 def test_system_shared_versions_should_be_available_in_global_queries
89 Version.find(2).update_attribute :sharing, 'system'
89 Version.find(2).update_attribute :sharing, 'system'
90 query = IssueQuery.new(:project => nil, :name => '_')
90 query = IssueQuery.new(:project => nil, :name => '_')
91 assert query.available_filters.has_key?('fixed_version_id')
91 assert query.available_filters.has_key?('fixed_version_id')
92 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
92 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
93 end
93 end
94
94
95 def test_project_filter_in_global_queries
95 def test_project_filter_in_global_queries
96 query = IssueQuery.new(:project => nil, :name => '_')
96 query = IssueQuery.new(:project => nil, :name => '_')
97 project_filter = query.available_filters["project_id"]
97 project_filter = query.available_filters["project_id"]
98 assert_not_nil project_filter
98 assert_not_nil project_filter
99 project_ids = project_filter[:values].map{|p| p[1]}
99 project_ids = project_filter[:values].map{|p| p[1]}
100 assert project_ids.include?("1") #public project
100 assert project_ids.include?("1") #public project
101 assert !project_ids.include?("2") #private project user cannot see
101 assert !project_ids.include?("2") #private project user cannot see
102 end
102 end
103
103
104 def find_issues_with_query(query)
104 def find_issues_with_query(query)
105 Issue.joins(:status, :tracker, :project, :priority).where(
105 Issue.joins(:status, :tracker, :project, :priority).where(
106 query.statement
106 query.statement
107 ).to_a
107 ).to_a
108 end
108 end
109
109
110 def assert_find_issues_with_query_is_successful(query)
110 def assert_find_issues_with_query_is_successful(query)
111 assert_nothing_raised do
111 assert_nothing_raised do
112 find_issues_with_query(query)
112 find_issues_with_query(query)
113 end
113 end
114 end
114 end
115
115
116 def assert_query_statement_includes(query, condition)
116 def assert_query_statement_includes(query, condition)
117 assert_include condition, query.statement
117 assert_include condition, query.statement
118 end
118 end
119
119
120 def assert_query_result(expected, query)
120 def assert_query_result(expected, query)
121 assert_nothing_raised do
121 assert_nothing_raised do
122 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
122 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
123 assert_equal expected.size, query.issue_count
123 assert_equal expected.size, query.issue_count
124 end
124 end
125 end
125 end
126
126
127 def test_query_should_allow_shared_versions_for_a_project_query
127 def test_query_should_allow_shared_versions_for_a_project_query
128 subproject_version = Version.find(4)
128 subproject_version = Version.find(4)
129 query = IssueQuery.new(:project => Project.find(1), :name => '_')
129 query = IssueQuery.new(:project => Project.find(1), :name => '_')
130 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
130 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
131
131
132 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
132 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
133 end
133 end
134
134
135 def test_query_with_multiple_custom_fields
135 def test_query_with_multiple_custom_fields
136 query = IssueQuery.find(1)
136 query = IssueQuery.find(1)
137 assert query.valid?
137 assert query.valid?
138 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
138 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
139 issues = find_issues_with_query(query)
139 issues = find_issues_with_query(query)
140 assert_equal 1, issues.length
140 assert_equal 1, issues.length
141 assert_equal Issue.find(3), issues.first
141 assert_equal Issue.find(3), issues.first
142 end
142 end
143
143
144 def test_operator_none
144 def test_operator_none
145 query = IssueQuery.new(:project => Project.find(1), :name => '_')
145 query = IssueQuery.new(:project => Project.find(1), :name => '_')
146 query.add_filter('fixed_version_id', '!*', [''])
146 query.add_filter('fixed_version_id', '!*', [''])
147 query.add_filter('cf_1', '!*', [''])
147 query.add_filter('cf_1', '!*', [''])
148 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
148 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
149 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
149 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
150 find_issues_with_query(query)
150 find_issues_with_query(query)
151 end
151 end
152
152
153 def test_operator_none_for_integer
153 def test_operator_none_for_integer
154 query = IssueQuery.new(:project => Project.find(1), :name => '_')
154 query = IssueQuery.new(:project => Project.find(1), :name => '_')
155 query.add_filter('estimated_hours', '!*', [''])
155 query.add_filter('estimated_hours', '!*', [''])
156 issues = find_issues_with_query(query)
156 issues = find_issues_with_query(query)
157 assert !issues.empty?
157 assert !issues.empty?
158 assert issues.all? {|i| !i.estimated_hours}
158 assert issues.all? {|i| !i.estimated_hours}
159 end
159 end
160
160
161 def test_operator_none_for_date
161 def test_operator_none_for_date
162 query = IssueQuery.new(:project => Project.find(1), :name => '_')
162 query = IssueQuery.new(:project => Project.find(1), :name => '_')
163 query.add_filter('start_date', '!*', [''])
163 query.add_filter('start_date', '!*', [''])
164 issues = find_issues_with_query(query)
164 issues = find_issues_with_query(query)
165 assert !issues.empty?
165 assert !issues.empty?
166 assert issues.all? {|i| i.start_date.nil?}
166 assert issues.all? {|i| i.start_date.nil?}
167 end
167 end
168
168
169 def test_operator_none_for_string_custom_field
169 def test_operator_none_for_string_custom_field
170 query = IssueQuery.new(:project => Project.find(1), :name => '_')
170 query = IssueQuery.new(:project => Project.find(1), :name => '_')
171 query.add_filter('cf_2', '!*', [''])
171 query.add_filter('cf_2', '!*', [''])
172 assert query.has_filter?('cf_2')
172 assert query.has_filter?('cf_2')
173 issues = find_issues_with_query(query)
173 issues = find_issues_with_query(query)
174 assert !issues.empty?
174 assert !issues.empty?
175 assert issues.all? {|i| i.custom_field_value(2).blank?}
175 assert issues.all? {|i| i.custom_field_value(2).blank?}
176 end
176 end
177
177
178 def test_operator_all
178 def test_operator_all
179 query = IssueQuery.new(:project => Project.find(1), :name => '_')
179 query = IssueQuery.new(:project => Project.find(1), :name => '_')
180 query.add_filter('fixed_version_id', '*', [''])
180 query.add_filter('fixed_version_id', '*', [''])
181 query.add_filter('cf_1', '*', [''])
181 query.add_filter('cf_1', '*', [''])
182 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
182 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
183 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
183 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
184 find_issues_with_query(query)
184 find_issues_with_query(query)
185 end
185 end
186
186
187 def test_operator_all_for_date
187 def test_operator_all_for_date
188 query = IssueQuery.new(:project => Project.find(1), :name => '_')
188 query = IssueQuery.new(:project => Project.find(1), :name => '_')
189 query.add_filter('start_date', '*', [''])
189 query.add_filter('start_date', '*', [''])
190 issues = find_issues_with_query(query)
190 issues = find_issues_with_query(query)
191 assert !issues.empty?
191 assert !issues.empty?
192 assert issues.all? {|i| i.start_date.present?}
192 assert issues.all? {|i| i.start_date.present?}
193 end
193 end
194
194
195 def test_operator_all_for_string_custom_field
195 def test_operator_all_for_string_custom_field
196 query = IssueQuery.new(:project => Project.find(1), :name => '_')
196 query = IssueQuery.new(:project => Project.find(1), :name => '_')
197 query.add_filter('cf_2', '*', [''])
197 query.add_filter('cf_2', '*', [''])
198 assert query.has_filter?('cf_2')
198 assert query.has_filter?('cf_2')
199 issues = find_issues_with_query(query)
199 issues = find_issues_with_query(query)
200 assert !issues.empty?
200 assert !issues.empty?
201 assert issues.all? {|i| i.custom_field_value(2).present?}
201 assert issues.all? {|i| i.custom_field_value(2).present?}
202 end
202 end
203
203
204 def test_numeric_filter_should_not_accept_non_numeric_values
204 def test_numeric_filter_should_not_accept_non_numeric_values
205 query = IssueQuery.new(:name => '_')
205 query = IssueQuery.new(:name => '_')
206 query.add_filter('estimated_hours', '=', ['a'])
206 query.add_filter('estimated_hours', '=', ['a'])
207
207
208 assert query.has_filter?('estimated_hours')
208 assert query.has_filter?('estimated_hours')
209 assert !query.valid?
209 assert !query.valid?
210 end
210 end
211
211
212 def test_operator_is_on_float
212 def test_operator_is_on_float
213 Issue.where(:id => 2).update_all("estimated_hours = 171.2")
213 Issue.where(:id => 2).update_all("estimated_hours = 171.2")
214 query = IssueQuery.new(:name => '_')
214 query = IssueQuery.new(:name => '_')
215 query.add_filter('estimated_hours', '=', ['171.20'])
215 query.add_filter('estimated_hours', '=', ['171.20'])
216 issues = find_issues_with_query(query)
216 issues = find_issues_with_query(query)
217 assert_equal 1, issues.size
217 assert_equal 1, issues.size
218 assert_equal 2, issues.first.id
218 assert_equal 2, issues.first.id
219 end
219 end
220
220
221 def test_operator_is_on_integer_custom_field
221 def test_operator_is_on_integer_custom_field
222 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
222 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
223 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
223 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
224 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
224 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
225 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
225 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
226
226
227 query = IssueQuery.new(:name => '_')
227 query = IssueQuery.new(:name => '_')
228 query.add_filter("cf_#{f.id}", '=', ['12'])
228 query.add_filter("cf_#{f.id}", '=', ['12'])
229 issues = find_issues_with_query(query)
229 issues = find_issues_with_query(query)
230 assert_equal 1, issues.size
230 assert_equal 1, issues.size
231 assert_equal 2, issues.first.id
231 assert_equal 2, issues.first.id
232 end
232 end
233
233
234 def test_operator_is_on_integer_custom_field_should_accept_negative_value
234 def test_operator_is_on_integer_custom_field_should_accept_negative_value
235 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
235 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
236 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
236 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
237 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
237 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
238 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
238 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
239
239
240 query = IssueQuery.new(:name => '_')
240 query = IssueQuery.new(:name => '_')
241 query.add_filter("cf_#{f.id}", '=', ['-12'])
241 query.add_filter("cf_#{f.id}", '=', ['-12'])
242 assert query.valid?
242 assert query.valid?
243 issues = find_issues_with_query(query)
243 issues = find_issues_with_query(query)
244 assert_equal 1, issues.size
244 assert_equal 1, issues.size
245 assert_equal 2, issues.first.id
245 assert_equal 2, issues.first.id
246 end
246 end
247
247
248 def test_operator_is_on_float_custom_field
248 def test_operator_is_on_float_custom_field
249 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
249 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
250 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
250 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
251 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
251 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
252 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
252 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
253
253
254 query = IssueQuery.new(:name => '_')
254 query = IssueQuery.new(:name => '_')
255 query.add_filter("cf_#{f.id}", '=', ['12.7'])
255 query.add_filter("cf_#{f.id}", '=', ['12.7'])
256 issues = find_issues_with_query(query)
256 issues = find_issues_with_query(query)
257 assert_equal 1, issues.size
257 assert_equal 1, issues.size
258 assert_equal 2, issues.first.id
258 assert_equal 2, issues.first.id
259 end
259 end
260
260
261 def test_operator_is_on_float_custom_field_should_accept_negative_value
261 def test_operator_is_on_float_custom_field_should_accept_negative_value
262 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
262 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
263 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
263 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
264 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
264 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
265 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
265 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
266
266
267 query = IssueQuery.new(:name => '_')
267 query = IssueQuery.new(:name => '_')
268 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
268 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
269 assert query.valid?
269 assert query.valid?
270 issues = find_issues_with_query(query)
270 issues = find_issues_with_query(query)
271 assert_equal 1, issues.size
271 assert_equal 1, issues.size
272 assert_equal 2, issues.first.id
272 assert_equal 2, issues.first.id
273 end
273 end
274
274
275 def test_operator_is_on_multi_list_custom_field
275 def test_operator_is_on_multi_list_custom_field
276 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
276 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
277 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
277 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
278 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
278 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
279 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
279 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
280 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
280 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
281
281
282 query = IssueQuery.new(:name => '_')
282 query = IssueQuery.new(:name => '_')
283 query.add_filter("cf_#{f.id}", '=', ['value1'])
283 query.add_filter("cf_#{f.id}", '=', ['value1'])
284 issues = find_issues_with_query(query)
284 issues = find_issues_with_query(query)
285 assert_equal [1, 3], issues.map(&:id).sort
285 assert_equal [1, 3], issues.map(&:id).sort
286
286
287 query = IssueQuery.new(:name => '_')
287 query = IssueQuery.new(:name => '_')
288 query.add_filter("cf_#{f.id}", '=', ['value2'])
288 query.add_filter("cf_#{f.id}", '=', ['value2'])
289 issues = find_issues_with_query(query)
289 issues = find_issues_with_query(query)
290 assert_equal [1], issues.map(&:id).sort
290 assert_equal [1], issues.map(&:id).sort
291 end
291 end
292
292
293 def test_operator_is_not_on_multi_list_custom_field
293 def test_operator_is_not_on_multi_list_custom_field
294 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
294 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
295 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
295 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
296 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
296 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
297 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
297 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
298 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
298 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
299
299
300 query = IssueQuery.new(:name => '_')
300 query = IssueQuery.new(:name => '_')
301 query.add_filter("cf_#{f.id}", '!', ['value1'])
301 query.add_filter("cf_#{f.id}", '!', ['value1'])
302 issues = find_issues_with_query(query)
302 issues = find_issues_with_query(query)
303 assert !issues.map(&:id).include?(1)
303 assert !issues.map(&:id).include?(1)
304 assert !issues.map(&:id).include?(3)
304 assert !issues.map(&:id).include?(3)
305
305
306 query = IssueQuery.new(:name => '_')
306 query = IssueQuery.new(:name => '_')
307 query.add_filter("cf_#{f.id}", '!', ['value2'])
307 query.add_filter("cf_#{f.id}", '!', ['value2'])
308 issues = find_issues_with_query(query)
308 issues = find_issues_with_query(query)
309 assert !issues.map(&:id).include?(1)
309 assert !issues.map(&:id).include?(1)
310 assert issues.map(&:id).include?(3)
310 assert issues.map(&:id).include?(3)
311 end
311 end
312
312
313 def test_operator_is_on_is_private_field
313 def test_operator_is_on_is_private_field
314 # is_private filter only available for those who can set issues private
314 # is_private filter only available for those who can set issues private
315 User.current = User.find(2)
315 User.current = User.find(2)
316
316
317 query = IssueQuery.new(:name => '_')
317 query = IssueQuery.new(:name => '_')
318 assert query.available_filters.key?('is_private')
318 assert query.available_filters.key?('is_private')
319
319
320 query.add_filter("is_private", '=', ['1'])
320 query.add_filter("is_private", '=', ['1'])
321 issues = find_issues_with_query(query)
321 issues = find_issues_with_query(query)
322 assert issues.any?
322 assert issues.any?
323 assert_nil issues.detect {|issue| !issue.is_private?}
323 assert_nil issues.detect {|issue| !issue.is_private?}
324 ensure
324 ensure
325 User.current = nil
325 User.current = nil
326 end
326 end
327
327
328 def test_operator_is_not_on_is_private_field
328 def test_operator_is_not_on_is_private_field
329 # is_private filter only available for those who can set issues private
329 # is_private filter only available for those who can set issues private
330 User.current = User.find(2)
330 User.current = User.find(2)
331
331
332 query = IssueQuery.new(:name => '_')
332 query = IssueQuery.new(:name => '_')
333 assert query.available_filters.key?('is_private')
333 assert query.available_filters.key?('is_private')
334
334
335 query.add_filter("is_private", '!', ['1'])
335 query.add_filter("is_private", '!', ['1'])
336 issues = find_issues_with_query(query)
336 issues = find_issues_with_query(query)
337 assert issues.any?
337 assert issues.any?
338 assert_nil issues.detect {|issue| issue.is_private?}
338 assert_nil issues.detect {|issue| issue.is_private?}
339 ensure
339 ensure
340 User.current = nil
340 User.current = nil
341 end
341 end
342
342
343 def test_operator_greater_than
343 def test_operator_greater_than
344 query = IssueQuery.new(:project => Project.find(1), :name => '_')
344 query = IssueQuery.new(:project => Project.find(1), :name => '_')
345 query.add_filter('done_ratio', '>=', ['40'])
345 query.add_filter('done_ratio', '>=', ['40'])
346 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
346 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
347 find_issues_with_query(query)
347 find_issues_with_query(query)
348 end
348 end
349
349
350 def test_operator_greater_than_a_float
350 def test_operator_greater_than_a_float
351 query = IssueQuery.new(:project => Project.find(1), :name => '_')
351 query = IssueQuery.new(:project => Project.find(1), :name => '_')
352 query.add_filter('estimated_hours', '>=', ['40.5'])
352 query.add_filter('estimated_hours', '>=', ['40.5'])
353 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
353 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
354 find_issues_with_query(query)
354 find_issues_with_query(query)
355 end
355 end
356
356
357 def test_operator_greater_than_on_int_custom_field
357 def test_operator_greater_than_on_int_custom_field
358 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
358 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
359 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
359 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
360 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
360 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
361 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
361 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
362
362
363 query = IssueQuery.new(:project => Project.find(1), :name => '_')
363 query = IssueQuery.new(:project => Project.find(1), :name => '_')
364 query.add_filter("cf_#{f.id}", '>=', ['8'])
364 query.add_filter("cf_#{f.id}", '>=', ['8'])
365 issues = find_issues_with_query(query)
365 issues = find_issues_with_query(query)
366 assert_equal 1, issues.size
366 assert_equal 1, issues.size
367 assert_equal 2, issues.first.id
367 assert_equal 2, issues.first.id
368 end
368 end
369
369
370 def test_operator_lesser_than
370 def test_operator_lesser_than
371 query = IssueQuery.new(:project => Project.find(1), :name => '_')
371 query = IssueQuery.new(:project => Project.find(1), :name => '_')
372 query.add_filter('done_ratio', '<=', ['30'])
372 query.add_filter('done_ratio', '<=', ['30'])
373 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
373 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
374 find_issues_with_query(query)
374 find_issues_with_query(query)
375 end
375 end
376
376
377 def test_operator_lesser_than_on_custom_field
377 def test_operator_lesser_than_on_custom_field
378 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
378 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
379 query = IssueQuery.new(:project => Project.find(1), :name => '_')
379 query = IssueQuery.new(:project => Project.find(1), :name => '_')
380 query.add_filter("cf_#{f.id}", '<=', ['30'])
380 query.add_filter("cf_#{f.id}", '<=', ['30'])
381 assert_match /CAST.+ <= 30\.0/, query.statement
381 assert_match /CAST.+ <= 30\.0/, query.statement
382 find_issues_with_query(query)
382 find_issues_with_query(query)
383 end
383 end
384
384
385 def test_operator_lesser_than_on_date_custom_field
385 def test_operator_lesser_than_on_date_custom_field
386 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
386 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
387 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
387 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
388 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
388 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
389 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
389 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
390
390
391 query = IssueQuery.new(:project => Project.find(1), :name => '_')
391 query = IssueQuery.new(:project => Project.find(1), :name => '_')
392 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
392 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
393 issue_ids = find_issues_with_query(query).map(&:id)
393 issue_ids = find_issues_with_query(query).map(&:id)
394 assert_include 1, issue_ids
394 assert_include 1, issue_ids
395 assert_not_include 2, issue_ids
395 assert_not_include 2, issue_ids
396 assert_not_include 3, issue_ids
396 assert_not_include 3, issue_ids
397 end
397 end
398
398
399 def test_operator_between
399 def test_operator_between
400 query = IssueQuery.new(:project => Project.find(1), :name => '_')
400 query = IssueQuery.new(:project => Project.find(1), :name => '_')
401 query.add_filter('done_ratio', '><', ['30', '40'])
401 query.add_filter('done_ratio', '><', ['30', '40'])
402 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
402 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
403 find_issues_with_query(query)
403 find_issues_with_query(query)
404 end
404 end
405
405
406 def test_operator_between_on_custom_field
406 def test_operator_between_on_custom_field
407 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
407 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
408 query = IssueQuery.new(:project => Project.find(1), :name => '_')
408 query = IssueQuery.new(:project => Project.find(1), :name => '_')
409 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
409 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
410 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
410 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
411 find_issues_with_query(query)
411 find_issues_with_query(query)
412 end
412 end
413
413
414 def test_date_filter_should_not_accept_non_date_values
414 def test_date_filter_should_not_accept_non_date_values
415 query = IssueQuery.new(:name => '_')
415 query = IssueQuery.new(:name => '_')
416 query.add_filter('created_on', '=', ['a'])
416 query.add_filter('created_on', '=', ['a'])
417
417
418 assert query.has_filter?('created_on')
418 assert query.has_filter?('created_on')
419 assert !query.valid?
419 assert !query.valid?
420 end
420 end
421
421
422 def test_date_filter_should_not_accept_invalid_date_values
422 def test_date_filter_should_not_accept_invalid_date_values
423 query = IssueQuery.new(:name => '_')
423 query = IssueQuery.new(:name => '_')
424 query.add_filter('created_on', '=', ['2011-01-34'])
424 query.add_filter('created_on', '=', ['2011-01-34'])
425
425
426 assert query.has_filter?('created_on')
426 assert query.has_filter?('created_on')
427 assert !query.valid?
427 assert !query.valid?
428 end
428 end
429
429
430 def test_relative_date_filter_should_not_accept_non_integer_values
430 def test_relative_date_filter_should_not_accept_non_integer_values
431 query = IssueQuery.new(:name => '_')
431 query = IssueQuery.new(:name => '_')
432 query.add_filter('created_on', '>t-', ['a'])
432 query.add_filter('created_on', '>t-', ['a'])
433
433
434 assert query.has_filter?('created_on')
434 assert query.has_filter?('created_on')
435 assert !query.valid?
435 assert !query.valid?
436 end
436 end
437
437
438 def test_operator_date_equals
438 def test_operator_date_equals
439 query = IssueQuery.new(:name => '_')
439 query = IssueQuery.new(:name => '_')
440 query.add_filter('due_date', '=', ['2011-07-10'])
440 query.add_filter('due_date', '=', ['2011-07-10'])
441 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
441 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
442 find_issues_with_query(query)
442 find_issues_with_query(query)
443 end
443 end
444
444
445 def test_operator_date_lesser_than
445 def test_operator_date_lesser_than
446 query = IssueQuery.new(:name => '_')
446 query = IssueQuery.new(:name => '_')
447 query.add_filter('due_date', '<=', ['2011-07-10'])
447 query.add_filter('due_date', '<=', ['2011-07-10'])
448 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
448 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
449 find_issues_with_query(query)
449 find_issues_with_query(query)
450 end
450 end
451
451
452 def test_operator_date_lesser_than_with_timestamp
452 def test_operator_date_lesser_than_with_timestamp
453 query = IssueQuery.new(:name => '_')
453 query = IssueQuery.new(:name => '_')
454 query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
454 query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
455 assert_match /issues\.updated_on <= '2011-07-10 19:13:52/, query.statement
455 assert_match /issues\.updated_on <= '2011-07-10 19:13:52/, query.statement
456 find_issues_with_query(query)
456 find_issues_with_query(query)
457 end
457 end
458
458
459 def test_operator_date_greater_than
459 def test_operator_date_greater_than
460 query = IssueQuery.new(:name => '_')
460 query = IssueQuery.new(:name => '_')
461 query.add_filter('due_date', '>=', ['2011-07-10'])
461 query.add_filter('due_date', '>=', ['2011-07-10'])
462 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
462 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
463 find_issues_with_query(query)
463 find_issues_with_query(query)
464 end
464 end
465
465
466 def test_operator_date_greater_than_with_timestamp
466 def test_operator_date_greater_than_with_timestamp
467 query = IssueQuery.new(:name => '_')
467 query = IssueQuery.new(:name => '_')
468 query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
468 query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
469 assert_match /issues\.updated_on > '2011-07-10 19:13:51(\.0+)?'/, query.statement
469 assert_match /issues\.updated_on > '2011-07-10 19:13:51(\.0+)?'/, query.statement
470 find_issues_with_query(query)
470 find_issues_with_query(query)
471 end
471 end
472
472
473 def test_operator_date_between
473 def test_operator_date_between
474 query = IssueQuery.new(:name => '_')
474 query = IssueQuery.new(:name => '_')
475 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
475 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
476 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?'/, query.statement
476 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?'/, query.statement
477 find_issues_with_query(query)
477 find_issues_with_query(query)
478 end
478 end
479
479
480 def test_operator_in_more_than
480 def test_operator_in_more_than
481 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
481 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
482 query = IssueQuery.new(:project => Project.find(1), :name => '_')
482 query = IssueQuery.new(:project => Project.find(1), :name => '_')
483 query.add_filter('due_date', '>t+', ['15'])
483 query.add_filter('due_date', '>t+', ['15'])
484 issues = find_issues_with_query(query)
484 issues = find_issues_with_query(query)
485 assert !issues.empty?
485 assert !issues.empty?
486 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
486 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
487 end
487 end
488
488
489 def test_operator_in_less_than
489 def test_operator_in_less_than
490 query = IssueQuery.new(:project => Project.find(1), :name => '_')
490 query = IssueQuery.new(:project => Project.find(1), :name => '_')
491 query.add_filter('due_date', '<t+', ['15'])
491 query.add_filter('due_date', '<t+', ['15'])
492 issues = find_issues_with_query(query)
492 issues = find_issues_with_query(query)
493 assert !issues.empty?
493 assert !issues.empty?
494 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
494 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
495 end
495 end
496
496
497 def test_operator_in_the_next_days
497 def test_operator_in_the_next_days
498 query = IssueQuery.new(:project => Project.find(1), :name => '_')
498 query = IssueQuery.new(:project => Project.find(1), :name => '_')
499 query.add_filter('due_date', '><t+', ['15'])
499 query.add_filter('due_date', '><t+', ['15'])
500 issues = find_issues_with_query(query)
500 issues = find_issues_with_query(query)
501 assert !issues.empty?
501 assert !issues.empty?
502 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
502 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
503 end
503 end
504
504
505 def test_operator_less_than_ago
505 def test_operator_less_than_ago
506 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
506 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
507 query = IssueQuery.new(:project => Project.find(1), :name => '_')
507 query = IssueQuery.new(:project => Project.find(1), :name => '_')
508 query.add_filter('due_date', '>t-', ['3'])
508 query.add_filter('due_date', '>t-', ['3'])
509 issues = find_issues_with_query(query)
509 issues = find_issues_with_query(query)
510 assert !issues.empty?
510 assert !issues.empty?
511 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
511 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
512 end
512 end
513
513
514 def test_operator_in_the_past_days
514 def test_operator_in_the_past_days
515 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
515 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
516 query = IssueQuery.new(:project => Project.find(1), :name => '_')
516 query = IssueQuery.new(:project => Project.find(1), :name => '_')
517 query.add_filter('due_date', '><t-', ['3'])
517 query.add_filter('due_date', '><t-', ['3'])
518 issues = find_issues_with_query(query)
518 issues = find_issues_with_query(query)
519 assert !issues.empty?
519 assert !issues.empty?
520 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
520 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
521 end
521 end
522
522
523 def test_operator_more_than_ago
523 def test_operator_more_than_ago
524 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
524 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
525 query = IssueQuery.new(:project => Project.find(1), :name => '_')
525 query = IssueQuery.new(:project => Project.find(1), :name => '_')
526 query.add_filter('due_date', '<t-', ['10'])
526 query.add_filter('due_date', '<t-', ['10'])
527 assert query.statement.include?("#{Issue.table_name}.due_date <=")
527 assert query.statement.include?("#{Issue.table_name}.due_date <=")
528 issues = find_issues_with_query(query)
528 issues = find_issues_with_query(query)
529 assert !issues.empty?
529 assert !issues.empty?
530 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
530 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
531 end
531 end
532
532
533 def test_operator_in
533 def test_operator_in
534 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
534 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
535 query = IssueQuery.new(:project => Project.find(1), :name => '_')
535 query = IssueQuery.new(:project => Project.find(1), :name => '_')
536 query.add_filter('due_date', 't+', ['2'])
536 query.add_filter('due_date', 't+', ['2'])
537 issues = find_issues_with_query(query)
537 issues = find_issues_with_query(query)
538 assert !issues.empty?
538 assert !issues.empty?
539 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
539 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
540 end
540 end
541
541
542 def test_operator_ago
542 def test_operator_ago
543 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
543 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
544 query = IssueQuery.new(:project => Project.find(1), :name => '_')
544 query = IssueQuery.new(:project => Project.find(1), :name => '_')
545 query.add_filter('due_date', 't-', ['3'])
545 query.add_filter('due_date', 't-', ['3'])
546 issues = find_issues_with_query(query)
546 issues = find_issues_with_query(query)
547 assert !issues.empty?
547 assert !issues.empty?
548 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
548 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
549 end
549 end
550
550
551 def test_operator_today
551 def test_operator_today
552 query = IssueQuery.new(:project => Project.find(1), :name => '_')
552 query = IssueQuery.new(:project => Project.find(1), :name => '_')
553 query.add_filter('due_date', 't', [''])
553 query.add_filter('due_date', 't', [''])
554 issues = find_issues_with_query(query)
554 issues = find_issues_with_query(query)
555 assert !issues.empty?
555 assert !issues.empty?
556 issues.each {|issue| assert_equal Date.today, issue.due_date}
556 issues.each {|issue| assert_equal Date.today, issue.due_date}
557 end
557 end
558
558
559 def test_operator_this_week_on_date
559 def test_operator_this_week_on_date
560 query = IssueQuery.new(:project => Project.find(1), :name => '_')
560 query = IssueQuery.new(:project => Project.find(1), :name => '_')
561 query.add_filter('due_date', 'w', [''])
561 query.add_filter('due_date', 'w', [''])
562 find_issues_with_query(query)
562 find_issues_with_query(query)
563 end
563 end
564
564
565 def test_operator_this_week_on_datetime
565 def test_operator_this_week_on_datetime
566 query = IssueQuery.new(:project => Project.find(1), :name => '_')
566 query = IssueQuery.new(:project => Project.find(1), :name => '_')
567 query.add_filter('created_on', 'w', [''])
567 query.add_filter('created_on', 'w', [''])
568 find_issues_with_query(query)
568 find_issues_with_query(query)
569 end
569 end
570
570
571 def test_operator_contains
571 def test_operator_contains
572 query = IssueQuery.new(:project => Project.find(1), :name => '_')
572 query = IssueQuery.new(:project => Project.find(1), :name => '_')
573 query.add_filter('subject', '~', ['uNable'])
573 query.add_filter('subject', '~', ['uNable'])
574 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
574 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
575 result = find_issues_with_query(query)
575 result = find_issues_with_query(query)
576 assert result.empty?
576 assert result.empty?
577 result.each {|issue| assert issue.subject.downcase.include?('unable') }
577 result.each {|issue| assert issue.subject.downcase.include?('unable') }
578 end
578 end
579
579
580 def test_range_for_this_week_with_week_starting_on_monday
580 def test_range_for_this_week_with_week_starting_on_monday
581 I18n.locale = :fr
581 I18n.locale = :fr
582 assert_equal '1', I18n.t(:general_first_day_of_week)
582 assert_equal '1', I18n.t(:general_first_day_of_week)
583
583
584 Date.stubs(:today).returns(Date.parse('2011-04-29'))
584 Date.stubs(:today).returns(Date.parse('2011-04-29'))
585
585
586 query = IssueQuery.new(:project => Project.find(1), :name => '_')
586 query = IssueQuery.new(:project => Project.find(1), :name => '_')
587 query.add_filter('due_date', 'w', [''])
587 query.add_filter('due_date', 'w', [''])
588 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
588 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
589 I18n.locale = :en
589 I18n.locale = :en
590 end
590 end
591
591
592 def test_range_for_this_week_with_week_starting_on_sunday
592 def test_range_for_this_week_with_week_starting_on_sunday
593 I18n.locale = :en
593 I18n.locale = :en
594 assert_equal '7', I18n.t(:general_first_day_of_week)
594 assert_equal '7', I18n.t(:general_first_day_of_week)
595
595
596 Date.stubs(:today).returns(Date.parse('2011-04-29'))
596 Date.stubs(:today).returns(Date.parse('2011-04-29'))
597
597
598 query = IssueQuery.new(:project => Project.find(1), :name => '_')
598 query = IssueQuery.new(:project => Project.find(1), :name => '_')
599 query.add_filter('due_date', 'w', [''])
599 query.add_filter('due_date', 'w', [''])
600 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
600 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
601 end
601 end
602
602
603 def test_operator_does_not_contains
603 def test_operator_does_not_contains
604 query = IssueQuery.new(:project => Project.find(1), :name => '_')
604 query = IssueQuery.new(:project => Project.find(1), :name => '_')
605 query.add_filter('subject', '!~', ['uNable'])
605 query.add_filter('subject', '!~', ['uNable'])
606 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
606 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
607 find_issues_with_query(query)
607 find_issues_with_query(query)
608 end
608 end
609
609
610 def test_filter_assigned_to_me
610 def test_filter_assigned_to_me
611 user = User.find(2)
611 user = User.find(2)
612 group = Group.find(10)
612 group = Group.find(10)
613 User.current = user
613 User.current = user
614 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
614 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
615 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
615 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
616 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
616 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
617 group.users << user
617 group.users << user
618
618
619 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
619 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
620 result = query.issues
620 result = query.issues
621 assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
621 assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
622
622
623 assert result.include?(i1)
623 assert result.include?(i1)
624 assert result.include?(i2)
624 assert result.include?(i2)
625 assert !result.include?(i3)
625 assert !result.include?(i3)
626 end
626 end
627
627
628 def test_user_custom_field_filtered_on_me
628 def test_user_custom_field_filtered_on_me
629 User.current = User.find(2)
629 User.current = User.find(2)
630 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
630 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
631 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
631 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
632 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
632 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
633
633
634 query = IssueQuery.new(:name => '_', :project => Project.find(1))
634 query = IssueQuery.new(:name => '_', :project => Project.find(1))
635 filter = query.available_filters["cf_#{cf.id}"]
635 filter = query.available_filters["cf_#{cf.id}"]
636 assert_not_nil filter
636 assert_not_nil filter
637 assert_include 'me', filter[:values].map{|v| v[1]}
637 assert_include 'me', filter[:values].map{|v| v[1]}
638
638
639 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
639 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
640 result = query.issues
640 result = query.issues
641 assert_equal 1, result.size
641 assert_equal 1, result.size
642 assert_equal issue1, result.first
642 assert_equal issue1, result.first
643 end
643 end
644
644
645 def test_filter_my_projects
645 def test_filter_my_projects
646 User.current = User.find(2)
646 User.current = User.find(2)
647 query = IssueQuery.new(:name => '_')
647 query = IssueQuery.new(:name => '_')
648 filter = query.available_filters['project_id']
648 filter = query.available_filters['project_id']
649 assert_not_nil filter
649 assert_not_nil filter
650 assert_include 'mine', filter[:values].map{|v| v[1]}
650 assert_include 'mine', filter[:values].map{|v| v[1]}
651
651
652 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
652 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
653 result = query.issues
653 result = query.issues
654 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
654 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
655 end
655 end
656
656
657 def test_filter_watched_issues
657 def test_filter_watched_issues
658 User.current = User.find(1)
658 User.current = User.find(1)
659 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
659 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
660 result = find_issues_with_query(query)
660 result = find_issues_with_query(query)
661 assert_not_nil result
661 assert_not_nil result
662 assert !result.empty?
662 assert !result.empty?
663 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
663 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
664 User.current = nil
664 User.current = nil
665 end
665 end
666
666
667 def test_filter_unwatched_issues
667 def test_filter_unwatched_issues
668 User.current = User.find(1)
668 User.current = User.find(1)
669 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
669 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
670 result = find_issues_with_query(query)
670 result = find_issues_with_query(query)
671 assert_not_nil result
671 assert_not_nil result
672 assert !result.empty?
672 assert !result.empty?
673 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
673 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
674 User.current = nil
674 User.current = nil
675 end
675 end
676
676
677 def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
677 def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
678 field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_filter => true)
678 field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_filter => true)
679 Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
679 Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
680 Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
680 Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
681
681
682 query = IssueQuery.new(:name => '_', :project => Project.find(1))
682 query = IssueQuery.new(:name => '_', :project => Project.find(1))
683 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
683 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
684 assert_equal 2, find_issues_with_query(query).size
684 assert_equal 2, find_issues_with_query(query).size
685
685
686 field.project_ids = [1, 3] # Disable the field for project 4
686 field.project_ids = [1, 3] # Disable the field for project 4
687 field.save!
687 field.save!
688 assert_equal 1, find_issues_with_query(query).size
688 assert_equal 1, find_issues_with_query(query).size
689 end
689 end
690
690
691 def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
691 def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
692 field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
692 field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
693 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
693 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
694 Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
694 Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
695
695
696 query = IssueQuery.new(:name => '_', :project => Project.find(1))
696 query = IssueQuery.new(:name => '_', :project => Project.find(1))
697 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
697 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
698 assert_equal 2, find_issues_with_query(query).size
698 assert_equal 2, find_issues_with_query(query).size
699
699
700 field.tracker_ids = [1] # Disable the field for tracker 2
700 field.tracker_ids = [1] # Disable the field for tracker 2
701 field.save!
701 field.save!
702 assert_equal 1, find_issues_with_query(query).size
702 assert_equal 1, find_issues_with_query(query).size
703 end
703 end
704
704
705 def test_filter_on_project_custom_field
705 def test_filter_on_project_custom_field
706 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
706 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
707 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
707 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
708 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
708 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
709
709
710 query = IssueQuery.new(:name => '_')
710 query = IssueQuery.new(:name => '_')
711 filter_name = "project.cf_#{field.id}"
711 filter_name = "project.cf_#{field.id}"
712 assert_include filter_name, query.available_filters.keys
712 assert_include filter_name, query.available_filters.keys
713 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
713 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
714 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
714 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
715 end
715 end
716
716
717 def test_filter_on_author_custom_field
717 def test_filter_on_author_custom_field
718 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
718 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
719 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
719 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
720
720
721 query = IssueQuery.new(:name => '_')
721 query = IssueQuery.new(:name => '_')
722 filter_name = "author.cf_#{field.id}"
722 filter_name = "author.cf_#{field.id}"
723 assert_include filter_name, query.available_filters.keys
723 assert_include filter_name, query.available_filters.keys
724 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
724 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
725 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
725 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
726 end
726 end
727
727
728 def test_filter_on_assigned_to_custom_field
728 def test_filter_on_assigned_to_custom_field
729 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
729 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
730 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
730 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
731
731
732 query = IssueQuery.new(:name => '_')
732 query = IssueQuery.new(:name => '_')
733 filter_name = "assigned_to.cf_#{field.id}"
733 filter_name = "assigned_to.cf_#{field.id}"
734 assert_include filter_name, query.available_filters.keys
734 assert_include filter_name, query.available_filters.keys
735 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
735 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
736 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
736 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
737 end
737 end
738
738
739 def test_filter_on_fixed_version_custom_field
739 def test_filter_on_fixed_version_custom_field
740 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
740 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
741 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
741 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
742
742
743 query = IssueQuery.new(:name => '_')
743 query = IssueQuery.new(:name => '_')
744 filter_name = "fixed_version.cf_#{field.id}"
744 filter_name = "fixed_version.cf_#{field.id}"
745 assert_include filter_name, query.available_filters.keys
745 assert_include filter_name, query.available_filters.keys
746 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
746 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
747 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
747 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
748 end
748 end
749
749
750 def test_filter_on_relations_with_a_specific_issue
750 def test_filter_on_relations_with_a_specific_issue
751 IssueRelation.delete_all
751 IssueRelation.delete_all
752 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
752 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
753 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
753 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
754
754
755 query = IssueQuery.new(:name => '_')
755 query = IssueQuery.new(:name => '_')
756 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
756 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
757 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
757 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
758
758
759 query = IssueQuery.new(:name => '_')
759 query = IssueQuery.new(:name => '_')
760 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
760 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
761 assert_equal [1], find_issues_with_query(query).map(&:id).sort
761 assert_equal [1], find_issues_with_query(query).map(&:id).sort
762 end
762 end
763
763
764 def test_filter_on_relations_with_any_issues_in_a_project
764 def test_filter_on_relations_with_any_issues_in_a_project
765 IssueRelation.delete_all
765 IssueRelation.delete_all
766 with_settings :cross_project_issue_relations => '1' do
766 with_settings :cross_project_issue_relations => '1' do
767 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
767 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
768 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
768 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
769 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
769 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
770 end
770 end
771
771
772 query = IssueQuery.new(:name => '_')
772 query = IssueQuery.new(:name => '_')
773 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
773 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
774 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
774 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
775
775
776 query = IssueQuery.new(:name => '_')
776 query = IssueQuery.new(:name => '_')
777 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
777 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
778 assert_equal [1], find_issues_with_query(query).map(&:id).sort
778 assert_equal [1], find_issues_with_query(query).map(&:id).sort
779
779
780 query = IssueQuery.new(:name => '_')
780 query = IssueQuery.new(:name => '_')
781 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
781 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
782 assert_equal [], find_issues_with_query(query).map(&:id).sort
782 assert_equal [], find_issues_with_query(query).map(&:id).sort
783 end
783 end
784
784
785 def test_filter_on_relations_with_any_issues_not_in_a_project
785 def test_filter_on_relations_with_any_issues_not_in_a_project
786 IssueRelation.delete_all
786 IssueRelation.delete_all
787 with_settings :cross_project_issue_relations => '1' do
787 with_settings :cross_project_issue_relations => '1' do
788 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
788 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
789 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
789 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
790 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
790 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
791 end
791 end
792
792
793 query = IssueQuery.new(:name => '_')
793 query = IssueQuery.new(:name => '_')
794 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
794 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
795 assert_equal [1], find_issues_with_query(query).map(&:id).sort
795 assert_equal [1], find_issues_with_query(query).map(&:id).sort
796 end
796 end
797
797
798 def test_filter_on_relations_with_no_issues_in_a_project
798 def test_filter_on_relations_with_no_issues_in_a_project
799 IssueRelation.delete_all
799 IssueRelation.delete_all
800 with_settings :cross_project_issue_relations => '1' do
800 with_settings :cross_project_issue_relations => '1' do
801 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
801 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
802 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
802 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
803 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
803 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
804 end
804 end
805
805
806 query = IssueQuery.new(:name => '_')
806 query = IssueQuery.new(:name => '_')
807 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
807 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
808 ids = find_issues_with_query(query).map(&:id).sort
808 ids = find_issues_with_query(query).map(&:id).sort
809 assert_include 2, ids
809 assert_include 2, ids
810 assert_not_include 1, ids
810 assert_not_include 1, ids
811 assert_not_include 3, ids
811 assert_not_include 3, ids
812 end
812 end
813
813
814 def test_filter_on_relations_with_no_issues
814 def test_filter_on_relations_with_no_issues
815 IssueRelation.delete_all
815 IssueRelation.delete_all
816 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
816 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
817 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
817 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
818
818
819 query = IssueQuery.new(:name => '_')
819 query = IssueQuery.new(:name => '_')
820 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
820 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
821 ids = find_issues_with_query(query).map(&:id)
821 ids = find_issues_with_query(query).map(&:id)
822 assert_equal [], ids & [1, 2, 3]
822 assert_equal [], ids & [1, 2, 3]
823 assert_include 4, ids
823 assert_include 4, ids
824 end
824 end
825
825
826 def test_filter_on_relations_with_any_issues
826 def test_filter_on_relations_with_any_issues
827 IssueRelation.delete_all
827 IssueRelation.delete_all
828 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
828 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
829 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
829 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
830
830
831 query = IssueQuery.new(:name => '_')
831 query = IssueQuery.new(:name => '_')
832 query.filters = {"relates" => {:operator => '*', :values => ['']}}
832 query.filters = {"relates" => {:operator => '*', :values => ['']}}
833 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
833 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
834 end
834 end
835
835
836 def test_filter_on_relations_should_not_ignore_other_filter
836 def test_filter_on_relations_should_not_ignore_other_filter
837 issue = Issue.generate!
837 issue = Issue.generate!
838 issue1 = Issue.generate!(:status_id => 1)
838 issue1 = Issue.generate!(:status_id => 1)
839 issue2 = Issue.generate!(:status_id => 2)
839 issue2 = Issue.generate!(:status_id => 2)
840 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
840 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
841 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
841 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
842
842
843 query = IssueQuery.new(:name => '_')
843 query = IssueQuery.new(:name => '_')
844 query.filters = {
844 query.filters = {
845 "status_id" => {:operator => '=', :values => ['1']},
845 "status_id" => {:operator => '=', :values => ['1']},
846 "relates" => {:operator => '=', :values => [issue.id.to_s]}
846 "relates" => {:operator => '=', :values => [issue.id.to_s]}
847 }
847 }
848 assert_equal [issue1], find_issues_with_query(query)
848 assert_equal [issue1], find_issues_with_query(query)
849 end
849 end
850
850
851 def test_statement_should_be_nil_with_no_filters
851 def test_statement_should_be_nil_with_no_filters
852 q = IssueQuery.new(:name => '_')
852 q = IssueQuery.new(:name => '_')
853 q.filters = {}
853 q.filters = {}
854
854
855 assert q.valid?
855 assert q.valid?
856 assert_nil q.statement
856 assert_nil q.statement
857 end
857 end
858
858
859 def test_default_columns
859 def test_default_columns
860 q = IssueQuery.new
860 q = IssueQuery.new
861 assert q.columns.any?
861 assert q.columns.any?
862 assert q.inline_columns.any?
862 assert q.inline_columns.any?
863 assert q.block_columns.empty?
863 assert q.block_columns.empty?
864 end
864 end
865
865
866 def test_set_column_names
866 def test_set_column_names
867 q = IssueQuery.new
867 q = IssueQuery.new
868 q.column_names = ['tracker', :subject, '', 'unknonw_column']
868 q.column_names = ['tracker', :subject, '', 'unknonw_column']
869 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
869 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
870 end
870 end
871
871
872 def test_has_column_should_accept_a_column_name
872 def test_has_column_should_accept_a_column_name
873 q = IssueQuery.new
873 q = IssueQuery.new
874 q.column_names = ['tracker', :subject]
874 q.column_names = ['tracker', :subject]
875 assert q.has_column?(:tracker)
875 assert q.has_column?(:tracker)
876 assert !q.has_column?(:category)
876 assert !q.has_column?(:category)
877 end
877 end
878
878
879 def test_has_column_should_accept_a_column
879 def test_has_column_should_accept_a_column
880 q = IssueQuery.new
880 q = IssueQuery.new
881 q.column_names = ['tracker', :subject]
881 q.column_names = ['tracker', :subject]
882
882
883 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
883 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
884 assert_kind_of QueryColumn, tracker_column
884 assert_kind_of QueryColumn, tracker_column
885 category_column = q.available_columns.detect {|c| c.name==:category}
885 category_column = q.available_columns.detect {|c| c.name==:category}
886 assert_kind_of QueryColumn, category_column
886 assert_kind_of QueryColumn, category_column
887
887
888 assert q.has_column?(tracker_column)
888 assert q.has_column?(tracker_column)
889 assert !q.has_column?(category_column)
889 assert !q.has_column?(category_column)
890 end
890 end
891
891
892 def test_inline_and_block_columns
892 def test_inline_and_block_columns
893 q = IssueQuery.new
893 q = IssueQuery.new
894 q.column_names = ['subject', 'description', 'tracker']
894 q.column_names = ['subject', 'description', 'tracker']
895
895
896 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
896 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
897 assert_equal [:description], q.block_columns.map(&:name)
897 assert_equal [:description], q.block_columns.map(&:name)
898 end
898 end
899
899
900 def test_custom_field_columns_should_be_inline
900 def test_custom_field_columns_should_be_inline
901 q = IssueQuery.new
901 q = IssueQuery.new
902 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
902 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
903 assert columns.any?
903 assert columns.any?
904 assert_nil columns.detect {|column| !column.inline?}
904 assert_nil columns.detect {|column| !column.inline?}
905 end
905 end
906
906
907 def test_query_should_preload_spent_hours
907 def test_query_should_preload_spent_hours
908 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
908 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
909 assert q.has_column?(:spent_hours)
909 assert q.has_column?(:spent_hours)
910 issues = q.issues
910 issues = q.issues
911 assert_not_nil issues.first.instance_variable_get("@spent_hours")
911 assert_not_nil issues.first.instance_variable_get("@spent_hours")
912 end
912 end
913
913
914 def test_groupable_columns_should_include_custom_fields
914 def test_groupable_columns_should_include_custom_fields
915 q = IssueQuery.new
915 q = IssueQuery.new
916 column = q.groupable_columns.detect {|c| c.name == :cf_1}
916 column = q.groupable_columns.detect {|c| c.name == :cf_1}
917 assert_not_nil column
917 assert_not_nil column
918 assert_kind_of QueryCustomFieldColumn, column
918 assert_kind_of QueryCustomFieldColumn, column
919 end
919 end
920
920
921 def test_groupable_columns_should_not_include_multi_custom_fields
921 def test_groupable_columns_should_not_include_multi_custom_fields
922 field = CustomField.find(1)
922 field = CustomField.find(1)
923 field.update_attribute :multiple, true
923 field.update_attribute :multiple, true
924
924
925 q = IssueQuery.new
925 q = IssueQuery.new
926 column = q.groupable_columns.detect {|c| c.name == :cf_1}
926 column = q.groupable_columns.detect {|c| c.name == :cf_1}
927 assert_nil column
927 assert_nil column
928 end
928 end
929
929
930 def test_groupable_columns_should_include_user_custom_fields
930 def test_groupable_columns_should_include_user_custom_fields
931 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
931 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
932
932
933 q = IssueQuery.new
933 q = IssueQuery.new
934 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
934 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
935 end
935 end
936
936
937 def test_groupable_columns_should_include_version_custom_fields
937 def test_groupable_columns_should_include_version_custom_fields
938 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
938 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
939
939
940 q = IssueQuery.new
940 q = IssueQuery.new
941 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
941 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
942 end
942 end
943
943
944 def test_grouped_with_valid_column
944 def test_grouped_with_valid_column
945 q = IssueQuery.new(:group_by => 'status')
945 q = IssueQuery.new(:group_by => 'status')
946 assert q.grouped?
946 assert q.grouped?
947 assert_not_nil q.group_by_column
947 assert_not_nil q.group_by_column
948 assert_equal :status, q.group_by_column.name
948 assert_equal :status, q.group_by_column.name
949 assert_not_nil q.group_by_statement
949 assert_not_nil q.group_by_statement
950 assert_equal 'status', q.group_by_statement
950 assert_equal 'status', q.group_by_statement
951 end
951 end
952
952
953 def test_grouped_with_invalid_column
953 def test_grouped_with_invalid_column
954 q = IssueQuery.new(:group_by => 'foo')
954 q = IssueQuery.new(:group_by => 'foo')
955 assert !q.grouped?
955 assert !q.grouped?
956 assert_nil q.group_by_column
956 assert_nil q.group_by_column
957 assert_nil q.group_by_statement
957 assert_nil q.group_by_statement
958 end
958 end
959
959
960 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
960 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
961 with_settings :user_format => 'lastname_coma_firstname' do
961 with_settings :user_format => 'lastname_coma_firstname' do
962 q = IssueQuery.new
962 q = IssueQuery.new
963 assert q.sortable_columns.has_key?('assigned_to')
963 assert q.sortable_columns.has_key?('assigned_to')
964 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
964 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
965 end
965 end
966 end
966 end
967
967
968 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
968 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
969 with_settings :user_format => 'lastname_coma_firstname' do
969 with_settings :user_format => 'lastname_coma_firstname' do
970 q = IssueQuery.new
970 q = IssueQuery.new
971 assert q.sortable_columns.has_key?('author')
971 assert q.sortable_columns.has_key?('author')
972 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
972 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
973 end
973 end
974 end
974 end
975
975
976 def test_sortable_columns_should_include_custom_field
976 def test_sortable_columns_should_include_custom_field
977 q = IssueQuery.new
977 q = IssueQuery.new
978 assert q.sortable_columns['cf_1']
978 assert q.sortable_columns['cf_1']
979 end
979 end
980
980
981 def test_sortable_columns_should_not_include_multi_custom_field
981 def test_sortable_columns_should_not_include_multi_custom_field
982 field = CustomField.find(1)
982 field = CustomField.find(1)
983 field.update_attribute :multiple, true
983 field.update_attribute :multiple, true
984
984
985 q = IssueQuery.new
985 q = IssueQuery.new
986 assert !q.sortable_columns['cf_1']
986 assert !q.sortable_columns['cf_1']
987 end
987 end
988
988
989 def test_default_sort
989 def test_default_sort
990 q = IssueQuery.new
990 q = IssueQuery.new
991 assert_equal [], q.sort_criteria
991 assert_equal [], q.sort_criteria
992 end
992 end
993
993
994 def test_set_sort_criteria_with_hash
994 def test_set_sort_criteria_with_hash
995 q = IssueQuery.new
995 q = IssueQuery.new
996 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
996 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
997 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
997 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
998 end
998 end
999
999
1000 def test_set_sort_criteria_with_array
1000 def test_set_sort_criteria_with_array
1001 q = IssueQuery.new
1001 q = IssueQuery.new
1002 q.sort_criteria = [['priority', 'desc'], 'tracker']
1002 q.sort_criteria = [['priority', 'desc'], 'tracker']
1003 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1003 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1004 end
1004 end
1005
1005
1006 def test_create_query_with_sort
1006 def test_create_query_with_sort
1007 q = IssueQuery.new(:name => 'Sorted')
1007 q = IssueQuery.new(:name => 'Sorted')
1008 q.sort_criteria = [['priority', 'desc'], 'tracker']
1008 q.sort_criteria = [['priority', 'desc'], 'tracker']
1009 assert q.save
1009 assert q.save
1010 q.reload
1010 q.reload
1011 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1011 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1012 end
1012 end
1013
1013
1014 def test_sort_by_string_custom_field_asc
1014 def test_sort_by_string_custom_field_asc
1015 q = IssueQuery.new
1015 q = IssueQuery.new
1016 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1016 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1017 assert c
1017 assert c
1018 assert c.sortable
1018 assert c.sortable
1019 issues = q.issues(:order => "#{c.sortable} ASC")
1019 issues = q.issues(:order => "#{c.sortable} ASC")
1020 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1020 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1021 assert !values.empty?
1021 assert !values.empty?
1022 assert_equal values.sort, values
1022 assert_equal values.sort, values
1023 end
1023 end
1024
1024
1025 def test_sort_by_string_custom_field_desc
1025 def test_sort_by_string_custom_field_desc
1026 q = IssueQuery.new
1026 q = IssueQuery.new
1027 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1027 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1028 assert c
1028 assert c
1029 assert c.sortable
1029 assert c.sortable
1030 issues = q.issues(:order => "#{c.sortable} DESC")
1030 issues = q.issues(:order => "#{c.sortable} DESC")
1031 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1031 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1032 assert !values.empty?
1032 assert !values.empty?
1033 assert_equal values.sort.reverse, values
1033 assert_equal values.sort.reverse, values
1034 end
1034 end
1035
1035
1036 def test_sort_by_float_custom_field_asc
1036 def test_sort_by_float_custom_field_asc
1037 q = IssueQuery.new
1037 q = IssueQuery.new
1038 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
1038 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
1039 assert c
1039 assert c
1040 assert c.sortable
1040 assert c.sortable
1041 issues = q.issues(:order => "#{c.sortable} ASC")
1041 issues = q.issues(:order => "#{c.sortable} ASC")
1042 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
1042 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
1043 assert !values.empty?
1043 assert !values.empty?
1044 assert_equal values.sort, values
1044 assert_equal values.sort, values
1045 end
1045 end
1046
1046
1047 def test_invalid_query_should_raise_query_statement_invalid_error
1047 def test_invalid_query_should_raise_query_statement_invalid_error
1048 q = IssueQuery.new
1048 q = IssueQuery.new
1049 assert_raise Query::StatementInvalid do
1049 assert_raise Query::StatementInvalid do
1050 q.issues(:conditions => "foo = 1")
1050 q.issues(:conditions => "foo = 1")
1051 end
1051 end
1052 end
1052 end
1053
1053
1054 def test_issue_count
1054 def test_issue_count
1055 q = IssueQuery.new(:name => '_')
1055 q = IssueQuery.new(:name => '_')
1056 issue_count = q.issue_count
1056 issue_count = q.issue_count
1057 assert_equal q.issues.size, issue_count
1057 assert_equal q.issues.size, issue_count
1058 end
1058 end
1059
1059
1060 def test_issue_count_with_archived_issues
1060 def test_issue_count_with_archived_issues
1061 p = Project.generate! do |project|
1061 p = Project.generate! do |project|
1062 project.status = Project::STATUS_ARCHIVED
1062 project.status = Project::STATUS_ARCHIVED
1063 end
1063 end
1064 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
1064 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
1065 assert !i.visible?
1065 assert !i.visible?
1066
1066
1067 test_issue_count
1067 test_issue_count
1068 end
1068 end
1069
1069
1070 def test_issue_count_by_association_group
1070 def test_issue_count_by_association_group
1071 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1071 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1072 count_by_group = q.issue_count_by_group
1072 count_by_group = q.issue_count_by_group
1073 assert_kind_of Hash, count_by_group
1073 assert_kind_of Hash, count_by_group
1074 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1074 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1075 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1075 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1076 assert count_by_group.has_key?(User.find(3))
1076 assert count_by_group.has_key?(User.find(3))
1077 end
1077 end
1078
1078
1079 def test_issue_count_by_list_custom_field_group
1079 def test_issue_count_by_list_custom_field_group
1080 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
1080 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
1081 count_by_group = q.issue_count_by_group
1081 count_by_group = q.issue_count_by_group
1082 assert_kind_of Hash, count_by_group
1082 assert_kind_of Hash, count_by_group
1083 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1083 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1084 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1084 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1085 assert count_by_group.has_key?('MySQL')
1085 assert count_by_group.has_key?('MySQL')
1086 end
1086 end
1087
1087
1088 def test_issue_count_by_date_custom_field_group
1088 def test_issue_count_by_date_custom_field_group
1089 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1089 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1090 count_by_group = q.issue_count_by_group
1090 count_by_group = q.issue_count_by_group
1091 assert_kind_of Hash, count_by_group
1091 assert_kind_of Hash, count_by_group
1092 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1092 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1093 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1093 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1094 end
1094 end
1095
1095
1096 def test_issue_count_with_nil_group_only
1096 def test_issue_count_with_nil_group_only
1097 Issue.update_all("assigned_to_id = NULL")
1097 Issue.update_all("assigned_to_id = NULL")
1098
1098
1099 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1099 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1100 count_by_group = q.issue_count_by_group
1100 count_by_group = q.issue_count_by_group
1101 assert_kind_of Hash, count_by_group
1101 assert_kind_of Hash, count_by_group
1102 assert_equal 1, count_by_group.keys.size
1102 assert_equal 1, count_by_group.keys.size
1103 assert_nil count_by_group.keys.first
1103 assert_nil count_by_group.keys.first
1104 end
1104 end
1105
1105
1106 def test_issue_ids
1106 def test_issue_ids
1107 q = IssueQuery.new(:name => '_')
1107 q = IssueQuery.new(:name => '_')
1108 order = "issues.subject, issues.id"
1108 order = "issues.subject, issues.id"
1109 issues = q.issues(:order => order)
1109 issues = q.issues(:order => order)
1110 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1110 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1111 end
1111 end
1112
1112
1113 def test_label_for
1113 def test_label_for
1114 set_language_if_valid 'en'
1114 set_language_if_valid 'en'
1115 q = IssueQuery.new
1115 q = IssueQuery.new
1116 assert_equal 'Assignee', q.label_for('assigned_to_id')
1116 assert_equal 'Assignee', q.label_for('assigned_to_id')
1117 end
1117 end
1118
1118
1119 def test_label_for_fr
1119 def test_label_for_fr
1120 set_language_if_valid 'fr'
1120 set_language_if_valid 'fr'
1121 q = IssueQuery.new
1121 q = IssueQuery.new
1122 assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
1122 assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
1123 end
1123 end
1124
1124
1125 def test_editable_by
1125 def test_editable_by
1126 admin = User.find(1)
1126 admin = User.find(1)
1127 manager = User.find(2)
1127 manager = User.find(2)
1128 developer = User.find(3)
1128 developer = User.find(3)
1129
1129
1130 # Public query on project 1
1130 # Public query on project 1
1131 q = IssueQuery.find(1)
1131 q = IssueQuery.find(1)
1132 assert q.editable_by?(admin)
1132 assert q.editable_by?(admin)
1133 assert q.editable_by?(manager)
1133 assert q.editable_by?(manager)
1134 assert !q.editable_by?(developer)
1134 assert !q.editable_by?(developer)
1135
1135
1136 # Private query on project 1
1136 # Private query on project 1
1137 q = IssueQuery.find(2)
1137 q = IssueQuery.find(2)
1138 assert q.editable_by?(admin)
1138 assert q.editable_by?(admin)
1139 assert !q.editable_by?(manager)
1139 assert !q.editable_by?(manager)
1140 assert q.editable_by?(developer)
1140 assert q.editable_by?(developer)
1141
1141
1142 # Private query for all projects
1142 # Private query for all projects
1143 q = IssueQuery.find(3)
1143 q = IssueQuery.find(3)
1144 assert q.editable_by?(admin)
1144 assert q.editable_by?(admin)
1145 assert !q.editable_by?(manager)
1145 assert !q.editable_by?(manager)
1146 assert q.editable_by?(developer)
1146 assert q.editable_by?(developer)
1147
1147
1148 # Public query for all projects
1148 # Public query for all projects
1149 q = IssueQuery.find(4)
1149 q = IssueQuery.find(4)
1150 assert q.editable_by?(admin)
1150 assert q.editable_by?(admin)
1151 assert !q.editable_by?(manager)
1151 assert !q.editable_by?(manager)
1152 assert !q.editable_by?(developer)
1152 assert !q.editable_by?(developer)
1153 end
1153 end
1154
1154
1155 def test_visible_scope
1155 def test_visible_scope
1156 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1156 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1157
1157
1158 assert query_ids.include?(1), 'public query on public project was not visible'
1158 assert query_ids.include?(1), 'public query on public project was not visible'
1159 assert query_ids.include?(4), 'public query for all projects was not visible'
1159 assert query_ids.include?(4), 'public query for all projects was not visible'
1160 assert !query_ids.include?(2), 'private query on public project was visible'
1160 assert !query_ids.include?(2), 'private query on public project was visible'
1161 assert !query_ids.include?(3), 'private query for all projects was visible'
1161 assert !query_ids.include?(3), 'private query for all projects was visible'
1162 assert !query_ids.include?(7), 'public query on private project was visible'
1162 assert !query_ids.include?(7), 'public query on private project was visible'
1163 end
1163 end
1164
1164
1165 def test_query_with_public_visibility_should_be_visible_to_anyone
1165 def test_query_with_public_visibility_should_be_visible_to_anyone
1166 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
1166 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
1167
1167
1168 assert q.visible?(User.anonymous)
1168 assert q.visible?(User.anonymous)
1169 assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
1169 assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
1170
1170
1171 assert q.visible?(User.find(7))
1171 assert q.visible?(User.find(7))
1172 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1172 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1173
1173
1174 assert q.visible?(User.find(2))
1174 assert q.visible?(User.find(2))
1175 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1175 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1176
1176
1177 assert q.visible?(User.find(1))
1177 assert q.visible?(User.find(1))
1178 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1178 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1179 end
1179 end
1180
1180
1181 def test_query_with_roles_visibility_should_be_visible_to_user_with_role
1181 def test_query_with_roles_visibility_should_be_visible_to_user_with_role
1182 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
1182 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
1183
1183
1184 assert !q.visible?(User.anonymous)
1184 assert !q.visible?(User.anonymous)
1185 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1185 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1186
1186
1187 assert !q.visible?(User.find(7))
1187 assert !q.visible?(User.find(7))
1188 assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
1188 assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
1189
1189
1190 assert q.visible?(User.find(2))
1190 assert q.visible?(User.find(2))
1191 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1191 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1192
1192
1193 assert q.visible?(User.find(1))
1193 assert q.visible?(User.find(1))
1194 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1194 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1195 end
1195 end
1196
1196
1197 def test_query_with_private_visibility_should_be_visible_to_owner
1197 def test_query_with_private_visibility_should_be_visible_to_owner
1198 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
1198 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
1199
1199
1200 assert !q.visible?(User.anonymous)
1200 assert !q.visible?(User.anonymous)
1201 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1201 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1202
1202
1203 assert q.visible?(User.find(7))
1203 assert q.visible?(User.find(7))
1204 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1204 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1205
1205
1206 assert !q.visible?(User.find(2))
1206 assert !q.visible?(User.find(2))
1207 assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
1207 assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
1208
1208
1209 assert q.visible?(User.find(1))
1209 assert q.visible?(User.find(1))
1210 assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
1210 assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
1211 end
1211 end
1212
1212
1213 test "#available_filters should include users of visible projects in cross-project view" do
1213 test "#available_filters should include users of visible projects in cross-project view" do
1214 users = IssueQuery.new.available_filters["assigned_to_id"]
1214 users = IssueQuery.new.available_filters["assigned_to_id"]
1215 assert_not_nil users
1215 assert_not_nil users
1216 assert users[:values].map{|u|u[1]}.include?("3")
1216 assert users[:values].map{|u|u[1]}.include?("3")
1217 end
1217 end
1218
1218
1219 test "#available_filters should include users of subprojects" do
1219 test "#available_filters should include users of subprojects" do
1220 user1 = User.generate!
1220 user1 = User.generate!
1221 user2 = User.generate!
1221 user2 = User.generate!
1222 project = Project.find(1)
1222 project = Project.find(1)
1223 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1223 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1224
1224
1225 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1225 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1226 assert_not_nil users
1226 assert_not_nil users
1227 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1227 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1228 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1228 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1229 end
1229 end
1230
1230
1231 test "#available_filters should include visible projects in cross-project view" do
1231 test "#available_filters should include visible projects in cross-project view" do
1232 projects = IssueQuery.new.available_filters["project_id"]
1232 projects = IssueQuery.new.available_filters["project_id"]
1233 assert_not_nil projects
1233 assert_not_nil projects
1234 assert projects[:values].map{|u|u[1]}.include?("1")
1234 assert projects[:values].map{|u|u[1]}.include?("1")
1235 end
1235 end
1236
1236
1237 test "#available_filters should include 'member_of_group' filter" do
1237 test "#available_filters should include 'member_of_group' filter" do
1238 query = IssueQuery.new
1238 query = IssueQuery.new
1239 assert query.available_filters.keys.include?("member_of_group")
1239 assert query.available_filters.keys.include?("member_of_group")
1240 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1240 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1241 assert query.available_filters["member_of_group"][:values].present?
1241 assert query.available_filters["member_of_group"][:values].present?
1242 assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
1242 assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
1243 query.available_filters["member_of_group"][:values].sort
1243 query.available_filters["member_of_group"][:values].sort
1244 end
1244 end
1245
1245
1246 test "#available_filters should include 'assigned_to_role' filter" do
1246 test "#available_filters should include 'assigned_to_role' filter" do
1247 query = IssueQuery.new
1247 query = IssueQuery.new
1248 assert query.available_filters.keys.include?("assigned_to_role")
1248 assert query.available_filters.keys.include?("assigned_to_role")
1249 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1249 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1250
1250
1251 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1251 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1252 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1252 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1253 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1253 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1254
1254
1255 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1255 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1256 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1256 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1257 end
1257 end
1258
1258
1259 def test_available_filters_should_include_custom_field_according_to_user_visibility
1259 def test_available_filters_should_include_custom_field_according_to_user_visibility
1260 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1260 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1261 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1261 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1262
1262
1263 with_current_user User.find(3) do
1263 with_current_user User.find(3) do
1264 query = IssueQuery.new
1264 query = IssueQuery.new
1265 assert_include "cf_#{visible_field.id}", query.available_filters.keys
1265 assert_include "cf_#{visible_field.id}", query.available_filters.keys
1266 assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
1266 assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
1267 end
1267 end
1268 end
1268 end
1269
1269
1270 def test_available_columns_should_include_custom_field_according_to_user_visibility
1270 def test_available_columns_should_include_custom_field_according_to_user_visibility
1271 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1271 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1272 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1272 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1273
1273
1274 with_current_user User.find(3) do
1274 with_current_user User.find(3) do
1275 query = IssueQuery.new
1275 query = IssueQuery.new
1276 assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
1276 assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
1277 assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
1277 assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
1278 end
1278 end
1279 end
1279 end
1280
1280
1281 context "#statement" do
1281 context "#statement" do
1282 context "with 'member_of_group' filter" do
1282 context "with 'member_of_group' filter" do
1283 setup do
1283 setup do
1284 Group.destroy_all # No fixtures
1284 Group.destroy_all # No fixtures
1285 @user_in_group = User.generate!
1285 @user_in_group = User.generate!
1286 @second_user_in_group = User.generate!
1286 @second_user_in_group = User.generate!
1287 @user_in_group2 = User.generate!
1287 @user_in_group2 = User.generate!
1288 @user_not_in_group = User.generate!
1288 @user_not_in_group = User.generate!
1289
1289
1290 @group = Group.generate!.reload
1290 @group = Group.generate!.reload
1291 @group.users << @user_in_group
1291 @group.users << @user_in_group
1292 @group.users << @second_user_in_group
1292 @group.users << @second_user_in_group
1293
1293
1294 @group2 = Group.generate!.reload
1294 @group2 = Group.generate!.reload
1295 @group2.users << @user_in_group2
1295 @group2.users << @user_in_group2
1296
1296
1297 end
1297 end
1298
1298
1299 should "search assigned to for users in the group" do
1299 should "search assigned to for users in the group" do
1300 @query = IssueQuery.new(:name => '_')
1300 @query = IssueQuery.new(:name => '_')
1301 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1301 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1302
1302
1303 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
1303 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
1304 assert_find_issues_with_query_is_successful @query
1304 assert_find_issues_with_query_is_successful @query
1305 end
1305 end
1306
1306
1307 should "search not assigned to any group member (none)" do
1307 should "search not assigned to any group member (none)" do
1308 @query = IssueQuery.new(:name => '_')
1308 @query = IssueQuery.new(:name => '_')
1309 @query.add_filter('member_of_group', '!*', [''])
1309 @query.add_filter('member_of_group', '!*', [''])
1310
1310
1311 # Users not in a group
1311 # Users not in a group
1312 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1312 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1313 assert_find_issues_with_query_is_successful @query
1313 assert_find_issues_with_query_is_successful @query
1314 end
1314 end
1315
1315
1316 should "search assigned to any group member (all)" do
1316 should "search assigned to any group member (all)" do
1317 @query = IssueQuery.new(:name => '_')
1317 @query = IssueQuery.new(:name => '_')
1318 @query.add_filter('member_of_group', '*', [''])
1318 @query.add_filter('member_of_group', '*', [''])
1319
1319
1320 # Only users in a group
1320 # Only users in a group
1321 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1321 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1322 assert_find_issues_with_query_is_successful @query
1322 assert_find_issues_with_query_is_successful @query
1323 end
1323 end
1324
1324
1325 should "return an empty set with = empty group" do
1325 should "return an empty set with = empty group" do
1326 @empty_group = Group.generate!
1326 @empty_group = Group.generate!
1327 @query = IssueQuery.new(:name => '_')
1327 @query = IssueQuery.new(:name => '_')
1328 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1328 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1329
1329
1330 assert_equal [], find_issues_with_query(@query)
1330 assert_equal [], find_issues_with_query(@query)
1331 end
1331 end
1332
1332
1333 should "return issues with ! empty group" do
1333 should "return issues with ! empty group" do
1334 @empty_group = Group.generate!
1334 @empty_group = Group.generate!
1335 @query = IssueQuery.new(:name => '_')
1335 @query = IssueQuery.new(:name => '_')
1336 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1336 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1337
1337
1338 assert_find_issues_with_query_is_successful @query
1338 assert_find_issues_with_query_is_successful @query
1339 end
1339 end
1340 end
1340 end
1341
1341
1342 context "with 'assigned_to_role' filter" do
1342 context "with 'assigned_to_role' filter" do
1343 setup do
1343 setup do
1344 @manager_role = Role.find_by_name('Manager')
1344 @manager_role = Role.find_by_name('Manager')
1345 @developer_role = Role.find_by_name('Developer')
1345 @developer_role = Role.find_by_name('Developer')
1346
1346
1347 @project = Project.generate!
1347 @project = Project.generate!
1348 @manager = User.generate!
1348 @manager = User.generate!
1349 @developer = User.generate!
1349 @developer = User.generate!
1350 @boss = User.generate!
1350 @boss = User.generate!
1351 @guest = User.generate!
1351 @guest = User.generate!
1352 User.add_to_project(@manager, @project, @manager_role)
1352 User.add_to_project(@manager, @project, @manager_role)
1353 User.add_to_project(@developer, @project, @developer_role)
1353 User.add_to_project(@developer, @project, @developer_role)
1354 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1354 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1355
1355
1356 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1356 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1357 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1357 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1358 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1358 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1359 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1359 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1360 @issue5 = Issue.generate!(:project => @project)
1360 @issue5 = Issue.generate!(:project => @project)
1361 end
1361 end
1362
1362
1363 should "search assigned to for users with the Role" do
1363 should "search assigned to for users with the Role" do
1364 @query = IssueQuery.new(:name => '_', :project => @project)
1364 @query = IssueQuery.new(:name => '_', :project => @project)
1365 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1365 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1366
1366
1367 assert_query_result [@issue1, @issue3], @query
1367 assert_query_result [@issue1, @issue3], @query
1368 end
1368 end
1369
1369
1370 should "search assigned to for users with the Role on the issue project" do
1370 should "search assigned to for users with the Role on the issue project" do
1371 other_project = Project.generate!
1371 other_project = Project.generate!
1372 User.add_to_project(@developer, other_project, @manager_role)
1372 User.add_to_project(@developer, other_project, @manager_role)
1373
1373
1374 @query = IssueQuery.new(:name => '_', :project => @project)
1374 @query = IssueQuery.new(:name => '_', :project => @project)
1375 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1375 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1376
1376
1377 assert_query_result [@issue1, @issue3], @query
1377 assert_query_result [@issue1, @issue3], @query
1378 end
1378 end
1379
1379
1380 should "return an empty set with empty role" do
1380 should "return an empty set with empty role" do
1381 @empty_role = Role.generate!
1381 @empty_role = Role.generate!
1382 @query = IssueQuery.new(:name => '_', :project => @project)
1382 @query = IssueQuery.new(:name => '_', :project => @project)
1383 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1383 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1384
1384
1385 assert_query_result [], @query
1385 assert_query_result [], @query
1386 end
1386 end
1387
1387
1388 should "search assigned to for users without the Role" do
1388 should "search assigned to for users without the Role" do
1389 @query = IssueQuery.new(:name => '_', :project => @project)
1389 @query = IssueQuery.new(:name => '_', :project => @project)
1390 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1390 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1391
1391
1392 assert_query_result [@issue2, @issue4, @issue5], @query
1392 assert_query_result [@issue2, @issue4, @issue5], @query
1393 end
1393 end
1394
1394
1395 should "search assigned to for users not assigned to any Role (none)" do
1395 should "search assigned to for users not assigned to any Role (none)" do
1396 @query = IssueQuery.new(:name => '_', :project => @project)
1396 @query = IssueQuery.new(:name => '_', :project => @project)
1397 @query.add_filter('assigned_to_role', '!*', [''])
1397 @query.add_filter('assigned_to_role', '!*', [''])
1398
1398
1399 assert_query_result [@issue4, @issue5], @query
1399 assert_query_result [@issue4, @issue5], @query
1400 end
1400 end
1401
1401
1402 should "search assigned to for users assigned to any Role (all)" do
1402 should "search assigned to for users assigned to any Role (all)" do
1403 @query = IssueQuery.new(:name => '_', :project => @project)
1403 @query = IssueQuery.new(:name => '_', :project => @project)
1404 @query.add_filter('assigned_to_role', '*', [''])
1404 @query.add_filter('assigned_to_role', '*', [''])
1405
1405
1406 assert_query_result [@issue1, @issue2, @issue3], @query
1406 assert_query_result [@issue1, @issue2, @issue3], @query
1407 end
1407 end
1408
1408
1409 should "return issues with ! empty role" do
1409 should "return issues with ! empty role" do
1410 @empty_role = Role.generate!
1410 @empty_role = Role.generate!
1411 @query = IssueQuery.new(:name => '_', :project => @project)
1411 @query = IssueQuery.new(:name => '_', :project => @project)
1412 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1412 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1413
1413
1414 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1414 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1415 end
1415 end
1416 end
1416 end
1417 end
1417 end
1418
1419 def test_query_column_should_accept_a_symbol_as_caption
1420 c = QueryColumn.new('foo', :caption => :general_text_Yes)
1421 assert_equal 'Yes', c.caption
1422 end
1423
1424 def test_query_column_should_accept_a_proc_as_caption
1425 c = QueryColumn.new('foo', :caption => lambda {'Foo'})
1426 assert_equal 'Foo', c.caption
1427 end
1418 end
1428 end
General Comments 0
You need to be logged in to leave comments. Login now