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