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