##// END OF EJS Templates
Fixed date filters accuracy with SQLite (#2221)....
Jean-Philippe Lang -
r2052:9e1192a54de6
parent child
Show More
@@ -1,385 +1,397
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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, :default_order
19 attr_accessor :name, :sortable, :default_order
20 include GLoc
20 include GLoc
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.default_order = options[:default_order]
25 self.default_order = options[:default_order]
26 end
26 end
27
27
28 def caption
28 def caption
29 set_language_if_valid(User.current.language)
29 set_language_if_valid(User.current.language)
30 l("field_#{name}")
30 l("field_#{name}")
31 end
31 end
32 end
32 end
33
33
34 class QueryCustomFieldColumn < QueryColumn
34 class QueryCustomFieldColumn < QueryColumn
35
35
36 def initialize(custom_field)
36 def initialize(custom_field)
37 self.name = "cf_#{custom_field.id}".to_sym
37 self.name = "cf_#{custom_field.id}".to_sym
38 self.sortable = false
38 self.sortable = false
39 @cf = custom_field
39 @cf = custom_field
40 end
40 end
41
41
42 def caption
42 def caption
43 @cf.name
43 @cf.name
44 end
44 end
45
45
46 def custom_field
46 def custom_field
47 @cf
47 @cf
48 end
48 end
49 end
49 end
50
50
51 class Query < ActiveRecord::Base
51 class Query < ActiveRecord::Base
52 belongs_to :project
52 belongs_to :project
53 belongs_to :user
53 belongs_to :user
54 serialize :filters
54 serialize :filters
55 serialize :column_names
55 serialize :column_names
56
56
57 attr_protected :project_id, :user_id
57 attr_protected :project_id, :user_id
58
58
59 validates_presence_of :name, :on => :save
59 validates_presence_of :name, :on => :save
60 validates_length_of :name, :maximum => 255
60 validates_length_of :name, :maximum => 255
61
61
62 @@operators = { "=" => :label_equals,
62 @@operators = { "=" => :label_equals,
63 "!" => :label_not_equals,
63 "!" => :label_not_equals,
64 "o" => :label_open_issues,
64 "o" => :label_open_issues,
65 "c" => :label_closed_issues,
65 "c" => :label_closed_issues,
66 "!*" => :label_none,
66 "!*" => :label_none,
67 "*" => :label_all,
67 "*" => :label_all,
68 ">=" => '>=',
68 ">=" => '>=',
69 "<=" => '<=',
69 "<=" => '<=',
70 "<t+" => :label_in_less_than,
70 "<t+" => :label_in_less_than,
71 ">t+" => :label_in_more_than,
71 ">t+" => :label_in_more_than,
72 "t+" => :label_in,
72 "t+" => :label_in,
73 "t" => :label_today,
73 "t" => :label_today,
74 "w" => :label_this_week,
74 "w" => :label_this_week,
75 ">t-" => :label_less_than_ago,
75 ">t-" => :label_less_than_ago,
76 "<t-" => :label_more_than_ago,
76 "<t-" => :label_more_than_ago,
77 "t-" => :label_ago,
77 "t-" => :label_ago,
78 "~" => :label_contains,
78 "~" => :label_contains,
79 "!~" => :label_not_contains }
79 "!~" => :label_not_contains }
80
80
81 cattr_reader :operators
81 cattr_reader :operators
82
82
83 @@operators_by_filter_type = { :list => [ "=", "!" ],
83 @@operators_by_filter_type = { :list => [ "=", "!" ],
84 :list_status => [ "o", "=", "!", "c", "*" ],
84 :list_status => [ "o", "=", "!", "c", "*" ],
85 :list_optional => [ "=", "!", "!*", "*" ],
85 :list_optional => [ "=", "!", "!*", "*" ],
86 :list_subprojects => [ "*", "!*", "=" ],
86 :list_subprojects => [ "*", "!*", "=" ],
87 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
87 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
88 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
88 :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
89 :string => [ "=", "~", "!", "!~" ],
89 :string => [ "=", "~", "!", "!~" ],
90 :text => [ "~", "!~" ],
90 :text => [ "~", "!~" ],
91 :integer => [ "=", ">=", "<=", "!*", "*" ] }
91 :integer => [ "=", ">=", "<=", "!*", "*" ] }
92
92
93 cattr_reader :operators_by_filter_type
93 cattr_reader :operators_by_filter_type
94
94
95 @@available_columns = [
95 @@available_columns = [
96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
96 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
97 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
98 QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
99 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
99 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
100 QueryColumn.new(:author),
100 QueryColumn.new(:author),
101 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
101 QueryColumn.new(:assigned_to, :sortable => "#{User.table_name}.lastname"),
102 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
102 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
103 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
103 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
104 QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
104 QueryColumn.new(:fixed_version, :sortable => "#{Version.table_name}.effective_date", :default_order => 'desc'),
105 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
105 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
106 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
106 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
107 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
107 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
108 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
108 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
109 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
109 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
110 ]
110 ]
111 cattr_reader :available_columns
111 cattr_reader :available_columns
112
112
113 def initialize(attributes = nil)
113 def initialize(attributes = nil)
114 super attributes
114 super attributes
115 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
115 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
116 set_language_if_valid(User.current.language)
116 set_language_if_valid(User.current.language)
117 end
117 end
118
118
119 def after_initialize
119 def after_initialize
120 # Store the fact that project is nil (used in #editable_by?)
120 # Store the fact that project is nil (used in #editable_by?)
121 @is_for_all = project.nil?
121 @is_for_all = project.nil?
122 end
122 end
123
123
124 def validate
124 def validate
125 filters.each_key do |field|
125 filters.each_key do |field|
126 errors.add label_for(field), :activerecord_error_blank unless
126 errors.add label_for(field), :activerecord_error_blank unless
127 # filter requires one or more values
127 # filter requires one or more values
128 (values_for(field) and !values_for(field).first.blank?) or
128 (values_for(field) and !values_for(field).first.blank?) or
129 # filter doesn't require any value
129 # filter doesn't require any value
130 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
130 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
131 end if filters
131 end if filters
132 end
132 end
133
133
134 def editable_by?(user)
134 def editable_by?(user)
135 return false unless user
135 return false unless user
136 # Admin can edit them all and regular users can edit their private queries
136 # Admin can edit them all and regular users can edit their private queries
137 return true if user.admin? || (!is_public && self.user_id == user.id)
137 return true if user.admin? || (!is_public && self.user_id == user.id)
138 # Members can not edit public queries that are for all project (only admin is allowed to)
138 # Members can not edit public queries that are for all project (only admin is allowed to)
139 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
139 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
140 end
140 end
141
141
142 def available_filters
142 def available_filters
143 return @available_filters if @available_filters
143 return @available_filters if @available_filters
144
144
145 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
145 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
146
146
147 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
147 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
148 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
148 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
149 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
149 "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
150 "subject" => { :type => :text, :order => 8 },
150 "subject" => { :type => :text, :order => 8 },
151 "created_on" => { :type => :date_past, :order => 9 },
151 "created_on" => { :type => :date_past, :order => 9 },
152 "updated_on" => { :type => :date_past, :order => 10 },
152 "updated_on" => { :type => :date_past, :order => 10 },
153 "start_date" => { :type => :date, :order => 11 },
153 "start_date" => { :type => :date, :order => 11 },
154 "due_date" => { :type => :date, :order => 12 },
154 "due_date" => { :type => :date, :order => 12 },
155 "estimated_hours" => { :type => :integer, :order => 13 },
155 "estimated_hours" => { :type => :integer, :order => 13 },
156 "done_ratio" => { :type => :integer, :order => 14 }}
156 "done_ratio" => { :type => :integer, :order => 14 }}
157
157
158 user_values = []
158 user_values = []
159 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
159 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
160 if project
160 if project
161 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
161 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
162 else
162 else
163 # members of the user's projects
163 # members of the user's projects
164 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
164 user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
165 end
165 end
166 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
166 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
167 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
167 @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
168
168
169 if project
169 if project
170 # project specific filters
170 # project specific filters
171 unless @project.issue_categories.empty?
171 unless @project.issue_categories.empty?
172 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
172 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
173 end
173 end
174 unless @project.versions.empty?
174 unless @project.versions.empty?
175 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
175 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
176 end
176 end
177 unless @project.active_children.empty?
177 unless @project.active_children.empty?
178 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
178 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.active_children.collect{|s| [s.name, s.id.to_s] } }
179 end
179 end
180 add_custom_fields_filters(@project.all_issue_custom_fields)
180 add_custom_fields_filters(@project.all_issue_custom_fields)
181 else
181 else
182 # global filters for cross project issue list
182 # global filters for cross project issue list
183 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
183 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
184 end
184 end
185 @available_filters
185 @available_filters
186 end
186 end
187
187
188 def add_filter(field, operator, values)
188 def add_filter(field, operator, values)
189 # values must be an array
189 # values must be an array
190 return unless values and values.is_a? Array # and !values.first.empty?
190 return unless values and values.is_a? Array # and !values.first.empty?
191 # check if field is defined as an available filter
191 # check if field is defined as an available filter
192 if available_filters.has_key? field
192 if available_filters.has_key? field
193 filter_options = available_filters[field]
193 filter_options = available_filters[field]
194 # check if operator is allowed for that filter
194 # check if operator is allowed for that filter
195 #if @@operators_by_filter_type[filter_options[:type]].include? operator
195 #if @@operators_by_filter_type[filter_options[:type]].include? operator
196 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
196 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
197 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
197 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
198 #end
198 #end
199 filters[field] = {:operator => operator, :values => values }
199 filters[field] = {:operator => operator, :values => values }
200 end
200 end
201 end
201 end
202
202
203 def add_short_filter(field, expression)
203 def add_short_filter(field, expression)
204 return unless expression
204 return unless expression
205 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
205 parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
206 add_filter field, (parms[0] || "="), [parms[1] || ""]
206 add_filter field, (parms[0] || "="), [parms[1] || ""]
207 end
207 end
208
208
209 def has_filter?(field)
209 def has_filter?(field)
210 filters and filters[field]
210 filters and filters[field]
211 end
211 end
212
212
213 def operator_for(field)
213 def operator_for(field)
214 has_filter?(field) ? filters[field][:operator] : nil
214 has_filter?(field) ? filters[field][:operator] : nil
215 end
215 end
216
216
217 def values_for(field)
217 def values_for(field)
218 has_filter?(field) ? filters[field][:values] : nil
218 has_filter?(field) ? filters[field][:values] : nil
219 end
219 end
220
220
221 def label_for(field)
221 def label_for(field)
222 label = available_filters[field][:name] if available_filters.has_key?(field)
222 label = available_filters[field][:name] if available_filters.has_key?(field)
223 label ||= field.gsub(/\_id$/, "")
223 label ||= field.gsub(/\_id$/, "")
224 end
224 end
225
225
226 def available_columns
226 def available_columns
227 return @available_columns if @available_columns
227 return @available_columns if @available_columns
228 @available_columns = Query.available_columns
228 @available_columns = Query.available_columns
229 @available_columns += (project ?
229 @available_columns += (project ?
230 project.all_issue_custom_fields :
230 project.all_issue_custom_fields :
231 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
231 IssueCustomField.find(:all, :conditions => {:is_for_all => true})
232 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
232 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
233 end
233 end
234
234
235 def columns
235 def columns
236 if has_default_columns?
236 if has_default_columns?
237 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
237 available_columns.select {|c| Setting.issue_list_default_columns.include?(c.name.to_s) }
238 else
238 else
239 # preserve the column_names order
239 # preserve the column_names order
240 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
240 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
241 end
241 end
242 end
242 end
243
243
244 def column_names=(names)
244 def column_names=(names)
245 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
245 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
246 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
246 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
247 write_attribute(:column_names, names)
247 write_attribute(:column_names, names)
248 end
248 end
249
249
250 def has_column?(column)
250 def has_column?(column)
251 column_names && column_names.include?(column.name)
251 column_names && column_names.include?(column.name)
252 end
252 end
253
253
254 def has_default_columns?
254 def has_default_columns?
255 column_names.nil? || column_names.empty?
255 column_names.nil? || column_names.empty?
256 end
256 end
257
257
258 def project_statement
258 def project_statement
259 project_clauses = []
259 project_clauses = []
260 if project && !@project.active_children.empty?
260 if project && !@project.active_children.empty?
261 ids = [project.id]
261 ids = [project.id]
262 if has_filter?("subproject_id")
262 if has_filter?("subproject_id")
263 case operator_for("subproject_id")
263 case operator_for("subproject_id")
264 when '='
264 when '='
265 # include the selected subprojects
265 # include the selected subprojects
266 ids += values_for("subproject_id").each(&:to_i)
266 ids += values_for("subproject_id").each(&:to_i)
267 when '!*'
267 when '!*'
268 # main project only
268 # main project only
269 else
269 else
270 # all subprojects
270 # all subprojects
271 ids += project.child_ids
271 ids += project.child_ids
272 end
272 end
273 elsif Setting.display_subprojects_issues?
273 elsif Setting.display_subprojects_issues?
274 ids += project.child_ids
274 ids += project.child_ids
275 end
275 end
276 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
276 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
277 elsif project
277 elsif project
278 project_clauses << "#{Project.table_name}.id = %d" % project.id
278 project_clauses << "#{Project.table_name}.id = %d" % project.id
279 end
279 end
280 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
280 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
281 project_clauses.join(' AND ')
281 project_clauses.join(' AND ')
282 end
282 end
283
283
284 def statement
284 def statement
285 # filters clauses
285 # filters clauses
286 filters_clauses = []
286 filters_clauses = []
287 filters.each_key do |field|
287 filters.each_key do |field|
288 next if field == "subproject_id"
288 next if field == "subproject_id"
289 v = values_for(field).clone
289 v = values_for(field).clone
290 next unless v and !v.empty?
290 next unless v and !v.empty?
291
291
292 sql = ''
292 sql = ''
293 is_custom_filter = false
293 is_custom_filter = false
294 if field =~ /^cf_(\d+)$/
294 if field =~ /^cf_(\d+)$/
295 # custom field
295 # custom field
296 db_table = CustomValue.table_name
296 db_table = CustomValue.table_name
297 db_field = 'value'
297 db_field = 'value'
298 is_custom_filter = true
298 is_custom_filter = true
299 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
299 sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
300 else
300 else
301 # regular field
301 # regular field
302 db_table = Issue.table_name
302 db_table = Issue.table_name
303 db_field = field
303 db_field = field
304 sql << '('
304 sql << '('
305 end
305 end
306
306
307 # "me" value subsitution
307 # "me" value subsitution
308 if %w(assigned_to_id author_id).include?(field)
308 if %w(assigned_to_id author_id).include?(field)
309 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
309 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
310 end
310 end
311
311
312 case operator_for field
312 case operator_for field
313 when "="
313 when "="
314 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
314 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
315 when "!"
315 when "!"
316 sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
316 sql = sql + "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
317 when "!*"
317 when "!*"
318 sql = sql + "#{db_table}.#{db_field} IS NULL"
318 sql = sql + "#{db_table}.#{db_field} IS NULL"
319 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
319 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
320 when "*"
320 when "*"
321 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
321 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
322 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
322 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
323 when ">="
323 when ">="
324 sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
324 sql = sql + "#{db_table}.#{db_field} >= #{v.first.to_i}"
325 when "<="
325 when "<="
326 sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
326 sql = sql + "#{db_table}.#{db_field} <= #{v.first.to_i}"
327 when "o"
327 when "o"
328 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
328 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
329 when "c"
329 when "c"
330 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
330 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
331 when ">t-"
331 when ">t-"
332 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today + 1).to_time)]
332 sql = sql + date_range_clause(db_table, db_field, - v.first.to_i, 0)
333 when "<t-"
333 when "<t-"
334 sql = sql + "#{db_table}.#{db_field} <= '%s'" % connection.quoted_date((Date.today - v.first.to_i).to_time)
334 sql = sql + date_range_clause(db_table, db_field, nil, - v.first.to_i)
335 when "t-"
335 when "t-"
336 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today - v.first.to_i).to_time), connection.quoted_date((Date.today - v.first.to_i + 1).to_time)]
336 sql = sql + date_range_clause(db_table, db_field, - v.first.to_i, - v.first.to_i)
337 when ">t+"
337 when ">t+"
338 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date((Date.today + v.first.to_i).to_time)
338 sql = sql + date_range_clause(db_table, db_field, v.first.to_i, nil)
339 when "<t+"
339 when "<t+"
340 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
340 sql = sql + date_range_clause(db_table, db_field, 0, v.first.to_i)
341 when "t+"
341 when "t+"
342 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date((Date.today + v.first.to_i).to_time), connection.quoted_date((Date.today + v.first.to_i + 1).to_time)]
342 sql = sql + date_range_clause(db_table, db_field, v.first.to_i, v.first.to_i)
343 when "t"
343 when "t"
344 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(Date.today.to_time), connection.quoted_date((Date.today+1).to_time)]
344 sql = sql + date_range_clause(db_table, db_field, 0, 0)
345 when "w"
345 when "w"
346 from = l(:general_first_day_of_week) == '7' ?
346 from = l(:general_first_day_of_week) == '7' ?
347 # week starts on sunday
347 # week starts on sunday
348 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
348 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
349 # week starts on monday (Rails default)
349 # week starts on monday (Rails default)
350 Time.now.at_beginning_of_week
350 Time.now.at_beginning_of_week
351 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
351 sql = sql + "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
352 when "~"
352 when "~"
353 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
353 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
354 when "!~"
354 when "!~"
355 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
355 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
356 end
356 end
357 sql << ')'
357 sql << ')'
358 filters_clauses << sql
358 filters_clauses << sql
359 end if filters and valid?
359 end if filters and valid?
360
360
361 (filters_clauses << project_statement).join(' AND ')
361 (filters_clauses << project_statement).join(' AND ')
362 end
362 end
363
363
364 private
364 private
365
365
366 def add_custom_fields_filters(custom_fields)
366 def add_custom_fields_filters(custom_fields)
367 @available_filters ||= {}
367 @available_filters ||= {}
368
368
369 custom_fields.select(&:is_filter?).each do |field|
369 custom_fields.select(&:is_filter?).each do |field|
370 case field.field_format
370 case field.field_format
371 when "text"
371 when "text"
372 options = { :type => :text, :order => 20 }
372 options = { :type => :text, :order => 20 }
373 when "list"
373 when "list"
374 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
374 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
375 when "date"
375 when "date"
376 options = { :type => :date, :order => 20 }
376 options = { :type => :date, :order => 20 }
377 when "bool"
377 when "bool"
378 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
378 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
379 else
379 else
380 options = { :type => :string, :order => 20 }
380 options = { :type => :string, :order => 20 }
381 end
381 end
382 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
382 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
383 end
383 end
384 end
384 end
385
386 # Returns a SQL clause for a date or datetime field.
387 def date_range_clause(table, field, from, to)
388 s = []
389 if from
390 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
391 end
392 if to
393 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
394 end
395 s.join(' AND ')
396 end
385 end
397 end
@@ -1,94 +1,111
1 ---
1 ---
2 issues_001:
2 issues_001:
3 created_on: <%= 3.days.ago.to_date.to_s(:db) %>
3 created_on: <%= 3.days.ago.to_date.to_s(:db) %>
4 project_id: 1
4 project_id: 1
5 updated_on: <%= 1.day.ago.to_date.to_s(:db) %>
5 updated_on: <%= 1.day.ago.to_date.to_s(:db) %>
6 priority_id: 4
6 priority_id: 4
7 subject: Can't print recipes
7 subject: Can't print recipes
8 id: 1
8 id: 1
9 fixed_version_id:
9 fixed_version_id:
10 category_id: 1
10 category_id: 1
11 description: Unable to print recipes
11 description: Unable to print recipes
12 tracker_id: 1
12 tracker_id: 1
13 assigned_to_id:
13 assigned_to_id:
14 author_id: 2
14 author_id: 2
15 status_id: 1
15 status_id: 1
16 start_date: <%= 1.day.ago.to_date.to_s(:db) %>
16 start_date: <%= 1.day.ago.to_date.to_s(:db) %>
17 due_date: <%= 10.day.from_now.to_date.to_s(:db) %>
17 due_date: <%= 10.day.from_now.to_date.to_s(:db) %>
18 issues_002:
18 issues_002:
19 created_on: 2006-07-19 21:04:21 +02:00
19 created_on: 2006-07-19 21:04:21 +02:00
20 project_id: 1
20 project_id: 1
21 updated_on: 2006-07-19 21:09:50 +02:00
21 updated_on: 2006-07-19 21:09:50 +02:00
22 priority_id: 5
22 priority_id: 5
23 subject: Add ingredients categories
23 subject: Add ingredients categories
24 id: 2
24 id: 2
25 fixed_version_id: 2
25 fixed_version_id: 2
26 category_id:
26 category_id:
27 description: Ingredients of the recipe should be classified by categories
27 description: Ingredients of the recipe should be classified by categories
28 tracker_id: 2
28 tracker_id: 2
29 assigned_to_id: 3
29 assigned_to_id: 3
30 author_id: 2
30 author_id: 2
31 status_id: 2
31 status_id: 2
32 start_date: <%= 2.day.ago.to_date.to_s(:db) %>
32 start_date: <%= 2.day.ago.to_date.to_s(:db) %>
33 due_date:
33 due_date:
34 issues_003:
34 issues_003:
35 created_on: 2006-07-19 21:07:27 +02:00
35 created_on: 2006-07-19 21:07:27 +02:00
36 project_id: 1
36 project_id: 1
37 updated_on: 2006-07-19 21:07:27 +02:00
37 updated_on: 2006-07-19 21:07:27 +02:00
38 priority_id: 4
38 priority_id: 4
39 subject: Error 281 when updating a recipe
39 subject: Error 281 when updating a recipe
40 id: 3
40 id: 3
41 fixed_version_id:
41 fixed_version_id:
42 category_id:
42 category_id:
43 description: Error 281 is encountered when saving a recipe
43 description: Error 281 is encountered when saving a recipe
44 tracker_id: 1
44 tracker_id: 1
45 assigned_to_id: 3
45 assigned_to_id: 3
46 author_id: 2
46 author_id: 2
47 status_id: 1
47 status_id: 1
48 start_date: <%= 1.day.from_now.to_date.to_s(:db) %>
48 start_date: <%= 1.day.from_now.to_date.to_s(:db) %>
49 due_date: <%= 40.day.ago.to_date.to_s(:db) %>
49 due_date: <%= 40.day.ago.to_date.to_s(:db) %>
50 issues_004:
50 issues_004:
51 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
51 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
52 project_id: 2
52 project_id: 2
53 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
53 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
54 priority_id: 4
54 priority_id: 4
55 subject: Issue on project 2
55 subject: Issue on project 2
56 id: 4
56 id: 4
57 fixed_version_id:
57 fixed_version_id:
58 category_id:
58 category_id:
59 description: Issue on project 2
59 description: Issue on project 2
60 tracker_id: 1
60 tracker_id: 1
61 assigned_to_id:
61 assigned_to_id:
62 author_id: 2
62 author_id: 2
63 status_id: 1
63 status_id: 1
64 issues_005:
64 issues_005:
65 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
65 created_on: <%= 5.days.ago.to_date.to_s(:db) %>
66 project_id: 3
66 project_id: 3
67 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
67 updated_on: <%= 2.days.ago.to_date.to_s(:db) %>
68 priority_id: 4
68 priority_id: 4
69 subject: Subproject issue
69 subject: Subproject issue
70 id: 5
70 id: 5
71 fixed_version_id:
71 fixed_version_id:
72 category_id:
72 category_id:
73 description: This is an issue on a cookbook subproject
73 description: This is an issue on a cookbook subproject
74 tracker_id: 1
74 tracker_id: 1
75 assigned_to_id:
75 assigned_to_id:
76 author_id: 2
76 author_id: 2
77 status_id: 1
77 status_id: 1
78 issues_006:
78 issues_006:
79 created_on: <%= 1.minute.ago.to_date.to_s(:db) %>
79 created_on: <%= 1.minute.ago.to_date.to_s(:db) %>
80 project_id: 5
80 project_id: 5
81 updated_on: <%= 1.minute.ago.to_date.to_s(:db) %>
81 updated_on: <%= 1.minute.ago.to_date.to_s(:db) %>
82 priority_id: 4
82 priority_id: 4
83 subject: Issue of a private subproject
83 subject: Issue of a private subproject
84 id: 6
84 id: 6
85 fixed_version_id:
85 fixed_version_id:
86 category_id:
86 category_id:
87 description: This is an issue of a private subproject of cookbook
87 description: This is an issue of a private subproject of cookbook
88 tracker_id: 1
88 tracker_id: 1
89 assigned_to_id:
89 assigned_to_id:
90 author_id: 2
90 author_id: 2
91 status_id: 1
91 status_id: 1
92 start_date: <%= Date.today.to_s(:db) %>
92 start_date: <%= Date.today.to_s(:db) %>
93 due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
93 due_date: <%= 1.days.from_now.to_date.to_s(:db) %>
94 issues_007:
95 created_on: <%= 10.days.ago.to_date.to_s(:db) %>
96 project_id: 1
97 updated_on: <%= 10.days.ago.to_date.to_s(:db) %>
98 priority_id: 3
99 subject: Issue due today
100 id: 7
101 fixed_version_id:
102 category_id:
103 description: This is an issue that is due today
104 tracker_id: 1
105 assigned_to_id:
106 author_id: 2
107 status_id: 1
108 start_date: <%= 10.days.ago.to_s(:db) %>
109 due_date: <%= Date.today.to_s(:db) %>
110 lock_version: 0
94 No newline at end of file
111
@@ -1,173 +1,212
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006-2008 Jean-Philippe Lang
2 # Copyright (C) 2006-2008 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.dirname(__FILE__) + '/../test_helper'
18 require File.dirname(__FILE__) + '/../test_helper'
19
19
20 class QueryTest < Test::Unit::TestCase
20 class QueryTest < Test::Unit::TestCase
21 fixtures :projects, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :queries
21 fixtures :projects, :enabled_modules, :users, :members, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :custom_fields, :custom_values, :versions, :queries
22
22
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
24 query = Query.new(:project => nil, :name => '_')
24 query = Query.new(:project => nil, :name => '_')
25 assert query.available_filters.has_key?('cf_1')
25 assert query.available_filters.has_key?('cf_1')
26 assert !query.available_filters.has_key?('cf_3')
26 assert !query.available_filters.has_key?('cf_3')
27 end
27 end
28
28
29 def find_issues_with_query(query)
29 def find_issues_with_query(query)
30 Issue.find :all,
30 Issue.find :all,
31 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
31 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
32 :conditions => query.statement
32 :conditions => query.statement
33 end
33 end
34
34
35 def test_query_with_multiple_custom_fields
35 def test_query_with_multiple_custom_fields
36 query = Query.find(1)
36 query = Query.find(1)
37 assert query.valid?
37 assert query.valid?
38 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
38 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
39 issues = find_issues_with_query(query)
39 issues = find_issues_with_query(query)
40 assert_equal 1, issues.length
40 assert_equal 1, issues.length
41 assert_equal Issue.find(3), issues.first
41 assert_equal Issue.find(3), issues.first
42 end
42 end
43
43
44 def test_operator_none
44 def test_operator_none
45 query = Query.new(:project => Project.find(1), :name => '_')
45 query = Query.new(:project => Project.find(1), :name => '_')
46 query.add_filter('fixed_version_id', '!*', [''])
46 query.add_filter('fixed_version_id', '!*', [''])
47 query.add_filter('cf_1', '!*', [''])
47 query.add_filter('cf_1', '!*', [''])
48 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
48 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
49 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
49 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
50 find_issues_with_query(query)
50 find_issues_with_query(query)
51 end
51 end
52
52
53 def test_operator_none_for_integer
53 def test_operator_none_for_integer
54 query = Query.new(:project => Project.find(1), :name => '_')
54 query = Query.new(:project => Project.find(1), :name => '_')
55 query.add_filter('estimated_hours', '!*', [''])
55 query.add_filter('estimated_hours', '!*', [''])
56 issues = find_issues_with_query(query)
56 issues = find_issues_with_query(query)
57 assert !issues.empty?
57 assert !issues.empty?
58 assert issues.all? {|i| !i.estimated_hours}
58 assert issues.all? {|i| !i.estimated_hours}
59 end
59 end
60
60
61 def test_operator_all
61 def test_operator_all
62 query = Query.new(:project => Project.find(1), :name => '_')
62 query = Query.new(:project => Project.find(1), :name => '_')
63 query.add_filter('fixed_version_id', '*', [''])
63 query.add_filter('fixed_version_id', '*', [''])
64 query.add_filter('cf_1', '*', [''])
64 query.add_filter('cf_1', '*', [''])
65 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
65 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
66 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
66 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
67 find_issues_with_query(query)
67 find_issues_with_query(query)
68 end
68 end
69
69
70 def test_operator_greater_than
70 def test_operator_greater_than
71 query = Query.new(:project => Project.find(1), :name => '_')
71 query = Query.new(:project => Project.find(1), :name => '_')
72 query.add_filter('done_ratio', '>=', ['40'])
72 query.add_filter('done_ratio', '>=', ['40'])
73 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40")
73 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40")
74 find_issues_with_query(query)
74 find_issues_with_query(query)
75 end
75 end
76
76
77 def test_operator_in_more_than
77 def test_operator_in_more_than
78 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
78 query = Query.new(:project => Project.find(1), :name => '_')
79 query = Query.new(:project => Project.find(1), :name => '_')
79 query.add_filter('due_date', '>t+', ['15'])
80 query.add_filter('due_date', '>t+', ['15'])
80 assert query.statement.include?("#{Issue.table_name}.due_date >=")
81 issues = find_issues_with_query(query)
81 find_issues_with_query(query)
82 assert !issues.empty?
83 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
82 end
84 end
83
85
84 def test_operator_in_less_than
86 def test_operator_in_less_than
85 query = Query.new(:project => Project.find(1), :name => '_')
87 query = Query.new(:project => Project.find(1), :name => '_')
86 query.add_filter('due_date', '<t+', ['15'])
88 query.add_filter('due_date', '<t+', ['15'])
87 assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
89 issues = find_issues_with_query(query)
88 find_issues_with_query(query)
90 assert !issues.empty?
91 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
92 end
93
94 def test_operator_less_than_ago
95 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
96 query = Query.new(:project => Project.find(1), :name => '_')
97 query.add_filter('due_date', '>t-', ['3'])
98 issues = find_issues_with_query(query)
99 assert !issues.empty?
100 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
101 end
102
103 def test_operator_more_than_ago
104 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
105 query = Query.new(:project => Project.find(1), :name => '_')
106 query.add_filter('due_date', '<t-', ['10'])
107 assert query.statement.include?("#{Issue.table_name}.due_date <=")
108 issues = find_issues_with_query(query)
109 assert !issues.empty?
110 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
111 end
112
113 def test_operator_in
114 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
115 query = Query.new(:project => Project.find(1), :name => '_')
116 query.add_filter('due_date', 't+', ['2'])
117 issues = find_issues_with_query(query)
118 assert !issues.empty?
119 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
120 end
121
122 def test_operator_ago
123 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
124 query = Query.new(:project => Project.find(1), :name => '_')
125 query.add_filter('due_date', 't-', ['3'])
126 issues = find_issues_with_query(query)
127 assert !issues.empty?
128 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
89 end
129 end
90
130
91 def test_operator_today
131 def test_operator_today
92 query = Query.new(:project => Project.find(1), :name => '_')
132 query = Query.new(:project => Project.find(1), :name => '_')
93 query.add_filter('due_date', 't', [''])
133 query.add_filter('due_date', 't', [''])
94 assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
134 issues = find_issues_with_query(query)
95 find_issues_with_query(query)
135 assert !issues.empty?
136 issues.each {|issue| assert_equal Date.today, issue.due_date}
96 end
137 end
97
138
98 def test_operator_this_week_on_date
139 def test_operator_this_week_on_date
99 query = Query.new(:project => Project.find(1), :name => '_')
140 query = Query.new(:project => Project.find(1), :name => '_')
100 query.add_filter('due_date', 'w', [''])
141 query.add_filter('due_date', 'w', [''])
101 assert query.statement.include?("#{Issue.table_name}.due_date BETWEEN")
102 find_issues_with_query(query)
142 find_issues_with_query(query)
103 end
143 end
104
144
105 def test_operator_this_week_on_datetime
145 def test_operator_this_week_on_datetime
106 query = Query.new(:project => Project.find(1), :name => '_')
146 query = Query.new(:project => Project.find(1), :name => '_')
107 query.add_filter('created_on', 'w', [''])
147 query.add_filter('created_on', 'w', [''])
108 assert query.statement.include?("#{Issue.table_name}.created_on BETWEEN")
109 find_issues_with_query(query)
148 find_issues_with_query(query)
110 end
149 end
111
150
112 def test_operator_contains
151 def test_operator_contains
113 query = Query.new(:project => Project.find(1), :name => '_')
152 query = Query.new(:project => Project.find(1), :name => '_')
114 query.add_filter('subject', '~', ['string'])
153 query.add_filter('subject', '~', ['string'])
115 assert query.statement.include?("#{Issue.table_name}.subject LIKE '%string%'")
154 assert query.statement.include?("#{Issue.table_name}.subject LIKE '%string%'")
116 find_issues_with_query(query)
155 find_issues_with_query(query)
117 end
156 end
118
157
119 def test_operator_does_not_contains
158 def test_operator_does_not_contains
120 query = Query.new(:project => Project.find(1), :name => '_')
159 query = Query.new(:project => Project.find(1), :name => '_')
121 query.add_filter('subject', '!~', ['string'])
160 query.add_filter('subject', '!~', ['string'])
122 assert query.statement.include?("#{Issue.table_name}.subject NOT LIKE '%string%'")
161 assert query.statement.include?("#{Issue.table_name}.subject NOT LIKE '%string%'")
123 find_issues_with_query(query)
162 find_issues_with_query(query)
124 end
163 end
125
164
126 def test_default_columns
165 def test_default_columns
127 q = Query.new
166 q = Query.new
128 assert !q.columns.empty?
167 assert !q.columns.empty?
129 end
168 end
130
169
131 def test_set_column_names
170 def test_set_column_names
132 q = Query.new
171 q = Query.new
133 q.column_names = ['tracker', :subject, '', 'unknonw_column']
172 q.column_names = ['tracker', :subject, '', 'unknonw_column']
134 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
173 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
135 c = q.columns.first
174 c = q.columns.first
136 assert q.has_column?(c)
175 assert q.has_column?(c)
137 end
176 end
138
177
139 def test_label_for
178 def test_label_for
140 q = Query.new
179 q = Query.new
141 assert_equal 'assigned_to', q.label_for('assigned_to_id')
180 assert_equal 'assigned_to', q.label_for('assigned_to_id')
142 end
181 end
143
182
144 def test_editable_by
183 def test_editable_by
145 admin = User.find(1)
184 admin = User.find(1)
146 manager = User.find(2)
185 manager = User.find(2)
147 developer = User.find(3)
186 developer = User.find(3)
148
187
149 # Public query on project 1
188 # Public query on project 1
150 q = Query.find(1)
189 q = Query.find(1)
151 assert q.editable_by?(admin)
190 assert q.editable_by?(admin)
152 assert q.editable_by?(manager)
191 assert q.editable_by?(manager)
153 assert !q.editable_by?(developer)
192 assert !q.editable_by?(developer)
154
193
155 # Private query on project 1
194 # Private query on project 1
156 q = Query.find(2)
195 q = Query.find(2)
157 assert q.editable_by?(admin)
196 assert q.editable_by?(admin)
158 assert !q.editable_by?(manager)
197 assert !q.editable_by?(manager)
159 assert q.editable_by?(developer)
198 assert q.editable_by?(developer)
160
199
161 # Private query for all projects
200 # Private query for all projects
162 q = Query.find(3)
201 q = Query.find(3)
163 assert q.editable_by?(admin)
202 assert q.editable_by?(admin)
164 assert !q.editable_by?(manager)
203 assert !q.editable_by?(manager)
165 assert q.editable_by?(developer)
204 assert q.editable_by?(developer)
166
205
167 # Public query for all projects
206 # Public query for all projects
168 q = Query.find(4)
207 q = Query.find(4)
169 assert q.editable_by?(admin)
208 assert q.editable_by?(admin)
170 assert !q.editable_by?(manager)
209 assert !q.editable_by?(manager)
171 assert !q.editable_by?(developer)
210 assert !q.editable_by?(developer)
172 end
211 end
173 end
212 end
General Comments 0
You need to be logged in to leave comments. Login now