##// END OF EJS Templates
Merged r12058 from trunk (#14401)....
Jean-Philippe Lang -
r11832:5eeca35317de
parent child
Show More
@@ -1,405 +1,404
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 IssueQuery < Query
18 class IssueQuery < Query
19
19
20 self.queried_class = Issue
20 self.queried_class = Issue
21
21
22 self.available_columns = [
22 self.available_columns = [
23 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
23 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
24 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
24 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
25 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
25 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
26 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
26 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
27 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
27 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
28 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
28 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
29 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
29 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
30 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
30 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
31 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
31 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
32 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
32 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
33 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
33 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
34 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
34 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
35 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
35 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
36 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
36 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
37 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
37 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
38 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
38 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
39 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
39 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
40 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
40 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
41 QueryColumn.new(:relations, :caption => :label_related_issues),
41 QueryColumn.new(:relations, :caption => :label_related_issues),
42 QueryColumn.new(:description, :inline => false)
42 QueryColumn.new(:description, :inline => false)
43 ]
43 ]
44
44
45 scope :visible, lambda {|*args|
45 scope :visible, lambda {|*args|
46 user = args.shift || User.current
46 user = args.shift || User.current
47 base = Project.allowed_to_condition(user, :view_issues, *args)
47 base = Project.allowed_to_condition(user, :view_issues, *args)
48 user_id = user.logged? ? user.id : 0
48 user_id = user.logged? ? user.id : 0
49
49
50 includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
50 includes(:project).where("(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id)
51 }
51 }
52
52
53 def initialize(attributes=nil, *args)
53 def initialize(attributes=nil, *args)
54 super attributes
54 super attributes
55 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
55 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
56 end
56 end
57
57
58 # Returns true if the query is visible to +user+ or the current user.
58 # Returns true if the query is visible to +user+ or the current user.
59 def visible?(user=User.current)
59 def visible?(user=User.current)
60 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
60 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
61 end
61 end
62
62
63 def initialize_available_filters
63 def initialize_available_filters
64 principals = []
64 principals = []
65 subprojects = []
65 subprojects = []
66 versions = []
66 versions = []
67 categories = []
67 categories = []
68 issue_custom_fields = []
68 issue_custom_fields = []
69
69
70 if project
70 if project
71 principals += project.principals.sort
71 principals += project.principals.sort
72 unless project.leaf?
72 unless project.leaf?
73 subprojects = project.descendants.visible.all
73 subprojects = project.descendants.visible.all
74 principals += Principal.member_of(subprojects)
74 principals += Principal.member_of(subprojects)
75 end
75 end
76 versions = project.shared_versions.all
76 versions = project.shared_versions.all
77 categories = project.issue_categories.all
77 categories = project.issue_categories.all
78 issue_custom_fields = project.all_issue_custom_fields
78 issue_custom_fields = project.all_issue_custom_fields
79 else
79 else
80 if all_projects.any?
80 if all_projects.any?
81 principals += Principal.member_of(all_projects)
81 principals += Principal.member_of(all_projects)
82 end
82 end
83 versions = Version.visible.find_all_by_sharing('system')
83 versions = Version.visible.find_all_by_sharing('system')
84 issue_custom_fields = IssueCustomField.where(:is_filter => true, :is_for_all => true).all
84 issue_custom_fields = IssueCustomField.where(:is_filter => true, :is_for_all => true).all
85 end
85 end
86 principals.uniq!
86 principals.uniq!
87 principals.sort!
87 principals.sort!
88 users = principals.select {|p| p.is_a?(User)}
88 users = principals.select {|p| p.is_a?(User)}
89
89
90
90
91 add_available_filter "status_id",
91 add_available_filter "status_id",
92 :type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] }
92 :type => :list_status, :values => IssueStatus.sorted.all.collect{|s| [s.name, s.id.to_s] }
93
93
94 if project.nil?
94 if project.nil?
95 project_values = []
95 project_values = []
96 if User.current.logged? && User.current.memberships.any?
96 if User.current.logged? && User.current.memberships.any?
97 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
97 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
98 end
98 end
99 project_values += all_projects_values
99 project_values += all_projects_values
100 add_available_filter("project_id",
100 add_available_filter("project_id",
101 :type => :list, :values => project_values
101 :type => :list, :values => project_values
102 ) unless project_values.empty?
102 ) unless project_values.empty?
103 end
103 end
104
104
105 add_available_filter "tracker_id",
105 add_available_filter "tracker_id",
106 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
106 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
107 add_available_filter "priority_id",
107 add_available_filter "priority_id",
108 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
108 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
109
109
110 author_values = []
110 author_values = []
111 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
111 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
112 author_values += users.collect{|s| [s.name, s.id.to_s] }
112 author_values += users.collect{|s| [s.name, s.id.to_s] }
113 add_available_filter("author_id",
113 add_available_filter("author_id",
114 :type => :list, :values => author_values
114 :type => :list, :values => author_values
115 ) unless author_values.empty?
115 ) unless author_values.empty?
116
116
117 assigned_to_values = []
117 assigned_to_values = []
118 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
118 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
119 assigned_to_values += (Setting.issue_group_assignment? ?
119 assigned_to_values += (Setting.issue_group_assignment? ?
120 principals : users).collect{|s| [s.name, s.id.to_s] }
120 principals : users).collect{|s| [s.name, s.id.to_s] }
121 add_available_filter("assigned_to_id",
121 add_available_filter("assigned_to_id",
122 :type => :list_optional, :values => assigned_to_values
122 :type => :list_optional, :values => assigned_to_values
123 ) unless assigned_to_values.empty?
123 ) unless assigned_to_values.empty?
124
124
125 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
125 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
126 add_available_filter("member_of_group",
126 add_available_filter("member_of_group",
127 :type => :list_optional, :values => group_values
127 :type => :list_optional, :values => group_values
128 ) unless group_values.empty?
128 ) unless group_values.empty?
129
129
130 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
130 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
131 add_available_filter("assigned_to_role",
131 add_available_filter("assigned_to_role",
132 :type => :list_optional, :values => role_values
132 :type => :list_optional, :values => role_values
133 ) unless role_values.empty?
133 ) unless role_values.empty?
134
134
135 if versions.any?
135 if versions.any?
136 add_available_filter "fixed_version_id",
136 add_available_filter "fixed_version_id",
137 :type => :list_optional,
137 :type => :list_optional,
138 :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
138 :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
139 end
139 end
140
140
141 if categories.any?
141 if categories.any?
142 add_available_filter "category_id",
142 add_available_filter "category_id",
143 :type => :list_optional,
143 :type => :list_optional,
144 :values => categories.collect{|s| [s.name, s.id.to_s] }
144 :values => categories.collect{|s| [s.name, s.id.to_s] }
145 end
145 end
146
146
147 add_available_filter "subject", :type => :text
147 add_available_filter "subject", :type => :text
148 add_available_filter "created_on", :type => :date_past
148 add_available_filter "created_on", :type => :date_past
149 add_available_filter "updated_on", :type => :date_past
149 add_available_filter "updated_on", :type => :date_past
150 add_available_filter "closed_on", :type => :date_past
150 add_available_filter "closed_on", :type => :date_past
151 add_available_filter "start_date", :type => :date
151 add_available_filter "start_date", :type => :date
152 add_available_filter "due_date", :type => :date
152 add_available_filter "due_date", :type => :date
153 add_available_filter "estimated_hours", :type => :float
153 add_available_filter "estimated_hours", :type => :float
154 add_available_filter "done_ratio", :type => :integer
154 add_available_filter "done_ratio", :type => :integer
155
155
156 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
156 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
157 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
157 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
158 add_available_filter "is_private",
158 add_available_filter "is_private",
159 :type => :list,
159 :type => :list,
160 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
160 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
161 end
161 end
162
162
163 if User.current.logged?
163 if User.current.logged?
164 add_available_filter "watcher_id",
164 add_available_filter "watcher_id",
165 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
165 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
166 end
166 end
167
167
168 if subprojects.any?
168 if subprojects.any?
169 add_available_filter "subproject_id",
169 add_available_filter "subproject_id",
170 :type => :list_subprojects,
170 :type => :list_subprojects,
171 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
171 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
172 end
172 end
173
173
174 add_custom_fields_filters(issue_custom_fields)
174 add_custom_fields_filters(issue_custom_fields)
175
175
176 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
176 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
177
177
178 IssueRelation::TYPES.each do |relation_type, options|
178 IssueRelation::TYPES.each do |relation_type, options|
179 add_available_filter relation_type, :type => :relation, :label => options[:name]
179 add_available_filter relation_type, :type => :relation, :label => options[:name]
180 end
180 end
181
181
182 Tracker.disabled_core_fields(trackers).each {|field|
182 Tracker.disabled_core_fields(trackers).each {|field|
183 delete_available_filter field
183 delete_available_filter field
184 }
184 }
185 end
185 end
186
186
187 def available_columns
187 def available_columns
188 return @available_columns if @available_columns
188 return @available_columns if @available_columns
189 @available_columns = self.class.available_columns.dup
189 @available_columns = self.class.available_columns.dup
190 @available_columns += (project ?
190 @available_columns += (project ?
191 project.all_issue_custom_fields :
191 project.all_issue_custom_fields :
192 IssueCustomField.all
192 IssueCustomField.all
193 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
193 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
194
194
195 if User.current.allowed_to?(:view_time_entries, project, :global => true)
195 if User.current.allowed_to?(:view_time_entries, project, :global => true)
196 index = nil
196 index = nil
197 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
197 @available_columns.each_with_index {|column, i| index = i if column.name == :estimated_hours}
198 index = (index ? index + 1 : -1)
198 index = (index ? index + 1 : -1)
199 # insert the column after estimated_hours or at the end
199 # insert the column after estimated_hours or at the end
200 @available_columns.insert index, QueryColumn.new(:spent_hours,
200 @available_columns.insert index, QueryColumn.new(:spent_hours,
201 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
201 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
202 :default_order => 'desc',
202 :default_order => 'desc',
203 :caption => :label_spent_time
203 :caption => :label_spent_time
204 )
204 )
205 end
205 end
206
206
207 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
207 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
208 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
208 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
209 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
209 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
210 end
210 end
211
211
212 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
212 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
213 @available_columns.reject! {|column|
213 @available_columns.reject! {|column|
214 disabled_fields.include?(column.name.to_s)
214 disabled_fields.include?(column.name.to_s)
215 }
215 }
216
216
217 @available_columns
217 @available_columns
218 end
218 end
219
219
220 def default_columns_names
220 def default_columns_names
221 @default_columns_names ||= begin
221 @default_columns_names ||= begin
222 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
222 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
223
223
224 project.present? ? default_columns : [:project] | default_columns
224 project.present? ? default_columns : [:project] | default_columns
225 end
225 end
226 end
226 end
227
227
228 # Returns the issue count
228 # Returns the issue count
229 def issue_count
229 def issue_count
230 Issue.visible.count(:include => [:status, :project], :conditions => statement)
230 Issue.visible.count(:include => [:status, :project], :conditions => statement)
231 rescue ::ActiveRecord::StatementInvalid => e
231 rescue ::ActiveRecord::StatementInvalid => e
232 raise StatementInvalid.new(e.message)
232 raise StatementInvalid.new(e.message)
233 end
233 end
234
234
235 # Returns the issue count by group or nil if query is not grouped
235 # Returns the issue count by group or nil if query is not grouped
236 def issue_count_by_group
236 def issue_count_by_group
237 r = nil
237 r = nil
238 if grouped?
238 if grouped?
239 begin
239 begin
240 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
240 # Rails3 will raise an (unexpected) RecordNotFound if there's only a nil group value
241 r = Issue.visible.count(:joins => joins_for_order_statement(group_by_statement), :group => group_by_statement, :include => [:status, :project], :conditions => statement)
241 r = Issue.visible.count(:joins => joins_for_order_statement(group_by_statement), :group => group_by_statement, :include => [:status, :project], :conditions => statement)
242 rescue ActiveRecord::RecordNotFound
242 rescue ActiveRecord::RecordNotFound
243 r = {nil => issue_count}
243 r = {nil => issue_count}
244 end
244 end
245 c = group_by_column
245 c = group_by_column
246 if c.is_a?(QueryCustomFieldColumn)
246 if c.is_a?(QueryCustomFieldColumn)
247 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
247 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
248 end
248 end
249 end
249 end
250 r
250 r
251 rescue ::ActiveRecord::StatementInvalid => e
251 rescue ::ActiveRecord::StatementInvalid => e
252 raise StatementInvalid.new(e.message)
252 raise StatementInvalid.new(e.message)
253 end
253 end
254
254
255 # Returns the issues
255 # Returns the issues
256 # Valid options are :order, :offset, :limit, :include, :conditions
256 # Valid options are :order, :offset, :limit, :include, :conditions
257 def issues(options={})
257 def issues(options={})
258 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
258 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
259
259
260 issues = Issue.visible.where(options[:conditions]).all(
260 issues = Issue.visible.where(options[:conditions]).all(
261 :include => ([:status, :project] + (options[:include] || [])).uniq,
261 :include => ([:status, :project] + (options[:include] || [])).uniq,
262 :conditions => statement,
262 :conditions => statement,
263 :order => order_option,
263 :order => order_option,
264 :joins => joins_for_order_statement(order_option.join(',')),
264 :joins => joins_for_order_statement(order_option.join(',')),
265 :limit => options[:limit],
265 :limit => options[:limit],
266 :offset => options[:offset]
266 :offset => options[:offset]
267 )
267 )
268
268
269 if has_column?(:spent_hours)
269 if has_column?(:spent_hours)
270 Issue.load_visible_spent_hours(issues)
270 Issue.load_visible_spent_hours(issues)
271 end
271 end
272 if has_column?(:relations)
272 if has_column?(:relations)
273 Issue.load_visible_relations(issues)
273 Issue.load_visible_relations(issues)
274 end
274 end
275 issues
275 issues
276 rescue ::ActiveRecord::StatementInvalid => e
276 rescue ::ActiveRecord::StatementInvalid => e
277 raise StatementInvalid.new(e.message)
277 raise StatementInvalid.new(e.message)
278 end
278 end
279
279
280 # Returns the issues ids
280 # Returns the issues ids
281 def issue_ids(options={})
281 def issue_ids(options={})
282 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
282 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
283
283
284 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
284 Issue.visible.scoped(:conditions => options[:conditions]).scoped(:include => ([:status, :project] + (options[:include] || [])).uniq,
285 :conditions => statement,
285 :conditions => statement,
286 :order => order_option,
286 :order => order_option,
287 :joins => joins_for_order_statement(order_option.join(',')),
287 :joins => joins_for_order_statement(order_option.join(',')),
288 :limit => options[:limit],
288 :limit => options[:limit],
289 :offset => options[:offset]).find_ids
289 :offset => options[:offset]).find_ids
290 rescue ::ActiveRecord::StatementInvalid => e
290 rescue ::ActiveRecord::StatementInvalid => e
291 raise StatementInvalid.new(e.message)
291 raise StatementInvalid.new(e.message)
292 end
292 end
293
293
294 # Returns the journals
294 # Returns the journals
295 # Valid options are :order, :offset, :limit
295 # Valid options are :order, :offset, :limit
296 def journals(options={})
296 def journals(options={})
297 Journal.visible.all(
297 Journal.visible.all(
298 :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
298 :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
299 :conditions => statement,
299 :conditions => statement,
300 :order => options[:order],
300 :order => options[:order],
301 :limit => options[:limit],
301 :limit => options[:limit],
302 :offset => options[:offset]
302 :offset => options[:offset]
303 )
303 )
304 rescue ::ActiveRecord::StatementInvalid => e
304 rescue ::ActiveRecord::StatementInvalid => e
305 raise StatementInvalid.new(e.message)
305 raise StatementInvalid.new(e.message)
306 end
306 end
307
307
308 # Returns the versions
308 # Returns the versions
309 # Valid options are :conditions
309 # Valid options are :conditions
310 def versions(options={})
310 def versions(options={})
311 Version.visible.where(options[:conditions]).all(
311 Version.visible.where(options[:conditions]).all(
312 :include => :project,
312 :include => :project,
313 :conditions => project_statement
313 :conditions => project_statement
314 )
314 )
315 rescue ::ActiveRecord::StatementInvalid => e
315 rescue ::ActiveRecord::StatementInvalid => e
316 raise StatementInvalid.new(e.message)
316 raise StatementInvalid.new(e.message)
317 end
317 end
318
318
319 def sql_for_watcher_id_field(field, operator, value)
319 def sql_for_watcher_id_field(field, operator, value)
320 db_table = Watcher.table_name
320 db_table = Watcher.table_name
321 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
321 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
322 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
322 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
323 end
323 end
324
324
325 def sql_for_member_of_group_field(field, operator, value)
325 def sql_for_member_of_group_field(field, operator, value)
326 if operator == '*' # Any group
326 if operator == '*' # Any group
327 groups = Group.all
327 groups = Group.all
328 operator = '=' # Override the operator since we want to find by assigned_to
328 operator = '=' # Override the operator since we want to find by assigned_to
329 elsif operator == "!*"
329 elsif operator == "!*"
330 groups = Group.all
330 groups = Group.all
331 operator = '!' # Override the operator since we want to find by assigned_to
331 operator = '!' # Override the operator since we want to find by assigned_to
332 else
332 else
333 groups = Group.find_all_by_id(value)
333 groups = Group.find_all_by_id(value)
334 end
334 end
335 groups ||= []
335 groups ||= []
336
336
337 members_of_groups = groups.inject([]) {|user_ids, group|
337 members_of_groups = groups.inject([]) {|user_ids, group|
338 user_ids + group.user_ids + [group.id]
338 user_ids + group.user_ids + [group.id]
339 }.uniq.compact.sort.collect(&:to_s)
339 }.uniq.compact.sort.collect(&:to_s)
340
340
341 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
341 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
342 end
342 end
343
343
344 def sql_for_assigned_to_role_field(field, operator, value)
344 def sql_for_assigned_to_role_field(field, operator, value)
345 case operator
345 case operator
346 when "*", "!*" # Member / Not member
346 when "*", "!*" # Member / Not member
347 sw = operator == "!*" ? 'NOT' : ''
347 sw = operator == "!*" ? 'NOT' : ''
348 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
348 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
349 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
349 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
350 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
350 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
351 when "=", "!"
351 when "=", "!"
352 role_cond = value.any? ?
352 role_cond = value.any? ?
353 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
353 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" :
354 "1=0"
354 "1=0"
355
355
356 sw = operator == "!" ? 'NOT' : ''
356 sw = operator == "!" ? 'NOT' : ''
357 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
357 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
358 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
358 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
359 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
359 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
360 end
360 end
361 end
361 end
362
362
363 def sql_for_is_private_field(field, operator, value)
363 def sql_for_is_private_field(field, operator, value)
364 op = (operator == "=" ? 'IN' : 'NOT IN')
364 op = (operator == "=" ? 'IN' : 'NOT IN')
365 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
365 va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',')
366
366
367 "#{Issue.table_name}.is_private #{op} (#{va})"
367 "#{Issue.table_name}.is_private #{op} (#{va})"
368 end
368 end
369
369
370 def sql_for_relations(field, operator, value, options={})
370 def sql_for_relations(field, operator, value, options={})
371 relation_options = IssueRelation::TYPES[field]
371 relation_options = IssueRelation::TYPES[field]
372 return relation_options unless relation_options
372 return relation_options unless relation_options
373
373
374 relation_type = field
374 relation_type = field
375 join_column, target_join_column = "issue_from_id", "issue_to_id"
375 join_column, target_join_column = "issue_from_id", "issue_to_id"
376 if relation_options[:reverse] || options[:reverse]
376 if relation_options[:reverse] || options[:reverse]
377 relation_type = relation_options[:reverse] || relation_type
377 relation_type = relation_options[:reverse] || relation_type
378 join_column, target_join_column = target_join_column, join_column
378 join_column, target_join_column = target_join_column, join_column
379 end
379 end
380
380
381 sql = case operator
381 sql = case operator
382 when "*", "!*"
382 when "*", "!*"
383 op = (operator == "*" ? 'IN' : 'NOT IN')
383 op = (operator == "*" ? 'IN' : 'NOT IN')
384 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')"
384 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')"
385 when "=", "!"
385 when "=", "!"
386 op = (operator == "=" ? 'IN' : 'NOT IN')
386 op = (operator == "=" ? 'IN' : 'NOT IN')
387 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
387 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
388 when "=p", "=!p", "!p"
388 when "=p", "=!p", "!p"
389 op = (operator == "!p" ? 'NOT IN' : 'IN')
389 op = (operator == "!p" ? 'NOT IN' : 'IN')
390 comp = (operator == "=!p" ? '<>' : '=')
390 comp = (operator == "=!p" ? '<>' : '=')
391 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
391 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name}, #{Issue.table_name} relissues WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
392 end
392 end
393
393
394 if relation_options[:sym] == field && !options[:reverse]
394 if relation_options[:sym] == field && !options[:reverse]
395 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
395 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
396 sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
396 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
397 else
398 sql
399 end
397 end
398 "(#{sql})"
400 end
399 end
401
400
402 IssueRelation::TYPES.keys.each do |relation_type|
401 IssueRelation::TYPES.keys.each do |relation_type|
403 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
402 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
404 end
403 end
405 end
404 end
@@ -1,1248 +1,1263
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 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 include Redmine::I18n
21 include Redmine::I18n
22
22
23 fixtures :projects, :enabled_modules, :users, :members,
23 fixtures :projects, :enabled_modules, :users, :members,
24 :member_roles, :roles, :trackers, :issue_statuses,
24 :member_roles, :roles, :trackers, :issue_statuses,
25 :issue_categories, :enumerations, :issues,
25 :issue_categories, :enumerations, :issues,
26 :watchers, :custom_fields, :custom_values, :versions,
26 :watchers, :custom_fields, :custom_values, :versions,
27 :queries,
27 :queries,
28 :projects_trackers,
28 :projects_trackers,
29 :custom_fields_trackers
29 :custom_fields_trackers
30
30
31 def test_available_filters_should_be_ordered
31 def test_available_filters_should_be_ordered
32 query = IssueQuery.new
32 query = IssueQuery.new
33 assert_equal 0, query.available_filters.keys.index('status_id')
33 assert_equal 0, query.available_filters.keys.index('status_id')
34 end
34 end
35
35
36 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
36 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
37 query = IssueQuery.new(:project => nil, :name => '_')
37 query = IssueQuery.new(:project => nil, :name => '_')
38 assert query.available_filters.has_key?('cf_1')
38 assert query.available_filters.has_key?('cf_1')
39 assert !query.available_filters.has_key?('cf_3')
39 assert !query.available_filters.has_key?('cf_3')
40 end
40 end
41
41
42 def test_system_shared_versions_should_be_available_in_global_queries
42 def test_system_shared_versions_should_be_available_in_global_queries
43 Version.find(2).update_attribute :sharing, 'system'
43 Version.find(2).update_attribute :sharing, 'system'
44 query = IssueQuery.new(:project => nil, :name => '_')
44 query = IssueQuery.new(:project => nil, :name => '_')
45 assert query.available_filters.has_key?('fixed_version_id')
45 assert query.available_filters.has_key?('fixed_version_id')
46 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
46 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
47 end
47 end
48
48
49 def test_project_filter_in_global_queries
49 def test_project_filter_in_global_queries
50 query = IssueQuery.new(:project => nil, :name => '_')
50 query = IssueQuery.new(:project => nil, :name => '_')
51 project_filter = query.available_filters["project_id"]
51 project_filter = query.available_filters["project_id"]
52 assert_not_nil project_filter
52 assert_not_nil project_filter
53 project_ids = project_filter[:values].map{|p| p[1]}
53 project_ids = project_filter[:values].map{|p| p[1]}
54 assert project_ids.include?("1") #public project
54 assert project_ids.include?("1") #public project
55 assert !project_ids.include?("2") #private project user cannot see
55 assert !project_ids.include?("2") #private project user cannot see
56 end
56 end
57
57
58 def find_issues_with_query(query)
58 def find_issues_with_query(query)
59 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
59 Issue.includes([:assigned_to, :status, :tracker, :project, :priority]).where(
60 query.statement
60 query.statement
61 ).all
61 ).all
62 end
62 end
63
63
64 def assert_find_issues_with_query_is_successful(query)
64 def assert_find_issues_with_query_is_successful(query)
65 assert_nothing_raised do
65 assert_nothing_raised do
66 find_issues_with_query(query)
66 find_issues_with_query(query)
67 end
67 end
68 end
68 end
69
69
70 def assert_query_statement_includes(query, condition)
70 def assert_query_statement_includes(query, condition)
71 assert_include condition, query.statement
71 assert_include condition, query.statement
72 end
72 end
73
73
74 def assert_query_result(expected, query)
74 def assert_query_result(expected, query)
75 assert_nothing_raised do
75 assert_nothing_raised do
76 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
76 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
77 assert_equal expected.size, query.issue_count
77 assert_equal expected.size, query.issue_count
78 end
78 end
79 end
79 end
80
80
81 def test_query_should_allow_shared_versions_for_a_project_query
81 def test_query_should_allow_shared_versions_for_a_project_query
82 subproject_version = Version.find(4)
82 subproject_version = Version.find(4)
83 query = IssueQuery.new(:project => Project.find(1), :name => '_')
83 query = IssueQuery.new(:project => Project.find(1), :name => '_')
84 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
84 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
85
85
86 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
86 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
87 end
87 end
88
88
89 def test_query_with_multiple_custom_fields
89 def test_query_with_multiple_custom_fields
90 query = IssueQuery.find(1)
90 query = IssueQuery.find(1)
91 assert query.valid?
91 assert query.valid?
92 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
92 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
93 issues = find_issues_with_query(query)
93 issues = find_issues_with_query(query)
94 assert_equal 1, issues.length
94 assert_equal 1, issues.length
95 assert_equal Issue.find(3), issues.first
95 assert_equal Issue.find(3), issues.first
96 end
96 end
97
97
98 def test_operator_none
98 def test_operator_none
99 query = IssueQuery.new(:project => Project.find(1), :name => '_')
99 query = IssueQuery.new(:project => Project.find(1), :name => '_')
100 query.add_filter('fixed_version_id', '!*', [''])
100 query.add_filter('fixed_version_id', '!*', [''])
101 query.add_filter('cf_1', '!*', [''])
101 query.add_filter('cf_1', '!*', [''])
102 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
102 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
103 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
103 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
104 find_issues_with_query(query)
104 find_issues_with_query(query)
105 end
105 end
106
106
107 def test_operator_none_for_integer
107 def test_operator_none_for_integer
108 query = IssueQuery.new(:project => Project.find(1), :name => '_')
108 query = IssueQuery.new(:project => Project.find(1), :name => '_')
109 query.add_filter('estimated_hours', '!*', [''])
109 query.add_filter('estimated_hours', '!*', [''])
110 issues = find_issues_with_query(query)
110 issues = find_issues_with_query(query)
111 assert !issues.empty?
111 assert !issues.empty?
112 assert issues.all? {|i| !i.estimated_hours}
112 assert issues.all? {|i| !i.estimated_hours}
113 end
113 end
114
114
115 def test_operator_none_for_date
115 def test_operator_none_for_date
116 query = IssueQuery.new(:project => Project.find(1), :name => '_')
116 query = IssueQuery.new(:project => Project.find(1), :name => '_')
117 query.add_filter('start_date', '!*', [''])
117 query.add_filter('start_date', '!*', [''])
118 issues = find_issues_with_query(query)
118 issues = find_issues_with_query(query)
119 assert !issues.empty?
119 assert !issues.empty?
120 assert issues.all? {|i| i.start_date.nil?}
120 assert issues.all? {|i| i.start_date.nil?}
121 end
121 end
122
122
123 def test_operator_none_for_string_custom_field
123 def test_operator_none_for_string_custom_field
124 query = IssueQuery.new(:project => Project.find(1), :name => '_')
124 query = IssueQuery.new(:project => Project.find(1), :name => '_')
125 query.add_filter('cf_2', '!*', [''])
125 query.add_filter('cf_2', '!*', [''])
126 assert query.has_filter?('cf_2')
126 assert query.has_filter?('cf_2')
127 issues = find_issues_with_query(query)
127 issues = find_issues_with_query(query)
128 assert !issues.empty?
128 assert !issues.empty?
129 assert issues.all? {|i| i.custom_field_value(2).blank?}
129 assert issues.all? {|i| i.custom_field_value(2).blank?}
130 end
130 end
131
131
132 def test_operator_all
132 def test_operator_all
133 query = IssueQuery.new(:project => Project.find(1), :name => '_')
133 query = IssueQuery.new(:project => Project.find(1), :name => '_')
134 query.add_filter('fixed_version_id', '*', [''])
134 query.add_filter('fixed_version_id', '*', [''])
135 query.add_filter('cf_1', '*', [''])
135 query.add_filter('cf_1', '*', [''])
136 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
136 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
137 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
137 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
138 find_issues_with_query(query)
138 find_issues_with_query(query)
139 end
139 end
140
140
141 def test_operator_all_for_date
141 def test_operator_all_for_date
142 query = IssueQuery.new(:project => Project.find(1), :name => '_')
142 query = IssueQuery.new(:project => Project.find(1), :name => '_')
143 query.add_filter('start_date', '*', [''])
143 query.add_filter('start_date', '*', [''])
144 issues = find_issues_with_query(query)
144 issues = find_issues_with_query(query)
145 assert !issues.empty?
145 assert !issues.empty?
146 assert issues.all? {|i| i.start_date.present?}
146 assert issues.all? {|i| i.start_date.present?}
147 end
147 end
148
148
149 def test_operator_all_for_string_custom_field
149 def test_operator_all_for_string_custom_field
150 query = IssueQuery.new(:project => Project.find(1), :name => '_')
150 query = IssueQuery.new(:project => Project.find(1), :name => '_')
151 query.add_filter('cf_2', '*', [''])
151 query.add_filter('cf_2', '*', [''])
152 assert query.has_filter?('cf_2')
152 assert query.has_filter?('cf_2')
153 issues = find_issues_with_query(query)
153 issues = find_issues_with_query(query)
154 assert !issues.empty?
154 assert !issues.empty?
155 assert issues.all? {|i| i.custom_field_value(2).present?}
155 assert issues.all? {|i| i.custom_field_value(2).present?}
156 end
156 end
157
157
158 def test_numeric_filter_should_not_accept_non_numeric_values
158 def test_numeric_filter_should_not_accept_non_numeric_values
159 query = IssueQuery.new(:name => '_')
159 query = IssueQuery.new(:name => '_')
160 query.add_filter('estimated_hours', '=', ['a'])
160 query.add_filter('estimated_hours', '=', ['a'])
161
161
162 assert query.has_filter?('estimated_hours')
162 assert query.has_filter?('estimated_hours')
163 assert !query.valid?
163 assert !query.valid?
164 end
164 end
165
165
166 def test_operator_is_on_float
166 def test_operator_is_on_float
167 Issue.update_all("estimated_hours = 171.2", "id=2")
167 Issue.update_all("estimated_hours = 171.2", "id=2")
168
168
169 query = IssueQuery.new(:name => '_')
169 query = IssueQuery.new(:name => '_')
170 query.add_filter('estimated_hours', '=', ['171.20'])
170 query.add_filter('estimated_hours', '=', ['171.20'])
171 issues = find_issues_with_query(query)
171 issues = find_issues_with_query(query)
172 assert_equal 1, issues.size
172 assert_equal 1, issues.size
173 assert_equal 2, issues.first.id
173 assert_equal 2, issues.first.id
174 end
174 end
175
175
176 def test_operator_is_on_integer_custom_field
176 def test_operator_is_on_integer_custom_field
177 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
177 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
178 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
178 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
179 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
179 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
180 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
180 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
181
181
182 query = IssueQuery.new(:name => '_')
182 query = IssueQuery.new(:name => '_')
183 query.add_filter("cf_#{f.id}", '=', ['12'])
183 query.add_filter("cf_#{f.id}", '=', ['12'])
184 issues = find_issues_with_query(query)
184 issues = find_issues_with_query(query)
185 assert_equal 1, issues.size
185 assert_equal 1, issues.size
186 assert_equal 2, issues.first.id
186 assert_equal 2, issues.first.id
187 end
187 end
188
188
189 def test_operator_is_on_integer_custom_field_should_accept_negative_value
189 def test_operator_is_on_integer_custom_field_should_accept_negative_value
190 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
190 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true)
191 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
191 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
192 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
192 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
193 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
193 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
194
194
195 query = IssueQuery.new(:name => '_')
195 query = IssueQuery.new(:name => '_')
196 query.add_filter("cf_#{f.id}", '=', ['-12'])
196 query.add_filter("cf_#{f.id}", '=', ['-12'])
197 assert query.valid?
197 assert query.valid?
198 issues = find_issues_with_query(query)
198 issues = find_issues_with_query(query)
199 assert_equal 1, issues.size
199 assert_equal 1, issues.size
200 assert_equal 2, issues.first.id
200 assert_equal 2, issues.first.id
201 end
201 end
202
202
203 def test_operator_is_on_float_custom_field
203 def test_operator_is_on_float_custom_field
204 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
204 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
205 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
205 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
206 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
206 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
207 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
207 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
208
208
209 query = IssueQuery.new(:name => '_')
209 query = IssueQuery.new(:name => '_')
210 query.add_filter("cf_#{f.id}", '=', ['12.7'])
210 query.add_filter("cf_#{f.id}", '=', ['12.7'])
211 issues = find_issues_with_query(query)
211 issues = find_issues_with_query(query)
212 assert_equal 1, issues.size
212 assert_equal 1, issues.size
213 assert_equal 2, issues.first.id
213 assert_equal 2, issues.first.id
214 end
214 end
215
215
216 def test_operator_is_on_float_custom_field_should_accept_negative_value
216 def test_operator_is_on_float_custom_field_should_accept_negative_value
217 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
217 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true)
218 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
218 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
219 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
219 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
220 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
220 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
221
221
222 query = IssueQuery.new(:name => '_')
222 query = IssueQuery.new(:name => '_')
223 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
223 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
224 assert query.valid?
224 assert query.valid?
225 issues = find_issues_with_query(query)
225 issues = find_issues_with_query(query)
226 assert_equal 1, issues.size
226 assert_equal 1, issues.size
227 assert_equal 2, issues.first.id
227 assert_equal 2, issues.first.id
228 end
228 end
229
229
230 def test_operator_is_on_multi_list_custom_field
230 def test_operator_is_on_multi_list_custom_field
231 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
231 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
232 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
232 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
233 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
233 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
234 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
234 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
235 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
235 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
236
236
237 query = IssueQuery.new(:name => '_')
237 query = IssueQuery.new(:name => '_')
238 query.add_filter("cf_#{f.id}", '=', ['value1'])
238 query.add_filter("cf_#{f.id}", '=', ['value1'])
239 issues = find_issues_with_query(query)
239 issues = find_issues_with_query(query)
240 assert_equal [1, 3], issues.map(&:id).sort
240 assert_equal [1, 3], issues.map(&:id).sort
241
241
242 query = IssueQuery.new(:name => '_')
242 query = IssueQuery.new(:name => '_')
243 query.add_filter("cf_#{f.id}", '=', ['value2'])
243 query.add_filter("cf_#{f.id}", '=', ['value2'])
244 issues = find_issues_with_query(query)
244 issues = find_issues_with_query(query)
245 assert_equal [1], issues.map(&:id).sort
245 assert_equal [1], issues.map(&:id).sort
246 end
246 end
247
247
248 def test_operator_is_not_on_multi_list_custom_field
248 def test_operator_is_not_on_multi_list_custom_field
249 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
249 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
250 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
250 :possible_values => ['value1', 'value2', 'value3'], :multiple => true)
251 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
251 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
252 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
252 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
253 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
253 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
254
254
255 query = IssueQuery.new(:name => '_')
255 query = IssueQuery.new(:name => '_')
256 query.add_filter("cf_#{f.id}", '!', ['value1'])
256 query.add_filter("cf_#{f.id}", '!', ['value1'])
257 issues = find_issues_with_query(query)
257 issues = find_issues_with_query(query)
258 assert !issues.map(&:id).include?(1)
258 assert !issues.map(&:id).include?(1)
259 assert !issues.map(&:id).include?(3)
259 assert !issues.map(&:id).include?(3)
260
260
261 query = IssueQuery.new(:name => '_')
261 query = IssueQuery.new(:name => '_')
262 query.add_filter("cf_#{f.id}", '!', ['value2'])
262 query.add_filter("cf_#{f.id}", '!', ['value2'])
263 issues = find_issues_with_query(query)
263 issues = find_issues_with_query(query)
264 assert !issues.map(&:id).include?(1)
264 assert !issues.map(&:id).include?(1)
265 assert issues.map(&:id).include?(3)
265 assert issues.map(&:id).include?(3)
266 end
266 end
267
267
268 def test_operator_is_on_is_private_field
268 def test_operator_is_on_is_private_field
269 # is_private filter only available for those who can set issues private
269 # is_private filter only available for those who can set issues private
270 User.current = User.find(2)
270 User.current = User.find(2)
271
271
272 query = IssueQuery.new(:name => '_')
272 query = IssueQuery.new(:name => '_')
273 assert query.available_filters.key?('is_private')
273 assert query.available_filters.key?('is_private')
274
274
275 query.add_filter("is_private", '=', ['1'])
275 query.add_filter("is_private", '=', ['1'])
276 issues = find_issues_with_query(query)
276 issues = find_issues_with_query(query)
277 assert issues.any?
277 assert issues.any?
278 assert_nil issues.detect {|issue| !issue.is_private?}
278 assert_nil issues.detect {|issue| !issue.is_private?}
279 ensure
279 ensure
280 User.current = nil
280 User.current = nil
281 end
281 end
282
282
283 def test_operator_is_not_on_is_private_field
283 def test_operator_is_not_on_is_private_field
284 # is_private filter only available for those who can set issues private
284 # is_private filter only available for those who can set issues private
285 User.current = User.find(2)
285 User.current = User.find(2)
286
286
287 query = IssueQuery.new(:name => '_')
287 query = IssueQuery.new(:name => '_')
288 assert query.available_filters.key?('is_private')
288 assert query.available_filters.key?('is_private')
289
289
290 query.add_filter("is_private", '!', ['1'])
290 query.add_filter("is_private", '!', ['1'])
291 issues = find_issues_with_query(query)
291 issues = find_issues_with_query(query)
292 assert issues.any?
292 assert issues.any?
293 assert_nil issues.detect {|issue| issue.is_private?}
293 assert_nil issues.detect {|issue| issue.is_private?}
294 ensure
294 ensure
295 User.current = nil
295 User.current = nil
296 end
296 end
297
297
298 def test_operator_greater_than
298 def test_operator_greater_than
299 query = IssueQuery.new(:project => Project.find(1), :name => '_')
299 query = IssueQuery.new(:project => Project.find(1), :name => '_')
300 query.add_filter('done_ratio', '>=', ['40'])
300 query.add_filter('done_ratio', '>=', ['40'])
301 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
301 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
302 find_issues_with_query(query)
302 find_issues_with_query(query)
303 end
303 end
304
304
305 def test_operator_greater_than_a_float
305 def test_operator_greater_than_a_float
306 query = IssueQuery.new(:project => Project.find(1), :name => '_')
306 query = IssueQuery.new(:project => Project.find(1), :name => '_')
307 query.add_filter('estimated_hours', '>=', ['40.5'])
307 query.add_filter('estimated_hours', '>=', ['40.5'])
308 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
308 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
309 find_issues_with_query(query)
309 find_issues_with_query(query)
310 end
310 end
311
311
312 def test_operator_greater_than_on_int_custom_field
312 def test_operator_greater_than_on_int_custom_field
313 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
313 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
314 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
314 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
315 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
315 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
316 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
316 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
317
317
318 query = IssueQuery.new(:project => Project.find(1), :name => '_')
318 query = IssueQuery.new(:project => Project.find(1), :name => '_')
319 query.add_filter("cf_#{f.id}", '>=', ['8'])
319 query.add_filter("cf_#{f.id}", '>=', ['8'])
320 issues = find_issues_with_query(query)
320 issues = find_issues_with_query(query)
321 assert_equal 1, issues.size
321 assert_equal 1, issues.size
322 assert_equal 2, issues.first.id
322 assert_equal 2, issues.first.id
323 end
323 end
324
324
325 def test_operator_lesser_than
325 def test_operator_lesser_than
326 query = IssueQuery.new(:project => Project.find(1), :name => '_')
326 query = IssueQuery.new(:project => Project.find(1), :name => '_')
327 query.add_filter('done_ratio', '<=', ['30'])
327 query.add_filter('done_ratio', '<=', ['30'])
328 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
328 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
329 find_issues_with_query(query)
329 find_issues_with_query(query)
330 end
330 end
331
331
332 def test_operator_lesser_than_on_custom_field
332 def test_operator_lesser_than_on_custom_field
333 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
333 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
334 query = IssueQuery.new(:project => Project.find(1), :name => '_')
334 query = IssueQuery.new(:project => Project.find(1), :name => '_')
335 query.add_filter("cf_#{f.id}", '<=', ['30'])
335 query.add_filter("cf_#{f.id}", '<=', ['30'])
336 assert_match /CAST.+ <= 30\.0/, query.statement
336 assert_match /CAST.+ <= 30\.0/, query.statement
337 find_issues_with_query(query)
337 find_issues_with_query(query)
338 end
338 end
339
339
340 def test_operator_lesser_than_on_date_custom_field
340 def test_operator_lesser_than_on_date_custom_field
341 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true)
341 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true)
342 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
342 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
343 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
343 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
344 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
344 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
345
345
346 query = IssueQuery.new(:project => Project.find(1), :name => '_')
346 query = IssueQuery.new(:project => Project.find(1), :name => '_')
347 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
347 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
348 issue_ids = find_issues_with_query(query).map(&:id)
348 issue_ids = find_issues_with_query(query).map(&:id)
349 assert_include 1, issue_ids
349 assert_include 1, issue_ids
350 assert_not_include 2, issue_ids
350 assert_not_include 2, issue_ids
351 assert_not_include 3, issue_ids
351 assert_not_include 3, issue_ids
352 end
352 end
353
353
354 def test_operator_between
354 def test_operator_between
355 query = IssueQuery.new(:project => Project.find(1), :name => '_')
355 query = IssueQuery.new(:project => Project.find(1), :name => '_')
356 query.add_filter('done_ratio', '><', ['30', '40'])
356 query.add_filter('done_ratio', '><', ['30', '40'])
357 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
357 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
358 find_issues_with_query(query)
358 find_issues_with_query(query)
359 end
359 end
360
360
361 def test_operator_between_on_custom_field
361 def test_operator_between_on_custom_field
362 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
362 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
363 query = IssueQuery.new(:project => Project.find(1), :name => '_')
363 query = IssueQuery.new(:project => Project.find(1), :name => '_')
364 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
364 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
365 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
365 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
366 find_issues_with_query(query)
366 find_issues_with_query(query)
367 end
367 end
368
368
369 def test_date_filter_should_not_accept_non_date_values
369 def test_date_filter_should_not_accept_non_date_values
370 query = IssueQuery.new(:name => '_')
370 query = IssueQuery.new(:name => '_')
371 query.add_filter('created_on', '=', ['a'])
371 query.add_filter('created_on', '=', ['a'])
372
372
373 assert query.has_filter?('created_on')
373 assert query.has_filter?('created_on')
374 assert !query.valid?
374 assert !query.valid?
375 end
375 end
376
376
377 def test_date_filter_should_not_accept_invalid_date_values
377 def test_date_filter_should_not_accept_invalid_date_values
378 query = IssueQuery.new(:name => '_')
378 query = IssueQuery.new(:name => '_')
379 query.add_filter('created_on', '=', ['2011-01-34'])
379 query.add_filter('created_on', '=', ['2011-01-34'])
380
380
381 assert query.has_filter?('created_on')
381 assert query.has_filter?('created_on')
382 assert !query.valid?
382 assert !query.valid?
383 end
383 end
384
384
385 def test_relative_date_filter_should_not_accept_non_integer_values
385 def test_relative_date_filter_should_not_accept_non_integer_values
386 query = IssueQuery.new(:name => '_')
386 query = IssueQuery.new(:name => '_')
387 query.add_filter('created_on', '>t-', ['a'])
387 query.add_filter('created_on', '>t-', ['a'])
388
388
389 assert query.has_filter?('created_on')
389 assert query.has_filter?('created_on')
390 assert !query.valid?
390 assert !query.valid?
391 end
391 end
392
392
393 def test_operator_date_equals
393 def test_operator_date_equals
394 query = IssueQuery.new(:name => '_')
394 query = IssueQuery.new(:name => '_')
395 query.add_filter('due_date', '=', ['2011-07-10'])
395 query.add_filter('due_date', '=', ['2011-07-10'])
396 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
396 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
397 find_issues_with_query(query)
397 find_issues_with_query(query)
398 end
398 end
399
399
400 def test_operator_date_lesser_than
400 def test_operator_date_lesser_than
401 query = IssueQuery.new(:name => '_')
401 query = IssueQuery.new(:name => '_')
402 query.add_filter('due_date', '<=', ['2011-07-10'])
402 query.add_filter('due_date', '<=', ['2011-07-10'])
403 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
403 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
404 find_issues_with_query(query)
404 find_issues_with_query(query)
405 end
405 end
406
406
407 def test_operator_date_greater_than
407 def test_operator_date_greater_than
408 query = IssueQuery.new(:name => '_')
408 query = IssueQuery.new(:name => '_')
409 query.add_filter('due_date', '>=', ['2011-07-10'])
409 query.add_filter('due_date', '>=', ['2011-07-10'])
410 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
410 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
411 find_issues_with_query(query)
411 find_issues_with_query(query)
412 end
412 end
413
413
414 def test_operator_date_between
414 def test_operator_date_between
415 query = IssueQuery.new(:name => '_')
415 query = IssueQuery.new(:name => '_')
416 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
416 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
417 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
417 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
418 find_issues_with_query(query)
418 find_issues_with_query(query)
419 end
419 end
420
420
421 def test_operator_in_more_than
421 def test_operator_in_more_than
422 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
422 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
423 query = IssueQuery.new(:project => Project.find(1), :name => '_')
423 query = IssueQuery.new(:project => Project.find(1), :name => '_')
424 query.add_filter('due_date', '>t+', ['15'])
424 query.add_filter('due_date', '>t+', ['15'])
425 issues = find_issues_with_query(query)
425 issues = find_issues_with_query(query)
426 assert !issues.empty?
426 assert !issues.empty?
427 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
427 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
428 end
428 end
429
429
430 def test_operator_in_less_than
430 def test_operator_in_less_than
431 query = IssueQuery.new(:project => Project.find(1), :name => '_')
431 query = IssueQuery.new(:project => Project.find(1), :name => '_')
432 query.add_filter('due_date', '<t+', ['15'])
432 query.add_filter('due_date', '<t+', ['15'])
433 issues = find_issues_with_query(query)
433 issues = find_issues_with_query(query)
434 assert !issues.empty?
434 assert !issues.empty?
435 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
435 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
436 end
436 end
437
437
438 def test_operator_in_the_next_days
438 def test_operator_in_the_next_days
439 query = IssueQuery.new(:project => Project.find(1), :name => '_')
439 query = IssueQuery.new(:project => Project.find(1), :name => '_')
440 query.add_filter('due_date', '><t+', ['15'])
440 query.add_filter('due_date', '><t+', ['15'])
441 issues = find_issues_with_query(query)
441 issues = find_issues_with_query(query)
442 assert !issues.empty?
442 assert !issues.empty?
443 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
443 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
444 end
444 end
445
445
446 def test_operator_less_than_ago
446 def test_operator_less_than_ago
447 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
447 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
448 query = IssueQuery.new(:project => Project.find(1), :name => '_')
448 query = IssueQuery.new(:project => Project.find(1), :name => '_')
449 query.add_filter('due_date', '>t-', ['3'])
449 query.add_filter('due_date', '>t-', ['3'])
450 issues = find_issues_with_query(query)
450 issues = find_issues_with_query(query)
451 assert !issues.empty?
451 assert !issues.empty?
452 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
452 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
453 end
453 end
454
454
455 def test_operator_in_the_past_days
455 def test_operator_in_the_past_days
456 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
456 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
457 query = IssueQuery.new(:project => Project.find(1), :name => '_')
457 query = IssueQuery.new(:project => Project.find(1), :name => '_')
458 query.add_filter('due_date', '><t-', ['3'])
458 query.add_filter('due_date', '><t-', ['3'])
459 issues = find_issues_with_query(query)
459 issues = find_issues_with_query(query)
460 assert !issues.empty?
460 assert !issues.empty?
461 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
461 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
462 end
462 end
463
463
464 def test_operator_more_than_ago
464 def test_operator_more_than_ago
465 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
465 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
466 query = IssueQuery.new(:project => Project.find(1), :name => '_')
466 query = IssueQuery.new(:project => Project.find(1), :name => '_')
467 query.add_filter('due_date', '<t-', ['10'])
467 query.add_filter('due_date', '<t-', ['10'])
468 assert query.statement.include?("#{Issue.table_name}.due_date <=")
468 assert query.statement.include?("#{Issue.table_name}.due_date <=")
469 issues = find_issues_with_query(query)
469 issues = find_issues_with_query(query)
470 assert !issues.empty?
470 assert !issues.empty?
471 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
471 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
472 end
472 end
473
473
474 def test_operator_in
474 def test_operator_in
475 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
475 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
476 query = IssueQuery.new(:project => Project.find(1), :name => '_')
476 query = IssueQuery.new(:project => Project.find(1), :name => '_')
477 query.add_filter('due_date', 't+', ['2'])
477 query.add_filter('due_date', 't+', ['2'])
478 issues = find_issues_with_query(query)
478 issues = find_issues_with_query(query)
479 assert !issues.empty?
479 assert !issues.empty?
480 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
480 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
481 end
481 end
482
482
483 def test_operator_ago
483 def test_operator_ago
484 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
484 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
485 query = IssueQuery.new(:project => Project.find(1), :name => '_')
485 query = IssueQuery.new(:project => Project.find(1), :name => '_')
486 query.add_filter('due_date', 't-', ['3'])
486 query.add_filter('due_date', 't-', ['3'])
487 issues = find_issues_with_query(query)
487 issues = find_issues_with_query(query)
488 assert !issues.empty?
488 assert !issues.empty?
489 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
489 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
490 end
490 end
491
491
492 def test_operator_today
492 def test_operator_today
493 query = IssueQuery.new(:project => Project.find(1), :name => '_')
493 query = IssueQuery.new(:project => Project.find(1), :name => '_')
494 query.add_filter('due_date', 't', [''])
494 query.add_filter('due_date', 't', [''])
495 issues = find_issues_with_query(query)
495 issues = find_issues_with_query(query)
496 assert !issues.empty?
496 assert !issues.empty?
497 issues.each {|issue| assert_equal Date.today, issue.due_date}
497 issues.each {|issue| assert_equal Date.today, issue.due_date}
498 end
498 end
499
499
500 def test_operator_this_week_on_date
500 def test_operator_this_week_on_date
501 query = IssueQuery.new(:project => Project.find(1), :name => '_')
501 query = IssueQuery.new(:project => Project.find(1), :name => '_')
502 query.add_filter('due_date', 'w', [''])
502 query.add_filter('due_date', 'w', [''])
503 find_issues_with_query(query)
503 find_issues_with_query(query)
504 end
504 end
505
505
506 def test_operator_this_week_on_datetime
506 def test_operator_this_week_on_datetime
507 query = IssueQuery.new(:project => Project.find(1), :name => '_')
507 query = IssueQuery.new(:project => Project.find(1), :name => '_')
508 query.add_filter('created_on', 'w', [''])
508 query.add_filter('created_on', 'w', [''])
509 find_issues_with_query(query)
509 find_issues_with_query(query)
510 end
510 end
511
511
512 def test_operator_contains
512 def test_operator_contains
513 query = IssueQuery.new(:project => Project.find(1), :name => '_')
513 query = IssueQuery.new(:project => Project.find(1), :name => '_')
514 query.add_filter('subject', '~', ['uNable'])
514 query.add_filter('subject', '~', ['uNable'])
515 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
515 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
516 result = find_issues_with_query(query)
516 result = find_issues_with_query(query)
517 assert result.empty?
517 assert result.empty?
518 result.each {|issue| assert issue.subject.downcase.include?('unable') }
518 result.each {|issue| assert issue.subject.downcase.include?('unable') }
519 end
519 end
520
520
521 def test_range_for_this_week_with_week_starting_on_monday
521 def test_range_for_this_week_with_week_starting_on_monday
522 I18n.locale = :fr
522 I18n.locale = :fr
523 assert_equal '1', I18n.t(:general_first_day_of_week)
523 assert_equal '1', I18n.t(:general_first_day_of_week)
524
524
525 Date.stubs(:today).returns(Date.parse('2011-04-29'))
525 Date.stubs(:today).returns(Date.parse('2011-04-29'))
526
526
527 query = IssueQuery.new(:project => Project.find(1), :name => '_')
527 query = IssueQuery.new(:project => Project.find(1), :name => '_')
528 query.add_filter('due_date', 'w', [''])
528 query.add_filter('due_date', 'w', [''])
529 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
529 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
530 I18n.locale = :en
530 I18n.locale = :en
531 end
531 end
532
532
533 def test_range_for_this_week_with_week_starting_on_sunday
533 def test_range_for_this_week_with_week_starting_on_sunday
534 I18n.locale = :en
534 I18n.locale = :en
535 assert_equal '7', I18n.t(:general_first_day_of_week)
535 assert_equal '7', I18n.t(:general_first_day_of_week)
536
536
537 Date.stubs(:today).returns(Date.parse('2011-04-29'))
537 Date.stubs(:today).returns(Date.parse('2011-04-29'))
538
538
539 query = IssueQuery.new(:project => Project.find(1), :name => '_')
539 query = IssueQuery.new(:project => Project.find(1), :name => '_')
540 query.add_filter('due_date', 'w', [''])
540 query.add_filter('due_date', 'w', [''])
541 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
541 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
542 end
542 end
543
543
544 def test_operator_does_not_contains
544 def test_operator_does_not_contains
545 query = IssueQuery.new(:project => Project.find(1), :name => '_')
545 query = IssueQuery.new(:project => Project.find(1), :name => '_')
546 query.add_filter('subject', '!~', ['uNable'])
546 query.add_filter('subject', '!~', ['uNable'])
547 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
547 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
548 find_issues_with_query(query)
548 find_issues_with_query(query)
549 end
549 end
550
550
551 def test_filter_assigned_to_me
551 def test_filter_assigned_to_me
552 user = User.find(2)
552 user = User.find(2)
553 group = Group.find(10)
553 group = Group.find(10)
554 User.current = user
554 User.current = user
555 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
555 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
556 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
556 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
557 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
557 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
558 group.users << user
558 group.users << user
559
559
560 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
560 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
561 result = query.issues
561 result = query.issues
562 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
562 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
563
563
564 assert result.include?(i1)
564 assert result.include?(i1)
565 assert result.include?(i2)
565 assert result.include?(i2)
566 assert !result.include?(i3)
566 assert !result.include?(i3)
567 end
567 end
568
568
569 def test_user_custom_field_filtered_on_me
569 def test_user_custom_field_filtered_on_me
570 User.current = User.find(2)
570 User.current = User.find(2)
571 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
571 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
572 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
572 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
573 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
573 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
574
574
575 query = IssueQuery.new(:name => '_', :project => Project.find(1))
575 query = IssueQuery.new(:name => '_', :project => Project.find(1))
576 filter = query.available_filters["cf_#{cf.id}"]
576 filter = query.available_filters["cf_#{cf.id}"]
577 assert_not_nil filter
577 assert_not_nil filter
578 assert_include 'me', filter[:values].map{|v| v[1]}
578 assert_include 'me', filter[:values].map{|v| v[1]}
579
579
580 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
580 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
581 result = query.issues
581 result = query.issues
582 assert_equal 1, result.size
582 assert_equal 1, result.size
583 assert_equal issue1, result.first
583 assert_equal issue1, result.first
584 end
584 end
585
585
586 def test_filter_my_projects
586 def test_filter_my_projects
587 User.current = User.find(2)
587 User.current = User.find(2)
588 query = IssueQuery.new(:name => '_')
588 query = IssueQuery.new(:name => '_')
589 filter = query.available_filters['project_id']
589 filter = query.available_filters['project_id']
590 assert_not_nil filter
590 assert_not_nil filter
591 assert_include 'mine', filter[:values].map{|v| v[1]}
591 assert_include 'mine', filter[:values].map{|v| v[1]}
592
592
593 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
593 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
594 result = query.issues
594 result = query.issues
595 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
595 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
596 end
596 end
597
597
598 def test_filter_watched_issues
598 def test_filter_watched_issues
599 User.current = User.find(1)
599 User.current = User.find(1)
600 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
600 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
601 result = find_issues_with_query(query)
601 result = find_issues_with_query(query)
602 assert_not_nil result
602 assert_not_nil result
603 assert !result.empty?
603 assert !result.empty?
604 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
604 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
605 User.current = nil
605 User.current = nil
606 end
606 end
607
607
608 def test_filter_unwatched_issues
608 def test_filter_unwatched_issues
609 User.current = User.find(1)
609 User.current = User.find(1)
610 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
610 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
611 result = find_issues_with_query(query)
611 result = find_issues_with_query(query)
612 assert_not_nil result
612 assert_not_nil result
613 assert !result.empty?
613 assert !result.empty?
614 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
614 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
615 User.current = nil
615 User.current = nil
616 end
616 end
617
617
618 def test_filter_on_project_custom_field
618 def test_filter_on_project_custom_field
619 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
619 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
620 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
620 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
621 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
621 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
622
622
623 query = IssueQuery.new(:name => '_')
623 query = IssueQuery.new(:name => '_')
624 filter_name = "project.cf_#{field.id}"
624 filter_name = "project.cf_#{field.id}"
625 assert_include filter_name, query.available_filters.keys
625 assert_include filter_name, query.available_filters.keys
626 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
626 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
627 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
627 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
628 end
628 end
629
629
630 def test_filter_on_author_custom_field
630 def test_filter_on_author_custom_field
631 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
631 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
632 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
632 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
633
633
634 query = IssueQuery.new(:name => '_')
634 query = IssueQuery.new(:name => '_')
635 filter_name = "author.cf_#{field.id}"
635 filter_name = "author.cf_#{field.id}"
636 assert_include filter_name, query.available_filters.keys
636 assert_include filter_name, query.available_filters.keys
637 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
637 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
638 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
638 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
639 end
639 end
640
640
641 def test_filter_on_assigned_to_custom_field
641 def test_filter_on_assigned_to_custom_field
642 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
642 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
643 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
643 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
644
644
645 query = IssueQuery.new(:name => '_')
645 query = IssueQuery.new(:name => '_')
646 filter_name = "assigned_to.cf_#{field.id}"
646 filter_name = "assigned_to.cf_#{field.id}"
647 assert_include filter_name, query.available_filters.keys
647 assert_include filter_name, query.available_filters.keys
648 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
648 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
649 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
649 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
650 end
650 end
651
651
652 def test_filter_on_fixed_version_custom_field
652 def test_filter_on_fixed_version_custom_field
653 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
653 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
654 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
654 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
655
655
656 query = IssueQuery.new(:name => '_')
656 query = IssueQuery.new(:name => '_')
657 filter_name = "fixed_version.cf_#{field.id}"
657 filter_name = "fixed_version.cf_#{field.id}"
658 assert_include filter_name, query.available_filters.keys
658 assert_include filter_name, query.available_filters.keys
659 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
659 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
660 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
660 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
661 end
661 end
662
662
663 def test_filter_on_relations_with_a_specific_issue
663 def test_filter_on_relations_with_a_specific_issue
664 IssueRelation.delete_all
664 IssueRelation.delete_all
665 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
665 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
666 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
666 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
667
667
668 query = IssueQuery.new(:name => '_')
668 query = IssueQuery.new(:name => '_')
669 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
669 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
670 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
670 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
671
671
672 query = IssueQuery.new(:name => '_')
672 query = IssueQuery.new(:name => '_')
673 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
673 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
674 assert_equal [1], find_issues_with_query(query).map(&:id).sort
674 assert_equal [1], find_issues_with_query(query).map(&:id).sort
675 end
675 end
676
676
677 def test_filter_on_relations_with_any_issues_in_a_project
677 def test_filter_on_relations_with_any_issues_in_a_project
678 IssueRelation.delete_all
678 IssueRelation.delete_all
679 with_settings :cross_project_issue_relations => '1' do
679 with_settings :cross_project_issue_relations => '1' do
680 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
680 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
681 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
681 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
682 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
682 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
683 end
683 end
684
684
685 query = IssueQuery.new(:name => '_')
685 query = IssueQuery.new(:name => '_')
686 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
686 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
687 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
687 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
688
688
689 query = IssueQuery.new(:name => '_')
689 query = IssueQuery.new(:name => '_')
690 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
690 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
691 assert_equal [1], find_issues_with_query(query).map(&:id).sort
691 assert_equal [1], find_issues_with_query(query).map(&:id).sort
692
692
693 query = IssueQuery.new(:name => '_')
693 query = IssueQuery.new(:name => '_')
694 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
694 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
695 assert_equal [], find_issues_with_query(query).map(&:id).sort
695 assert_equal [], find_issues_with_query(query).map(&:id).sort
696 end
696 end
697
697
698 def test_filter_on_relations_with_any_issues_not_in_a_project
698 def test_filter_on_relations_with_any_issues_not_in_a_project
699 IssueRelation.delete_all
699 IssueRelation.delete_all
700 with_settings :cross_project_issue_relations => '1' do
700 with_settings :cross_project_issue_relations => '1' do
701 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
701 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
702 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
702 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
703 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
703 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
704 end
704 end
705
705
706 query = IssueQuery.new(:name => '_')
706 query = IssueQuery.new(:name => '_')
707 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
707 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
708 assert_equal [1], find_issues_with_query(query).map(&:id).sort
708 assert_equal [1], find_issues_with_query(query).map(&:id).sort
709 end
709 end
710
710
711 def test_filter_on_relations_with_no_issues_in_a_project
711 def test_filter_on_relations_with_no_issues_in_a_project
712 IssueRelation.delete_all
712 IssueRelation.delete_all
713 with_settings :cross_project_issue_relations => '1' do
713 with_settings :cross_project_issue_relations => '1' do
714 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
714 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
715 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
715 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
716 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
716 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
717 end
717 end
718
718
719 query = IssueQuery.new(:name => '_')
719 query = IssueQuery.new(:name => '_')
720 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
720 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
721 ids = find_issues_with_query(query).map(&:id).sort
721 ids = find_issues_with_query(query).map(&:id).sort
722 assert_include 2, ids
722 assert_include 2, ids
723 assert_not_include 1, ids
723 assert_not_include 1, ids
724 assert_not_include 3, ids
724 assert_not_include 3, ids
725 end
725 end
726
726
727 def test_filter_on_relations_with_no_issues
727 def test_filter_on_relations_with_no_issues
728 IssueRelation.delete_all
728 IssueRelation.delete_all
729 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
729 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
730 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
730 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
731
731
732 query = IssueQuery.new(:name => '_')
732 query = IssueQuery.new(:name => '_')
733 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
733 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
734 ids = find_issues_with_query(query).map(&:id)
734 ids = find_issues_with_query(query).map(&:id)
735 assert_equal [], ids & [1, 2, 3]
735 assert_equal [], ids & [1, 2, 3]
736 assert_include 4, ids
736 assert_include 4, ids
737 end
737 end
738
738
739 def test_filter_on_relations_with_any_issues
739 def test_filter_on_relations_with_any_issues
740 IssueRelation.delete_all
740 IssueRelation.delete_all
741 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
741 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
742 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
742 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
743
743
744 query = IssueQuery.new(:name => '_')
744 query = IssueQuery.new(:name => '_')
745 query.filters = {"relates" => {:operator => '*', :values => ['']}}
745 query.filters = {"relates" => {:operator => '*', :values => ['']}}
746 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
746 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
747 end
747 end
748
748
749 def test_filter_on_relations_should_not_ignore_other_filter
750 issue = Issue.generate!
751 issue1 = Issue.generate!(:status_id => 1)
752 issue2 = Issue.generate!(:status_id => 2)
753 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
754 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
755
756 query = IssueQuery.new(:name => '_')
757 query.filters = {
758 "status_id" => {:operator => '=', :values => ['1']},
759 "relates" => {:operator => '=', :values => [issue.id.to_s]}
760 }
761 assert_equal [issue1], find_issues_with_query(query)
762 end
763
749 def test_statement_should_be_nil_with_no_filters
764 def test_statement_should_be_nil_with_no_filters
750 q = IssueQuery.new(:name => '_')
765 q = IssueQuery.new(:name => '_')
751 q.filters = {}
766 q.filters = {}
752
767
753 assert q.valid?
768 assert q.valid?
754 assert_nil q.statement
769 assert_nil q.statement
755 end
770 end
756
771
757 def test_default_columns
772 def test_default_columns
758 q = IssueQuery.new
773 q = IssueQuery.new
759 assert q.columns.any?
774 assert q.columns.any?
760 assert q.inline_columns.any?
775 assert q.inline_columns.any?
761 assert q.block_columns.empty?
776 assert q.block_columns.empty?
762 end
777 end
763
778
764 def test_set_column_names
779 def test_set_column_names
765 q = IssueQuery.new
780 q = IssueQuery.new
766 q.column_names = ['tracker', :subject, '', 'unknonw_column']
781 q.column_names = ['tracker', :subject, '', 'unknonw_column']
767 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
782 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
768 end
783 end
769
784
770 def test_has_column_should_accept_a_column_name
785 def test_has_column_should_accept_a_column_name
771 q = IssueQuery.new
786 q = IssueQuery.new
772 q.column_names = ['tracker', :subject]
787 q.column_names = ['tracker', :subject]
773 assert q.has_column?(:tracker)
788 assert q.has_column?(:tracker)
774 assert !q.has_column?(:category)
789 assert !q.has_column?(:category)
775 end
790 end
776
791
777 def test_has_column_should_accept_a_column
792 def test_has_column_should_accept_a_column
778 q = IssueQuery.new
793 q = IssueQuery.new
779 q.column_names = ['tracker', :subject]
794 q.column_names = ['tracker', :subject]
780
795
781 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
796 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
782 assert_kind_of QueryColumn, tracker_column
797 assert_kind_of QueryColumn, tracker_column
783 category_column = q.available_columns.detect {|c| c.name==:category}
798 category_column = q.available_columns.detect {|c| c.name==:category}
784 assert_kind_of QueryColumn, category_column
799 assert_kind_of QueryColumn, category_column
785
800
786 assert q.has_column?(tracker_column)
801 assert q.has_column?(tracker_column)
787 assert !q.has_column?(category_column)
802 assert !q.has_column?(category_column)
788 end
803 end
789
804
790 def test_inline_and_block_columns
805 def test_inline_and_block_columns
791 q = IssueQuery.new
806 q = IssueQuery.new
792 q.column_names = ['subject', 'description', 'tracker']
807 q.column_names = ['subject', 'description', 'tracker']
793
808
794 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
809 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
795 assert_equal [:description], q.block_columns.map(&:name)
810 assert_equal [:description], q.block_columns.map(&:name)
796 end
811 end
797
812
798 def test_custom_field_columns_should_be_inline
813 def test_custom_field_columns_should_be_inline
799 q = IssueQuery.new
814 q = IssueQuery.new
800 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
815 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
801 assert columns.any?
816 assert columns.any?
802 assert_nil columns.detect {|column| !column.inline?}
817 assert_nil columns.detect {|column| !column.inline?}
803 end
818 end
804
819
805 def test_query_should_preload_spent_hours
820 def test_query_should_preload_spent_hours
806 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
821 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
807 assert q.has_column?(:spent_hours)
822 assert q.has_column?(:spent_hours)
808 issues = q.issues
823 issues = q.issues
809 assert_not_nil issues.first.instance_variable_get("@spent_hours")
824 assert_not_nil issues.first.instance_variable_get("@spent_hours")
810 end
825 end
811
826
812 def test_groupable_columns_should_include_custom_fields
827 def test_groupable_columns_should_include_custom_fields
813 q = IssueQuery.new
828 q = IssueQuery.new
814 column = q.groupable_columns.detect {|c| c.name == :cf_1}
829 column = q.groupable_columns.detect {|c| c.name == :cf_1}
815 assert_not_nil column
830 assert_not_nil column
816 assert_kind_of QueryCustomFieldColumn, column
831 assert_kind_of QueryCustomFieldColumn, column
817 end
832 end
818
833
819 def test_groupable_columns_should_not_include_multi_custom_fields
834 def test_groupable_columns_should_not_include_multi_custom_fields
820 field = CustomField.find(1)
835 field = CustomField.find(1)
821 field.update_attribute :multiple, true
836 field.update_attribute :multiple, true
822
837
823 q = IssueQuery.new
838 q = IssueQuery.new
824 column = q.groupable_columns.detect {|c| c.name == :cf_1}
839 column = q.groupable_columns.detect {|c| c.name == :cf_1}
825 assert_nil column
840 assert_nil column
826 end
841 end
827
842
828 def test_groupable_columns_should_include_user_custom_fields
843 def test_groupable_columns_should_include_user_custom_fields
829 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
844 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
830
845
831 q = IssueQuery.new
846 q = IssueQuery.new
832 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
847 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
833 end
848 end
834
849
835 def test_groupable_columns_should_include_version_custom_fields
850 def test_groupable_columns_should_include_version_custom_fields
836 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
851 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
837
852
838 q = IssueQuery.new
853 q = IssueQuery.new
839 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
854 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
840 end
855 end
841
856
842 def test_grouped_with_valid_column
857 def test_grouped_with_valid_column
843 q = IssueQuery.new(:group_by => 'status')
858 q = IssueQuery.new(:group_by => 'status')
844 assert q.grouped?
859 assert q.grouped?
845 assert_not_nil q.group_by_column
860 assert_not_nil q.group_by_column
846 assert_equal :status, q.group_by_column.name
861 assert_equal :status, q.group_by_column.name
847 assert_not_nil q.group_by_statement
862 assert_not_nil q.group_by_statement
848 assert_equal 'status', q.group_by_statement
863 assert_equal 'status', q.group_by_statement
849 end
864 end
850
865
851 def test_grouped_with_invalid_column
866 def test_grouped_with_invalid_column
852 q = IssueQuery.new(:group_by => 'foo')
867 q = IssueQuery.new(:group_by => 'foo')
853 assert !q.grouped?
868 assert !q.grouped?
854 assert_nil q.group_by_column
869 assert_nil q.group_by_column
855 assert_nil q.group_by_statement
870 assert_nil q.group_by_statement
856 end
871 end
857
872
858 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
873 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
859 with_settings :user_format => 'lastname_coma_firstname' do
874 with_settings :user_format => 'lastname_coma_firstname' do
860 q = IssueQuery.new
875 q = IssueQuery.new
861 assert q.sortable_columns.has_key?('assigned_to')
876 assert q.sortable_columns.has_key?('assigned_to')
862 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
877 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
863 end
878 end
864 end
879 end
865
880
866 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
881 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
867 with_settings :user_format => 'lastname_coma_firstname' do
882 with_settings :user_format => 'lastname_coma_firstname' do
868 q = IssueQuery.new
883 q = IssueQuery.new
869 assert q.sortable_columns.has_key?('author')
884 assert q.sortable_columns.has_key?('author')
870 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
885 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
871 end
886 end
872 end
887 end
873
888
874 def test_sortable_columns_should_include_custom_field
889 def test_sortable_columns_should_include_custom_field
875 q = IssueQuery.new
890 q = IssueQuery.new
876 assert q.sortable_columns['cf_1']
891 assert q.sortable_columns['cf_1']
877 end
892 end
878
893
879 def test_sortable_columns_should_not_include_multi_custom_field
894 def test_sortable_columns_should_not_include_multi_custom_field
880 field = CustomField.find(1)
895 field = CustomField.find(1)
881 field.update_attribute :multiple, true
896 field.update_attribute :multiple, true
882
897
883 q = IssueQuery.new
898 q = IssueQuery.new
884 assert !q.sortable_columns['cf_1']
899 assert !q.sortable_columns['cf_1']
885 end
900 end
886
901
887 def test_default_sort
902 def test_default_sort
888 q = IssueQuery.new
903 q = IssueQuery.new
889 assert_equal [], q.sort_criteria
904 assert_equal [], q.sort_criteria
890 end
905 end
891
906
892 def test_set_sort_criteria_with_hash
907 def test_set_sort_criteria_with_hash
893 q = IssueQuery.new
908 q = IssueQuery.new
894 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
909 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
895 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
910 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
896 end
911 end
897
912
898 def test_set_sort_criteria_with_array
913 def test_set_sort_criteria_with_array
899 q = IssueQuery.new
914 q = IssueQuery.new
900 q.sort_criteria = [['priority', 'desc'], 'tracker']
915 q.sort_criteria = [['priority', 'desc'], 'tracker']
901 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
916 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
902 end
917 end
903
918
904 def test_create_query_with_sort
919 def test_create_query_with_sort
905 q = IssueQuery.new(:name => 'Sorted')
920 q = IssueQuery.new(:name => 'Sorted')
906 q.sort_criteria = [['priority', 'desc'], 'tracker']
921 q.sort_criteria = [['priority', 'desc'], 'tracker']
907 assert q.save
922 assert q.save
908 q.reload
923 q.reload
909 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
924 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
910 end
925 end
911
926
912 def test_sort_by_string_custom_field_asc
927 def test_sort_by_string_custom_field_asc
913 q = IssueQuery.new
928 q = IssueQuery.new
914 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
929 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
915 assert c
930 assert c
916 assert c.sortable
931 assert c.sortable
917 issues = q.issues(:order => "#{c.sortable} ASC")
932 issues = q.issues(:order => "#{c.sortable} ASC")
918 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
933 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
919 assert !values.empty?
934 assert !values.empty?
920 assert_equal values.sort, values
935 assert_equal values.sort, values
921 end
936 end
922
937
923 def test_sort_by_string_custom_field_desc
938 def test_sort_by_string_custom_field_desc
924 q = IssueQuery.new
939 q = IssueQuery.new
925 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
940 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
926 assert c
941 assert c
927 assert c.sortable
942 assert c.sortable
928 issues = q.issues(:order => "#{c.sortable} DESC")
943 issues = q.issues(:order => "#{c.sortable} DESC")
929 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
944 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
930 assert !values.empty?
945 assert !values.empty?
931 assert_equal values.sort.reverse, values
946 assert_equal values.sort.reverse, values
932 end
947 end
933
948
934 def test_sort_by_float_custom_field_asc
949 def test_sort_by_float_custom_field_asc
935 q = IssueQuery.new
950 q = IssueQuery.new
936 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
951 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
937 assert c
952 assert c
938 assert c.sortable
953 assert c.sortable
939 issues = q.issues(:order => "#{c.sortable} ASC")
954 issues = q.issues(:order => "#{c.sortable} ASC")
940 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
955 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
941 assert !values.empty?
956 assert !values.empty?
942 assert_equal values.sort, values
957 assert_equal values.sort, values
943 end
958 end
944
959
945 def test_invalid_query_should_raise_query_statement_invalid_error
960 def test_invalid_query_should_raise_query_statement_invalid_error
946 q = IssueQuery.new
961 q = IssueQuery.new
947 assert_raise Query::StatementInvalid do
962 assert_raise Query::StatementInvalid do
948 q.issues(:conditions => "foo = 1")
963 q.issues(:conditions => "foo = 1")
949 end
964 end
950 end
965 end
951
966
952 def test_issue_count
967 def test_issue_count
953 q = IssueQuery.new(:name => '_')
968 q = IssueQuery.new(:name => '_')
954 issue_count = q.issue_count
969 issue_count = q.issue_count
955 assert_equal q.issues.size, issue_count
970 assert_equal q.issues.size, issue_count
956 end
971 end
957
972
958 def test_issue_count_with_archived_issues
973 def test_issue_count_with_archived_issues
959 p = Project.generate! do |project|
974 p = Project.generate! do |project|
960 project.status = Project::STATUS_ARCHIVED
975 project.status = Project::STATUS_ARCHIVED
961 end
976 end
962 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
977 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
963 assert !i.visible?
978 assert !i.visible?
964
979
965 test_issue_count
980 test_issue_count
966 end
981 end
967
982
968 def test_issue_count_by_association_group
983 def test_issue_count_by_association_group
969 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
984 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
970 count_by_group = q.issue_count_by_group
985 count_by_group = q.issue_count_by_group
971 assert_kind_of Hash, count_by_group
986 assert_kind_of Hash, count_by_group
972 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
987 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
973 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
988 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
974 assert count_by_group.has_key?(User.find(3))
989 assert count_by_group.has_key?(User.find(3))
975 end
990 end
976
991
977 def test_issue_count_by_list_custom_field_group
992 def test_issue_count_by_list_custom_field_group
978 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
993 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
979 count_by_group = q.issue_count_by_group
994 count_by_group = q.issue_count_by_group
980 assert_kind_of Hash, count_by_group
995 assert_kind_of Hash, count_by_group
981 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
996 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
982 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
997 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
983 assert count_by_group.has_key?('MySQL')
998 assert count_by_group.has_key?('MySQL')
984 end
999 end
985
1000
986 def test_issue_count_by_date_custom_field_group
1001 def test_issue_count_by_date_custom_field_group
987 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1002 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
988 count_by_group = q.issue_count_by_group
1003 count_by_group = q.issue_count_by_group
989 assert_kind_of Hash, count_by_group
1004 assert_kind_of Hash, count_by_group
990 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1005 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
991 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1006 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
992 end
1007 end
993
1008
994 def test_issue_count_with_nil_group_only
1009 def test_issue_count_with_nil_group_only
995 Issue.update_all("assigned_to_id = NULL")
1010 Issue.update_all("assigned_to_id = NULL")
996
1011
997 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1012 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
998 count_by_group = q.issue_count_by_group
1013 count_by_group = q.issue_count_by_group
999 assert_kind_of Hash, count_by_group
1014 assert_kind_of Hash, count_by_group
1000 assert_equal 1, count_by_group.keys.size
1015 assert_equal 1, count_by_group.keys.size
1001 assert_nil count_by_group.keys.first
1016 assert_nil count_by_group.keys.first
1002 end
1017 end
1003
1018
1004 def test_issue_ids
1019 def test_issue_ids
1005 q = IssueQuery.new(:name => '_')
1020 q = IssueQuery.new(:name => '_')
1006 order = "issues.subject, issues.id"
1021 order = "issues.subject, issues.id"
1007 issues = q.issues(:order => order)
1022 issues = q.issues(:order => order)
1008 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1023 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1009 end
1024 end
1010
1025
1011 def test_label_for
1026 def test_label_for
1012 set_language_if_valid 'en'
1027 set_language_if_valid 'en'
1013 q = IssueQuery.new
1028 q = IssueQuery.new
1014 assert_equal 'Assignee', q.label_for('assigned_to_id')
1029 assert_equal 'Assignee', q.label_for('assigned_to_id')
1015 end
1030 end
1016
1031
1017 def test_label_for_fr
1032 def test_label_for_fr
1018 set_language_if_valid 'fr'
1033 set_language_if_valid 'fr'
1019 q = IssueQuery.new
1034 q = IssueQuery.new
1020 s = "Assign\xc3\xa9 \xc3\xa0"
1035 s = "Assign\xc3\xa9 \xc3\xa0"
1021 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
1036 s.force_encoding('UTF-8') if s.respond_to?(:force_encoding)
1022 assert_equal s, q.label_for('assigned_to_id')
1037 assert_equal s, q.label_for('assigned_to_id')
1023 end
1038 end
1024
1039
1025 def test_editable_by
1040 def test_editable_by
1026 admin = User.find(1)
1041 admin = User.find(1)
1027 manager = User.find(2)
1042 manager = User.find(2)
1028 developer = User.find(3)
1043 developer = User.find(3)
1029
1044
1030 # Public query on project 1
1045 # Public query on project 1
1031 q = IssueQuery.find(1)
1046 q = IssueQuery.find(1)
1032 assert q.editable_by?(admin)
1047 assert q.editable_by?(admin)
1033 assert q.editable_by?(manager)
1048 assert q.editable_by?(manager)
1034 assert !q.editable_by?(developer)
1049 assert !q.editable_by?(developer)
1035
1050
1036 # Private query on project 1
1051 # Private query on project 1
1037 q = IssueQuery.find(2)
1052 q = IssueQuery.find(2)
1038 assert q.editable_by?(admin)
1053 assert q.editable_by?(admin)
1039 assert !q.editable_by?(manager)
1054 assert !q.editable_by?(manager)
1040 assert q.editable_by?(developer)
1055 assert q.editable_by?(developer)
1041
1056
1042 # Private query for all projects
1057 # Private query for all projects
1043 q = IssueQuery.find(3)
1058 q = IssueQuery.find(3)
1044 assert q.editable_by?(admin)
1059 assert q.editable_by?(admin)
1045 assert !q.editable_by?(manager)
1060 assert !q.editable_by?(manager)
1046 assert q.editable_by?(developer)
1061 assert q.editable_by?(developer)
1047
1062
1048 # Public query for all projects
1063 # Public query for all projects
1049 q = IssueQuery.find(4)
1064 q = IssueQuery.find(4)
1050 assert q.editable_by?(admin)
1065 assert q.editable_by?(admin)
1051 assert !q.editable_by?(manager)
1066 assert !q.editable_by?(manager)
1052 assert !q.editable_by?(developer)
1067 assert !q.editable_by?(developer)
1053 end
1068 end
1054
1069
1055 def test_visible_scope
1070 def test_visible_scope
1056 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1071 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1057
1072
1058 assert query_ids.include?(1), 'public query on public project was not visible'
1073 assert query_ids.include?(1), 'public query on public project was not visible'
1059 assert query_ids.include?(4), 'public query for all projects was not visible'
1074 assert query_ids.include?(4), 'public query for all projects was not visible'
1060 assert !query_ids.include?(2), 'private query on public project was visible'
1075 assert !query_ids.include?(2), 'private query on public project was visible'
1061 assert !query_ids.include?(3), 'private query for all projects was visible'
1076 assert !query_ids.include?(3), 'private query for all projects was visible'
1062 assert !query_ids.include?(7), 'public query on private project was visible'
1077 assert !query_ids.include?(7), 'public query on private project was visible'
1063 end
1078 end
1064
1079
1065 test "#available_filters should include users of visible projects in cross-project view" do
1080 test "#available_filters should include users of visible projects in cross-project view" do
1066 users = IssueQuery.new.available_filters["assigned_to_id"]
1081 users = IssueQuery.new.available_filters["assigned_to_id"]
1067 assert_not_nil users
1082 assert_not_nil users
1068 assert users[:values].map{|u|u[1]}.include?("3")
1083 assert users[:values].map{|u|u[1]}.include?("3")
1069 end
1084 end
1070
1085
1071 test "#available_filters should include users of subprojects" do
1086 test "#available_filters should include users of subprojects" do
1072 user1 = User.generate!
1087 user1 = User.generate!
1073 user2 = User.generate!
1088 user2 = User.generate!
1074 project = Project.find(1)
1089 project = Project.find(1)
1075 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1090 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1076
1091
1077 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1092 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1078 assert_not_nil users
1093 assert_not_nil users
1079 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1094 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1080 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1095 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1081 end
1096 end
1082
1097
1083 test "#available_filters should include visible projects in cross-project view" do
1098 test "#available_filters should include visible projects in cross-project view" do
1084 projects = IssueQuery.new.available_filters["project_id"]
1099 projects = IssueQuery.new.available_filters["project_id"]
1085 assert_not_nil projects
1100 assert_not_nil projects
1086 assert projects[:values].map{|u|u[1]}.include?("1")
1101 assert projects[:values].map{|u|u[1]}.include?("1")
1087 end
1102 end
1088
1103
1089 test "#available_filters should include 'member_of_group' filter" do
1104 test "#available_filters should include 'member_of_group' filter" do
1090 query = IssueQuery.new
1105 query = IssueQuery.new
1091 assert query.available_filters.keys.include?("member_of_group")
1106 assert query.available_filters.keys.include?("member_of_group")
1092 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1107 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1093 assert query.available_filters["member_of_group"][:values].present?
1108 assert query.available_filters["member_of_group"][:values].present?
1094 assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]},
1109 assert_equal Group.all.sort.map {|g| [g.name, g.id.to_s]},
1095 query.available_filters["member_of_group"][:values].sort
1110 query.available_filters["member_of_group"][:values].sort
1096 end
1111 end
1097
1112
1098 test "#available_filters should include 'assigned_to_role' filter" do
1113 test "#available_filters should include 'assigned_to_role' filter" do
1099 query = IssueQuery.new
1114 query = IssueQuery.new
1100 assert query.available_filters.keys.include?("assigned_to_role")
1115 assert query.available_filters.keys.include?("assigned_to_role")
1101 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1116 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1102
1117
1103 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1118 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1104 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1119 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1105 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1120 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1106
1121
1107 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1122 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1108 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1123 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1109 end
1124 end
1110
1125
1111 context "#statement" do
1126 context "#statement" do
1112 context "with 'member_of_group' filter" do
1127 context "with 'member_of_group' filter" do
1113 setup do
1128 setup do
1114 Group.destroy_all # No fixtures
1129 Group.destroy_all # No fixtures
1115 @user_in_group = User.generate!
1130 @user_in_group = User.generate!
1116 @second_user_in_group = User.generate!
1131 @second_user_in_group = User.generate!
1117 @user_in_group2 = User.generate!
1132 @user_in_group2 = User.generate!
1118 @user_not_in_group = User.generate!
1133 @user_not_in_group = User.generate!
1119
1134
1120 @group = Group.generate!.reload
1135 @group = Group.generate!.reload
1121 @group.users << @user_in_group
1136 @group.users << @user_in_group
1122 @group.users << @second_user_in_group
1137 @group.users << @second_user_in_group
1123
1138
1124 @group2 = Group.generate!.reload
1139 @group2 = Group.generate!.reload
1125 @group2.users << @user_in_group2
1140 @group2.users << @user_in_group2
1126
1141
1127 end
1142 end
1128
1143
1129 should "search assigned to for users in the group" do
1144 should "search assigned to for users in the group" do
1130 @query = IssueQuery.new(:name => '_')
1145 @query = IssueQuery.new(:name => '_')
1131 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1146 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1132
1147
1133 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
1148 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@group.id}')"
1134 assert_find_issues_with_query_is_successful @query
1149 assert_find_issues_with_query_is_successful @query
1135 end
1150 end
1136
1151
1137 should "search not assigned to any group member (none)" do
1152 should "search not assigned to any group member (none)" do
1138 @query = IssueQuery.new(:name => '_')
1153 @query = IssueQuery.new(:name => '_')
1139 @query.add_filter('member_of_group', '!*', [''])
1154 @query.add_filter('member_of_group', '!*', [''])
1140
1155
1141 # Users not in a group
1156 # Users not in a group
1142 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1157 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1143 assert_find_issues_with_query_is_successful @query
1158 assert_find_issues_with_query_is_successful @query
1144 end
1159 end
1145
1160
1146 should "search assigned to any group member (all)" do
1161 should "search assigned to any group member (all)" do
1147 @query = IssueQuery.new(:name => '_')
1162 @query = IssueQuery.new(:name => '_')
1148 @query.add_filter('member_of_group', '*', [''])
1163 @query.add_filter('member_of_group', '*', [''])
1149
1164
1150 # Only users in a group
1165 # Only users in a group
1151 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1166 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}','#{@group.id}','#{@group2.id}')"
1152 assert_find_issues_with_query_is_successful @query
1167 assert_find_issues_with_query_is_successful @query
1153 end
1168 end
1154
1169
1155 should "return an empty set with = empty group" do
1170 should "return an empty set with = empty group" do
1156 @empty_group = Group.generate!
1171 @empty_group = Group.generate!
1157 @query = IssueQuery.new(:name => '_')
1172 @query = IssueQuery.new(:name => '_')
1158 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1173 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1159
1174
1160 assert_equal [], find_issues_with_query(@query)
1175 assert_equal [], find_issues_with_query(@query)
1161 end
1176 end
1162
1177
1163 should "return issues with ! empty group" do
1178 should "return issues with ! empty group" do
1164 @empty_group = Group.generate!
1179 @empty_group = Group.generate!
1165 @query = IssueQuery.new(:name => '_')
1180 @query = IssueQuery.new(:name => '_')
1166 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1181 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1167
1182
1168 assert_find_issues_with_query_is_successful @query
1183 assert_find_issues_with_query_is_successful @query
1169 end
1184 end
1170 end
1185 end
1171
1186
1172 context "with 'assigned_to_role' filter" do
1187 context "with 'assigned_to_role' filter" do
1173 setup do
1188 setup do
1174 @manager_role = Role.find_by_name('Manager')
1189 @manager_role = Role.find_by_name('Manager')
1175 @developer_role = Role.find_by_name('Developer')
1190 @developer_role = Role.find_by_name('Developer')
1176
1191
1177 @project = Project.generate!
1192 @project = Project.generate!
1178 @manager = User.generate!
1193 @manager = User.generate!
1179 @developer = User.generate!
1194 @developer = User.generate!
1180 @boss = User.generate!
1195 @boss = User.generate!
1181 @guest = User.generate!
1196 @guest = User.generate!
1182 User.add_to_project(@manager, @project, @manager_role)
1197 User.add_to_project(@manager, @project, @manager_role)
1183 User.add_to_project(@developer, @project, @developer_role)
1198 User.add_to_project(@developer, @project, @developer_role)
1184 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1199 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1185
1200
1186 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1201 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1187 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1202 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1188 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1203 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1189 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1204 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1190 @issue5 = Issue.generate!(:project => @project)
1205 @issue5 = Issue.generate!(:project => @project)
1191 end
1206 end
1192
1207
1193 should "search assigned to for users with the Role" do
1208 should "search assigned to for users with the Role" do
1194 @query = IssueQuery.new(:name => '_', :project => @project)
1209 @query = IssueQuery.new(:name => '_', :project => @project)
1195 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1210 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1196
1211
1197 assert_query_result [@issue1, @issue3], @query
1212 assert_query_result [@issue1, @issue3], @query
1198 end
1213 end
1199
1214
1200 should "search assigned to for users with the Role on the issue project" do
1215 should "search assigned to for users with the Role on the issue project" do
1201 other_project = Project.generate!
1216 other_project = Project.generate!
1202 User.add_to_project(@developer, other_project, @manager_role)
1217 User.add_to_project(@developer, other_project, @manager_role)
1203
1218
1204 @query = IssueQuery.new(:name => '_', :project => @project)
1219 @query = IssueQuery.new(:name => '_', :project => @project)
1205 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1220 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1206
1221
1207 assert_query_result [@issue1, @issue3], @query
1222 assert_query_result [@issue1, @issue3], @query
1208 end
1223 end
1209
1224
1210 should "return an empty set with empty role" do
1225 should "return an empty set with empty role" do
1211 @empty_role = Role.generate!
1226 @empty_role = Role.generate!
1212 @query = IssueQuery.new(:name => '_', :project => @project)
1227 @query = IssueQuery.new(:name => '_', :project => @project)
1213 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1228 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1214
1229
1215 assert_query_result [], @query
1230 assert_query_result [], @query
1216 end
1231 end
1217
1232
1218 should "search assigned to for users without the Role" do
1233 should "search assigned to for users without the Role" do
1219 @query = IssueQuery.new(:name => '_', :project => @project)
1234 @query = IssueQuery.new(:name => '_', :project => @project)
1220 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1235 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1221
1236
1222 assert_query_result [@issue2, @issue4, @issue5], @query
1237 assert_query_result [@issue2, @issue4, @issue5], @query
1223 end
1238 end
1224
1239
1225 should "search assigned to for users not assigned to any Role (none)" do
1240 should "search assigned to for users not assigned to any Role (none)" do
1226 @query = IssueQuery.new(:name => '_', :project => @project)
1241 @query = IssueQuery.new(:name => '_', :project => @project)
1227 @query.add_filter('assigned_to_role', '!*', [''])
1242 @query.add_filter('assigned_to_role', '!*', [''])
1228
1243
1229 assert_query_result [@issue4, @issue5], @query
1244 assert_query_result [@issue4, @issue5], @query
1230 end
1245 end
1231
1246
1232 should "search assigned to for users assigned to any Role (all)" do
1247 should "search assigned to for users assigned to any Role (all)" do
1233 @query = IssueQuery.new(:name => '_', :project => @project)
1248 @query = IssueQuery.new(:name => '_', :project => @project)
1234 @query.add_filter('assigned_to_role', '*', [''])
1249 @query.add_filter('assigned_to_role', '*', [''])
1235
1250
1236 assert_query_result [@issue1, @issue2, @issue3], @query
1251 assert_query_result [@issue1, @issue2, @issue3], @query
1237 end
1252 end
1238
1253
1239 should "return issues with ! empty role" do
1254 should "return issues with ! empty role" do
1240 @empty_role = Role.generate!
1255 @empty_role = Role.generate!
1241 @query = IssueQuery.new(:name => '_', :project => @project)
1256 @query = IssueQuery.new(:name => '_', :project => @project)
1242 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1257 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1243
1258
1244 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1259 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1245 end
1260 end
1246 end
1261 end
1247 end
1262 end
1248 end
1263 end
General Comments 0
You need to be logged in to leave comments. Login now