##// END OF EJS Templates
Test failure with JRuby 1.7.2 (#12228)....
Jean-Philippe Lang -
r11050:10a38010fbe7
parent child
Show More
@@ -1,791 +1,792
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2013 Jean-Philippe Lang
2 # Copyright (C) 2006-2013 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}"
31 @caption_key = options[:caption] || "field_#{name}"
32 end
32 end
33
33
34 def caption
34 def caption
35 l(@caption_key)
35 l(@caption_key)
36 end
36 end
37
37
38 # Returns true if the column is sortable, otherwise false
38 # Returns true if the column is sortable, otherwise false
39 def sortable?
39 def sortable?
40 !@sortable.nil?
40 !@sortable.nil?
41 end
41 end
42
42
43 def sortable
43 def sortable
44 @sortable.is_a?(Proc) ? @sortable.call : @sortable
44 @sortable.is_a?(Proc) ? @sortable.call : @sortable
45 end
45 end
46
46
47 def inline?
47 def inline?
48 @inline
48 @inline
49 end
49 end
50
50
51 def value(object)
51 def value(object)
52 object.send name
52 object.send name
53 end
53 end
54
54
55 def css_classes
55 def css_classes
56 name
56 name
57 end
57 end
58 end
58 end
59
59
60 class QueryCustomFieldColumn < QueryColumn
60 class QueryCustomFieldColumn < QueryColumn
61
61
62 def initialize(custom_field)
62 def initialize(custom_field)
63 self.name = "cf_#{custom_field.id}".to_sym
63 self.name = "cf_#{custom_field.id}".to_sym
64 self.sortable = custom_field.order_statement || false
64 self.sortable = custom_field.order_statement || false
65 self.groupable = custom_field.group_statement || false
65 self.groupable = custom_field.group_statement || false
66 @inline = true
66 @inline = true
67 @cf = custom_field
67 @cf = custom_field
68 end
68 end
69
69
70 def caption
70 def caption
71 @cf.name
71 @cf.name
72 end
72 end
73
73
74 def custom_field
74 def custom_field
75 @cf
75 @cf
76 end
76 end
77
77
78 def value(object)
78 def value(object)
79 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
79 cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)}
80 cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
80 cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first
81 end
81 end
82
82
83 def css_classes
83 def css_classes
84 @css_classes ||= "#{name} #{@cf.field_format}"
84 @css_classes ||= "#{name} #{@cf.field_format}"
85 end
85 end
86 end
86 end
87
87
88 class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
88 class QueryAssociationCustomFieldColumn < QueryCustomFieldColumn
89
89
90 def initialize(association, custom_field)
90 def initialize(association, custom_field)
91 super(custom_field)
91 super(custom_field)
92 self.name = "#{association}.cf_#{custom_field.id}".to_sym
92 self.name = "#{association}.cf_#{custom_field.id}".to_sym
93 # TODO: support sorting/grouping by association custom field
93 # TODO: support sorting/grouping by association custom field
94 self.sortable = false
94 self.sortable = false
95 self.groupable = false
95 self.groupable = false
96 @association = association
96 @association = association
97 end
97 end
98
98
99 def value(object)
99 def value(object)
100 if assoc = object.send(@association)
100 if assoc = object.send(@association)
101 super(assoc)
101 super(assoc)
102 end
102 end
103 end
103 end
104
104
105 def css_classes
105 def css_classes
106 @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
106 @css_classes ||= "#{@association}_cf_#{@cf.id} #{@cf.field_format}"
107 end
107 end
108 end
108 end
109
109
110 class Query < ActiveRecord::Base
110 class Query < ActiveRecord::Base
111 class StatementInvalid < ::ActiveRecord::StatementInvalid
111 class StatementInvalid < ::ActiveRecord::StatementInvalid
112 end
112 end
113
113
114 belongs_to :project
114 belongs_to :project
115 belongs_to :user
115 belongs_to :user
116 serialize :filters
116 serialize :filters
117 serialize :column_names
117 serialize :column_names
118 serialize :sort_criteria, Array
118 serialize :sort_criteria, Array
119
119
120 attr_protected :project_id, :user_id
120 attr_protected :project_id, :user_id
121
121
122 validates_presence_of :name
122 validates_presence_of :name
123 validates_length_of :name, :maximum => 255
123 validates_length_of :name, :maximum => 255
124 validate :validate_query_filters
124 validate :validate_query_filters
125
125
126 class_attribute :operators
126 class_attribute :operators
127 self.operators = {
127 self.operators = {
128 "=" => :label_equals,
128 "=" => :label_equals,
129 "!" => :label_not_equals,
129 "!" => :label_not_equals,
130 "o" => :label_open_issues,
130 "o" => :label_open_issues,
131 "c" => :label_closed_issues,
131 "c" => :label_closed_issues,
132 "!*" => :label_none,
132 "!*" => :label_none,
133 "*" => :label_any,
133 "*" => :label_any,
134 ">=" => :label_greater_or_equal,
134 ">=" => :label_greater_or_equal,
135 "<=" => :label_less_or_equal,
135 "<=" => :label_less_or_equal,
136 "><" => :label_between,
136 "><" => :label_between,
137 "<t+" => :label_in_less_than,
137 "<t+" => :label_in_less_than,
138 ">t+" => :label_in_more_than,
138 ">t+" => :label_in_more_than,
139 "><t+"=> :label_in_the_next_days,
139 "><t+"=> :label_in_the_next_days,
140 "t+" => :label_in,
140 "t+" => :label_in,
141 "t" => :label_today,
141 "t" => :label_today,
142 "ld" => :label_yesterday,
142 "ld" => :label_yesterday,
143 "w" => :label_this_week,
143 "w" => :label_this_week,
144 "lw" => :label_last_week,
144 "lw" => :label_last_week,
145 "l2w" => [:label_last_n_weeks, {:count => 2}],
145 "l2w" => [:label_last_n_weeks, {:count => 2}],
146 "m" => :label_this_month,
146 "m" => :label_this_month,
147 "lm" => :label_last_month,
147 "lm" => :label_last_month,
148 "y" => :label_this_year,
148 "y" => :label_this_year,
149 ">t-" => :label_less_than_ago,
149 ">t-" => :label_less_than_ago,
150 "<t-" => :label_more_than_ago,
150 "<t-" => :label_more_than_ago,
151 "><t-"=> :label_in_the_past_days,
151 "><t-"=> :label_in_the_past_days,
152 "t-" => :label_ago,
152 "t-" => :label_ago,
153 "~" => :label_contains,
153 "~" => :label_contains,
154 "!~" => :label_not_contains,
154 "!~" => :label_not_contains,
155 "=p" => :label_any_issues_in_project,
155 "=p" => :label_any_issues_in_project,
156 "=!p" => :label_any_issues_not_in_project,
156 "=!p" => :label_any_issues_not_in_project,
157 "!p" => :label_no_issues_in_project
157 "!p" => :label_no_issues_in_project
158 }
158 }
159
159
160 class_attribute :operators_by_filter_type
160 class_attribute :operators_by_filter_type
161 self.operators_by_filter_type = {
161 self.operators_by_filter_type = {
162 :list => [ "=", "!" ],
162 :list => [ "=", "!" ],
163 :list_status => [ "o", "=", "!", "c", "*" ],
163 :list_status => [ "o", "=", "!", "c", "*" ],
164 :list_optional => [ "=", "!", "!*", "*" ],
164 :list_optional => [ "=", "!", "!*", "*" ],
165 :list_subprojects => [ "*", "!*", "=" ],
165 :list_subprojects => [ "*", "!*", "=" ],
166 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
166 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "><t+", "t+", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", ">t-", "<t-", "><t-", "t-", "!*", "*" ],
167 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
167 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "><t-", "t-", "t", "ld", "w", "lw", "l2w", "m", "lm", "y", "!*", "*" ],
168 :string => [ "=", "~", "!", "!~", "!*", "*" ],
168 :string => [ "=", "~", "!", "!~", "!*", "*" ],
169 :text => [ "~", "!~", "!*", "*" ],
169 :text => [ "~", "!~", "!*", "*" ],
170 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
170 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
171 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
171 :float => [ "=", ">=", "<=", "><", "!*", "*" ],
172 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
172 :relation => ["=", "=p", "=!p", "!p", "!*", "*"]
173 }
173 }
174
174
175 class_attribute :available_columns
175 class_attribute :available_columns
176 self.available_columns = []
176 self.available_columns = []
177
177
178 class_attribute :queried_class
178 class_attribute :queried_class
179
179
180 def queried_table_name
180 def queried_table_name
181 @queried_table_name ||= self.class.queried_class.table_name
181 @queried_table_name ||= self.class.queried_class.table_name
182 end
182 end
183
183
184 def initialize(attributes=nil, *args)
184 def initialize(attributes=nil, *args)
185 super attributes
185 super attributes
186 @is_for_all = project.nil?
186 @is_for_all = project.nil?
187 end
187 end
188
188
189 # Builds the query from the given params
189 # Builds the query from the given params
190 def build_from_params(params)
190 def build_from_params(params)
191 if params[:fields] || params[:f]
191 if params[:fields] || params[:f]
192 self.filters = {}
192 self.filters = {}
193 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
193 add_filters(params[:fields] || params[:f], params[:operators] || params[:op], params[:values] || params[:v])
194 else
194 else
195 available_filters.keys.each do |field|
195 available_filters.keys.each do |field|
196 add_short_filter(field, params[field]) if params[field]
196 add_short_filter(field, params[field]) if params[field]
197 end
197 end
198 end
198 end
199 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
199 self.group_by = params[:group_by] || (params[:query] && params[:query][:group_by])
200 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
200 self.column_names = params[:c] || (params[:query] && params[:query][:column_names])
201 self
201 self
202 end
202 end
203
203
204 # Builds a new query from the given params and attributes
204 # Builds a new query from the given params and attributes
205 def self.build_from_params(params, attributes={})
205 def self.build_from_params(params, attributes={})
206 new(attributes).build_from_params(params)
206 new(attributes).build_from_params(params)
207 end
207 end
208
208
209 def validate_query_filters
209 def validate_query_filters
210 filters.each_key do |field|
210 filters.each_key do |field|
211 if values_for(field)
211 if values_for(field)
212 case type_for(field)
212 case type_for(field)
213 when :integer
213 when :integer
214 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
214 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+$/) }
215 when :float
215 when :float
216 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
216 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^[+-]?\d+(\.\d*)?$/) }
217 when :date, :date_past
217 when :date, :date_past
218 case operator_for(field)
218 case operator_for(field)
219 when "=", ">=", "<=", "><"
219 when "=", ">=", "<=", "><"
220 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
220 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
221 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
221 when ">t-", "<t-", "t-", ">t+", "<t+", "t+", "><t+", "><t-"
222 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
222 add_filter_error(field, :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
223 end
223 end
224 end
224 end
225 end
225 end
226
226
227 add_filter_error(field, :blank) unless
227 add_filter_error(field, :blank) unless
228 # filter requires one or more values
228 # filter requires one or more values
229 (values_for(field) and !values_for(field).first.blank?) or
229 (values_for(field) and !values_for(field).first.blank?) or
230 # filter doesn't require any value
230 # filter doesn't require any value
231 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
231 ["o", "c", "!*", "*", "t", "ld", "w", "lw", "l2w", "m", "lm", "y"].include? operator_for(field)
232 end if filters
232 end if filters
233 end
233 end
234
234
235 def add_filter_error(field, message)
235 def add_filter_error(field, message)
236 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
236 m = label_for(field) + " " + l(message, :scope => 'activerecord.errors.messages')
237 errors.add(:base, m)
237 errors.add(:base, m)
238 end
238 end
239
239
240 def editable_by?(user)
240 def editable_by?(user)
241 return false unless user
241 return false unless user
242 # Admin can edit them all and regular users can edit their private queries
242 # Admin can edit them all and regular users can edit their private queries
243 return true if user.admin? || (!is_public && self.user_id == user.id)
243 return true if user.admin? || (!is_public && self.user_id == user.id)
244 # Members can not edit public queries that are for all project (only admin is allowed to)
244 # Members can not edit public queries that are for all project (only admin is allowed to)
245 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
245 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
246 end
246 end
247
247
248 def trackers
248 def trackers
249 @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers
249 @trackers ||= project.nil? ? Tracker.sorted.all : project.rolled_up_trackers
250 end
250 end
251
251
252 # Returns a hash of localized labels for all filter operators
252 # Returns a hash of localized labels for all filter operators
253 def self.operators_labels
253 def self.operators_labels
254 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
254 operators.inject({}) {|h, operator| h[operator.first] = l(*operator.last); h}
255 end
255 end
256
256
257 # Returns a representation of the available filters for JSON serialization
257 # Returns a representation of the available filters for JSON serialization
258 def available_filters_as_json
258 def available_filters_as_json
259 json = {}
259 json = {}
260 available_filters.each do |field, options|
260 available_filters.each do |field, options|
261 json[field] = options.slice(:type, :name, :values).stringify_keys
261 json[field] = options.slice(:type, :name, :values).stringify_keys
262 end
262 end
263 json
263 json
264 end
264 end
265
265
266 def all_projects
266 def all_projects
267 @all_projects ||= Project.visible.all
267 @all_projects ||= Project.visible.all
268 end
268 end
269
269
270 def all_projects_values
270 def all_projects_values
271 return @all_projects_values if @all_projects_values
271 return @all_projects_values if @all_projects_values
272
272
273 values = []
273 values = []
274 Project.project_tree(all_projects) do |p, level|
274 Project.project_tree(all_projects) do |p, level|
275 prefix = (level > 0 ? ('--' * level + ' ') : '')
275 prefix = (level > 0 ? ('--' * level + ' ') : '')
276 values << ["#{prefix}#{p.name}", p.id.to_s]
276 values << ["#{prefix}#{p.name}", p.id.to_s]
277 end
277 end
278 @all_projects_values = values
278 @all_projects_values = values
279 end
279 end
280
280
281 def add_filter(field, operator, values=nil)
281 def add_filter(field, operator, values=nil)
282 # values must be an array
282 # values must be an array
283 return unless values.nil? || values.is_a?(Array)
283 return unless values.nil? || values.is_a?(Array)
284 # check if field is defined as an available filter
284 # check if field is defined as an available filter
285 if available_filters.has_key? field
285 if available_filters.has_key? field
286 filter_options = available_filters[field]
286 filter_options = available_filters[field]
287 filters[field] = {:operator => operator, :values => (values || [''])}
287 filters[field] = {:operator => operator, :values => (values || [''])}
288 end
288 end
289 end
289 end
290
290
291 def add_short_filter(field, expression)
291 def add_short_filter(field, expression)
292 return unless expression && available_filters.has_key?(field)
292 return unless expression && available_filters.has_key?(field)
293 field_type = available_filters[field][:type]
293 field_type = available_filters[field][:type]
294 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
294 operators_by_filter_type[field_type].sort.reverse.detect do |operator|
295 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
295 next unless expression =~ /^#{Regexp.escape(operator)}(.*)$/
296 add_filter field, operator, $1.present? ? $1.split('|') : ['']
296 values = $1
297 add_filter field, operator, values.present? ? values.split('|') : ['']
297 end || add_filter(field, '=', expression.split('|'))
298 end || add_filter(field, '=', expression.split('|'))
298 end
299 end
299
300
300 # Add multiple filters using +add_filter+
301 # Add multiple filters using +add_filter+
301 def add_filters(fields, operators, values)
302 def add_filters(fields, operators, values)
302 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
303 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
303 fields.each do |field|
304 fields.each do |field|
304 add_filter(field, operators[field], values && values[field])
305 add_filter(field, operators[field], values && values[field])
305 end
306 end
306 end
307 end
307 end
308 end
308
309
309 def has_filter?(field)
310 def has_filter?(field)
310 filters and filters[field]
311 filters and filters[field]
311 end
312 end
312
313
313 def type_for(field)
314 def type_for(field)
314 available_filters[field][:type] if available_filters.has_key?(field)
315 available_filters[field][:type] if available_filters.has_key?(field)
315 end
316 end
316
317
317 def operator_for(field)
318 def operator_for(field)
318 has_filter?(field) ? filters[field][:operator] : nil
319 has_filter?(field) ? filters[field][:operator] : nil
319 end
320 end
320
321
321 def values_for(field)
322 def values_for(field)
322 has_filter?(field) ? filters[field][:values] : nil
323 has_filter?(field) ? filters[field][:values] : nil
323 end
324 end
324
325
325 def value_for(field, index=0)
326 def value_for(field, index=0)
326 (values_for(field) || [])[index]
327 (values_for(field) || [])[index]
327 end
328 end
328
329
329 def label_for(field)
330 def label_for(field)
330 label = available_filters[field][:name] if available_filters.has_key?(field)
331 label = available_filters[field][:name] if available_filters.has_key?(field)
331 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
332 label ||= l("field_#{field.to_s.gsub(/_id$/, '')}", :default => field)
332 end
333 end
333
334
334 def self.add_available_column(column)
335 def self.add_available_column(column)
335 self.available_columns << (column) if column.is_a?(QueryColumn)
336 self.available_columns << (column) if column.is_a?(QueryColumn)
336 end
337 end
337
338
338 # Returns an array of columns that can be used to group the results
339 # Returns an array of columns that can be used to group the results
339 def groupable_columns
340 def groupable_columns
340 available_columns.select {|c| c.groupable}
341 available_columns.select {|c| c.groupable}
341 end
342 end
342
343
343 # Returns a Hash of columns and the key for sorting
344 # Returns a Hash of columns and the key for sorting
344 def sortable_columns
345 def sortable_columns
345 available_columns.inject({}) {|h, column|
346 available_columns.inject({}) {|h, column|
346 h[column.name.to_s] = column.sortable
347 h[column.name.to_s] = column.sortable
347 h
348 h
348 }
349 }
349 end
350 end
350
351
351 def columns
352 def columns
352 # preserve the column_names order
353 # preserve the column_names order
353 (has_default_columns? ? default_columns_names : column_names).collect do |name|
354 (has_default_columns? ? default_columns_names : column_names).collect do |name|
354 available_columns.find { |col| col.name == name }
355 available_columns.find { |col| col.name == name }
355 end.compact
356 end.compact
356 end
357 end
357
358
358 def inline_columns
359 def inline_columns
359 columns.select(&:inline?)
360 columns.select(&:inline?)
360 end
361 end
361
362
362 def block_columns
363 def block_columns
363 columns.reject(&:inline?)
364 columns.reject(&:inline?)
364 end
365 end
365
366
366 def available_inline_columns
367 def available_inline_columns
367 available_columns.select(&:inline?)
368 available_columns.select(&:inline?)
368 end
369 end
369
370
370 def available_block_columns
371 def available_block_columns
371 available_columns.reject(&:inline?)
372 available_columns.reject(&:inline?)
372 end
373 end
373
374
374 def default_columns_names
375 def default_columns_names
375 []
376 []
376 end
377 end
377
378
378 def column_names=(names)
379 def column_names=(names)
379 if names
380 if names
380 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
381 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
381 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
382 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
382 # Set column_names to nil if default columns
383 # Set column_names to nil if default columns
383 if names == default_columns_names
384 if names == default_columns_names
384 names = nil
385 names = nil
385 end
386 end
386 end
387 end
387 write_attribute(:column_names, names)
388 write_attribute(:column_names, names)
388 end
389 end
389
390
390 def has_column?(column)
391 def has_column?(column)
391 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
392 column_names && column_names.include?(column.is_a?(QueryColumn) ? column.name : column)
392 end
393 end
393
394
394 def has_default_columns?
395 def has_default_columns?
395 column_names.nil? || column_names.empty?
396 column_names.nil? || column_names.empty?
396 end
397 end
397
398
398 def sort_criteria=(arg)
399 def sort_criteria=(arg)
399 c = []
400 c = []
400 if arg.is_a?(Hash)
401 if arg.is_a?(Hash)
401 arg = arg.keys.sort.collect {|k| arg[k]}
402 arg = arg.keys.sort.collect {|k| arg[k]}
402 end
403 end
403 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
404 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, (o == 'desc' || o == false) ? 'desc' : 'asc']}
404 write_attribute(:sort_criteria, c)
405 write_attribute(:sort_criteria, c)
405 end
406 end
406
407
407 def sort_criteria
408 def sort_criteria
408 read_attribute(:sort_criteria) || []
409 read_attribute(:sort_criteria) || []
409 end
410 end
410
411
411 def sort_criteria_key(arg)
412 def sort_criteria_key(arg)
412 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
413 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
413 end
414 end
414
415
415 def sort_criteria_order(arg)
416 def sort_criteria_order(arg)
416 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
417 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
417 end
418 end
418
419
419 def sort_criteria_order_for(key)
420 def sort_criteria_order_for(key)
420 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
421 sort_criteria.detect {|k, order| key.to_s == k}.try(:last)
421 end
422 end
422
423
423 # Returns the SQL sort order that should be prepended for grouping
424 # Returns the SQL sort order that should be prepended for grouping
424 def group_by_sort_order
425 def group_by_sort_order
425 if grouped? && (column = group_by_column)
426 if grouped? && (column = group_by_column)
426 order = sort_criteria_order_for(column.name) || column.default_order
427 order = sort_criteria_order_for(column.name) || column.default_order
427 column.sortable.is_a?(Array) ?
428 column.sortable.is_a?(Array) ?
428 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
429 column.sortable.collect {|s| "#{s} #{order}"}.join(',') :
429 "#{column.sortable} #{order}"
430 "#{column.sortable} #{order}"
430 end
431 end
431 end
432 end
432
433
433 # Returns true if the query is a grouped query
434 # Returns true if the query is a grouped query
434 def grouped?
435 def grouped?
435 !group_by_column.nil?
436 !group_by_column.nil?
436 end
437 end
437
438
438 def group_by_column
439 def group_by_column
439 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
440 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
440 end
441 end
441
442
442 def group_by_statement
443 def group_by_statement
443 group_by_column.try(:groupable)
444 group_by_column.try(:groupable)
444 end
445 end
445
446
446 def project_statement
447 def project_statement
447 project_clauses = []
448 project_clauses = []
448 if project && !project.descendants.active.empty?
449 if project && !project.descendants.active.empty?
449 ids = [project.id]
450 ids = [project.id]
450 if has_filter?("subproject_id")
451 if has_filter?("subproject_id")
451 case operator_for("subproject_id")
452 case operator_for("subproject_id")
452 when '='
453 when '='
453 # include the selected subprojects
454 # include the selected subprojects
454 ids += values_for("subproject_id").each(&:to_i)
455 ids += values_for("subproject_id").each(&:to_i)
455 when '!*'
456 when '!*'
456 # main project only
457 # main project only
457 else
458 else
458 # all subprojects
459 # all subprojects
459 ids += project.descendants.collect(&:id)
460 ids += project.descendants.collect(&:id)
460 end
461 end
461 elsif Setting.display_subprojects_issues?
462 elsif Setting.display_subprojects_issues?
462 ids += project.descendants.collect(&:id)
463 ids += project.descendants.collect(&:id)
463 end
464 end
464 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
465 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
465 elsif project
466 elsif project
466 project_clauses << "#{Project.table_name}.id = %d" % project.id
467 project_clauses << "#{Project.table_name}.id = %d" % project.id
467 end
468 end
468 project_clauses.any? ? project_clauses.join(' AND ') : nil
469 project_clauses.any? ? project_clauses.join(' AND ') : nil
469 end
470 end
470
471
471 def statement
472 def statement
472 # filters clauses
473 # filters clauses
473 filters_clauses = []
474 filters_clauses = []
474 filters.each_key do |field|
475 filters.each_key do |field|
475 next if field == "subproject_id"
476 next if field == "subproject_id"
476 v = values_for(field).clone
477 v = values_for(field).clone
477 next unless v and !v.empty?
478 next unless v and !v.empty?
478 operator = operator_for(field)
479 operator = operator_for(field)
479
480
480 # "me" value subsitution
481 # "me" value subsitution
481 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
482 if %w(assigned_to_id author_id user_id watcher_id).include?(field)
482 if v.delete("me")
483 if v.delete("me")
483 if User.current.logged?
484 if User.current.logged?
484 v.push(User.current.id.to_s)
485 v.push(User.current.id.to_s)
485 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
486 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
486 else
487 else
487 v.push("0")
488 v.push("0")
488 end
489 end
489 end
490 end
490 end
491 end
491
492
492 if field == 'project_id'
493 if field == 'project_id'
493 if v.delete('mine')
494 if v.delete('mine')
494 v += User.current.memberships.map(&:project_id).map(&:to_s)
495 v += User.current.memberships.map(&:project_id).map(&:to_s)
495 end
496 end
496 end
497 end
497
498
498 if field =~ /cf_(\d+)$/
499 if field =~ /cf_(\d+)$/
499 # custom field
500 # custom field
500 filters_clauses << sql_for_custom_field(field, operator, v, $1)
501 filters_clauses << sql_for_custom_field(field, operator, v, $1)
501 elsif respond_to?("sql_for_#{field}_field")
502 elsif respond_to?("sql_for_#{field}_field")
502 # specific statement
503 # specific statement
503 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
504 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
504 else
505 else
505 # regular field
506 # regular field
506 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
507 filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')'
507 end
508 end
508 end if filters and valid?
509 end if filters and valid?
509
510
510 filters_clauses << project_statement
511 filters_clauses << project_statement
511 filters_clauses.reject!(&:blank?)
512 filters_clauses.reject!(&:blank?)
512
513
513 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
514 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
514 end
515 end
515
516
516 private
517 private
517
518
518 def sql_for_custom_field(field, operator, value, custom_field_id)
519 def sql_for_custom_field(field, operator, value, custom_field_id)
519 db_table = CustomValue.table_name
520 db_table = CustomValue.table_name
520 db_field = 'value'
521 db_field = 'value'
521 filter = @available_filters[field]
522 filter = @available_filters[field]
522 return nil unless filter
523 return nil unless filter
523 if filter[:format] == 'user'
524 if filter[:format] == 'user'
524 if value.delete('me')
525 if value.delete('me')
525 value.push User.current.id.to_s
526 value.push User.current.id.to_s
526 end
527 end
527 end
528 end
528 not_in = nil
529 not_in = nil
529 if operator == '!'
530 if operator == '!'
530 # Makes ! operator work for custom fields with multiple values
531 # Makes ! operator work for custom fields with multiple values
531 operator = '='
532 operator = '='
532 not_in = 'NOT'
533 not_in = 'NOT'
533 end
534 end
534 customized_key = "id"
535 customized_key = "id"
535 customized_class = queried_class
536 customized_class = queried_class
536 if field =~ /^(.+)\.cf_/
537 if field =~ /^(.+)\.cf_/
537 assoc = $1
538 assoc = $1
538 customized_key = "#{assoc}_id"
539 customized_key = "#{assoc}_id"
539 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
540 customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil
540 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
541 raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class
541 end
542 end
542 "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} 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} WHERE " +
543 "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} 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} WHERE " +
543 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
544 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
544 end
545 end
545
546
546 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
547 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
547 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
548 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
548 sql = ''
549 sql = ''
549 case operator
550 case operator
550 when "="
551 when "="
551 if value.any?
552 if value.any?
552 case type_for(field)
553 case type_for(field)
553 when :date, :date_past
554 when :date, :date_past
554 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
555 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
555 when :integer
556 when :integer
556 if is_custom_filter
557 if is_custom_filter
557 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})"
558 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})"
558 else
559 else
559 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
560 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
560 end
561 end
561 when :float
562 when :float
562 if is_custom_filter
563 if is_custom_filter
563 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})"
564 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})"
564 else
565 else
565 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
566 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
566 end
567 end
567 else
568 else
568 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
569 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
569 end
570 end
570 else
571 else
571 # IN an empty set
572 # IN an empty set
572 sql = "1=0"
573 sql = "1=0"
573 end
574 end
574 when "!"
575 when "!"
575 if value.any?
576 if value.any?
576 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
577 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
577 else
578 else
578 # NOT IN an empty set
579 # NOT IN an empty set
579 sql = "1=1"
580 sql = "1=1"
580 end
581 end
581 when "!*"
582 when "!*"
582 sql = "#{db_table}.#{db_field} IS NULL"
583 sql = "#{db_table}.#{db_field} IS NULL"
583 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
584 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
584 when "*"
585 when "*"
585 sql = "#{db_table}.#{db_field} IS NOT NULL"
586 sql = "#{db_table}.#{db_field} IS NOT NULL"
586 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
587 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
587 when ">="
588 when ">="
588 if [:date, :date_past].include?(type_for(field))
589 if [:date, :date_past].include?(type_for(field))
589 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
590 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
590 else
591 else
591 if is_custom_filter
592 if is_custom_filter
592 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})"
593 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})"
593 else
594 else
594 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
595 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
595 end
596 end
596 end
597 end
597 when "<="
598 when "<="
598 if [:date, :date_past].include?(type_for(field))
599 if [:date, :date_past].include?(type_for(field))
599 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
600 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
600 else
601 else
601 if is_custom_filter
602 if is_custom_filter
602 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})"
603 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})"
603 else
604 else
604 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
605 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
605 end
606 end
606 end
607 end
607 when "><"
608 when "><"
608 if [:date, :date_past].include?(type_for(field))
609 if [:date, :date_past].include?(type_for(field))
609 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
610 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
610 else
611 else
611 if is_custom_filter
612 if is_custom_filter
612 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})"
613 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})"
613 else
614 else
614 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
615 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
615 end
616 end
616 end
617 end
617 when "o"
618 when "o"
618 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
619 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id"
619 when "c"
620 when "c"
620 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
621 sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id"
621 when "><t-"
622 when "><t-"
622 # between today - n days and today
623 # between today - n days and today
623 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
624 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
624 when ">t-"
625 when ">t-"
625 # >= today - n days
626 # >= today - n days
626 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
627 sql = relative_date_clause(db_table, db_field, - value.first.to_i, nil)
627 when "<t-"
628 when "<t-"
628 # <= today - n days
629 # <= today - n days
629 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
630 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
630 when "t-"
631 when "t-"
631 # = n days in past
632 # = n days in past
632 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
633 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
633 when "><t+"
634 when "><t+"
634 # between today and today + n days
635 # between today and today + n days
635 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
636 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
636 when ">t+"
637 when ">t+"
637 # >= today + n days
638 # >= today + n days
638 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
639 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
639 when "<t+"
640 when "<t+"
640 # <= today + n days
641 # <= today + n days
641 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
642 sql = relative_date_clause(db_table, db_field, nil, value.first.to_i)
642 when "t+"
643 when "t+"
643 # = today + n days
644 # = today + n days
644 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
645 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
645 when "t"
646 when "t"
646 # = today
647 # = today
647 sql = relative_date_clause(db_table, db_field, 0, 0)
648 sql = relative_date_clause(db_table, db_field, 0, 0)
648 when "ld"
649 when "ld"
649 # = yesterday
650 # = yesterday
650 sql = relative_date_clause(db_table, db_field, -1, -1)
651 sql = relative_date_clause(db_table, db_field, -1, -1)
651 when "w"
652 when "w"
652 # = this week
653 # = this week
653 first_day_of_week = l(:general_first_day_of_week).to_i
654 first_day_of_week = l(:general_first_day_of_week).to_i
654 day_of_week = Date.today.cwday
655 day_of_week = Date.today.cwday
655 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
656 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
656 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
657 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
657 when "lw"
658 when "lw"
658 # = last week
659 # = last week
659 first_day_of_week = l(:general_first_day_of_week).to_i
660 first_day_of_week = l(:general_first_day_of_week).to_i
660 day_of_week = Date.today.cwday
661 day_of_week = Date.today.cwday
661 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
662 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
662 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
663 sql = relative_date_clause(db_table, db_field, - days_ago - 7, - days_ago - 1)
663 when "l2w"
664 when "l2w"
664 # = last 2 weeks
665 # = last 2 weeks
665 first_day_of_week = l(:general_first_day_of_week).to_i
666 first_day_of_week = l(:general_first_day_of_week).to_i
666 day_of_week = Date.today.cwday
667 day_of_week = Date.today.cwday
667 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
668 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
668 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
669 sql = relative_date_clause(db_table, db_field, - days_ago - 14, - days_ago - 1)
669 when "m"
670 when "m"
670 # = this month
671 # = this month
671 date = Date.today
672 date = Date.today
672 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
673 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
673 when "lm"
674 when "lm"
674 # = last month
675 # = last month
675 date = Date.today.prev_month
676 date = Date.today.prev_month
676 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
677 sql = date_clause(db_table, db_field, date.beginning_of_month, date.end_of_month)
677 when "y"
678 when "y"
678 # = this year
679 # = this year
679 date = Date.today
680 date = Date.today
680 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
681 sql = date_clause(db_table, db_field, date.beginning_of_year, date.end_of_year)
681 when "~"
682 when "~"
682 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
683 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
683 when "!~"
684 when "!~"
684 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
685 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
685 else
686 else
686 raise "Unknown query operator #{operator}"
687 raise "Unknown query operator #{operator}"
687 end
688 end
688
689
689 return sql
690 return sql
690 end
691 end
691
692
692 def add_custom_fields_filters(custom_fields, assoc=nil)
693 def add_custom_fields_filters(custom_fields, assoc=nil)
693 return unless custom_fields.present?
694 return unless custom_fields.present?
694 @available_filters ||= {}
695 @available_filters ||= {}
695
696
696 custom_fields.select(&:is_filter?).each do |field|
697 custom_fields.select(&:is_filter?).each do |field|
697 case field.field_format
698 case field.field_format
698 when "text"
699 when "text"
699 options = { :type => :text, :order => 20 }
700 options = { :type => :text, :order => 20 }
700 when "list"
701 when "list"
701 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
702 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
702 when "date"
703 when "date"
703 options = { :type => :date, :order => 20 }
704 options = { :type => :date, :order => 20 }
704 when "bool"
705 when "bool"
705 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
706 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
706 when "int"
707 when "int"
707 options = { :type => :integer, :order => 20 }
708 options = { :type => :integer, :order => 20 }
708 when "float"
709 when "float"
709 options = { :type => :float, :order => 20 }
710 options = { :type => :float, :order => 20 }
710 when "user", "version"
711 when "user", "version"
711 next unless project
712 next unless project
712 values = field.possible_values_options(project)
713 values = field.possible_values_options(project)
713 if User.current.logged? && field.field_format == 'user'
714 if User.current.logged? && field.field_format == 'user'
714 values.unshift ["<< #{l(:label_me)} >>", "me"]
715 values.unshift ["<< #{l(:label_me)} >>", "me"]
715 end
716 end
716 options = { :type => :list_optional, :values => values, :order => 20}
717 options = { :type => :list_optional, :values => values, :order => 20}
717 else
718 else
718 options = { :type => :string, :order => 20 }
719 options = { :type => :string, :order => 20 }
719 end
720 end
720 filter_id = "cf_#{field.id}"
721 filter_id = "cf_#{field.id}"
721 filter_name = field.name
722 filter_name = field.name
722 if assoc.present?
723 if assoc.present?
723 filter_id = "#{assoc}.#{filter_id}"
724 filter_id = "#{assoc}.#{filter_id}"
724 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
725 filter_name = l("label_attribute_of_#{assoc}", :name => filter_name)
725 end
726 end
726 @available_filters[filter_id] = options.merge({
727 @available_filters[filter_id] = options.merge({
727 :name => filter_name,
728 :name => filter_name,
728 :format => field.field_format,
729 :format => field.field_format,
729 :field => field
730 :field => field
730 })
731 })
731 end
732 end
732 end
733 end
733
734
734 def add_associations_custom_fields_filters(*associations)
735 def add_associations_custom_fields_filters(*associations)
735 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
736 fields_by_class = CustomField.where(:is_filter => true).group_by(&:class)
736 associations.each do |assoc|
737 associations.each do |assoc|
737 association_klass = queried_class.reflect_on_association(assoc).klass
738 association_klass = queried_class.reflect_on_association(assoc).klass
738 fields_by_class.each do |field_class, fields|
739 fields_by_class.each do |field_class, fields|
739 if field_class.customized_class <= association_klass
740 if field_class.customized_class <= association_klass
740 add_custom_fields_filters(fields, assoc)
741 add_custom_fields_filters(fields, assoc)
741 end
742 end
742 end
743 end
743 end
744 end
744 end
745 end
745
746
746 # Returns a SQL clause for a date or datetime field.
747 # Returns a SQL clause for a date or datetime field.
747 def date_clause(table, field, from, to)
748 def date_clause(table, field, from, to)
748 s = []
749 s = []
749 if from
750 if from
750 from_yesterday = from - 1
751 from_yesterday = from - 1
751 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
752 from_yesterday_time = Time.local(from_yesterday.year, from_yesterday.month, from_yesterday.day)
752 if self.class.default_timezone == :utc
753 if self.class.default_timezone == :utc
753 from_yesterday_time = from_yesterday_time.utc
754 from_yesterday_time = from_yesterday_time.utc
754 end
755 end
755 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
756 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date(from_yesterday_time.end_of_day)])
756 end
757 end
757 if to
758 if to
758 to_time = Time.local(to.year, to.month, to.day)
759 to_time = Time.local(to.year, to.month, to.day)
759 if self.class.default_timezone == :utc
760 if self.class.default_timezone == :utc
760 to_time = to_time.utc
761 to_time = to_time.utc
761 end
762 end
762 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
763 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to_time.end_of_day)])
763 end
764 end
764 s.join(' AND ')
765 s.join(' AND ')
765 end
766 end
766
767
767 # Returns a SQL clause for a date or datetime field using relative dates.
768 # Returns a SQL clause for a date or datetime field using relative dates.
768 def relative_date_clause(table, field, days_from, days_to)
769 def relative_date_clause(table, field, days_from, days_to)
769 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
770 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
770 end
771 end
771
772
772 # Additional joins required for the given sort options
773 # Additional joins required for the given sort options
773 def joins_for_order_statement(order_options)
774 def joins_for_order_statement(order_options)
774 joins = []
775 joins = []
775
776
776 if order_options
777 if order_options
777 if order_options.include?('authors')
778 if order_options.include?('authors')
778 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
779 joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id"
779 end
780 end
780 order_options.scan(/cf_\d+/).uniq.each do |name|
781 order_options.scan(/cf_\d+/).uniq.each do |name|
781 column = available_columns.detect {|c| c.name.to_s == name}
782 column = available_columns.detect {|c| c.name.to_s == name}
782 join = column && column.custom_field.join_for_order_statement
783 join = column && column.custom_field.join_for_order_statement
783 if join
784 if join
784 joins << join
785 joins << join
785 end
786 end
786 end
787 end
787 end
788 end
788
789
789 joins.any? ? joins.join(' ') : nil
790 joins.any? ? joins.join(' ') : nil
790 end
791 end
791 end
792 end
General Comments 0
You need to be logged in to leave comments. Login now