##// END OF EJS Templates
Add the possibility to filter issues after Target Version's Status and Due Date (#23215)....
Jean-Philippe Lang -
r15499:b6ed06ca3c59
parent child
Show More
@@ -1,524 +1,549
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2016 Jean-Philippe Lang
2 # Copyright (C) 2006-2016 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 self.view_permission = :view_issues
21 self.view_permission = :view_issues
22
22
23 self.available_columns = [
23 self.available_columns = [
24 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
24 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
25 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
25 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
26 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
26 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
27 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
27 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
28 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
28 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
29 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
29 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
30 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
30 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
31 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
31 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
32 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
32 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
33 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
33 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
34 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
34 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
35 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
35 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
36 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
36 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
37 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
37 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
38 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
38 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
39 QueryColumn.new(:total_estimated_hours,
39 QueryColumn.new(:total_estimated_hours,
40 :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
40 :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
41 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
41 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
42 :default_order => 'desc'),
42 :default_order => 'desc'),
43 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
43 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
44 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
44 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
45 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
45 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
46 QueryColumn.new(:relations, :caption => :label_related_issues),
46 QueryColumn.new(:relations, :caption => :label_related_issues),
47 QueryColumn.new(:description, :inline => false)
47 QueryColumn.new(:description, :inline => false)
48 ]
48 ]
49
49
50 def initialize(attributes=nil, *args)
50 def initialize(attributes=nil, *args)
51 super attributes
51 super attributes
52 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
52 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
53 end
53 end
54
54
55 def draw_relations
55 def draw_relations
56 r = options[:draw_relations]
56 r = options[:draw_relations]
57 r.nil? || r == '1'
57 r.nil? || r == '1'
58 end
58 end
59
59
60 def draw_relations=(arg)
60 def draw_relations=(arg)
61 options[:draw_relations] = (arg == '0' ? '0' : nil)
61 options[:draw_relations] = (arg == '0' ? '0' : nil)
62 end
62 end
63
63
64 def draw_progress_line
64 def draw_progress_line
65 r = options[:draw_progress_line]
65 r = options[:draw_progress_line]
66 r == '1'
66 r == '1'
67 end
67 end
68
68
69 def draw_progress_line=(arg)
69 def draw_progress_line=(arg)
70 options[:draw_progress_line] = (arg == '1' ? '1' : nil)
70 options[:draw_progress_line] = (arg == '1' ? '1' : nil)
71 end
71 end
72
72
73 def build_from_params(params)
73 def build_from_params(params)
74 super
74 super
75 self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
75 self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
76 self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line])
76 self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line])
77 self
77 self
78 end
78 end
79
79
80 def initialize_available_filters
80 def initialize_available_filters
81 principals = []
81 principals = []
82 subprojects = []
82 subprojects = []
83 versions = []
83 versions = []
84 categories = []
84 categories = []
85 issue_custom_fields = []
85 issue_custom_fields = []
86
86
87 if project
87 if project
88 principals += project.principals.visible
88 principals += project.principals.visible
89 unless project.leaf?
89 unless project.leaf?
90 subprojects = project.descendants.visible.to_a
90 subprojects = project.descendants.visible.to_a
91 principals += Principal.member_of(subprojects).visible
91 principals += Principal.member_of(subprojects).visible
92 end
92 end
93 versions = project.shared_versions.to_a
93 versions = project.shared_versions.to_a
94 categories = project.issue_categories.to_a
94 categories = project.issue_categories.to_a
95 issue_custom_fields = project.all_issue_custom_fields
95 issue_custom_fields = project.all_issue_custom_fields
96 else
96 else
97 if all_projects.any?
97 if all_projects.any?
98 principals += Principal.member_of(all_projects).visible
98 principals += Principal.member_of(all_projects).visible
99 end
99 end
100 versions = Version.visible.where(:sharing => 'system').to_a
100 versions = Version.visible.where(:sharing => 'system').to_a
101 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
101 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
102 end
102 end
103 principals.uniq!
103 principals.uniq!
104 principals.sort!
104 principals.sort!
105 principals.reject! {|p| p.is_a?(GroupBuiltin)}
105 principals.reject! {|p| p.is_a?(GroupBuiltin)}
106 users = principals.select {|p| p.is_a?(User)}
106 users = principals.select {|p| p.is_a?(User)}
107
107
108 add_available_filter "status_id",
108 add_available_filter "status_id",
109 :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
109 :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
110
110
111 if project.nil?
111 if project.nil?
112 project_values = []
112 project_values = []
113 if User.current.logged? && User.current.memberships.any?
113 if User.current.logged? && User.current.memberships.any?
114 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
114 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
115 end
115 end
116 project_values += all_projects_values
116 project_values += all_projects_values
117 add_available_filter("project_id",
117 add_available_filter("project_id",
118 :type => :list, :values => project_values
118 :type => :list, :values => project_values
119 ) unless project_values.empty?
119 ) unless project_values.empty?
120 end
120 end
121
121
122 add_available_filter "tracker_id",
122 add_available_filter "tracker_id",
123 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
123 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
124 add_available_filter "priority_id",
124 add_available_filter "priority_id",
125 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
125 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
126
126
127 author_values = []
127 author_values = []
128 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
128 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
129 author_values += users.collect{|s| [s.name, s.id.to_s] }
129 author_values += users.collect{|s| [s.name, s.id.to_s] }
130 add_available_filter("author_id",
130 add_available_filter("author_id",
131 :type => :list, :values => author_values
131 :type => :list, :values => author_values
132 ) unless author_values.empty?
132 ) unless author_values.empty?
133
133
134 assigned_to_values = []
134 assigned_to_values = []
135 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
135 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
136 assigned_to_values += (Setting.issue_group_assignment? ?
136 assigned_to_values += (Setting.issue_group_assignment? ?
137 principals : users).collect{|s| [s.name, s.id.to_s] }
137 principals : users).collect{|s| [s.name, s.id.to_s] }
138 add_available_filter("assigned_to_id",
138 add_available_filter("assigned_to_id",
139 :type => :list_optional, :values => assigned_to_values
139 :type => :list_optional, :values => assigned_to_values
140 ) unless assigned_to_values.empty?
140 ) unless assigned_to_values.empty?
141
141
142 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
142 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
143 add_available_filter("member_of_group",
143 add_available_filter("member_of_group",
144 :type => :list_optional, :values => group_values
144 :type => :list_optional, :values => group_values
145 ) unless group_values.empty?
145 ) unless group_values.empty?
146
146
147 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
147 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
148 add_available_filter("assigned_to_role",
148 add_available_filter("assigned_to_role",
149 :type => :list_optional, :values => role_values
149 :type => :list_optional, :values => role_values
150 ) unless role_values.empty?
150 ) unless role_values.empty?
151
151
152 add_available_filter "fixed_version_id",
152 add_available_filter "fixed_version_id",
153 :type => :list_optional,
153 :type => :list_optional,
154 :values => Version.sort_by_status(versions).collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s, l("version_status_#{s.status}")] }
154 :values => Version.sort_by_status(versions).collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s, l("version_status_#{s.status}")] }
155
155
156 add_available_filter "fixed_version.due_date",
157 :type => :date,
158 :name => l(:label_attribute_of_fixed_version, :name => l(:field_effective_date))
159
160 add_available_filter "fixed_version.status",
161 :type => :list,
162 :name => l(:label_attribute_of_fixed_version, :name => l(:field_status)),
163 :values => Version::VERSION_STATUSES.map{|t| [t, t] }
164
156 add_available_filter "category_id",
165 add_available_filter "category_id",
157 :type => :list_optional,
166 :type => :list_optional,
158 :values => categories.collect{|s| [s.name, s.id.to_s] }
167 :values => categories.collect{|s| [s.name, s.id.to_s] }
159
168
160 add_available_filter "subject", :type => :text
169 add_available_filter "subject", :type => :text
161 add_available_filter "description", :type => :text
170 add_available_filter "description", :type => :text
162 add_available_filter "created_on", :type => :date_past
171 add_available_filter "created_on", :type => :date_past
163 add_available_filter "updated_on", :type => :date_past
172 add_available_filter "updated_on", :type => :date_past
164 add_available_filter "closed_on", :type => :date_past
173 add_available_filter "closed_on", :type => :date_past
165 add_available_filter "start_date", :type => :date
174 add_available_filter "start_date", :type => :date
166 add_available_filter "due_date", :type => :date
175 add_available_filter "due_date", :type => :date
167 add_available_filter "estimated_hours", :type => :float
176 add_available_filter "estimated_hours", :type => :float
168 add_available_filter "done_ratio", :type => :integer
177 add_available_filter "done_ratio", :type => :integer
169
178
170 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
179 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
171 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
180 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
172 add_available_filter "is_private",
181 add_available_filter "is_private",
173 :type => :list,
182 :type => :list,
174 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
183 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
175 end
184 end
176
185
177 if User.current.logged?
186 if User.current.logged?
178 add_available_filter "watcher_id",
187 add_available_filter "watcher_id",
179 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
188 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
180 end
189 end
181
190
182 if subprojects.any?
191 if subprojects.any?
183 add_available_filter "subproject_id",
192 add_available_filter "subproject_id",
184 :type => :list_subprojects,
193 :type => :list_subprojects,
185 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
194 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
186 end
195 end
187
196
188 add_custom_fields_filters(issue_custom_fields)
197 add_custom_fields_filters(issue_custom_fields)
189
198
190 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
199 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
191
200
192 IssueRelation::TYPES.each do |relation_type, options|
201 IssueRelation::TYPES.each do |relation_type, options|
193 add_available_filter relation_type, :type => :relation, :label => options[:name]
202 add_available_filter relation_type, :type => :relation, :label => options[:name]
194 end
203 end
195 add_available_filter "parent_id", :type => :tree, :label => :field_parent_issue
204 add_available_filter "parent_id", :type => :tree, :label => :field_parent_issue
196 add_available_filter "child_id", :type => :tree, :label => :label_subtask_plural
205 add_available_filter "child_id", :type => :tree, :label => :label_subtask_plural
197
206
198 add_available_filter "issue_id", :type => :integer, :label => :label_issue
207 add_available_filter "issue_id", :type => :integer, :label => :label_issue
199
208
200 Tracker.disabled_core_fields(trackers).each {|field|
209 Tracker.disabled_core_fields(trackers).each {|field|
201 delete_available_filter field
210 delete_available_filter field
202 }
211 }
203 end
212 end
204
213
205 def available_columns
214 def available_columns
206 return @available_columns if @available_columns
215 return @available_columns if @available_columns
207 @available_columns = self.class.available_columns.dup
216 @available_columns = self.class.available_columns.dup
208 @available_columns += (project ?
217 @available_columns += (project ?
209 project.all_issue_custom_fields :
218 project.all_issue_custom_fields :
210 IssueCustomField
219 IssueCustomField
211 ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
220 ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
212
221
213 if User.current.allowed_to?(:view_time_entries, project, :global => true)
222 if User.current.allowed_to?(:view_time_entries, project, :global => true)
214 index = @available_columns.find_index {|column| column.name == :total_estimated_hours}
223 index = @available_columns.find_index {|column| column.name == :total_estimated_hours}
215 index = (index ? index + 1 : -1)
224 index = (index ? index + 1 : -1)
216 # insert the column after total_estimated_hours or at the end
225 # insert the column after total_estimated_hours or at the end
217 @available_columns.insert index, QueryColumn.new(:spent_hours,
226 @available_columns.insert index, QueryColumn.new(:spent_hours,
218 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
227 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
219 :default_order => 'desc',
228 :default_order => 'desc',
220 :caption => :label_spent_time,
229 :caption => :label_spent_time,
221 :totalable => true
230 :totalable => true
222 )
231 )
223 @available_columns.insert index+1, QueryColumn.new(:total_spent_hours,
232 @available_columns.insert index+1, QueryColumn.new(:total_spent_hours,
224 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" +
233 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" +
225 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
234 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
226 :default_order => 'desc',
235 :default_order => 'desc',
227 :caption => :label_total_spent_time
236 :caption => :label_total_spent_time
228 )
237 )
229 end
238 end
230
239
231 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
240 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
232 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
241 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
233 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
242 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
234 end
243 end
235
244
236 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
245 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
237 @available_columns.reject! {|column|
246 @available_columns.reject! {|column|
238 disabled_fields.include?(column.name.to_s)
247 disabled_fields.include?(column.name.to_s)
239 }
248 }
240
249
241 @available_columns
250 @available_columns
242 end
251 end
243
252
244 def default_columns_names
253 def default_columns_names
245 @default_columns_names ||= begin
254 @default_columns_names ||= begin
246 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
255 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
247
256
248 project.present? ? default_columns : [:project] | default_columns
257 project.present? ? default_columns : [:project] | default_columns
249 end
258 end
250 end
259 end
251
260
252 def default_totalable_names
261 def default_totalable_names
253 Setting.issue_list_default_totals.map(&:to_sym)
262 Setting.issue_list_default_totals.map(&:to_sym)
254 end
263 end
255
264
256 def base_scope
265 def base_scope
257 Issue.visible.joins(:status, :project).where(statement)
266 Issue.visible.joins(:status, :project).where(statement)
258 end
267 end
259
268
260 # Returns the issue count
269 # Returns the issue count
261 def issue_count
270 def issue_count
262 base_scope.count
271 base_scope.count
263 rescue ::ActiveRecord::StatementInvalid => e
272 rescue ::ActiveRecord::StatementInvalid => e
264 raise StatementInvalid.new(e.message)
273 raise StatementInvalid.new(e.message)
265 end
274 end
266
275
267 # Returns the issue count by group or nil if query is not grouped
276 # Returns the issue count by group or nil if query is not grouped
268 def issue_count_by_group
277 def issue_count_by_group
269 grouped_query do |scope|
278 grouped_query do |scope|
270 scope.count
279 scope.count
271 end
280 end
272 end
281 end
273
282
274 # Returns sum of all the issue's estimated_hours
283 # Returns sum of all the issue's estimated_hours
275 def total_for_estimated_hours(scope)
284 def total_for_estimated_hours(scope)
276 map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
285 map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
277 end
286 end
278
287
279 # Returns sum of all the issue's time entries hours
288 # Returns sum of all the issue's time entries hours
280 def total_for_spent_hours(scope)
289 def total_for_spent_hours(scope)
281 total = if group_by_column.try(:name) == :project
290 total = if group_by_column.try(:name) == :project
282 # TODO: remove this when https://github.com/rails/rails/issues/21922 is fixed
291 # TODO: remove this when https://github.com/rails/rails/issues/21922 is fixed
283 # We have to do a custom join without the time_entries.project_id column
292 # We have to do a custom join without the time_entries.project_id column
284 # that would trigger a ambiguous column name error
293 # that would trigger a ambiguous column name error
285 scope.joins("JOIN (SELECT issue_id, hours FROM #{TimeEntry.table_name}) AS joined_time_entries ON joined_time_entries.issue_id = #{Issue.table_name}.id").
294 scope.joins("JOIN (SELECT issue_id, hours FROM #{TimeEntry.table_name}) AS joined_time_entries ON joined_time_entries.issue_id = #{Issue.table_name}.id").
286 sum("joined_time_entries.hours")
295 sum("joined_time_entries.hours")
287 else
296 else
288 scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours")
297 scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours")
289 end
298 end
290 map_total(total) {|t| t.to_f.round(2)}
299 map_total(total) {|t| t.to_f.round(2)}
291 end
300 end
292
301
293 # Returns the issues
302 # Returns the issues
294 # Valid options are :order, :offset, :limit, :include, :conditions
303 # Valid options are :order, :offset, :limit, :include, :conditions
295 def issues(options={})
304 def issues(options={})
296 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
305 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
297
306
298 scope = Issue.visible.
307 scope = Issue.visible.
299 joins(:status, :project).
308 joins(:status, :project).
300 where(statement).
309 where(statement).
301 includes(([:status, :project] + (options[:include] || [])).uniq).
310 includes(([:status, :project] + (options[:include] || [])).uniq).
302 where(options[:conditions]).
311 where(options[:conditions]).
303 order(order_option).
312 order(order_option).
304 joins(joins_for_order_statement(order_option.join(','))).
313 joins(joins_for_order_statement(order_option.join(','))).
305 limit(options[:limit]).
314 limit(options[:limit]).
306 offset(options[:offset])
315 offset(options[:offset])
307
316
308 scope = scope.preload(:custom_values)
317 scope = scope.preload(:custom_values)
309 if has_column?(:author)
318 if has_column?(:author)
310 scope = scope.preload(:author)
319 scope = scope.preload(:author)
311 end
320 end
312
321
313 issues = scope.to_a
322 issues = scope.to_a
314
323
315 if has_column?(:spent_hours)
324 if has_column?(:spent_hours)
316 Issue.load_visible_spent_hours(issues)
325 Issue.load_visible_spent_hours(issues)
317 end
326 end
318 if has_column?(:total_spent_hours)
327 if has_column?(:total_spent_hours)
319 Issue.load_visible_total_spent_hours(issues)
328 Issue.load_visible_total_spent_hours(issues)
320 end
329 end
321 if has_column?(:relations)
330 if has_column?(:relations)
322 Issue.load_visible_relations(issues)
331 Issue.load_visible_relations(issues)
323 end
332 end
324 issues
333 issues
325 rescue ::ActiveRecord::StatementInvalid => e
334 rescue ::ActiveRecord::StatementInvalid => e
326 raise StatementInvalid.new(e.message)
335 raise StatementInvalid.new(e.message)
327 end
336 end
328
337
329 # Returns the issues ids
338 # Returns the issues ids
330 def issue_ids(options={})
339 def issue_ids(options={})
331 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
340 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
332
341
333 Issue.visible.
342 Issue.visible.
334 joins(:status, :project).
343 joins(:status, :project).
335 where(statement).
344 where(statement).
336 includes(([:status, :project] + (options[:include] || [])).uniq).
345 includes(([:status, :project] + (options[:include] || [])).uniq).
337 references(([:status, :project] + (options[:include] || [])).uniq).
346 references(([:status, :project] + (options[:include] || [])).uniq).
338 where(options[:conditions]).
347 where(options[:conditions]).
339 order(order_option).
348 order(order_option).
340 joins(joins_for_order_statement(order_option.join(','))).
349 joins(joins_for_order_statement(order_option.join(','))).
341 limit(options[:limit]).
350 limit(options[:limit]).
342 offset(options[:offset]).
351 offset(options[:offset]).
343 pluck(:id)
352 pluck(:id)
344 rescue ::ActiveRecord::StatementInvalid => e
353 rescue ::ActiveRecord::StatementInvalid => e
345 raise StatementInvalid.new(e.message)
354 raise StatementInvalid.new(e.message)
346 end
355 end
347
356
348 # Returns the journals
357 # Returns the journals
349 # Valid options are :order, :offset, :limit
358 # Valid options are :order, :offset, :limit
350 def journals(options={})
359 def journals(options={})
351 Journal.visible.
360 Journal.visible.
352 joins(:issue => [:project, :status]).
361 joins(:issue => [:project, :status]).
353 where(statement).
362 where(statement).
354 order(options[:order]).
363 order(options[:order]).
355 limit(options[:limit]).
364 limit(options[:limit]).
356 offset(options[:offset]).
365 offset(options[:offset]).
357 preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}).
366 preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}).
358 to_a
367 to_a
359 rescue ::ActiveRecord::StatementInvalid => e
368 rescue ::ActiveRecord::StatementInvalid => e
360 raise StatementInvalid.new(e.message)
369 raise StatementInvalid.new(e.message)
361 end
370 end
362
371
363 # Returns the versions
372 # Returns the versions
364 # Valid options are :conditions
373 # Valid options are :conditions
365 def versions(options={})
374 def versions(options={})
366 Version.visible.
375 Version.visible.
367 where(project_statement).
376 where(project_statement).
368 where(options[:conditions]).
377 where(options[:conditions]).
369 includes(:project).
378 includes(:project).
370 references(:project).
379 references(:project).
371 to_a
380 to_a
372 rescue ::ActiveRecord::StatementInvalid => e
381 rescue ::ActiveRecord::StatementInvalid => e
373 raise StatementInvalid.new(e.message)
382 raise StatementInvalid.new(e.message)
374 end
383 end
375
384
376 def sql_for_watcher_id_field(field, operator, value)
385 def sql_for_watcher_id_field(field, operator, value)
377 db_table = Watcher.table_name
386 db_table = Watcher.table_name
378 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
387 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
379 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
388 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
380 end
389 end
381
390
382 def sql_for_member_of_group_field(field, operator, value)
391 def sql_for_member_of_group_field(field, operator, value)
383 if operator == '*' # Any group
392 if operator == '*' # Any group
384 groups = Group.givable
393 groups = Group.givable
385 operator = '=' # Override the operator since we want to find by assigned_to
394 operator = '=' # Override the operator since we want to find by assigned_to
386 elsif operator == "!*"
395 elsif operator == "!*"
387 groups = Group.givable
396 groups = Group.givable
388 operator = '!' # Override the operator since we want to find by assigned_to
397 operator = '!' # Override the operator since we want to find by assigned_to
389 else
398 else
390 groups = Group.where(:id => value).to_a
399 groups = Group.where(:id => value).to_a
391 end
400 end
392 groups ||= []
401 groups ||= []
393
402
394 members_of_groups = groups.inject([]) {|user_ids, group|
403 members_of_groups = groups.inject([]) {|user_ids, group|
395 user_ids + group.user_ids + [group.id]
404 user_ids + group.user_ids + [group.id]
396 }.uniq.compact.sort.collect(&:to_s)
405 }.uniq.compact.sort.collect(&:to_s)
397
406
398 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
407 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
399 end
408 end
400
409
401 def sql_for_assigned_to_role_field(field, operator, value)
410 def sql_for_assigned_to_role_field(field, operator, value)
402 case operator
411 case operator
403 when "*", "!*" # Member / Not member
412 when "*", "!*" # Member / Not member
404 sw = operator == "!*" ? 'NOT' : ''
413 sw = operator == "!*" ? 'NOT' : ''
405 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
414 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
406 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
415 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
407 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
416 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
408 when "=", "!"
417 when "=", "!"
409 role_cond = value.any? ?
418 role_cond = value.any? ?
410 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
419 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
411 "1=0"
420 "1=0"
412
421
413 sw = operator == "!" ? 'NOT' : ''
422 sw = operator == "!" ? 'NOT' : ''
414 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
423 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
415 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
424 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
416 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
425 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
417 end
426 end
418 end
427 end
419
428
429 def sql_for_fixed_version_status_field(field, operator, value)
430 where = sql_for_field(field, operator, value, Version.table_name, "status")
431 version_ids = versions(:conditions => [where]).map(&:id)
432
433 nl = operator == "!" ? "#{Issue.table_name}.fixed_version_id IS NULL OR" : ''
434 "(#{nl} #{sql_for_field("fixed_version_id", "=", version_ids, Issue.table_name, "fixed_version_id")})"
435 end
436
437 def sql_for_fixed_version_due_date_field(field, operator, value)
438 where = sql_for_field(field, operator, value, Version.table_name, "effective_date")
439 version_ids = versions(:conditions => [where]).map(&:id)
440
441 nl = operator == "!*" ? "#{Issue.table_name}.fixed_version_id IS NULL OR" : ''
442 "(#{nl} #{sql_for_field("fixed_version_id", "=", version_ids, Issue.table_name, "fixed_version_id")})"
443 end
444
420 def sql_for_is_private_field(field, operator, value)
445 def sql_for_is_private_field(field, operator, value)
421 op = (operator == "=" ? 'IN' : 'NOT IN')
446 op = (operator == "=" ? 'IN' : 'NOT IN')
422 va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',')
447 va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',')
423
448
424 "#{Issue.table_name}.is_private #{op} (#{va})"
449 "#{Issue.table_name}.is_private #{op} (#{va})"
425 end
450 end
426
451
427 def sql_for_parent_id_field(field, operator, value)
452 def sql_for_parent_id_field(field, operator, value)
428 case operator
453 case operator
429 when "="
454 when "="
430 "#{Issue.table_name}.parent_id = #{value.first.to_i}"
455 "#{Issue.table_name}.parent_id = #{value.first.to_i}"
431 when "~"
456 when "~"
432 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
457 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
433 if root_id && lft && rgt
458 if root_id && lft && rgt
434 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft > #{lft} AND #{Issue.table_name}.rgt < #{rgt}"
459 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft > #{lft} AND #{Issue.table_name}.rgt < #{rgt}"
435 else
460 else
436 "1=0"
461 "1=0"
437 end
462 end
438 when "!*"
463 when "!*"
439 "#{Issue.table_name}.parent_id IS NULL"
464 "#{Issue.table_name}.parent_id IS NULL"
440 when "*"
465 when "*"
441 "#{Issue.table_name}.parent_id IS NOT NULL"
466 "#{Issue.table_name}.parent_id IS NOT NULL"
442 end
467 end
443 end
468 end
444
469
445 def sql_for_child_id_field(field, operator, value)
470 def sql_for_child_id_field(field, operator, value)
446 case operator
471 case operator
447 when "="
472 when "="
448 parent_id = Issue.where(:id => value.first.to_i).pluck(:parent_id).first
473 parent_id = Issue.where(:id => value.first.to_i).pluck(:parent_id).first
449 if parent_id
474 if parent_id
450 "#{Issue.table_name}.id = #{parent_id}"
475 "#{Issue.table_name}.id = #{parent_id}"
451 else
476 else
452 "1=0"
477 "1=0"
453 end
478 end
454 when "~"
479 when "~"
455 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
480 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
456 if root_id && lft && rgt
481 if root_id && lft && rgt
457 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft < #{lft} AND #{Issue.table_name}.rgt > #{rgt}"
482 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft < #{lft} AND #{Issue.table_name}.rgt > #{rgt}"
458 else
483 else
459 "1=0"
484 "1=0"
460 end
485 end
461 when "!*"
486 when "!*"
462 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1"
487 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1"
463 when "*"
488 when "*"
464 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft > 1"
489 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft > 1"
465 end
490 end
466 end
491 end
467
492
468 def sql_for_issue_id_field(field, operator, value)
493 def sql_for_issue_id_field(field, operator, value)
469 if operator == "="
494 if operator == "="
470 # accepts a comma separated list of ids
495 # accepts a comma separated list of ids
471 ids = value.first.to_s.scan(/\d+/).map(&:to_i)
496 ids = value.first.to_s.scan(/\d+/).map(&:to_i)
472 if ids.present?
497 if ids.present?
473 "#{Issue.table_name}.id IN (#{ids.join(",")})"
498 "#{Issue.table_name}.id IN (#{ids.join(",")})"
474 else
499 else
475 "1=0"
500 "1=0"
476 end
501 end
477 else
502 else
478 sql_for_field("id", operator, value, Issue.table_name, "id")
503 sql_for_field("id", operator, value, Issue.table_name, "id")
479 end
504 end
480 end
505 end
481
506
482 def sql_for_relations(field, operator, value, options={})
507 def sql_for_relations(field, operator, value, options={})
483 relation_options = IssueRelation::TYPES[field]
508 relation_options = IssueRelation::TYPES[field]
484 return relation_options unless relation_options
509 return relation_options unless relation_options
485
510
486 relation_type = field
511 relation_type = field
487 join_column, target_join_column = "issue_from_id", "issue_to_id"
512 join_column, target_join_column = "issue_from_id", "issue_to_id"
488 if relation_options[:reverse] || options[:reverse]
513 if relation_options[:reverse] || options[:reverse]
489 relation_type = relation_options[:reverse] || relation_type
514 relation_type = relation_options[:reverse] || relation_type
490 join_column, target_join_column = target_join_column, join_column
515 join_column, target_join_column = target_join_column, join_column
491 end
516 end
492
517
493 sql = case operator
518 sql = case operator
494 when "*", "!*"
519 when "*", "!*"
495 op = (operator == "*" ? 'IN' : 'NOT IN')
520 op = (operator == "*" ? 'IN' : 'NOT IN')
496 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')"
521 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}')"
497 when "=", "!"
522 when "=", "!"
498 op = (operator == "=" ? 'IN' : 'NOT IN')
523 op = (operator == "=" ? 'IN' : 'NOT IN')
499 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
524 "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})"
500 when "=p", "=!p", "!p"
525 when "=p", "=!p", "!p"
501 op = (operator == "!p" ? 'NOT IN' : 'IN')
526 op = (operator == "!p" ? 'NOT IN' : 'IN')
502 comp = (operator == "=!p" ? '<>' : '=')
527 comp = (operator == "=!p" ? '<>' : '=')
503 "#{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 = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
528 "#{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 = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})"
504 when "*o", "!o"
529 when "*o", "!o"
505 op = (operator == "!o" ? 'NOT IN' : 'IN')
530 op = (operator == "!o" ? 'NOT IN' : 'IN')
506 "#{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 = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
531 "#{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 = '#{self.class.connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{self.class.connection.quoted_false}))"
507 end
532 end
508
533
509 if relation_options[:sym] == field && !options[:reverse]
534 if relation_options[:sym] == field && !options[:reverse]
510 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
535 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
511 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
536 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
512 end
537 end
513 "(#{sql})"
538 "(#{sql})"
514 end
539 end
515
540
516 def find_assigned_to_id_filter_values(values)
541 def find_assigned_to_id_filter_values(values)
517 Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
542 Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
518 end
543 end
519 alias :find_author_id_filter_values :find_assigned_to_id_filter_values
544 alias :find_author_id_filter_values :find_assigned_to_id_filter_values
520
545
521 IssueRelation::TYPES.keys.each do |relation_type|
546 IssueRelation::TYPES.keys.each do |relation_type|
522 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
547 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
523 end
548 end
524 end
549 end
@@ -1,1777 +1,1807
1 # encoding: utf-8
1 # encoding: utf-8
2 #
2 #
3 # Redmine - project management software
3 # Redmine - project management software
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 #
5 #
6 # This program is free software; you can redistribute it and/or
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
9 # of the License, or (at your option) any later version.
10 #
10 #
11 # This program is distributed in the hope that it will be useful,
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
14 # GNU General Public License for more details.
15 #
15 #
16 # You should have received a copy of the GNU General Public License
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
19
20 require File.expand_path('../../test_helper', __FILE__)
20 require File.expand_path('../../test_helper', __FILE__)
21
21
22 class QueryTest < ActiveSupport::TestCase
22 class QueryTest < ActiveSupport::TestCase
23 include Redmine::I18n
23 include Redmine::I18n
24
24
25 fixtures :projects, :enabled_modules, :users, :members,
25 fixtures :projects, :enabled_modules, :users, :members,
26 :member_roles, :roles, :trackers, :issue_statuses,
26 :member_roles, :roles, :trackers, :issue_statuses,
27 :issue_categories, :enumerations, :issues,
27 :issue_categories, :enumerations, :issues,
28 :watchers, :custom_fields, :custom_values, :versions,
28 :watchers, :custom_fields, :custom_values, :versions,
29 :queries,
29 :queries,
30 :projects_trackers,
30 :projects_trackers,
31 :custom_fields_trackers,
31 :custom_fields_trackers,
32 :workflows
32 :workflows
33
33
34 def setup
34 def setup
35 User.current = nil
35 User.current = nil
36 end
36 end
37
37
38 def test_query_with_roles_visibility_should_validate_roles
38 def test_query_with_roles_visibility_should_validate_roles
39 set_language_if_valid 'en'
39 set_language_if_valid 'en'
40 query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
40 query = IssueQuery.new(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES)
41 assert !query.save
41 assert !query.save
42 assert_include "Roles cannot be blank", query.errors.full_messages
42 assert_include "Roles cannot be blank", query.errors.full_messages
43 query.role_ids = [1, 2]
43 query.role_ids = [1, 2]
44 assert query.save
44 assert query.save
45 end
45 end
46
46
47 def test_changing_roles_visibility_should_clear_roles
47 def test_changing_roles_visibility_should_clear_roles
48 query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
48 query = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1, 2])
49 assert_equal 2, query.roles.count
49 assert_equal 2, query.roles.count
50
50
51 query.visibility = IssueQuery::VISIBILITY_PUBLIC
51 query.visibility = IssueQuery::VISIBILITY_PUBLIC
52 query.save!
52 query.save!
53 assert_equal 0, query.roles.count
53 assert_equal 0, query.roles.count
54 end
54 end
55
55
56 def test_available_filters_should_be_ordered
56 def test_available_filters_should_be_ordered
57 set_language_if_valid 'en'
57 set_language_if_valid 'en'
58 query = IssueQuery.new
58 query = IssueQuery.new
59 assert_equal 0, query.available_filters.keys.index('status_id')
59 assert_equal 0, query.available_filters.keys.index('status_id')
60 expected_order = [
60 expected_order = [
61 "Status",
61 "Status",
62 "Project",
62 "Project",
63 "Tracker",
63 "Tracker",
64 "Priority"
64 "Priority"
65 ]
65 ]
66 assert_equal expected_order,
66 assert_equal expected_order,
67 (query.available_filters.values.map{|v| v[:name]} & expected_order)
67 (query.available_filters.values.map{|v| v[:name]} & expected_order)
68 end
68 end
69
69
70 def test_available_filters_with_custom_fields_should_be_ordered
70 def test_available_filters_with_custom_fields_should_be_ordered
71 set_language_if_valid 'en'
71 set_language_if_valid 'en'
72 UserCustomField.create!(
72 UserCustomField.create!(
73 :name => 'order test', :field_format => 'string',
73 :name => 'order test', :field_format => 'string',
74 :is_for_all => true, :is_filter => true
74 :is_for_all => true, :is_filter => true
75 )
75 )
76 query = IssueQuery.new
76 query = IssueQuery.new
77 expected_order = [
77 expected_order = [
78 "Searchable field",
78 "Searchable field",
79 "Database",
79 "Database",
80 "Project's Development status",
80 "Project's Development status",
81 "Author's order test",
81 "Author's order test",
82 "Assignee's order test"
82 "Assignee's order test"
83 ]
83 ]
84 assert_equal expected_order,
84 assert_equal expected_order,
85 (query.available_filters.values.map{|v| v[:name]} & expected_order)
85 (query.available_filters.values.map{|v| v[:name]} & expected_order)
86 end
86 end
87
87
88 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
88 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
89 query = IssueQuery.new(:project => nil, :name => '_')
89 query = IssueQuery.new(:project => nil, :name => '_')
90 assert query.available_filters.has_key?('cf_1')
90 assert query.available_filters.has_key?('cf_1')
91 assert !query.available_filters.has_key?('cf_3')
91 assert !query.available_filters.has_key?('cf_3')
92 end
92 end
93
93
94 def test_system_shared_versions_should_be_available_in_global_queries
94 def test_system_shared_versions_should_be_available_in_global_queries
95 Version.find(2).update_attribute :sharing, 'system'
95 Version.find(2).update_attribute :sharing, 'system'
96 query = IssueQuery.new(:project => nil, :name => '_')
96 query = IssueQuery.new(:project => nil, :name => '_')
97 assert query.available_filters.has_key?('fixed_version_id')
97 assert query.available_filters.has_key?('fixed_version_id')
98 assert query.available_filters['fixed_version_id'][:values].detect {|v| v[1] == '2'}
98 assert query.available_filters['fixed_version_id'][:values].detect {|v| v[1] == '2'}
99 end
99 end
100
100
101 def test_project_filter_in_global_queries
101 def test_project_filter_in_global_queries
102 query = IssueQuery.new(:project => nil, :name => '_')
102 query = IssueQuery.new(:project => nil, :name => '_')
103 project_filter = query.available_filters["project_id"]
103 project_filter = query.available_filters["project_id"]
104 assert_not_nil project_filter
104 assert_not_nil project_filter
105 project_ids = project_filter[:values].map{|p| p[1]}
105 project_ids = project_filter[:values].map{|p| p[1]}
106 assert project_ids.include?("1") #public project
106 assert project_ids.include?("1") #public project
107 assert !project_ids.include?("2") #private project user cannot see
107 assert !project_ids.include?("2") #private project user cannot see
108 end
108 end
109
109
110 def test_available_filters_should_not_include_fields_disabled_on_all_trackers
110 def test_available_filters_should_not_include_fields_disabled_on_all_trackers
111 Tracker.all.each do |tracker|
111 Tracker.all.each do |tracker|
112 tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
112 tracker.core_fields = Tracker::CORE_FIELDS - ['start_date']
113 tracker.save!
113 tracker.save!
114 end
114 end
115
115
116 query = IssueQuery.new(:name => '_')
116 query = IssueQuery.new(:name => '_')
117 assert_include 'due_date', query.available_filters
117 assert_include 'due_date', query.available_filters
118 assert_not_include 'start_date', query.available_filters
118 assert_not_include 'start_date', query.available_filters
119 end
119 end
120
120
121 def find_issues_with_query(query)
121 def find_issues_with_query(query)
122 Issue.joins(:status, :tracker, :project, :priority).where(
122 Issue.joins(:status, :tracker, :project, :priority).where(
123 query.statement
123 query.statement
124 ).to_a
124 ).to_a
125 end
125 end
126
126
127 def assert_find_issues_with_query_is_successful(query)
127 def assert_find_issues_with_query_is_successful(query)
128 assert_nothing_raised do
128 assert_nothing_raised do
129 find_issues_with_query(query)
129 find_issues_with_query(query)
130 end
130 end
131 end
131 end
132
132
133 def assert_query_statement_includes(query, condition)
133 def assert_query_statement_includes(query, condition)
134 assert_include condition, query.statement
134 assert_include condition, query.statement
135 end
135 end
136
136
137 def assert_query_result(expected, query)
137 def assert_query_result(expected, query)
138 assert_nothing_raised do
138 assert_nothing_raised do
139 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
139 assert_equal expected.map(&:id).sort, query.issues.map(&:id).sort
140 assert_equal expected.size, query.issue_count
140 assert_equal expected.size, query.issue_count
141 end
141 end
142 end
142 end
143
143
144 def test_query_should_allow_shared_versions_for_a_project_query
144 def test_query_should_allow_shared_versions_for_a_project_query
145 subproject_version = Version.find(4)
145 subproject_version = Version.find(4)
146 query = IssueQuery.new(:project => Project.find(1), :name => '_')
146 query = IssueQuery.new(:project => Project.find(1), :name => '_')
147 filter = query.available_filters["fixed_version_id"]
147 filter = query.available_filters["fixed_version_id"]
148 assert_not_nil filter
148 assert_not_nil filter
149 assert_include subproject_version.id.to_s, filter[:values].map(&:second)
149 assert_include subproject_version.id.to_s, filter[:values].map(&:second)
150 end
150 end
151
151
152 def test_query_with_multiple_custom_fields
152 def test_query_with_multiple_custom_fields
153 query = IssueQuery.find(1)
153 query = IssueQuery.find(1)
154 assert query.valid?
154 assert query.valid?
155 issues = find_issues_with_query(query)
155 issues = find_issues_with_query(query)
156 assert_equal 1, issues.length
156 assert_equal 1, issues.length
157 assert_equal Issue.find(3), issues.first
157 assert_equal Issue.find(3), issues.first
158 end
158 end
159
159
160 def test_operator_none
160 def test_operator_none
161 query = IssueQuery.new(:project => Project.find(1), :name => '_')
161 query = IssueQuery.new(:project => Project.find(1), :name => '_')
162 query.add_filter('fixed_version_id', '!*', [''])
162 query.add_filter('fixed_version_id', '!*', [''])
163 query.add_filter('cf_1', '!*', [''])
163 query.add_filter('cf_1', '!*', [''])
164 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
164 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
165 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
165 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
166 find_issues_with_query(query)
166 find_issues_with_query(query)
167 end
167 end
168
168
169 def test_operator_none_for_integer
169 def test_operator_none_for_integer
170 query = IssueQuery.new(:project => Project.find(1), :name => '_')
170 query = IssueQuery.new(:project => Project.find(1), :name => '_')
171 query.add_filter('estimated_hours', '!*', [''])
171 query.add_filter('estimated_hours', '!*', [''])
172 issues = find_issues_with_query(query)
172 issues = find_issues_with_query(query)
173 assert !issues.empty?
173 assert !issues.empty?
174 assert issues.all? {|i| !i.estimated_hours}
174 assert issues.all? {|i| !i.estimated_hours}
175 end
175 end
176
176
177 def test_operator_none_for_date
177 def test_operator_none_for_date
178 query = IssueQuery.new(:project => Project.find(1), :name => '_')
178 query = IssueQuery.new(:project => Project.find(1), :name => '_')
179 query.add_filter('start_date', '!*', [''])
179 query.add_filter('start_date', '!*', [''])
180 issues = find_issues_with_query(query)
180 issues = find_issues_with_query(query)
181 assert !issues.empty?
181 assert !issues.empty?
182 assert issues.all? {|i| i.start_date.nil?}
182 assert issues.all? {|i| i.start_date.nil?}
183 end
183 end
184
184
185 def test_operator_none_for_string_custom_field
185 def test_operator_none_for_string_custom_field
186 CustomField.find(2).update_attribute :default_value, ""
186 CustomField.find(2).update_attribute :default_value, ""
187 query = IssueQuery.new(:project => Project.find(1), :name => '_')
187 query = IssueQuery.new(:project => Project.find(1), :name => '_')
188 query.add_filter('cf_2', '!*', [''])
188 query.add_filter('cf_2', '!*', [''])
189 assert query.has_filter?('cf_2')
189 assert query.has_filter?('cf_2')
190 issues = find_issues_with_query(query)
190 issues = find_issues_with_query(query)
191 assert !issues.empty?
191 assert !issues.empty?
192 assert issues.all? {|i| i.custom_field_value(2).blank?}
192 assert issues.all? {|i| i.custom_field_value(2).blank?}
193 end
193 end
194
194
195 def test_operator_all
195 def test_operator_all
196 query = IssueQuery.new(:project => Project.find(1), :name => '_')
196 query = IssueQuery.new(:project => Project.find(1), :name => '_')
197 query.add_filter('fixed_version_id', '*', [''])
197 query.add_filter('fixed_version_id', '*', [''])
198 query.add_filter('cf_1', '*', [''])
198 query.add_filter('cf_1', '*', [''])
199 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
199 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
200 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
200 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
201 find_issues_with_query(query)
201 find_issues_with_query(query)
202 end
202 end
203
203
204 def test_operator_all_for_date
204 def test_operator_all_for_date
205 query = IssueQuery.new(:project => Project.find(1), :name => '_')
205 query = IssueQuery.new(:project => Project.find(1), :name => '_')
206 query.add_filter('start_date', '*', [''])
206 query.add_filter('start_date', '*', [''])
207 issues = find_issues_with_query(query)
207 issues = find_issues_with_query(query)
208 assert !issues.empty?
208 assert !issues.empty?
209 assert issues.all? {|i| i.start_date.present?}
209 assert issues.all? {|i| i.start_date.present?}
210 end
210 end
211
211
212 def test_operator_all_for_string_custom_field
212 def test_operator_all_for_string_custom_field
213 query = IssueQuery.new(:project => Project.find(1), :name => '_')
213 query = IssueQuery.new(:project => Project.find(1), :name => '_')
214 query.add_filter('cf_2', '*', [''])
214 query.add_filter('cf_2', '*', [''])
215 assert query.has_filter?('cf_2')
215 assert query.has_filter?('cf_2')
216 issues = find_issues_with_query(query)
216 issues = find_issues_with_query(query)
217 assert !issues.empty?
217 assert !issues.empty?
218 assert issues.all? {|i| i.custom_field_value(2).present?}
218 assert issues.all? {|i| i.custom_field_value(2).present?}
219 end
219 end
220
220
221 def test_numeric_filter_should_not_accept_non_numeric_values
221 def test_numeric_filter_should_not_accept_non_numeric_values
222 query = IssueQuery.new(:name => '_')
222 query = IssueQuery.new(:name => '_')
223 query.add_filter('estimated_hours', '=', ['a'])
223 query.add_filter('estimated_hours', '=', ['a'])
224
224
225 assert query.has_filter?('estimated_hours')
225 assert query.has_filter?('estimated_hours')
226 assert !query.valid?
226 assert !query.valid?
227 end
227 end
228
228
229 def test_operator_is_on_float
229 def test_operator_is_on_float
230 Issue.where(:id => 2).update_all("estimated_hours = 171.2")
230 Issue.where(:id => 2).update_all("estimated_hours = 171.2")
231 query = IssueQuery.new(:name => '_')
231 query = IssueQuery.new(:name => '_')
232 query.add_filter('estimated_hours', '=', ['171.20'])
232 query.add_filter('estimated_hours', '=', ['171.20'])
233 issues = find_issues_with_query(query)
233 issues = find_issues_with_query(query)
234 assert_equal 1, issues.size
234 assert_equal 1, issues.size
235 assert_equal 2, issues.first.id
235 assert_equal 2, issues.first.id
236 end
236 end
237
237
238 def test_operator_is_on_issue_id_should_accept_comma_separated_values
238 def test_operator_is_on_issue_id_should_accept_comma_separated_values
239 query = IssueQuery.new(:name => '_')
239 query = IssueQuery.new(:name => '_')
240 query.add_filter("issue_id", '=', ['1,3'])
240 query.add_filter("issue_id", '=', ['1,3'])
241 issues = find_issues_with_query(query)
241 issues = find_issues_with_query(query)
242 assert_equal 2, issues.size
242 assert_equal 2, issues.size
243 assert_equal [1,3], issues.map(&:id).sort
243 assert_equal [1,3], issues.map(&:id).sort
244 end
244 end
245
245
246 def test_operator_between_on_issue_id_should_return_range
246 def test_operator_between_on_issue_id_should_return_range
247 query = IssueQuery.new(:name => '_')
247 query = IssueQuery.new(:name => '_')
248 query.add_filter("issue_id", '><', ['2','3'])
248 query.add_filter("issue_id", '><', ['2','3'])
249 issues = find_issues_with_query(query)
249 issues = find_issues_with_query(query)
250 assert_equal 2, issues.size
250 assert_equal 2, issues.size
251 assert_equal [2,3], issues.map(&:id).sort
251 assert_equal [2,3], issues.map(&:id).sort
252 end
252 end
253
253
254 def test_operator_is_on_integer_custom_field
254 def test_operator_is_on_integer_custom_field
255 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
255 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
256 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
256 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
257 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
257 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
258 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
258 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
259
259
260 query = IssueQuery.new(:name => '_')
260 query = IssueQuery.new(:name => '_')
261 query.add_filter("cf_#{f.id}", '=', ['12'])
261 query.add_filter("cf_#{f.id}", '=', ['12'])
262 issues = find_issues_with_query(query)
262 issues = find_issues_with_query(query)
263 assert_equal 1, issues.size
263 assert_equal 1, issues.size
264 assert_equal 2, issues.first.id
264 assert_equal 2, issues.first.id
265 end
265 end
266
266
267 def test_operator_is_on_integer_custom_field_should_accept_negative_value
267 def test_operator_is_on_integer_custom_field_should_accept_negative_value
268 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
268 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_for_all => true, :is_filter => true, :trackers => Tracker.all)
269 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
269 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
270 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
270 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12')
271 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
271 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
272
272
273 query = IssueQuery.new(:name => '_')
273 query = IssueQuery.new(:name => '_')
274 query.add_filter("cf_#{f.id}", '=', ['-12'])
274 query.add_filter("cf_#{f.id}", '=', ['-12'])
275 assert query.valid?
275 assert query.valid?
276 issues = find_issues_with_query(query)
276 issues = find_issues_with_query(query)
277 assert_equal 1, issues.size
277 assert_equal 1, issues.size
278 assert_equal 2, issues.first.id
278 assert_equal 2, issues.first.id
279 end
279 end
280
280
281 def test_operator_is_on_float_custom_field
281 def test_operator_is_on_float_custom_field
282 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
282 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
283 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
283 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
284 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
284 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12.7')
285 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
285 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
286
286
287 query = IssueQuery.new(:name => '_')
287 query = IssueQuery.new(:name => '_')
288 query.add_filter("cf_#{f.id}", '=', ['12.7'])
288 query.add_filter("cf_#{f.id}", '=', ['12.7'])
289 issues = find_issues_with_query(query)
289 issues = find_issues_with_query(query)
290 assert_equal 1, issues.size
290 assert_equal 1, issues.size
291 assert_equal 2, issues.first.id
291 assert_equal 2, issues.first.id
292 end
292 end
293
293
294 def test_operator_is_on_float_custom_field_should_accept_negative_value
294 def test_operator_is_on_float_custom_field_should_accept_negative_value
295 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
295 f = IssueCustomField.create!(:name => 'filter', :field_format => 'float', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
296 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
296 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7.3')
297 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
297 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '-12.7')
298 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
298 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
299
299
300 query = IssueQuery.new(:name => '_')
300 query = IssueQuery.new(:name => '_')
301 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
301 query.add_filter("cf_#{f.id}", '=', ['-12.7'])
302 assert query.valid?
302 assert query.valid?
303 issues = find_issues_with_query(query)
303 issues = find_issues_with_query(query)
304 assert_equal 1, issues.size
304 assert_equal 1, issues.size
305 assert_equal 2, issues.first.id
305 assert_equal 2, issues.first.id
306 end
306 end
307
307
308 def test_operator_is_on_multi_list_custom_field
308 def test_operator_is_on_multi_list_custom_field
309 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
309 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
310 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
310 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
311 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
311 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
312 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
312 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
313 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
313 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
314
314
315 query = IssueQuery.new(:name => '_')
315 query = IssueQuery.new(:name => '_')
316 query.add_filter("cf_#{f.id}", '=', ['value1'])
316 query.add_filter("cf_#{f.id}", '=', ['value1'])
317 issues = find_issues_with_query(query)
317 issues = find_issues_with_query(query)
318 assert_equal [1, 3], issues.map(&:id).sort
318 assert_equal [1, 3], issues.map(&:id).sort
319
319
320 query = IssueQuery.new(:name => '_')
320 query = IssueQuery.new(:name => '_')
321 query.add_filter("cf_#{f.id}", '=', ['value2'])
321 query.add_filter("cf_#{f.id}", '=', ['value2'])
322 issues = find_issues_with_query(query)
322 issues = find_issues_with_query(query)
323 assert_equal [1], issues.map(&:id).sort
323 assert_equal [1], issues.map(&:id).sort
324 end
324 end
325
325
326 def test_operator_is_not_on_multi_list_custom_field
326 def test_operator_is_not_on_multi_list_custom_field
327 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
327 f = IssueCustomField.create!(:name => 'filter', :field_format => 'list', :is_filter => true, :is_for_all => true,
328 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
328 :possible_values => ['value1', 'value2', 'value3'], :multiple => true, :trackers => Tracker.all)
329 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
329 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value1')
330 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
330 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'value2')
331 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
331 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => 'value1')
332
332
333 query = IssueQuery.new(:name => '_')
333 query = IssueQuery.new(:name => '_')
334 query.add_filter("cf_#{f.id}", '!', ['value1'])
334 query.add_filter("cf_#{f.id}", '!', ['value1'])
335 issues = find_issues_with_query(query)
335 issues = find_issues_with_query(query)
336 assert !issues.map(&:id).include?(1)
336 assert !issues.map(&:id).include?(1)
337 assert !issues.map(&:id).include?(3)
337 assert !issues.map(&:id).include?(3)
338
338
339 query = IssueQuery.new(:name => '_')
339 query = IssueQuery.new(:name => '_')
340 query.add_filter("cf_#{f.id}", '!', ['value2'])
340 query.add_filter("cf_#{f.id}", '!', ['value2'])
341 issues = find_issues_with_query(query)
341 issues = find_issues_with_query(query)
342 assert !issues.map(&:id).include?(1)
342 assert !issues.map(&:id).include?(1)
343 assert issues.map(&:id).include?(3)
343 assert issues.map(&:id).include?(3)
344 end
344 end
345
345
346 def test_operator_is_on_string_custom_field_with_utf8_value
346 def test_operator_is_on_string_custom_field_with_utf8_value
347 f = IssueCustomField.create!(:name => 'filter', :field_format => 'string', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
347 f = IssueCustomField.create!(:name => 'filter', :field_format => 'string', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
348 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'KiÑ»ƒm')
348 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => 'KiÑ»ƒm')
349
349
350 query = IssueQuery.new(:name => '_')
350 query = IssueQuery.new(:name => '_')
351 query.add_filter("cf_#{f.id}", '=', ['KiÑ»ƒm'])
351 query.add_filter("cf_#{f.id}", '=', ['KiÑ»ƒm'])
352 issues = find_issues_with_query(query)
352 issues = find_issues_with_query(query)
353 assert_equal [1], issues.map(&:id).sort
353 assert_equal [1], issues.map(&:id).sort
354 end
354 end
355
355
356 def test_operator_is_on_is_private_field
356 def test_operator_is_on_is_private_field
357 # is_private filter only available for those who can set issues private
357 # is_private filter only available for those who can set issues private
358 User.current = User.find(2)
358 User.current = User.find(2)
359
359
360 query = IssueQuery.new(:name => '_')
360 query = IssueQuery.new(:name => '_')
361 assert query.available_filters.key?('is_private')
361 assert query.available_filters.key?('is_private')
362
362
363 query.add_filter("is_private", '=', ['1'])
363 query.add_filter("is_private", '=', ['1'])
364 issues = find_issues_with_query(query)
364 issues = find_issues_with_query(query)
365 assert issues.any?
365 assert issues.any?
366 assert_nil issues.detect {|issue| !issue.is_private?}
366 assert_nil issues.detect {|issue| !issue.is_private?}
367 ensure
367 ensure
368 User.current = nil
368 User.current = nil
369 end
369 end
370
370
371 def test_operator_is_not_on_is_private_field
371 def test_operator_is_not_on_is_private_field
372 # is_private filter only available for those who can set issues private
372 # is_private filter only available for those who can set issues private
373 User.current = User.find(2)
373 User.current = User.find(2)
374
374
375 query = IssueQuery.new(:name => '_')
375 query = IssueQuery.new(:name => '_')
376 assert query.available_filters.key?('is_private')
376 assert query.available_filters.key?('is_private')
377
377
378 query.add_filter("is_private", '!', ['1'])
378 query.add_filter("is_private", '!', ['1'])
379 issues = find_issues_with_query(query)
379 issues = find_issues_with_query(query)
380 assert issues.any?
380 assert issues.any?
381 assert_nil issues.detect {|issue| issue.is_private?}
381 assert_nil issues.detect {|issue| issue.is_private?}
382 ensure
382 ensure
383 User.current = nil
383 User.current = nil
384 end
384 end
385
385
386 def test_operator_greater_than
386 def test_operator_greater_than
387 query = IssueQuery.new(:project => Project.find(1), :name => '_')
387 query = IssueQuery.new(:project => Project.find(1), :name => '_')
388 query.add_filter('done_ratio', '>=', ['40'])
388 query.add_filter('done_ratio', '>=', ['40'])
389 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
389 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
390 find_issues_with_query(query)
390 find_issues_with_query(query)
391 end
391 end
392
392
393 def test_operator_greater_than_a_float
393 def test_operator_greater_than_a_float
394 query = IssueQuery.new(:project => Project.find(1), :name => '_')
394 query = IssueQuery.new(:project => Project.find(1), :name => '_')
395 query.add_filter('estimated_hours', '>=', ['40.5'])
395 query.add_filter('estimated_hours', '>=', ['40.5'])
396 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
396 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
397 find_issues_with_query(query)
397 find_issues_with_query(query)
398 end
398 end
399
399
400 def test_operator_greater_than_on_int_custom_field
400 def test_operator_greater_than_on_int_custom_field
401 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
401 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
402 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
402 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '7')
403 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
403 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '12')
404 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
404 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
405
405
406 query = IssueQuery.new(:project => Project.find(1), :name => '_')
406 query = IssueQuery.new(:project => Project.find(1), :name => '_')
407 query.add_filter("cf_#{f.id}", '>=', ['8'])
407 query.add_filter("cf_#{f.id}", '>=', ['8'])
408 issues = find_issues_with_query(query)
408 issues = find_issues_with_query(query)
409 assert_equal 1, issues.size
409 assert_equal 1, issues.size
410 assert_equal 2, issues.first.id
410 assert_equal 2, issues.first.id
411 end
411 end
412
412
413 def test_operator_lesser_than
413 def test_operator_lesser_than
414 query = IssueQuery.new(:project => Project.find(1), :name => '_')
414 query = IssueQuery.new(:project => Project.find(1), :name => '_')
415 query.add_filter('done_ratio', '<=', ['30'])
415 query.add_filter('done_ratio', '<=', ['30'])
416 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
416 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
417 find_issues_with_query(query)
417 find_issues_with_query(query)
418 end
418 end
419
419
420 def test_operator_lesser_than_on_custom_field
420 def test_operator_lesser_than_on_custom_field
421 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
421 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
422 query = IssueQuery.new(:project => Project.find(1), :name => '_')
422 query = IssueQuery.new(:project => Project.find(1), :name => '_')
423 query.add_filter("cf_#{f.id}", '<=', ['30'])
423 query.add_filter("cf_#{f.id}", '<=', ['30'])
424 assert_match /CAST.+ <= 30\.0/, query.statement
424 assert_match /CAST.+ <= 30\.0/, query.statement
425 find_issues_with_query(query)
425 find_issues_with_query(query)
426 end
426 end
427
427
428 def test_operator_lesser_than_on_date_custom_field
428 def test_operator_lesser_than_on_date_custom_field
429 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
429 f = IssueCustomField.create!(:name => 'filter', :field_format => 'date', :is_filter => true, :is_for_all => true, :trackers => Tracker.all)
430 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
430 CustomValue.create!(:custom_field => f, :customized => Issue.find(1), :value => '2013-04-11')
431 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
431 CustomValue.create!(:custom_field => f, :customized => Issue.find(2), :value => '2013-05-14')
432 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
432 CustomValue.create!(:custom_field => f, :customized => Issue.find(3), :value => '')
433
433
434 query = IssueQuery.new(:project => Project.find(1), :name => '_')
434 query = IssueQuery.new(:project => Project.find(1), :name => '_')
435 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
435 query.add_filter("cf_#{f.id}", '<=', ['2013-05-01'])
436 issue_ids = find_issues_with_query(query).map(&:id)
436 issue_ids = find_issues_with_query(query).map(&:id)
437 assert_include 1, issue_ids
437 assert_include 1, issue_ids
438 assert_not_include 2, issue_ids
438 assert_not_include 2, issue_ids
439 assert_not_include 3, issue_ids
439 assert_not_include 3, issue_ids
440 end
440 end
441
441
442 def test_operator_between
442 def test_operator_between
443 query = IssueQuery.new(:project => Project.find(1), :name => '_')
443 query = IssueQuery.new(:project => Project.find(1), :name => '_')
444 query.add_filter('done_ratio', '><', ['30', '40'])
444 query.add_filter('done_ratio', '><', ['30', '40'])
445 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
445 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
446 find_issues_with_query(query)
446 find_issues_with_query(query)
447 end
447 end
448
448
449 def test_operator_between_on_custom_field
449 def test_operator_between_on_custom_field
450 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
450 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
451 query = IssueQuery.new(:project => Project.find(1), :name => '_')
451 query = IssueQuery.new(:project => Project.find(1), :name => '_')
452 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
452 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
453 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
453 assert_match /CAST.+ BETWEEN 30.0 AND 40.0/, query.statement
454 find_issues_with_query(query)
454 find_issues_with_query(query)
455 end
455 end
456
456
457 def test_date_filter_should_not_accept_non_date_values
457 def test_date_filter_should_not_accept_non_date_values
458 query = IssueQuery.new(:name => '_')
458 query = IssueQuery.new(:name => '_')
459 query.add_filter('created_on', '=', ['a'])
459 query.add_filter('created_on', '=', ['a'])
460
460
461 assert query.has_filter?('created_on')
461 assert query.has_filter?('created_on')
462 assert !query.valid?
462 assert !query.valid?
463 end
463 end
464
464
465 def test_date_filter_should_not_accept_invalid_date_values
465 def test_date_filter_should_not_accept_invalid_date_values
466 query = IssueQuery.new(:name => '_')
466 query = IssueQuery.new(:name => '_')
467 query.add_filter('created_on', '=', ['2011-01-34'])
467 query.add_filter('created_on', '=', ['2011-01-34'])
468
468
469 assert query.has_filter?('created_on')
469 assert query.has_filter?('created_on')
470 assert !query.valid?
470 assert !query.valid?
471 end
471 end
472
472
473 def test_relative_date_filter_should_not_accept_non_integer_values
473 def test_relative_date_filter_should_not_accept_non_integer_values
474 query = IssueQuery.new(:name => '_')
474 query = IssueQuery.new(:name => '_')
475 query.add_filter('created_on', '>t-', ['a'])
475 query.add_filter('created_on', '>t-', ['a'])
476
476
477 assert query.has_filter?('created_on')
477 assert query.has_filter?('created_on')
478 assert !query.valid?
478 assert !query.valid?
479 end
479 end
480
480
481 def test_operator_date_equals
481 def test_operator_date_equals
482 query = IssueQuery.new(:name => '_')
482 query = IssueQuery.new(:name => '_')
483 query.add_filter('due_date', '=', ['2011-07-10'])
483 query.add_filter('due_date', '=', ['2011-07-10'])
484 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/,
484 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/,
485 query.statement
485 query.statement
486 find_issues_with_query(query)
486 find_issues_with_query(query)
487 end
487 end
488
488
489 def test_operator_date_lesser_than
489 def test_operator_date_lesser_than
490 query = IssueQuery.new(:name => '_')
490 query = IssueQuery.new(:name => '_')
491 query.add_filter('due_date', '<=', ['2011-07-10'])
491 query.add_filter('due_date', '<=', ['2011-07-10'])
492 assert_match /issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/, query.statement
492 assert_match /issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?/, query.statement
493 find_issues_with_query(query)
493 find_issues_with_query(query)
494 end
494 end
495
495
496 def test_operator_date_lesser_than_with_timestamp
496 def test_operator_date_lesser_than_with_timestamp
497 query = IssueQuery.new(:name => '_')
497 query = IssueQuery.new(:name => '_')
498 query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
498 query.add_filter('updated_on', '<=', ['2011-07-10T19:13:52'])
499 assert_match /issues\.updated_on <= '#{quoted_date "2011-07-10"} 19:13:52/, query.statement
499 assert_match /issues\.updated_on <= '#{quoted_date "2011-07-10"} 19:13:52/, query.statement
500 find_issues_with_query(query)
500 find_issues_with_query(query)
501 end
501 end
502
502
503 def test_operator_date_greater_than
503 def test_operator_date_greater_than
504 query = IssueQuery.new(:name => '_')
504 query = IssueQuery.new(:name => '_')
505 query.add_filter('due_date', '>=', ['2011-07-10'])
505 query.add_filter('due_date', '>=', ['2011-07-10'])
506 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?'/, query.statement
506 assert_match /issues\.due_date > '#{quoted_date "2011-07-09"} 23:59:59(\.\d+)?'/, query.statement
507 find_issues_with_query(query)
507 find_issues_with_query(query)
508 end
508 end
509
509
510 def test_operator_date_greater_than_with_timestamp
510 def test_operator_date_greater_than_with_timestamp
511 query = IssueQuery.new(:name => '_')
511 query = IssueQuery.new(:name => '_')
512 query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
512 query.add_filter('updated_on', '>=', ['2011-07-10T19:13:52'])
513 assert_match /issues\.updated_on > '#{quoted_date "2011-07-10"} 19:13:51(\.0+)?'/, query.statement
513 assert_match /issues\.updated_on > '#{quoted_date "2011-07-10"} 19:13:51(\.0+)?'/, query.statement
514 find_issues_with_query(query)
514 find_issues_with_query(query)
515 end
515 end
516
516
517 def test_operator_date_between
517 def test_operator_date_between
518 query = IssueQuery.new(:name => '_')
518 query = IssueQuery.new(:name => '_')
519 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
519 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
520 assert_match /issues\.due_date > '#{quoted_date "2011-06-22"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?'/,
520 assert_match /issues\.due_date > '#{quoted_date "2011-06-22"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-07-10"} 23:59:59(\.\d+)?'/,
521 query.statement
521 query.statement
522 find_issues_with_query(query)
522 find_issues_with_query(query)
523 end
523 end
524
524
525 def test_operator_in_more_than
525 def test_operator_in_more_than
526 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
526 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
527 query = IssueQuery.new(:project => Project.find(1), :name => '_')
527 query = IssueQuery.new(:project => Project.find(1), :name => '_')
528 query.add_filter('due_date', '>t+', ['15'])
528 query.add_filter('due_date', '>t+', ['15'])
529 issues = find_issues_with_query(query)
529 issues = find_issues_with_query(query)
530 assert !issues.empty?
530 assert !issues.empty?
531 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
531 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
532 end
532 end
533
533
534 def test_operator_in_less_than
534 def test_operator_in_less_than
535 query = IssueQuery.new(:project => Project.find(1), :name => '_')
535 query = IssueQuery.new(:project => Project.find(1), :name => '_')
536 query.add_filter('due_date', '<t+', ['15'])
536 query.add_filter('due_date', '<t+', ['15'])
537 issues = find_issues_with_query(query)
537 issues = find_issues_with_query(query)
538 assert !issues.empty?
538 assert !issues.empty?
539 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
539 issues.each {|issue| assert(issue.due_date <= (Date.today + 15))}
540 end
540 end
541
541
542 def test_operator_in_the_next_days
542 def test_operator_in_the_next_days
543 query = IssueQuery.new(:project => Project.find(1), :name => '_')
543 query = IssueQuery.new(:project => Project.find(1), :name => '_')
544 query.add_filter('due_date', '><t+', ['15'])
544 query.add_filter('due_date', '><t+', ['15'])
545 issues = find_issues_with_query(query)
545 issues = find_issues_with_query(query)
546 assert !issues.empty?
546 assert !issues.empty?
547 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
547 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
548 end
548 end
549
549
550 def test_operator_less_than_ago
550 def test_operator_less_than_ago
551 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
551 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
552 query = IssueQuery.new(:project => Project.find(1), :name => '_')
552 query = IssueQuery.new(:project => Project.find(1), :name => '_')
553 query.add_filter('due_date', '>t-', ['3'])
553 query.add_filter('due_date', '>t-', ['3'])
554 issues = find_issues_with_query(query)
554 issues = find_issues_with_query(query)
555 assert !issues.empty?
555 assert !issues.empty?
556 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
556 issues.each {|issue| assert(issue.due_date >= (Date.today - 3))}
557 end
557 end
558
558
559 def test_operator_in_the_past_days
559 def test_operator_in_the_past_days
560 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
560 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
561 query = IssueQuery.new(:project => Project.find(1), :name => '_')
561 query = IssueQuery.new(:project => Project.find(1), :name => '_')
562 query.add_filter('due_date', '><t-', ['3'])
562 query.add_filter('due_date', '><t-', ['3'])
563 issues = find_issues_with_query(query)
563 issues = find_issues_with_query(query)
564 assert !issues.empty?
564 assert !issues.empty?
565 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
565 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
566 end
566 end
567
567
568 def test_operator_more_than_ago
568 def test_operator_more_than_ago
569 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
569 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
570 query = IssueQuery.new(:project => Project.find(1), :name => '_')
570 query = IssueQuery.new(:project => Project.find(1), :name => '_')
571 query.add_filter('due_date', '<t-', ['10'])
571 query.add_filter('due_date', '<t-', ['10'])
572 assert query.statement.include?("#{Issue.table_name}.due_date <=")
572 assert query.statement.include?("#{Issue.table_name}.due_date <=")
573 issues = find_issues_with_query(query)
573 issues = find_issues_with_query(query)
574 assert !issues.empty?
574 assert !issues.empty?
575 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
575 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
576 end
576 end
577
577
578 def test_operator_in
578 def test_operator_in
579 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
579 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
580 query = IssueQuery.new(:project => Project.find(1), :name => '_')
580 query = IssueQuery.new(:project => Project.find(1), :name => '_')
581 query.add_filter('due_date', 't+', ['2'])
581 query.add_filter('due_date', 't+', ['2'])
582 issues = find_issues_with_query(query)
582 issues = find_issues_with_query(query)
583 assert !issues.empty?
583 assert !issues.empty?
584 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
584 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
585 end
585 end
586
586
587 def test_operator_ago
587 def test_operator_ago
588 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
588 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
589 query = IssueQuery.new(:project => Project.find(1), :name => '_')
589 query = IssueQuery.new(:project => Project.find(1), :name => '_')
590 query.add_filter('due_date', 't-', ['3'])
590 query.add_filter('due_date', 't-', ['3'])
591 issues = find_issues_with_query(query)
591 issues = find_issues_with_query(query)
592 assert !issues.empty?
592 assert !issues.empty?
593 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
593 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
594 end
594 end
595
595
596 def test_operator_today
596 def test_operator_today
597 query = IssueQuery.new(:project => Project.find(1), :name => '_')
597 query = IssueQuery.new(:project => Project.find(1), :name => '_')
598 query.add_filter('due_date', 't', [''])
598 query.add_filter('due_date', 't', [''])
599 issues = find_issues_with_query(query)
599 issues = find_issues_with_query(query)
600 assert !issues.empty?
600 assert !issues.empty?
601 issues.each {|issue| assert_equal Date.today, issue.due_date}
601 issues.each {|issue| assert_equal Date.today, issue.due_date}
602 end
602 end
603
603
604 def test_operator_date_periods
604 def test_operator_date_periods
605 %w(t ld w lw l2w m lm y).each do |operator|
605 %w(t ld w lw l2w m lm y).each do |operator|
606 query = IssueQuery.new(:name => '_')
606 query = IssueQuery.new(:name => '_')
607 query.add_filter('due_date', operator, [''])
607 query.add_filter('due_date', operator, [''])
608 assert query.valid?
608 assert query.valid?
609 assert query.issues
609 assert query.issues
610 end
610 end
611 end
611 end
612
612
613 def test_operator_datetime_periods
613 def test_operator_datetime_periods
614 %w(t ld w lw l2w m lm y).each do |operator|
614 %w(t ld w lw l2w m lm y).each do |operator|
615 query = IssueQuery.new(:name => '_')
615 query = IssueQuery.new(:name => '_')
616 query.add_filter('created_on', operator, [''])
616 query.add_filter('created_on', operator, [''])
617 assert query.valid?
617 assert query.valid?
618 assert query.issues
618 assert query.issues
619 end
619 end
620 end
620 end
621
621
622 def test_operator_contains
622 def test_operator_contains
623 issue = Issue.generate!(:subject => 'AbCdEfG')
623 issue = Issue.generate!(:subject => 'AbCdEfG')
624
624
625 query = IssueQuery.new(:name => '_')
625 query = IssueQuery.new(:name => '_')
626 query.add_filter('subject', '~', ['cdeF'])
626 query.add_filter('subject', '~', ['cdeF'])
627 result = find_issues_with_query(query)
627 result = find_issues_with_query(query)
628 assert_include issue, result
628 assert_include issue, result
629 result.each {|issue| assert issue.subject.downcase.include?('cdef') }
629 result.each {|issue| assert issue.subject.downcase.include?('cdef') }
630 end
630 end
631
631
632 def test_operator_contains_with_utf8_string
632 def test_operator_contains_with_utf8_string
633 issue = Issue.generate!(:subject => 'Subject contains Kiểm')
633 issue = Issue.generate!(:subject => 'Subject contains Kiểm')
634
634
635 query = IssueQuery.new(:name => '_')
635 query = IssueQuery.new(:name => '_')
636 query.add_filter('subject', '~', ['Kiểm'])
636 query.add_filter('subject', '~', ['Kiểm'])
637 result = find_issues_with_query(query)
637 result = find_issues_with_query(query)
638 assert_include issue, result
638 assert_include issue, result
639 assert_equal 1, result.size
639 assert_equal 1, result.size
640 end
640 end
641
641
642 def test_operator_does_not_contain
642 def test_operator_does_not_contain
643 issue = Issue.generate!(:subject => 'AbCdEfG')
643 issue = Issue.generate!(:subject => 'AbCdEfG')
644
644
645 query = IssueQuery.new(:name => '_')
645 query = IssueQuery.new(:name => '_')
646 query.add_filter('subject', '!~', ['cdeF'])
646 query.add_filter('subject', '!~', ['cdeF'])
647 result = find_issues_with_query(query)
647 result = find_issues_with_query(query)
648 assert_not_include issue, result
648 assert_not_include issue, result
649 end
649 end
650
650
651 def test_range_for_this_week_with_week_starting_on_monday
651 def test_range_for_this_week_with_week_starting_on_monday
652 I18n.locale = :fr
652 I18n.locale = :fr
653 assert_equal '1', I18n.t(:general_first_day_of_week)
653 assert_equal '1', I18n.t(:general_first_day_of_week)
654
654
655 Date.stubs(:today).returns(Date.parse('2011-04-29'))
655 Date.stubs(:today).returns(Date.parse('2011-04-29'))
656
656
657 query = IssueQuery.new(:project => Project.find(1), :name => '_')
657 query = IssueQuery.new(:project => Project.find(1), :name => '_')
658 query.add_filter('due_date', 'w', [''])
658 query.add_filter('due_date', 'w', [''])
659 assert_match /issues\.due_date > '#{quoted_date "2011-04-24"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-01"} 23:59:59(\.\d+)?/,
659 assert_match /issues\.due_date > '#{quoted_date "2011-04-24"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-05-01"} 23:59:59(\.\d+)?/,
660 query.statement
660 query.statement
661 I18n.locale = :en
661 I18n.locale = :en
662 end
662 end
663
663
664 def test_range_for_this_week_with_week_starting_on_sunday
664 def test_range_for_this_week_with_week_starting_on_sunday
665 I18n.locale = :en
665 I18n.locale = :en
666 assert_equal '7', I18n.t(:general_first_day_of_week)
666 assert_equal '7', I18n.t(:general_first_day_of_week)
667
667
668 Date.stubs(:today).returns(Date.parse('2011-04-29'))
668 Date.stubs(:today).returns(Date.parse('2011-04-29'))
669
669
670 query = IssueQuery.new(:project => Project.find(1), :name => '_')
670 query = IssueQuery.new(:project => Project.find(1), :name => '_')
671 query.add_filter('due_date', 'w', [''])
671 query.add_filter('due_date', 'w', [''])
672 assert_match /issues\.due_date > '#{quoted_date "2011-04-23"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?/,
672 assert_match /issues\.due_date > '#{quoted_date "2011-04-23"} 23:59:59(\.\d+)?' AND issues\.due_date <= '#{quoted_date "2011-04-30"} 23:59:59(\.\d+)?/,
673 query.statement
673 query.statement
674 end
674 end
675
675
676 def test_filter_assigned_to_me
676 def test_filter_assigned_to_me
677 user = User.find(2)
677 user = User.find(2)
678 group = Group.find(10)
678 group = Group.find(10)
679 User.current = user
679 User.current = user
680 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
680 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
681 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
681 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
682 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
682 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
683 group.users << user
683 group.users << user
684
684
685 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
685 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
686 result = query.issues
686 result = query.issues
687 assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
687 assert_equal Issue.visible.where(:assigned_to_id => ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
688
688
689 assert result.include?(i1)
689 assert result.include?(i1)
690 assert result.include?(i2)
690 assert result.include?(i2)
691 assert !result.include?(i3)
691 assert !result.include?(i3)
692 end
692 end
693
693
694 def test_user_custom_field_filtered_on_me
694 def test_user_custom_field_filtered_on_me
695 User.current = User.find(2)
695 User.current = User.find(2)
696 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
696 cf = IssueCustomField.create!(:field_format => 'user', :is_for_all => true, :is_filter => true, :name => 'User custom field', :tracker_ids => [1])
697 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
697 issue1 = Issue.create!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '2'}, :subject => 'Test', :author_id => 1)
698 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
698 issue2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {cf.id.to_s => '3'})
699
699
700 query = IssueQuery.new(:name => '_', :project => Project.find(1))
700 query = IssueQuery.new(:name => '_', :project => Project.find(1))
701 filter = query.available_filters["cf_#{cf.id}"]
701 filter = query.available_filters["cf_#{cf.id}"]
702 assert_not_nil filter
702 assert_not_nil filter
703 assert_include 'me', filter[:values].map{|v| v[1]}
703 assert_include 'me', filter[:values].map{|v| v[1]}
704
704
705 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
705 query.filters = { "cf_#{cf.id}" => {:operator => '=', :values => ['me']}}
706 result = query.issues
706 result = query.issues
707 assert_equal 1, result.size
707 assert_equal 1, result.size
708 assert_equal issue1, result.first
708 assert_equal issue1, result.first
709 end
709 end
710
710
711 def test_filter_on_me_by_anonymous_user
711 def test_filter_on_me_by_anonymous_user
712 User.current = nil
712 User.current = nil
713 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
713 query = IssueQuery.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
714 assert_equal [], query.issues
714 assert_equal [], query.issues
715 end
715 end
716
716
717 def test_filter_my_projects
717 def test_filter_my_projects
718 User.current = User.find(2)
718 User.current = User.find(2)
719 query = IssueQuery.new(:name => '_')
719 query = IssueQuery.new(:name => '_')
720 filter = query.available_filters['project_id']
720 filter = query.available_filters['project_id']
721 assert_not_nil filter
721 assert_not_nil filter
722 assert_include 'mine', filter[:values].map{|v| v[1]}
722 assert_include 'mine', filter[:values].map{|v| v[1]}
723
723
724 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
724 query.filters = { 'project_id' => {:operator => '=', :values => ['mine']}}
725 result = query.issues
725 result = query.issues
726 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
726 assert_nil result.detect {|issue| !User.current.member_of?(issue.project)}
727 end
727 end
728
728
729 def test_filter_watched_issues
729 def test_filter_watched_issues
730 User.current = User.find(1)
730 User.current = User.find(1)
731 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
731 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
732 result = find_issues_with_query(query)
732 result = find_issues_with_query(query)
733 assert_not_nil result
733 assert_not_nil result
734 assert !result.empty?
734 assert !result.empty?
735 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
735 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
736 User.current = nil
736 User.current = nil
737 end
737 end
738
738
739 def test_filter_unwatched_issues
739 def test_filter_unwatched_issues
740 User.current = User.find(1)
740 User.current = User.find(1)
741 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
741 query = IssueQuery.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
742 result = find_issues_with_query(query)
742 result = find_issues_with_query(query)
743 assert_not_nil result
743 assert_not_nil result
744 assert !result.empty?
744 assert !result.empty?
745 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
745 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
746 User.current = nil
746 User.current = nil
747 end
747 end
748
748
749 def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
749 def test_filter_on_custom_field_should_ignore_projects_with_field_disabled
750 field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
750 field = IssueCustomField.generate!(:trackers => Tracker.all, :project_ids => [1, 3, 4], :is_for_all => false, :is_filter => true)
751 Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
751 Issue.generate!(:project_id => 3, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
752 Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
752 Issue.generate!(:project_id => 4, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
753
753
754 query = IssueQuery.new(:name => '_', :project => Project.find(1))
754 query = IssueQuery.new(:name => '_', :project => Project.find(1))
755 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
755 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
756 assert_equal 2, find_issues_with_query(query).size
756 assert_equal 2, find_issues_with_query(query).size
757
757
758 field.project_ids = [1, 3] # Disable the field for project 4
758 field.project_ids = [1, 3] # Disable the field for project 4
759 field.save!
759 field.save!
760 assert_equal 1, find_issues_with_query(query).size
760 assert_equal 1, find_issues_with_query(query).size
761 end
761 end
762
762
763 def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
763 def test_filter_on_custom_field_should_ignore_trackers_with_field_disabled
764 field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
764 field = IssueCustomField.generate!(:tracker_ids => [1, 2], :is_for_all => true, :is_filter => true)
765 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
765 Issue.generate!(:project_id => 1, :tracker_id => 1, :custom_field_values => {field.id.to_s => 'Foo'})
766 Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
766 Issue.generate!(:project_id => 1, :tracker_id => 2, :custom_field_values => {field.id.to_s => 'Foo'})
767
767
768 query = IssueQuery.new(:name => '_', :project => Project.find(1))
768 query = IssueQuery.new(:name => '_', :project => Project.find(1))
769 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
769 query.filters = {"cf_#{field.id}" => {:operator => '=', :values => ['Foo']}}
770 assert_equal 2, find_issues_with_query(query).size
770 assert_equal 2, find_issues_with_query(query).size
771
771
772 field.tracker_ids = [1] # Disable the field for tracker 2
772 field.tracker_ids = [1] # Disable the field for tracker 2
773 field.save!
773 field.save!
774 assert_equal 1, find_issues_with_query(query).size
774 assert_equal 1, find_issues_with_query(query).size
775 end
775 end
776
776
777 def test_filter_on_project_custom_field
777 def test_filter_on_project_custom_field
778 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
778 field = ProjectCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
779 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
779 CustomValue.create!(:custom_field => field, :customized => Project.find(3), :value => 'Foo')
780 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
780 CustomValue.create!(:custom_field => field, :customized => Project.find(5), :value => 'Foo')
781
781
782 query = IssueQuery.new(:name => '_')
782 query = IssueQuery.new(:name => '_')
783 filter_name = "project.cf_#{field.id}"
783 filter_name = "project.cf_#{field.id}"
784 assert_include filter_name, query.available_filters.keys
784 assert_include filter_name, query.available_filters.keys
785 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
785 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
786 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
786 assert_equal [3, 5], find_issues_with_query(query).map(&:project_id).uniq.sort
787 end
787 end
788
788
789 def test_filter_on_author_custom_field
789 def test_filter_on_author_custom_field
790 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
790 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
791 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
791 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
792
792
793 query = IssueQuery.new(:name => '_')
793 query = IssueQuery.new(:name => '_')
794 filter_name = "author.cf_#{field.id}"
794 filter_name = "author.cf_#{field.id}"
795 assert_include filter_name, query.available_filters.keys
795 assert_include filter_name, query.available_filters.keys
796 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
796 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
797 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
797 assert_equal [3], find_issues_with_query(query).map(&:author_id).uniq.sort
798 end
798 end
799
799
800 def test_filter_on_assigned_to_custom_field
800 def test_filter_on_assigned_to_custom_field
801 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
801 field = UserCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
802 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
802 CustomValue.create!(:custom_field => field, :customized => User.find(3), :value => 'Foo')
803
803
804 query = IssueQuery.new(:name => '_')
804 query = IssueQuery.new(:name => '_')
805 filter_name = "assigned_to.cf_#{field.id}"
805 filter_name = "assigned_to.cf_#{field.id}"
806 assert_include filter_name, query.available_filters.keys
806 assert_include filter_name, query.available_filters.keys
807 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
807 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
808 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
808 assert_equal [3], find_issues_with_query(query).map(&:assigned_to_id).uniq.sort
809 end
809 end
810
810
811 def test_filter_on_fixed_version_custom_field
811 def test_filter_on_fixed_version_custom_field
812 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
812 field = VersionCustomField.create!(:name => 'Client', :is_filter => true, :field_format => 'string')
813 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
813 CustomValue.create!(:custom_field => field, :customized => Version.find(2), :value => 'Foo')
814
814
815 query = IssueQuery.new(:name => '_')
815 query = IssueQuery.new(:name => '_')
816 filter_name = "fixed_version.cf_#{field.id}"
816 filter_name = "fixed_version.cf_#{field.id}"
817 assert_include filter_name, query.available_filters.keys
817 assert_include filter_name, query.available_filters.keys
818 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
818 query.filters = {filter_name => {:operator => '=', :values => ['Foo']}}
819 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
819 assert_equal [2], find_issues_with_query(query).map(&:fixed_version_id).uniq.sort
820 end
820 end
821
821
822 def test_filter_on_fixed_version_due_date
823 query = IssueQuery.new(:name => '_')
824 filter_name = "fixed_version.due_date"
825 assert_include filter_name, query.available_filters.keys
826 query.filters = {filter_name => {:operator => '=', :values => [20.day.from_now.to_date.to_s(:db)]}}
827 issues = find_issues_with_query(query)
828 assert_equal [2], issues.map(&:fixed_version_id).uniq.sort
829 assert_equal [2, 12], issues.map(&:id).sort
830
831 query = IssueQuery.new(:name => '_')
832 query.filters = {filter_name => {:operator => '>=', :values => [21.day.from_now.to_date.to_s(:db)]}}
833 assert_equal 0, find_issues_with_query(query).size
834 end
835
836 def test_filter_on_fixed_version_status
837 query = IssueQuery.new(:name => '_')
838 filter_name = "fixed_version.status"
839 assert_include filter_name, query.available_filters.keys
840 query.filters = {filter_name => {:operator => '=', :values => ['closed']}}
841 issues = find_issues_with_query(query)
842
843 assert_equal [1], issues.map(&:fixed_version_id).sort
844 assert_equal [11], issues.map(&:id).sort
845
846 # "is not" operator should include issues without target version
847 query = IssueQuery.new(:name => '_')
848 query.filters = {filter_name => {:operator => '!', :values => ['open', 'closed', 'locked']}, "project_id" => {:operator => '=', :values => [1]}}
849 assert_equal [1, 3, 7, 8], find_issues_with_query(query).map(&:id).uniq.sort
850 end
851
822 def test_filter_on_relations_with_a_specific_issue
852 def test_filter_on_relations_with_a_specific_issue
823 IssueRelation.delete_all
853 IssueRelation.delete_all
824 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
854 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
825 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
855 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
826
856
827 query = IssueQuery.new(:name => '_')
857 query = IssueQuery.new(:name => '_')
828 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
858 query.filters = {"relates" => {:operator => '=', :values => ['1']}}
829 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
859 assert_equal [2, 3], find_issues_with_query(query).map(&:id).sort
830
860
831 query = IssueQuery.new(:name => '_')
861 query = IssueQuery.new(:name => '_')
832 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
862 query.filters = {"relates" => {:operator => '=', :values => ['2']}}
833 assert_equal [1], find_issues_with_query(query).map(&:id).sort
863 assert_equal [1], find_issues_with_query(query).map(&:id).sort
834 end
864 end
835
865
836 def test_filter_on_relations_with_any_issues_in_a_project
866 def test_filter_on_relations_with_any_issues_in_a_project
837 IssueRelation.delete_all
867 IssueRelation.delete_all
838 with_settings :cross_project_issue_relations => '1' do
868 with_settings :cross_project_issue_relations => '1' do
839 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
869 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
840 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
870 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(2).issues.first)
841 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
871 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
842 end
872 end
843
873
844 query = IssueQuery.new(:name => '_')
874 query = IssueQuery.new(:name => '_')
845 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
875 query.filters = {"relates" => {:operator => '=p', :values => ['2']}}
846 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
876 assert_equal [1, 2], find_issues_with_query(query).map(&:id).sort
847
877
848 query = IssueQuery.new(:name => '_')
878 query = IssueQuery.new(:name => '_')
849 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
879 query.filters = {"relates" => {:operator => '=p', :values => ['3']}}
850 assert_equal [1], find_issues_with_query(query).map(&:id).sort
880 assert_equal [1], find_issues_with_query(query).map(&:id).sort
851
881
852 query = IssueQuery.new(:name => '_')
882 query = IssueQuery.new(:name => '_')
853 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
883 query.filters = {"relates" => {:operator => '=p', :values => ['4']}}
854 assert_equal [], find_issues_with_query(query).map(&:id).sort
884 assert_equal [], find_issues_with_query(query).map(&:id).sort
855 end
885 end
856
886
857 def test_filter_on_relations_with_any_issues_not_in_a_project
887 def test_filter_on_relations_with_any_issues_not_in_a_project
858 IssueRelation.delete_all
888 IssueRelation.delete_all
859 with_settings :cross_project_issue_relations => '1' do
889 with_settings :cross_project_issue_relations => '1' do
860 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
890 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
861 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
891 #IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(1).issues.first)
862 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
892 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(3).issues.first)
863 end
893 end
864
894
865 query = IssueQuery.new(:name => '_')
895 query = IssueQuery.new(:name => '_')
866 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
896 query.filters = {"relates" => {:operator => '=!p', :values => ['1']}}
867 assert_equal [1], find_issues_with_query(query).map(&:id).sort
897 assert_equal [1], find_issues_with_query(query).map(&:id).sort
868 end
898 end
869
899
870 def test_filter_on_relations_with_no_issues_in_a_project
900 def test_filter_on_relations_with_no_issues_in_a_project
871 IssueRelation.delete_all
901 IssueRelation.delete_all
872 with_settings :cross_project_issue_relations => '1' do
902 with_settings :cross_project_issue_relations => '1' do
873 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
903 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Project.find(2).issues.first)
874 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
904 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(2), :issue_to => Project.find(3).issues.first)
875 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
905 IssueRelation.create!(:relation_type => "relates", :issue_to => Project.find(2).issues.first, :issue_from => Issue.find(3))
876 end
906 end
877
907
878 query = IssueQuery.new(:name => '_')
908 query = IssueQuery.new(:name => '_')
879 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
909 query.filters = {"relates" => {:operator => '!p', :values => ['2']}}
880 ids = find_issues_with_query(query).map(&:id).sort
910 ids = find_issues_with_query(query).map(&:id).sort
881 assert_include 2, ids
911 assert_include 2, ids
882 assert_not_include 1, ids
912 assert_not_include 1, ids
883 assert_not_include 3, ids
913 assert_not_include 3, ids
884 end
914 end
885
915
886 def test_filter_on_relations_with_any_open_issues
916 def test_filter_on_relations_with_any_open_issues
887 IssueRelation.delete_all
917 IssueRelation.delete_all
888 # Issue 1 is blocked by 8, which is closed
918 # Issue 1 is blocked by 8, which is closed
889 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
919 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
890 # Issue 2 is blocked by 3, which is open
920 # Issue 2 is blocked by 3, which is open
891 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
921 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
892
922
893 query = IssueQuery.new(:name => '_')
923 query = IssueQuery.new(:name => '_')
894 query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
924 query.filters = {"blocked" => {:operator => "*o", :values => ['']}}
895 ids = find_issues_with_query(query).map(&:id)
925 ids = find_issues_with_query(query).map(&:id)
896 assert_equal [], ids & [1]
926 assert_equal [], ids & [1]
897 assert_include 2, ids
927 assert_include 2, ids
898 end
928 end
899
929
900 def test_filter_on_relations_with_no_open_issues
930 def test_filter_on_relations_with_no_open_issues
901 IssueRelation.delete_all
931 IssueRelation.delete_all
902 # Issue 1 is blocked by 8, which is closed
932 # Issue 1 is blocked by 8, which is closed
903 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
933 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(1), :issue_to => Issue.find(8))
904 # Issue 2 is blocked by 3, which is open
934 # Issue 2 is blocked by 3, which is open
905 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
935 IssueRelation.create!(:relation_type => "blocked", :issue_from => Issue.find(2), :issue_to => Issue.find(3))
906
936
907 query = IssueQuery.new(:name => '_')
937 query = IssueQuery.new(:name => '_')
908 query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
938 query.filters = {"blocked" => {:operator => "!o", :values => ['']}}
909 ids = find_issues_with_query(query).map(&:id)
939 ids = find_issues_with_query(query).map(&:id)
910 assert_equal [], ids & [2]
940 assert_equal [], ids & [2]
911 assert_include 1, ids
941 assert_include 1, ids
912 end
942 end
913
943
914 def test_filter_on_relations_with_no_issues
944 def test_filter_on_relations_with_no_issues
915 IssueRelation.delete_all
945 IssueRelation.delete_all
916 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
946 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
917 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
947 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
918
948
919 query = IssueQuery.new(:name => '_')
949 query = IssueQuery.new(:name => '_')
920 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
950 query.filters = {"relates" => {:operator => '!*', :values => ['']}}
921 ids = find_issues_with_query(query).map(&:id)
951 ids = find_issues_with_query(query).map(&:id)
922 assert_equal [], ids & [1, 2, 3]
952 assert_equal [], ids & [1, 2, 3]
923 assert_include 4, ids
953 assert_include 4, ids
924 end
954 end
925
955
926 def test_filter_on_relations_with_any_issues
956 def test_filter_on_relations_with_any_issues
927 IssueRelation.delete_all
957 IssueRelation.delete_all
928 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
958 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(1), :issue_to => Issue.find(2))
929 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
959 IssueRelation.create!(:relation_type => "relates", :issue_from => Issue.find(3), :issue_to => Issue.find(1))
930
960
931 query = IssueQuery.new(:name => '_')
961 query = IssueQuery.new(:name => '_')
932 query.filters = {"relates" => {:operator => '*', :values => ['']}}
962 query.filters = {"relates" => {:operator => '*', :values => ['']}}
933 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
963 assert_equal [1, 2, 3], find_issues_with_query(query).map(&:id).sort
934 end
964 end
935
965
936 def test_filter_on_relations_should_not_ignore_other_filter
966 def test_filter_on_relations_should_not_ignore_other_filter
937 issue = Issue.generate!
967 issue = Issue.generate!
938 issue1 = Issue.generate!(:status_id => 1)
968 issue1 = Issue.generate!(:status_id => 1)
939 issue2 = Issue.generate!(:status_id => 2)
969 issue2 = Issue.generate!(:status_id => 2)
940 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
970 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue1)
941 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
971 IssueRelation.create!(:relation_type => "relates", :issue_from => issue, :issue_to => issue2)
942
972
943 query = IssueQuery.new(:name => '_')
973 query = IssueQuery.new(:name => '_')
944 query.filters = {
974 query.filters = {
945 "status_id" => {:operator => '=', :values => ['1']},
975 "status_id" => {:operator => '=', :values => ['1']},
946 "relates" => {:operator => '=', :values => [issue.id.to_s]}
976 "relates" => {:operator => '=', :values => [issue.id.to_s]}
947 }
977 }
948 assert_equal [issue1], find_issues_with_query(query)
978 assert_equal [issue1], find_issues_with_query(query)
949 end
979 end
950
980
951 def test_filter_on_parent
981 def test_filter_on_parent
952 Issue.delete_all
982 Issue.delete_all
953 parent = Issue.generate_with_descendants!
983 parent = Issue.generate_with_descendants!
954
984
955
985
956 query = IssueQuery.new(:name => '_')
986 query = IssueQuery.new(:name => '_')
957 query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
987 query.filters = {"parent_id" => {:operator => '=', :values => [parent.id.to_s]}}
958 assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
988 assert_equal parent.children.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
959
989
960 query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
990 query.filters = {"parent_id" => {:operator => '~', :values => [parent.id.to_s]}}
961 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
991 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
962
992
963 query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
993 query.filters = {"parent_id" => {:operator => '*', :values => ['']}}
964 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
994 assert_equal parent.descendants.map(&:id).sort, find_issues_with_query(query).map(&:id).sort
965
995
966 query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
996 query.filters = {"parent_id" => {:operator => '!*', :values => ['']}}
967 assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
997 assert_equal [parent.id], find_issues_with_query(query).map(&:id).sort
968 end
998 end
969
999
970 def test_filter_on_invalid_parent_should_return_no_results
1000 def test_filter_on_invalid_parent_should_return_no_results
971 query = IssueQuery.new(:name => '_')
1001 query = IssueQuery.new(:name => '_')
972 query.filters = {"parent_id" => {:operator => '=', :values => '99999999999'}}
1002 query.filters = {"parent_id" => {:operator => '=', :values => '99999999999'}}
973 assert_equal [], find_issues_with_query(query).map(&:id).sort
1003 assert_equal [], find_issues_with_query(query).map(&:id).sort
974
1004
975 query.filters = {"parent_id" => {:operator => '~', :values => '99999999999'}}
1005 query.filters = {"parent_id" => {:operator => '~', :values => '99999999999'}}
976 assert_equal [], find_issues_with_query(query)
1006 assert_equal [], find_issues_with_query(query)
977 end
1007 end
978
1008
979 def test_filter_on_child
1009 def test_filter_on_child
980 Issue.delete_all
1010 Issue.delete_all
981 parent = Issue.generate_with_descendants!
1011 parent = Issue.generate_with_descendants!
982 child, leaf = parent.children.sort_by(&:id)
1012 child, leaf = parent.children.sort_by(&:id)
983 grandchild = child.children.first
1013 grandchild = child.children.first
984
1014
985
1015
986 query = IssueQuery.new(:name => '_')
1016 query = IssueQuery.new(:name => '_')
987 query.filters = {"child_id" => {:operator => '=', :values => [grandchild.id.to_s]}}
1017 query.filters = {"child_id" => {:operator => '=', :values => [grandchild.id.to_s]}}
988 assert_equal [child.id], find_issues_with_query(query).map(&:id).sort
1018 assert_equal [child.id], find_issues_with_query(query).map(&:id).sort
989
1019
990 query.filters = {"child_id" => {:operator => '~', :values => [grandchild.id.to_s]}}
1020 query.filters = {"child_id" => {:operator => '~', :values => [grandchild.id.to_s]}}
991 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
1021 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
992
1022
993 query.filters = {"child_id" => {:operator => '*', :values => ['']}}
1023 query.filters = {"child_id" => {:operator => '*', :values => ['']}}
994 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
1024 assert_equal [parent, child].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
995
1025
996 query.filters = {"child_id" => {:operator => '!*', :values => ['']}}
1026 query.filters = {"child_id" => {:operator => '!*', :values => ['']}}
997 assert_equal [grandchild, leaf].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
1027 assert_equal [grandchild, leaf].map(&:id).sort, find_issues_with_query(query).map(&:id).sort
998 end
1028 end
999
1029
1000 def test_filter_on_invalid_child_should_return_no_results
1030 def test_filter_on_invalid_child_should_return_no_results
1001 query = IssueQuery.new(:name => '_')
1031 query = IssueQuery.new(:name => '_')
1002 query.filters = {"child_id" => {:operator => '=', :values => '99999999999'}}
1032 query.filters = {"child_id" => {:operator => '=', :values => '99999999999'}}
1003 assert_equal [], find_issues_with_query(query)
1033 assert_equal [], find_issues_with_query(query)
1004
1034
1005 query.filters = {"child_id" => {:operator => '~', :values => '99999999999'}}
1035 query.filters = {"child_id" => {:operator => '~', :values => '99999999999'}}
1006 assert_equal [].map(&:id).sort, find_issues_with_query(query)
1036 assert_equal [].map(&:id).sort, find_issues_with_query(query)
1007 end
1037 end
1008
1038
1009 def test_statement_should_be_nil_with_no_filters
1039 def test_statement_should_be_nil_with_no_filters
1010 q = IssueQuery.new(:name => '_')
1040 q = IssueQuery.new(:name => '_')
1011 q.filters = {}
1041 q.filters = {}
1012
1042
1013 assert q.valid?
1043 assert q.valid?
1014 assert_nil q.statement
1044 assert_nil q.statement
1015 end
1045 end
1016
1046
1017 def test_available_filters_as_json_should_include_missing_assigned_to_id_values
1047 def test_available_filters_as_json_should_include_missing_assigned_to_id_values
1018 user = User.generate!
1048 user = User.generate!
1019 with_current_user User.find(1) do
1049 with_current_user User.find(1) do
1020 q = IssueQuery.new
1050 q = IssueQuery.new
1021 q.filters = {"assigned_to_id" => {:operator => '=', :values => user.id.to_s}}
1051 q.filters = {"assigned_to_id" => {:operator => '=', :values => user.id.to_s}}
1022
1052
1023 filters = q.available_filters_as_json
1053 filters = q.available_filters_as_json
1024 assert_include [user.name, user.id.to_s], filters['assigned_to_id']['values']
1054 assert_include [user.name, user.id.to_s], filters['assigned_to_id']['values']
1025 end
1055 end
1026 end
1056 end
1027
1057
1028 def test_available_filters_as_json_should_include_missing_author_id_values
1058 def test_available_filters_as_json_should_include_missing_author_id_values
1029 user = User.generate!
1059 user = User.generate!
1030 with_current_user User.find(1) do
1060 with_current_user User.find(1) do
1031 q = IssueQuery.new
1061 q = IssueQuery.new
1032 q.filters = {"author_id" => {:operator => '=', :values => user.id.to_s}}
1062 q.filters = {"author_id" => {:operator => '=', :values => user.id.to_s}}
1033
1063
1034 filters = q.available_filters_as_json
1064 filters = q.available_filters_as_json
1035 assert_include [user.name, user.id.to_s], filters['author_id']['values']
1065 assert_include [user.name, user.id.to_s], filters['author_id']['values']
1036 end
1066 end
1037 end
1067 end
1038
1068
1039 def test_default_columns
1069 def test_default_columns
1040 q = IssueQuery.new
1070 q = IssueQuery.new
1041 assert q.columns.any?
1071 assert q.columns.any?
1042 assert q.inline_columns.any?
1072 assert q.inline_columns.any?
1043 assert q.block_columns.empty?
1073 assert q.block_columns.empty?
1044 end
1074 end
1045
1075
1046 def test_set_column_names
1076 def test_set_column_names
1047 q = IssueQuery.new
1077 q = IssueQuery.new
1048 q.column_names = ['tracker', :subject, '', 'unknonw_column']
1078 q.column_names = ['tracker', :subject, '', 'unknonw_column']
1049 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
1079 assert_equal [:id, :tracker, :subject], q.columns.collect {|c| c.name}
1050 end
1080 end
1051
1081
1052 def test_has_column_should_accept_a_column_name
1082 def test_has_column_should_accept_a_column_name
1053 q = IssueQuery.new
1083 q = IssueQuery.new
1054 q.column_names = ['tracker', :subject]
1084 q.column_names = ['tracker', :subject]
1055 assert q.has_column?(:tracker)
1085 assert q.has_column?(:tracker)
1056 assert !q.has_column?(:category)
1086 assert !q.has_column?(:category)
1057 end
1087 end
1058
1088
1059 def test_has_column_should_accept_a_column
1089 def test_has_column_should_accept_a_column
1060 q = IssueQuery.new
1090 q = IssueQuery.new
1061 q.column_names = ['tracker', :subject]
1091 q.column_names = ['tracker', :subject]
1062
1092
1063 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
1093 tracker_column = q.available_columns.detect {|c| c.name==:tracker}
1064 assert_kind_of QueryColumn, tracker_column
1094 assert_kind_of QueryColumn, tracker_column
1065 category_column = q.available_columns.detect {|c| c.name==:category}
1095 category_column = q.available_columns.detect {|c| c.name==:category}
1066 assert_kind_of QueryColumn, category_column
1096 assert_kind_of QueryColumn, category_column
1067
1097
1068 assert q.has_column?(tracker_column)
1098 assert q.has_column?(tracker_column)
1069 assert !q.has_column?(category_column)
1099 assert !q.has_column?(category_column)
1070 end
1100 end
1071
1101
1072 def test_inline_and_block_columns
1102 def test_inline_and_block_columns
1073 q = IssueQuery.new
1103 q = IssueQuery.new
1074 q.column_names = ['subject', 'description', 'tracker']
1104 q.column_names = ['subject', 'description', 'tracker']
1075
1105
1076 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
1106 assert_equal [:id, :subject, :tracker], q.inline_columns.map(&:name)
1077 assert_equal [:description], q.block_columns.map(&:name)
1107 assert_equal [:description], q.block_columns.map(&:name)
1078 end
1108 end
1079
1109
1080 def test_custom_field_columns_should_be_inline
1110 def test_custom_field_columns_should_be_inline
1081 q = IssueQuery.new
1111 q = IssueQuery.new
1082 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
1112 columns = q.available_columns.select {|column| column.is_a? QueryCustomFieldColumn}
1083 assert columns.any?
1113 assert columns.any?
1084 assert_nil columns.detect {|column| !column.inline?}
1114 assert_nil columns.detect {|column| !column.inline?}
1085 end
1115 end
1086
1116
1087 def test_query_should_preload_spent_hours
1117 def test_query_should_preload_spent_hours
1088 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
1118 q = IssueQuery.new(:name => '_', :column_names => [:subject, :spent_hours])
1089 assert q.has_column?(:spent_hours)
1119 assert q.has_column?(:spent_hours)
1090 issues = q.issues
1120 issues = q.issues
1091 assert_not_nil issues.first.instance_variable_get("@spent_hours")
1121 assert_not_nil issues.first.instance_variable_get("@spent_hours")
1092 end
1122 end
1093
1123
1094 def test_groupable_columns_should_include_custom_fields
1124 def test_groupable_columns_should_include_custom_fields
1095 q = IssueQuery.new
1125 q = IssueQuery.new
1096 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1126 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1097 assert_not_nil column
1127 assert_not_nil column
1098 assert_kind_of QueryCustomFieldColumn, column
1128 assert_kind_of QueryCustomFieldColumn, column
1099 end
1129 end
1100
1130
1101 def test_groupable_columns_should_not_include_multi_custom_fields
1131 def test_groupable_columns_should_not_include_multi_custom_fields
1102 field = CustomField.find(1)
1132 field = CustomField.find(1)
1103 field.update_attribute :multiple, true
1133 field.update_attribute :multiple, true
1104
1134
1105 q = IssueQuery.new
1135 q = IssueQuery.new
1106 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1136 column = q.groupable_columns.detect {|c| c.name == :cf_1}
1107 assert_nil column
1137 assert_nil column
1108 end
1138 end
1109
1139
1110 def test_groupable_columns_should_include_user_custom_fields
1140 def test_groupable_columns_should_include_user_custom_fields
1111 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
1141 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'user')
1112
1142
1113 q = IssueQuery.new
1143 q = IssueQuery.new
1114 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1144 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1115 end
1145 end
1116
1146
1117 def test_groupable_columns_should_include_version_custom_fields
1147 def test_groupable_columns_should_include_version_custom_fields
1118 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
1148 cf = IssueCustomField.create!(:name => 'User', :is_for_all => true, :tracker_ids => [1], :field_format => 'version')
1119
1149
1120 q = IssueQuery.new
1150 q = IssueQuery.new
1121 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1151 assert q.groupable_columns.detect {|c| c.name == "cf_#{cf.id}".to_sym}
1122 end
1152 end
1123
1153
1124 def test_grouped_with_valid_column
1154 def test_grouped_with_valid_column
1125 q = IssueQuery.new(:group_by => 'status')
1155 q = IssueQuery.new(:group_by => 'status')
1126 assert q.grouped?
1156 assert q.grouped?
1127 assert_not_nil q.group_by_column
1157 assert_not_nil q.group_by_column
1128 assert_equal :status, q.group_by_column.name
1158 assert_equal :status, q.group_by_column.name
1129 assert_not_nil q.group_by_statement
1159 assert_not_nil q.group_by_statement
1130 assert_equal 'status', q.group_by_statement
1160 assert_equal 'status', q.group_by_statement
1131 end
1161 end
1132
1162
1133 def test_grouped_with_invalid_column
1163 def test_grouped_with_invalid_column
1134 q = IssueQuery.new(:group_by => 'foo')
1164 q = IssueQuery.new(:group_by => 'foo')
1135 assert !q.grouped?
1165 assert !q.grouped?
1136 assert_nil q.group_by_column
1166 assert_nil q.group_by_column
1137 assert_nil q.group_by_statement
1167 assert_nil q.group_by_statement
1138 end
1168 end
1139
1169
1140 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
1170 def test_sortable_columns_should_sort_assignees_according_to_user_format_setting
1141 with_settings :user_format => 'lastname_comma_firstname' do
1171 with_settings :user_format => 'lastname_comma_firstname' do
1142 q = IssueQuery.new
1172 q = IssueQuery.new
1143 assert q.sortable_columns.has_key?('assigned_to')
1173 assert q.sortable_columns.has_key?('assigned_to')
1144 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
1174 assert_equal %w(users.lastname users.firstname users.id), q.sortable_columns['assigned_to']
1145 end
1175 end
1146 end
1176 end
1147
1177
1148 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
1178 def test_sortable_columns_should_sort_authors_according_to_user_format_setting
1149 with_settings :user_format => 'lastname_comma_firstname' do
1179 with_settings :user_format => 'lastname_comma_firstname' do
1150 q = IssueQuery.new
1180 q = IssueQuery.new
1151 assert q.sortable_columns.has_key?('author')
1181 assert q.sortable_columns.has_key?('author')
1152 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
1182 assert_equal %w(authors.lastname authors.firstname authors.id), q.sortable_columns['author']
1153 end
1183 end
1154 end
1184 end
1155
1185
1156 def test_sortable_columns_should_include_custom_field
1186 def test_sortable_columns_should_include_custom_field
1157 q = IssueQuery.new
1187 q = IssueQuery.new
1158 assert q.sortable_columns['cf_1']
1188 assert q.sortable_columns['cf_1']
1159 end
1189 end
1160
1190
1161 def test_sortable_columns_should_not_include_multi_custom_field
1191 def test_sortable_columns_should_not_include_multi_custom_field
1162 field = CustomField.find(1)
1192 field = CustomField.find(1)
1163 field.update_attribute :multiple, true
1193 field.update_attribute :multiple, true
1164
1194
1165 q = IssueQuery.new
1195 q = IssueQuery.new
1166 assert !q.sortable_columns['cf_1']
1196 assert !q.sortable_columns['cf_1']
1167 end
1197 end
1168
1198
1169 def test_default_sort
1199 def test_default_sort
1170 q = IssueQuery.new
1200 q = IssueQuery.new
1171 assert_equal [], q.sort_criteria
1201 assert_equal [], q.sort_criteria
1172 end
1202 end
1173
1203
1174 def test_set_sort_criteria_with_hash
1204 def test_set_sort_criteria_with_hash
1175 q = IssueQuery.new
1205 q = IssueQuery.new
1176 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
1206 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
1177 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1207 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1178 end
1208 end
1179
1209
1180 def test_set_sort_criteria_with_array
1210 def test_set_sort_criteria_with_array
1181 q = IssueQuery.new
1211 q = IssueQuery.new
1182 q.sort_criteria = [['priority', 'desc'], 'tracker']
1212 q.sort_criteria = [['priority', 'desc'], 'tracker']
1183 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1213 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1184 end
1214 end
1185
1215
1186 def test_create_query_with_sort
1216 def test_create_query_with_sort
1187 q = IssueQuery.new(:name => 'Sorted')
1217 q = IssueQuery.new(:name => 'Sorted')
1188 q.sort_criteria = [['priority', 'desc'], 'tracker']
1218 q.sort_criteria = [['priority', 'desc'], 'tracker']
1189 assert q.save
1219 assert q.save
1190 q.reload
1220 q.reload
1191 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1221 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
1192 end
1222 end
1193
1223
1194 def test_sort_by_string_custom_field_asc
1224 def test_sort_by_string_custom_field_asc
1195 q = IssueQuery.new
1225 q = IssueQuery.new
1196 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1226 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1197 assert c
1227 assert c
1198 assert c.sortable
1228 assert c.sortable
1199 issues = q.issues(:order => "#{c.sortable} ASC")
1229 issues = q.issues(:order => "#{c.sortable} ASC")
1200 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1230 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1201 assert !values.empty?
1231 assert !values.empty?
1202 assert_equal values.sort, values
1232 assert_equal values.sort, values
1203 end
1233 end
1204
1234
1205 def test_sort_by_string_custom_field_desc
1235 def test_sort_by_string_custom_field_desc
1206 q = IssueQuery.new
1236 q = IssueQuery.new
1207 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1237 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
1208 assert c
1238 assert c
1209 assert c.sortable
1239 assert c.sortable
1210 issues = q.issues(:order => "#{c.sortable} DESC")
1240 issues = q.issues(:order => "#{c.sortable} DESC")
1211 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1241 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
1212 assert !values.empty?
1242 assert !values.empty?
1213 assert_equal values.sort.reverse, values
1243 assert_equal values.sort.reverse, values
1214 end
1244 end
1215
1245
1216 def test_sort_by_float_custom_field_asc
1246 def test_sort_by_float_custom_field_asc
1217 q = IssueQuery.new
1247 q = IssueQuery.new
1218 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
1248 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
1219 assert c
1249 assert c
1220 assert c.sortable
1250 assert c.sortable
1221 issues = q.issues(:order => "#{c.sortable} ASC")
1251 issues = q.issues(:order => "#{c.sortable} ASC")
1222 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
1252 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
1223 assert !values.empty?
1253 assert !values.empty?
1224 assert_equal values.sort, values
1254 assert_equal values.sort, values
1225 end
1255 end
1226
1256
1227 def test_set_totalable_names
1257 def test_set_totalable_names
1228 q = IssueQuery.new
1258 q = IssueQuery.new
1229 q.totalable_names = ['estimated_hours', :spent_hours, '']
1259 q.totalable_names = ['estimated_hours', :spent_hours, '']
1230 assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
1260 assert_equal [:estimated_hours, :spent_hours], q.totalable_columns.map(&:name)
1231 end
1261 end
1232
1262
1233 def test_totalable_columns_should_default_to_settings
1263 def test_totalable_columns_should_default_to_settings
1234 with_settings :issue_list_default_totals => ['estimated_hours'] do
1264 with_settings :issue_list_default_totals => ['estimated_hours'] do
1235 q = IssueQuery.new
1265 q = IssueQuery.new
1236 assert_equal [:estimated_hours], q.totalable_columns.map(&:name)
1266 assert_equal [:estimated_hours], q.totalable_columns.map(&:name)
1237 end
1267 end
1238 end
1268 end
1239
1269
1240 def test_available_totalable_columns_should_include_estimated_hours
1270 def test_available_totalable_columns_should_include_estimated_hours
1241 q = IssueQuery.new
1271 q = IssueQuery.new
1242 assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
1272 assert_include :estimated_hours, q.available_totalable_columns.map(&:name)
1243 end
1273 end
1244
1274
1245 def test_available_totalable_columns_should_include_spent_hours
1275 def test_available_totalable_columns_should_include_spent_hours
1246 User.current = User.find(1)
1276 User.current = User.find(1)
1247
1277
1248 q = IssueQuery.new
1278 q = IssueQuery.new
1249 assert_include :spent_hours, q.available_totalable_columns.map(&:name)
1279 assert_include :spent_hours, q.available_totalable_columns.map(&:name)
1250 end
1280 end
1251
1281
1252 def test_available_totalable_columns_should_include_int_custom_field
1282 def test_available_totalable_columns_should_include_int_custom_field
1253 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1283 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1254 q = IssueQuery.new
1284 q = IssueQuery.new
1255 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1285 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1256 end
1286 end
1257
1287
1258 def test_available_totalable_columns_should_include_float_custom_field
1288 def test_available_totalable_columns_should_include_float_custom_field
1259 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1289 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1260 q = IssueQuery.new
1290 q = IssueQuery.new
1261 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1291 assert_include "cf_#{field.id}".to_sym, q.available_totalable_columns.map(&:name)
1262 end
1292 end
1263
1293
1264 def test_total_for_estimated_hours
1294 def test_total_for_estimated_hours
1265 Issue.delete_all
1295 Issue.delete_all
1266 Issue.generate!(:estimated_hours => 5.5)
1296 Issue.generate!(:estimated_hours => 5.5)
1267 Issue.generate!(:estimated_hours => 1.1)
1297 Issue.generate!(:estimated_hours => 1.1)
1268 Issue.generate!
1298 Issue.generate!
1269
1299
1270 q = IssueQuery.new
1300 q = IssueQuery.new
1271 assert_equal 6.6, q.total_for(:estimated_hours)
1301 assert_equal 6.6, q.total_for(:estimated_hours)
1272 end
1302 end
1273
1303
1274 def test_total_by_group_for_estimated_hours
1304 def test_total_by_group_for_estimated_hours
1275 Issue.delete_all
1305 Issue.delete_all
1276 Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2)
1306 Issue.generate!(:estimated_hours => 5.5, :assigned_to_id => 2)
1277 Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3)
1307 Issue.generate!(:estimated_hours => 1.1, :assigned_to_id => 3)
1278 Issue.generate!(:estimated_hours => 3.5)
1308 Issue.generate!(:estimated_hours => 3.5)
1279
1309
1280 q = IssueQuery.new(:group_by => 'assigned_to')
1310 q = IssueQuery.new(:group_by => 'assigned_to')
1281 assert_equal(
1311 assert_equal(
1282 {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
1312 {nil => 3.5, User.find(2) => 5.5, User.find(3) => 1.1},
1283 q.total_by_group_for(:estimated_hours)
1313 q.total_by_group_for(:estimated_hours)
1284 )
1314 )
1285 end
1315 end
1286
1316
1287 def test_total_for_spent_hours
1317 def test_total_for_spent_hours
1288 TimeEntry.delete_all
1318 TimeEntry.delete_all
1289 TimeEntry.generate!(:hours => 5.5)
1319 TimeEntry.generate!(:hours => 5.5)
1290 TimeEntry.generate!(:hours => 1.1)
1320 TimeEntry.generate!(:hours => 1.1)
1291
1321
1292 q = IssueQuery.new
1322 q = IssueQuery.new
1293 assert_equal 6.6, q.total_for(:spent_hours)
1323 assert_equal 6.6, q.total_for(:spent_hours)
1294 end
1324 end
1295
1325
1296 def test_total_by_group_for_spent_hours
1326 def test_total_by_group_for_spent_hours
1297 TimeEntry.delete_all
1327 TimeEntry.delete_all
1298 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1328 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1299 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1329 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1300 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1330 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1301 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1331 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1302
1332
1303 q = IssueQuery.new(:group_by => 'assigned_to')
1333 q = IssueQuery.new(:group_by => 'assigned_to')
1304 assert_equal(
1334 assert_equal(
1305 {User.find(2) => 5.5, User.find(3) => 1.1},
1335 {User.find(2) => 5.5, User.find(3) => 1.1},
1306 q.total_by_group_for(:spent_hours)
1336 q.total_by_group_for(:spent_hours)
1307 )
1337 )
1308 end
1338 end
1309
1339
1310 def test_total_by_project_group_for_spent_hours
1340 def test_total_by_project_group_for_spent_hours
1311 TimeEntry.delete_all
1341 TimeEntry.delete_all
1312 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1342 TimeEntry.generate!(:hours => 5.5, :issue_id => 1)
1313 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1343 TimeEntry.generate!(:hours => 1.1, :issue_id => 2)
1314 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1344 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1315 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1345 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1316
1346
1317 q = IssueQuery.new(:group_by => 'project')
1347 q = IssueQuery.new(:group_by => 'project')
1318 assert_equal(
1348 assert_equal(
1319 {Project.find(1) => 6.6},
1349 {Project.find(1) => 6.6},
1320 q.total_by_group_for(:spent_hours)
1350 q.total_by_group_for(:spent_hours)
1321 )
1351 )
1322 end
1352 end
1323
1353
1324 def test_total_for_int_custom_field
1354 def test_total_for_int_custom_field
1325 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1355 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1326 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1356 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1327 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1357 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1328 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1358 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1329
1359
1330 q = IssueQuery.new
1360 q = IssueQuery.new
1331 assert_equal 9, q.total_for("cf_#{field.id}")
1361 assert_equal 9, q.total_for("cf_#{field.id}")
1332 end
1362 end
1333
1363
1334 def test_total_by_group_for_int_custom_field
1364 def test_total_by_group_for_int_custom_field
1335 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1365 field = IssueCustomField.generate!(:field_format => 'int', :is_for_all => true)
1336 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1366 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2')
1337 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1367 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1338 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1368 Issue.where(:id => 1).update_all(:assigned_to_id => 2)
1339 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1369 Issue.where(:id => 2).update_all(:assigned_to_id => 3)
1340
1370
1341 q = IssueQuery.new(:group_by => 'assigned_to')
1371 q = IssueQuery.new(:group_by => 'assigned_to')
1342 assert_equal(
1372 assert_equal(
1343 {User.find(2) => 2, User.find(3) => 7},
1373 {User.find(2) => 2, User.find(3) => 7},
1344 q.total_by_group_for("cf_#{field.id}")
1374 q.total_by_group_for("cf_#{field.id}")
1345 )
1375 )
1346 end
1376 end
1347
1377
1348 def test_total_for_float_custom_field
1378 def test_total_for_float_custom_field
1349 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1379 field = IssueCustomField.generate!(:field_format => 'float', :is_for_all => true)
1350 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3')
1380 CustomValue.create!(:customized => Issue.find(1), :custom_field => field, :value => '2.3')
1351 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1381 CustomValue.create!(:customized => Issue.find(2), :custom_field => field, :value => '7')
1352 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1382 CustomValue.create!(:customized => Issue.find(3), :custom_field => field, :value => '')
1353
1383
1354 q = IssueQuery.new
1384 q = IssueQuery.new
1355 assert_equal 9.3, q.total_for("cf_#{field.id}")
1385 assert_equal 9.3, q.total_for("cf_#{field.id}")
1356 end
1386 end
1357
1387
1358 def test_invalid_query_should_raise_query_statement_invalid_error
1388 def test_invalid_query_should_raise_query_statement_invalid_error
1359 q = IssueQuery.new
1389 q = IssueQuery.new
1360 assert_raise Query::StatementInvalid do
1390 assert_raise Query::StatementInvalid do
1361 q.issues(:conditions => "foo = 1")
1391 q.issues(:conditions => "foo = 1")
1362 end
1392 end
1363 end
1393 end
1364
1394
1365 def test_issue_count
1395 def test_issue_count
1366 q = IssueQuery.new(:name => '_')
1396 q = IssueQuery.new(:name => '_')
1367 issue_count = q.issue_count
1397 issue_count = q.issue_count
1368 assert_equal q.issues.size, issue_count
1398 assert_equal q.issues.size, issue_count
1369 end
1399 end
1370
1400
1371 def test_issue_count_with_archived_issues
1401 def test_issue_count_with_archived_issues
1372 p = Project.generate! do |project|
1402 p = Project.generate! do |project|
1373 project.status = Project::STATUS_ARCHIVED
1403 project.status = Project::STATUS_ARCHIVED
1374 end
1404 end
1375 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
1405 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
1376 assert !i.visible?
1406 assert !i.visible?
1377
1407
1378 test_issue_count
1408 test_issue_count
1379 end
1409 end
1380
1410
1381 def test_issue_count_by_association_group
1411 def test_issue_count_by_association_group
1382 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1412 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1383 count_by_group = q.issue_count_by_group
1413 count_by_group = q.issue_count_by_group
1384 assert_kind_of Hash, count_by_group
1414 assert_kind_of Hash, count_by_group
1385 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1415 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1386 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1416 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1387 assert count_by_group.has_key?(User.find(3))
1417 assert count_by_group.has_key?(User.find(3))
1388 end
1418 end
1389
1419
1390 def test_issue_count_by_list_custom_field_group
1420 def test_issue_count_by_list_custom_field_group
1391 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
1421 q = IssueQuery.new(:name => '_', :group_by => 'cf_1')
1392 count_by_group = q.issue_count_by_group
1422 count_by_group = q.issue_count_by_group
1393 assert_kind_of Hash, count_by_group
1423 assert_kind_of Hash, count_by_group
1394 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1424 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1395 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1425 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1396 assert count_by_group.has_key?('MySQL')
1426 assert count_by_group.has_key?('MySQL')
1397 end
1427 end
1398
1428
1399 def test_issue_count_by_date_custom_field_group
1429 def test_issue_count_by_date_custom_field_group
1400 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1430 q = IssueQuery.new(:name => '_', :group_by => 'cf_8')
1401 count_by_group = q.issue_count_by_group
1431 count_by_group = q.issue_count_by_group
1402 assert_kind_of Hash, count_by_group
1432 assert_kind_of Hash, count_by_group
1403 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1433 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
1404 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1434 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
1405 end
1435 end
1406
1436
1407 def test_issue_count_with_nil_group_only
1437 def test_issue_count_with_nil_group_only
1408 Issue.update_all("assigned_to_id = NULL")
1438 Issue.update_all("assigned_to_id = NULL")
1409
1439
1410 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1440 q = IssueQuery.new(:name => '_', :group_by => 'assigned_to')
1411 count_by_group = q.issue_count_by_group
1441 count_by_group = q.issue_count_by_group
1412 assert_kind_of Hash, count_by_group
1442 assert_kind_of Hash, count_by_group
1413 assert_equal 1, count_by_group.keys.size
1443 assert_equal 1, count_by_group.keys.size
1414 assert_nil count_by_group.keys.first
1444 assert_nil count_by_group.keys.first
1415 end
1445 end
1416
1446
1417 def test_issue_ids
1447 def test_issue_ids
1418 q = IssueQuery.new(:name => '_')
1448 q = IssueQuery.new(:name => '_')
1419 order = "issues.subject, issues.id"
1449 order = "issues.subject, issues.id"
1420 issues = q.issues(:order => order)
1450 issues = q.issues(:order => order)
1421 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1451 assert_equal issues.map(&:id), q.issue_ids(:order => order)
1422 end
1452 end
1423
1453
1424 def test_label_for
1454 def test_label_for
1425 set_language_if_valid 'en'
1455 set_language_if_valid 'en'
1426 q = IssueQuery.new
1456 q = IssueQuery.new
1427 assert_equal 'Assignee', q.label_for('assigned_to_id')
1457 assert_equal 'Assignee', q.label_for('assigned_to_id')
1428 end
1458 end
1429
1459
1430 def test_label_for_fr
1460 def test_label_for_fr
1431 set_language_if_valid 'fr'
1461 set_language_if_valid 'fr'
1432 q = IssueQuery.new
1462 q = IssueQuery.new
1433 assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
1463 assert_equal "Assign\xc3\xa9 \xc3\xa0".force_encoding('UTF-8'), q.label_for('assigned_to_id')
1434 end
1464 end
1435
1465
1436 def test_editable_by
1466 def test_editable_by
1437 admin = User.find(1)
1467 admin = User.find(1)
1438 manager = User.find(2)
1468 manager = User.find(2)
1439 developer = User.find(3)
1469 developer = User.find(3)
1440
1470
1441 # Public query on project 1
1471 # Public query on project 1
1442 q = IssueQuery.find(1)
1472 q = IssueQuery.find(1)
1443 assert q.editable_by?(admin)
1473 assert q.editable_by?(admin)
1444 assert q.editable_by?(manager)
1474 assert q.editable_by?(manager)
1445 assert !q.editable_by?(developer)
1475 assert !q.editable_by?(developer)
1446
1476
1447 # Private query on project 1
1477 # Private query on project 1
1448 q = IssueQuery.find(2)
1478 q = IssueQuery.find(2)
1449 assert q.editable_by?(admin)
1479 assert q.editable_by?(admin)
1450 assert !q.editable_by?(manager)
1480 assert !q.editable_by?(manager)
1451 assert q.editable_by?(developer)
1481 assert q.editable_by?(developer)
1452
1482
1453 # Private query for all projects
1483 # Private query for all projects
1454 q = IssueQuery.find(3)
1484 q = IssueQuery.find(3)
1455 assert q.editable_by?(admin)
1485 assert q.editable_by?(admin)
1456 assert !q.editable_by?(manager)
1486 assert !q.editable_by?(manager)
1457 assert q.editable_by?(developer)
1487 assert q.editable_by?(developer)
1458
1488
1459 # Public query for all projects
1489 # Public query for all projects
1460 q = IssueQuery.find(4)
1490 q = IssueQuery.find(4)
1461 assert q.editable_by?(admin)
1491 assert q.editable_by?(admin)
1462 assert !q.editable_by?(manager)
1492 assert !q.editable_by?(manager)
1463 assert !q.editable_by?(developer)
1493 assert !q.editable_by?(developer)
1464 end
1494 end
1465
1495
1466 def test_visible_scope
1496 def test_visible_scope
1467 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1497 query_ids = IssueQuery.visible(User.anonymous).map(&:id)
1468
1498
1469 assert query_ids.include?(1), 'public query on public project was not visible'
1499 assert query_ids.include?(1), 'public query on public project was not visible'
1470 assert query_ids.include?(4), 'public query for all projects was not visible'
1500 assert query_ids.include?(4), 'public query for all projects was not visible'
1471 assert !query_ids.include?(2), 'private query on public project was visible'
1501 assert !query_ids.include?(2), 'private query on public project was visible'
1472 assert !query_ids.include?(3), 'private query for all projects was visible'
1502 assert !query_ids.include?(3), 'private query for all projects was visible'
1473 assert !query_ids.include?(7), 'public query on private project was visible'
1503 assert !query_ids.include?(7), 'public query on private project was visible'
1474 end
1504 end
1475
1505
1476 def test_query_with_public_visibility_should_be_visible_to_anyone
1506 def test_query_with_public_visibility_should_be_visible_to_anyone
1477 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
1507 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PUBLIC)
1478
1508
1479 assert q.visible?(User.anonymous)
1509 assert q.visible?(User.anonymous)
1480 assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
1510 assert IssueQuery.visible(User.anonymous).find_by_id(q.id)
1481
1511
1482 assert q.visible?(User.find(7))
1512 assert q.visible?(User.find(7))
1483 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1513 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1484
1514
1485 assert q.visible?(User.find(2))
1515 assert q.visible?(User.find(2))
1486 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1516 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1487
1517
1488 assert q.visible?(User.find(1))
1518 assert q.visible?(User.find(1))
1489 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1519 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1490 end
1520 end
1491
1521
1492 def test_query_with_roles_visibility_should_be_visible_to_user_with_role
1522 def test_query_with_roles_visibility_should_be_visible_to_user_with_role
1493 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
1523 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_ROLES, :role_ids => [1,2])
1494
1524
1495 assert !q.visible?(User.anonymous)
1525 assert !q.visible?(User.anonymous)
1496 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1526 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1497
1527
1498 assert !q.visible?(User.find(7))
1528 assert !q.visible?(User.find(7))
1499 assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
1529 assert_nil IssueQuery.visible(User.find(7)).find_by_id(q.id)
1500
1530
1501 assert q.visible?(User.find(2))
1531 assert q.visible?(User.find(2))
1502 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1532 assert IssueQuery.visible(User.find(2)).find_by_id(q.id)
1503
1533
1504 assert q.visible?(User.find(1))
1534 assert q.visible?(User.find(1))
1505 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1535 assert IssueQuery.visible(User.find(1)).find_by_id(q.id)
1506 end
1536 end
1507
1537
1508 def test_query_with_private_visibility_should_be_visible_to_owner
1538 def test_query_with_private_visibility_should_be_visible_to_owner
1509 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
1539 q = IssueQuery.create!(:name => 'Query', :visibility => IssueQuery::VISIBILITY_PRIVATE, :user => User.find(7))
1510
1540
1511 assert !q.visible?(User.anonymous)
1541 assert !q.visible?(User.anonymous)
1512 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1542 assert_nil IssueQuery.visible(User.anonymous).find_by_id(q.id)
1513
1543
1514 assert q.visible?(User.find(7))
1544 assert q.visible?(User.find(7))
1515 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1545 assert IssueQuery.visible(User.find(7)).find_by_id(q.id)
1516
1546
1517 assert !q.visible?(User.find(2))
1547 assert !q.visible?(User.find(2))
1518 assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
1548 assert_nil IssueQuery.visible(User.find(2)).find_by_id(q.id)
1519
1549
1520 assert q.visible?(User.find(1))
1550 assert q.visible?(User.find(1))
1521 assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
1551 assert_nil IssueQuery.visible(User.find(1)).find_by_id(q.id)
1522 end
1552 end
1523
1553
1524 test "#available_filters should include users of visible projects in cross-project view" do
1554 test "#available_filters should include users of visible projects in cross-project view" do
1525 users = IssueQuery.new.available_filters["assigned_to_id"]
1555 users = IssueQuery.new.available_filters["assigned_to_id"]
1526 assert_not_nil users
1556 assert_not_nil users
1527 assert users[:values].map{|u|u[1]}.include?("3")
1557 assert users[:values].map{|u|u[1]}.include?("3")
1528 end
1558 end
1529
1559
1530 test "#available_filters should include users of subprojects" do
1560 test "#available_filters should include users of subprojects" do
1531 user1 = User.generate!
1561 user1 = User.generate!
1532 user2 = User.generate!
1562 user2 = User.generate!
1533 project = Project.find(1)
1563 project = Project.find(1)
1534 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1564 Member.create!(:principal => user1, :project => project.children.visible.first, :role_ids => [1])
1535
1565
1536 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1566 users = IssueQuery.new(:project => project).available_filters["assigned_to_id"]
1537 assert_not_nil users
1567 assert_not_nil users
1538 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1568 assert users[:values].map{|u|u[1]}.include?(user1.id.to_s)
1539 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1569 assert !users[:values].map{|u|u[1]}.include?(user2.id.to_s)
1540 end
1570 end
1541
1571
1542 test "#available_filters should include visible projects in cross-project view" do
1572 test "#available_filters should include visible projects in cross-project view" do
1543 projects = IssueQuery.new.available_filters["project_id"]
1573 projects = IssueQuery.new.available_filters["project_id"]
1544 assert_not_nil projects
1574 assert_not_nil projects
1545 assert projects[:values].map{|u|u[1]}.include?("1")
1575 assert projects[:values].map{|u|u[1]}.include?("1")
1546 end
1576 end
1547
1577
1548 test "#available_filters should include 'member_of_group' filter" do
1578 test "#available_filters should include 'member_of_group' filter" do
1549 query = IssueQuery.new
1579 query = IssueQuery.new
1550 assert query.available_filters.keys.include?("member_of_group")
1580 assert query.available_filters.keys.include?("member_of_group")
1551 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1581 assert_equal :list_optional, query.available_filters["member_of_group"][:type]
1552 assert query.available_filters["member_of_group"][:values].present?
1582 assert query.available_filters["member_of_group"][:values].present?
1553 assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
1583 assert_equal Group.givable.sort.map {|g| [g.name, g.id.to_s]},
1554 query.available_filters["member_of_group"][:values].sort
1584 query.available_filters["member_of_group"][:values].sort
1555 end
1585 end
1556
1586
1557 test "#available_filters should include 'assigned_to_role' filter" do
1587 test "#available_filters should include 'assigned_to_role' filter" do
1558 query = IssueQuery.new
1588 query = IssueQuery.new
1559 assert query.available_filters.keys.include?("assigned_to_role")
1589 assert query.available_filters.keys.include?("assigned_to_role")
1560 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1590 assert_equal :list_optional, query.available_filters["assigned_to_role"][:type]
1561
1591
1562 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1592 assert query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
1563 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1593 assert query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
1564 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1594 assert query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
1565
1595
1566 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1596 assert ! query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
1567 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1597 assert ! query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
1568 end
1598 end
1569
1599
1570 def test_available_filters_should_include_custom_field_according_to_user_visibility
1600 def test_available_filters_should_include_custom_field_according_to_user_visibility
1571 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1601 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1572 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1602 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1573
1603
1574 with_current_user User.find(3) do
1604 with_current_user User.find(3) do
1575 query = IssueQuery.new
1605 query = IssueQuery.new
1576 assert_include "cf_#{visible_field.id}", query.available_filters.keys
1606 assert_include "cf_#{visible_field.id}", query.available_filters.keys
1577 assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
1607 assert_not_include "cf_#{hidden_field.id}", query.available_filters.keys
1578 end
1608 end
1579 end
1609 end
1580
1610
1581 def test_available_columns_should_include_custom_field_according_to_user_visibility
1611 def test_available_columns_should_include_custom_field_according_to_user_visibility
1582 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1612 visible_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => true)
1583 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1613 hidden_field = IssueCustomField.generate!(:is_for_all => true, :is_filter => true, :visible => false, :role_ids => [1])
1584
1614
1585 with_current_user User.find(3) do
1615 with_current_user User.find(3) do
1586 query = IssueQuery.new
1616 query = IssueQuery.new
1587 assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
1617 assert_include :"cf_#{visible_field.id}", query.available_columns.map(&:name)
1588 assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
1618 assert_not_include :"cf_#{hidden_field.id}", query.available_columns.map(&:name)
1589 end
1619 end
1590 end
1620 end
1591
1621
1592 def setup_member_of_group
1622 def setup_member_of_group
1593 Group.destroy_all # No fixtures
1623 Group.destroy_all # No fixtures
1594 @user_in_group = User.generate!
1624 @user_in_group = User.generate!
1595 @second_user_in_group = User.generate!
1625 @second_user_in_group = User.generate!
1596 @user_in_group2 = User.generate!
1626 @user_in_group2 = User.generate!
1597 @user_not_in_group = User.generate!
1627 @user_not_in_group = User.generate!
1598
1628
1599 @group = Group.generate!.reload
1629 @group = Group.generate!.reload
1600 @group.users << @user_in_group
1630 @group.users << @user_in_group
1601 @group.users << @second_user_in_group
1631 @group.users << @second_user_in_group
1602
1632
1603 @group2 = Group.generate!.reload
1633 @group2 = Group.generate!.reload
1604 @group2.users << @user_in_group2
1634 @group2.users << @user_in_group2
1605
1635
1606 @query = IssueQuery.new(:name => '_')
1636 @query = IssueQuery.new(:name => '_')
1607 end
1637 end
1608
1638
1609 test "member_of_group filter should search assigned to for users in the group" do
1639 test "member_of_group filter should search assigned to for users in the group" do
1610 setup_member_of_group
1640 setup_member_of_group
1611 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1641 @query.add_filter('member_of_group', '=', [@group.id.to_s])
1612
1642
1613 assert_find_issues_with_query_is_successful @query
1643 assert_find_issues_with_query_is_successful @query
1614 end
1644 end
1615
1645
1616 test "member_of_group filter should search not assigned to any group member (none)" do
1646 test "member_of_group filter should search not assigned to any group member (none)" do
1617 setup_member_of_group
1647 setup_member_of_group
1618 @query.add_filter('member_of_group', '!*', [''])
1648 @query.add_filter('member_of_group', '!*', [''])
1619
1649
1620 assert_find_issues_with_query_is_successful @query
1650 assert_find_issues_with_query_is_successful @query
1621 end
1651 end
1622
1652
1623 test "member_of_group filter should search assigned to any group member (all)" do
1653 test "member_of_group filter should search assigned to any group member (all)" do
1624 setup_member_of_group
1654 setup_member_of_group
1625 @query.add_filter('member_of_group', '*', [''])
1655 @query.add_filter('member_of_group', '*', [''])
1626
1656
1627 assert_find_issues_with_query_is_successful @query
1657 assert_find_issues_with_query_is_successful @query
1628 end
1658 end
1629
1659
1630 test "member_of_group filter should return an empty set with = empty group" do
1660 test "member_of_group filter should return an empty set with = empty group" do
1631 setup_member_of_group
1661 setup_member_of_group
1632 @empty_group = Group.generate!
1662 @empty_group = Group.generate!
1633 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1663 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
1634
1664
1635 assert_equal [], find_issues_with_query(@query)
1665 assert_equal [], find_issues_with_query(@query)
1636 end
1666 end
1637
1667
1638 test "member_of_group filter should return issues with ! empty group" do
1668 test "member_of_group filter should return issues with ! empty group" do
1639 setup_member_of_group
1669 setup_member_of_group
1640 @empty_group = Group.generate!
1670 @empty_group = Group.generate!
1641 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1671 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
1642
1672
1643 assert_find_issues_with_query_is_successful @query
1673 assert_find_issues_with_query_is_successful @query
1644 end
1674 end
1645
1675
1646 def setup_assigned_to_role
1676 def setup_assigned_to_role
1647 @manager_role = Role.find_by_name('Manager')
1677 @manager_role = Role.find_by_name('Manager')
1648 @developer_role = Role.find_by_name('Developer')
1678 @developer_role = Role.find_by_name('Developer')
1649
1679
1650 @project = Project.generate!
1680 @project = Project.generate!
1651 @manager = User.generate!
1681 @manager = User.generate!
1652 @developer = User.generate!
1682 @developer = User.generate!
1653 @boss = User.generate!
1683 @boss = User.generate!
1654 @guest = User.generate!
1684 @guest = User.generate!
1655 User.add_to_project(@manager, @project, @manager_role)
1685 User.add_to_project(@manager, @project, @manager_role)
1656 User.add_to_project(@developer, @project, @developer_role)
1686 User.add_to_project(@developer, @project, @developer_role)
1657 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1687 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
1658
1688
1659 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1689 @issue1 = Issue.generate!(:project => @project, :assigned_to_id => @manager.id)
1660 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1690 @issue2 = Issue.generate!(:project => @project, :assigned_to_id => @developer.id)
1661 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1691 @issue3 = Issue.generate!(:project => @project, :assigned_to_id => @boss.id)
1662 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1692 @issue4 = Issue.generate!(:project => @project, :assigned_to_id => @guest.id)
1663 @issue5 = Issue.generate!(:project => @project)
1693 @issue5 = Issue.generate!(:project => @project)
1664
1694
1665 @query = IssueQuery.new(:name => '_', :project => @project)
1695 @query = IssueQuery.new(:name => '_', :project => @project)
1666 end
1696 end
1667
1697
1668 test "assigned_to_role filter should search assigned to for users with the Role" do
1698 test "assigned_to_role filter should search assigned to for users with the Role" do
1669 setup_assigned_to_role
1699 setup_assigned_to_role
1670 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1700 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1671
1701
1672 assert_query_result [@issue1, @issue3], @query
1702 assert_query_result [@issue1, @issue3], @query
1673 end
1703 end
1674
1704
1675 test "assigned_to_role filter should search assigned to for users with the Role on the issue project" do
1705 test "assigned_to_role filter should search assigned to for users with the Role on the issue project" do
1676 setup_assigned_to_role
1706 setup_assigned_to_role
1677 other_project = Project.generate!
1707 other_project = Project.generate!
1678 User.add_to_project(@developer, other_project, @manager_role)
1708 User.add_to_project(@developer, other_project, @manager_role)
1679 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1709 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
1680
1710
1681 assert_query_result [@issue1, @issue3], @query
1711 assert_query_result [@issue1, @issue3], @query
1682 end
1712 end
1683
1713
1684 test "assigned_to_role filter should return an empty set with empty role" do
1714 test "assigned_to_role filter should return an empty set with empty role" do
1685 setup_assigned_to_role
1715 setup_assigned_to_role
1686 @empty_role = Role.generate!
1716 @empty_role = Role.generate!
1687 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1717 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
1688
1718
1689 assert_query_result [], @query
1719 assert_query_result [], @query
1690 end
1720 end
1691
1721
1692 test "assigned_to_role filter should search assigned to for users without the Role" do
1722 test "assigned_to_role filter should search assigned to for users without the Role" do
1693 setup_assigned_to_role
1723 setup_assigned_to_role
1694 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1724 @query.add_filter('assigned_to_role', '!', [@manager_role.id.to_s])
1695
1725
1696 assert_query_result [@issue2, @issue4, @issue5], @query
1726 assert_query_result [@issue2, @issue4, @issue5], @query
1697 end
1727 end
1698
1728
1699 test "assigned_to_role filter should search assigned to for users not assigned to any Role (none)" do
1729 test "assigned_to_role filter should search assigned to for users not assigned to any Role (none)" do
1700 setup_assigned_to_role
1730 setup_assigned_to_role
1701 @query.add_filter('assigned_to_role', '!*', [''])
1731 @query.add_filter('assigned_to_role', '!*', [''])
1702
1732
1703 assert_query_result [@issue4, @issue5], @query
1733 assert_query_result [@issue4, @issue5], @query
1704 end
1734 end
1705
1735
1706 test "assigned_to_role filter should search assigned to for users assigned to any Role (all)" do
1736 test "assigned_to_role filter should search assigned to for users assigned to any Role (all)" do
1707 setup_assigned_to_role
1737 setup_assigned_to_role
1708 @query.add_filter('assigned_to_role', '*', [''])
1738 @query.add_filter('assigned_to_role', '*', [''])
1709
1739
1710 assert_query_result [@issue1, @issue2, @issue3], @query
1740 assert_query_result [@issue1, @issue2, @issue3], @query
1711 end
1741 end
1712
1742
1713 test "assigned_to_role filter should return issues with ! empty role" do
1743 test "assigned_to_role filter should return issues with ! empty role" do
1714 setup_assigned_to_role
1744 setup_assigned_to_role
1715 @empty_role = Role.generate!
1745 @empty_role = Role.generate!
1716 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1746 @query.add_filter('assigned_to_role', '!', [@empty_role.id.to_s])
1717
1747
1718 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1748 assert_query_result [@issue1, @issue2, @issue3, @issue4, @issue5], @query
1719 end
1749 end
1720
1750
1721 def test_query_column_should_accept_a_symbol_as_caption
1751 def test_query_column_should_accept_a_symbol_as_caption
1722 set_language_if_valid 'en'
1752 set_language_if_valid 'en'
1723 c = QueryColumn.new('foo', :caption => :general_text_Yes)
1753 c = QueryColumn.new('foo', :caption => :general_text_Yes)
1724 assert_equal 'Yes', c.caption
1754 assert_equal 'Yes', c.caption
1725 end
1755 end
1726
1756
1727 def test_query_column_should_accept_a_proc_as_caption
1757 def test_query_column_should_accept_a_proc_as_caption
1728 c = QueryColumn.new('foo', :caption => lambda {'Foo'})
1758 c = QueryColumn.new('foo', :caption => lambda {'Foo'})
1729 assert_equal 'Foo', c.caption
1759 assert_equal 'Foo', c.caption
1730 end
1760 end
1731
1761
1732 def test_date_clause_should_respect_user_time_zone_with_local_default
1762 def test_date_clause_should_respect_user_time_zone_with_local_default
1733 @query = IssueQuery.new(:name => '_')
1763 @query = IssueQuery.new(:name => '_')
1734
1764
1735 # user is in Hawaii (-10)
1765 # user is in Hawaii (-10)
1736 User.current = users(:users_001)
1766 User.current = users(:users_001)
1737 User.current.pref.update_attribute :time_zone, 'Hawaii'
1767 User.current.pref.update_attribute :time_zone, 'Hawaii'
1738
1768
1739 # assume timestamps are stored in server local time
1769 # assume timestamps are stored in server local time
1740 local_zone = Time.zone
1770 local_zone = Time.zone
1741
1771
1742 from = Date.parse '2016-03-20'
1772 from = Date.parse '2016-03-20'
1743 to = Date.parse '2016-03-22'
1773 to = Date.parse '2016-03-22'
1744 assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
1774 assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
1745
1775
1746 # the dates should have been interpreted in the user's time zone and
1776 # the dates should have been interpreted in the user's time zone and
1747 # converted to local time
1777 # converted to local time
1748 # what we get exactly in the sql depends on the local time zone, therefore
1778 # what we get exactly in the sql depends on the local time zone, therefore
1749 # it's computed here.
1779 # it's computed here.
1750 f = User.current.time_zone.local(from.year, from.month, from.day).yesterday.end_of_day.in_time_zone(local_zone)
1780 f = User.current.time_zone.local(from.year, from.month, from.day).yesterday.end_of_day.in_time_zone(local_zone)
1751 t = User.current.time_zone.local(to.year, to.month, to.day).end_of_day.in_time_zone(local_zone)
1781 t = User.current.time_zone.local(to.year, to.month, to.day).end_of_day.in_time_zone(local_zone)
1752 assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
1782 assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
1753 end
1783 end
1754
1784
1755 def test_date_clause_should_respect_user_time_zone_with_utc_default
1785 def test_date_clause_should_respect_user_time_zone_with_utc_default
1756 @query = IssueQuery.new(:name => '_')
1786 @query = IssueQuery.new(:name => '_')
1757
1787
1758 # user is in Hawaii (-10)
1788 # user is in Hawaii (-10)
1759 User.current = users(:users_001)
1789 User.current = users(:users_001)
1760 User.current.pref.update_attribute :time_zone, 'Hawaii'
1790 User.current.pref.update_attribute :time_zone, 'Hawaii'
1761
1791
1762 # assume timestamps are stored as utc
1792 # assume timestamps are stored as utc
1763 ActiveRecord::Base.default_timezone = :utc
1793 ActiveRecord::Base.default_timezone = :utc
1764
1794
1765 from = Date.parse '2016-03-20'
1795 from = Date.parse '2016-03-20'
1766 to = Date.parse '2016-03-22'
1796 to = Date.parse '2016-03-22'
1767 assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
1797 assert c = @query.send(:date_clause, 'table', 'field', from, to, false)
1768 # the dates should have been interpreted in the user's time zone and
1798 # the dates should have been interpreted in the user's time zone and
1769 # converted to utc. March 20 in Hawaii begins at 10am UTC.
1799 # converted to utc. March 20 in Hawaii begins at 10am UTC.
1770 f = Time.new(2016, 3, 20, 9, 59, 59, 0).end_of_hour
1800 f = Time.new(2016, 3, 20, 9, 59, 59, 0).end_of_hour
1771 t = Time.new(2016, 3, 23, 9, 59, 59, 0).end_of_hour
1801 t = Time.new(2016, 3, 23, 9, 59, 59, 0).end_of_hour
1772 assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
1802 assert_equal "table.field > '#{Query.connection.quoted_date f}' AND table.field <= '#{Query.connection.quoted_date t}'", c
1773 ensure
1803 ensure
1774 ActiveRecord::Base.default_timezone = :local # restore Redmine default
1804 ActiveRecord::Base.default_timezone = :local # restore Redmine default
1775 end
1805 end
1776
1806
1777 end
1807 end
General Comments 0
You need to be logged in to leave comments. Login now