##// END OF EJS Templates
Merged r15751 (#23596)....
Jean-Philippe Lang -
r15388:62f9b56c1e69
parent child
Show More
@@ -1,565 +1,570
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
21
22 self.available_columns = [
22 self.available_columns = [
23 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
23 QueryColumn.new(:id, :sortable => "#{Issue.table_name}.id", :default_order => 'desc', :caption => '#', :frozen => true),
24 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
24 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
25 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
25 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
26 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
26 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
27 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
27 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
28 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
28 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
29 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
29 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
30 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
30 QueryColumn.new(:author, :sortable => lambda {User.fields_for_order_statement("authors")}, :groupable => true),
31 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
31 QueryColumn.new(:assigned_to, :sortable => lambda {User.fields_for_order_statement}, :groupable => true),
32 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
32 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
33 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
33 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
34 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
34 QueryColumn.new(:fixed_version, :sortable => lambda {Version.fields_for_order_statement}, :groupable => true),
35 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
35 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
36 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
36 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
37 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
37 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours", :totalable => true),
38 QueryColumn.new(:total_estimated_hours,
38 QueryColumn.new(:total_estimated_hours,
39 :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
39 :sortable => "COALESCE((SELECT SUM(estimated_hours) FROM #{Issue.table_name} subtasks" +
40 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
40 " 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 :default_order => 'desc'),
41 :default_order => 'desc'),
42 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
42 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
43 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
43 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
44 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
44 QueryColumn.new(:closed_on, :sortable => "#{Issue.table_name}.closed_on", :default_order => 'desc'),
45 QueryColumn.new(:relations, :caption => :label_related_issues),
45 QueryColumn.new(:relations, :caption => :label_related_issues),
46 QueryColumn.new(:description, :inline => false)
46 QueryColumn.new(:description, :inline => false)
47 ]
47 ]
48
48
49 scope :visible, lambda {|*args|
49 scope :visible, lambda {|*args|
50 user = args.shift || User.current
50 user = args.shift || User.current
51 base = Project.allowed_to_condition(user, :view_issues, *args)
51 base = Project.allowed_to_condition(user, :view_issues, *args)
52 scope = joins("LEFT OUTER JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id").
52 scope = joins("LEFT OUTER JOIN #{Project.table_name} ON #{table_name}.project_id = #{Project.table_name}.id").
53 where("#{table_name}.project_id IS NULL OR (#{base})")
53 where("#{table_name}.project_id IS NULL OR (#{base})")
54
54
55 if user.admin?
55 if user.admin?
56 scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
56 scope.where("#{table_name}.visibility <> ? OR #{table_name}.user_id = ?", VISIBILITY_PRIVATE, user.id)
57 elsif user.memberships.any?
57 elsif user.memberships.any?
58 scope.where("#{table_name}.visibility = ?" +
58 scope.where("#{table_name}.visibility = ?" +
59 " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
59 " OR (#{table_name}.visibility = ? AND #{table_name}.id IN (" +
60 "SELECT DISTINCT q.id FROM #{table_name} q" +
60 "SELECT DISTINCT q.id FROM #{table_name} q" +
61 " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
61 " INNER JOIN #{table_name_prefix}queries_roles#{table_name_suffix} qr on qr.query_id = q.id" +
62 " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
62 " INNER JOIN #{MemberRole.table_name} mr ON mr.role_id = qr.role_id" +
63 " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
63 " INNER JOIN #{Member.table_name} m ON m.id = mr.member_id AND m.user_id = ?" +
64 " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
64 " WHERE q.project_id IS NULL OR q.project_id = m.project_id))" +
65 " OR #{table_name}.user_id = ?",
65 " OR #{table_name}.user_id = ?",
66 VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
66 VISIBILITY_PUBLIC, VISIBILITY_ROLES, user.id, user.id)
67 elsif user.logged?
67 elsif user.logged?
68 scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
68 scope.where("#{table_name}.visibility = ? OR #{table_name}.user_id = ?", VISIBILITY_PUBLIC, user.id)
69 else
69 else
70 scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
70 scope.where("#{table_name}.visibility = ?", VISIBILITY_PUBLIC)
71 end
71 end
72 }
72 }
73
73
74 def initialize(attributes=nil, *args)
74 def initialize(attributes=nil, *args)
75 super attributes
75 super attributes
76 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
76 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
77 end
77 end
78
78
79 # Returns true if the query is visible to +user+ or the current user.
79 # Returns true if the query is visible to +user+ or the current user.
80 def visible?(user=User.current)
80 def visible?(user=User.current)
81 return true if user.admin?
81 return true if user.admin?
82 return false unless project.nil? || user.allowed_to?(:view_issues, project)
82 return false unless project.nil? || user.allowed_to?(:view_issues, project)
83 case visibility
83 case visibility
84 when VISIBILITY_PUBLIC
84 when VISIBILITY_PUBLIC
85 true
85 true
86 when VISIBILITY_ROLES
86 when VISIBILITY_ROLES
87 if project
87 if project
88 (user.roles_for_project(project) & roles).any?
88 (user.roles_for_project(project) & roles).any?
89 else
89 else
90 Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
90 Member.where(:user_id => user.id).joins(:roles).where(:member_roles => {:role_id => roles.map(&:id)}).any?
91 end
91 end
92 else
92 else
93 user == self.user
93 user == self.user
94 end
94 end
95 end
95 end
96
96
97 def is_private?
97 def is_private?
98 visibility == VISIBILITY_PRIVATE
98 visibility == VISIBILITY_PRIVATE
99 end
99 end
100
100
101 def is_public?
101 def is_public?
102 !is_private?
102 !is_private?
103 end
103 end
104
104
105 def draw_relations
105 def draw_relations
106 r = options[:draw_relations]
106 r = options[:draw_relations]
107 r.nil? || r == '1'
107 r.nil? || r == '1'
108 end
108 end
109
109
110 def draw_relations=(arg)
110 def draw_relations=(arg)
111 options[:draw_relations] = (arg == '0' ? '0' : nil)
111 options[:draw_relations] = (arg == '0' ? '0' : nil)
112 end
112 end
113
113
114 def draw_progress_line
114 def draw_progress_line
115 r = options[:draw_progress_line]
115 r = options[:draw_progress_line]
116 r == '1'
116 r == '1'
117 end
117 end
118
118
119 def draw_progress_line=(arg)
119 def draw_progress_line=(arg)
120 options[:draw_progress_line] = (arg == '1' ? '1' : nil)
120 options[:draw_progress_line] = (arg == '1' ? '1' : nil)
121 end
121 end
122
122
123 def build_from_params(params)
123 def build_from_params(params)
124 super
124 super
125 self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
125 self.draw_relations = params[:draw_relations] || (params[:query] && params[:query][:draw_relations])
126 self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line])
126 self.draw_progress_line = params[:draw_progress_line] || (params[:query] && params[:query][:draw_progress_line])
127 self
127 self
128 end
128 end
129
129
130 def initialize_available_filters
130 def initialize_available_filters
131 principals = []
131 principals = []
132 subprojects = []
132 subprojects = []
133 versions = []
133 versions = []
134 categories = []
134 categories = []
135 issue_custom_fields = []
135 issue_custom_fields = []
136
136
137 if project
137 if project
138 principals += project.principals.visible
138 principals += project.principals.visible
139 unless project.leaf?
139 unless project.leaf?
140 subprojects = project.descendants.visible.to_a
140 subprojects = project.descendants.visible.to_a
141 principals += Principal.member_of(subprojects).visible
141 principals += Principal.member_of(subprojects).visible
142 end
142 end
143 versions = project.shared_versions.to_a
143 versions = project.shared_versions.to_a
144 categories = project.issue_categories.to_a
144 categories = project.issue_categories.to_a
145 issue_custom_fields = project.all_issue_custom_fields
145 issue_custom_fields = project.all_issue_custom_fields
146 else
146 else
147 if all_projects.any?
147 if all_projects.any?
148 principals += Principal.member_of(all_projects).visible
148 principals += Principal.member_of(all_projects).visible
149 end
149 end
150 versions = Version.visible.where(:sharing => 'system').to_a
150 versions = Version.visible.where(:sharing => 'system').to_a
151 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
151 issue_custom_fields = IssueCustomField.where(:is_for_all => true)
152 end
152 end
153 principals.uniq!
153 principals.uniq!
154 principals.sort!
154 principals.sort!
155 principals.reject! {|p| p.is_a?(GroupBuiltin)}
155 principals.reject! {|p| p.is_a?(GroupBuiltin)}
156 users = principals.select {|p| p.is_a?(User)}
156 users = principals.select {|p| p.is_a?(User)}
157
157
158 add_available_filter "status_id",
158 add_available_filter "status_id",
159 :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
159 :type => :list_status, :values => IssueStatus.sorted.collect{|s| [s.name, s.id.to_s] }
160
160
161 if project.nil?
161 if project.nil?
162 project_values = []
162 project_values = []
163 if User.current.logged? && User.current.memberships.any?
163 if User.current.logged? && User.current.memberships.any?
164 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
164 project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
165 end
165 end
166 project_values += all_projects_values
166 project_values += all_projects_values
167 add_available_filter("project_id",
167 add_available_filter("project_id",
168 :type => :list, :values => project_values
168 :type => :list, :values => project_values
169 ) unless project_values.empty?
169 ) unless project_values.empty?
170 end
170 end
171
171
172 add_available_filter "tracker_id",
172 add_available_filter "tracker_id",
173 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
173 :type => :list, :values => trackers.collect{|s| [s.name, s.id.to_s] }
174 add_available_filter "priority_id",
174 add_available_filter "priority_id",
175 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
175 :type => :list, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] }
176
176
177 author_values = []
177 author_values = []
178 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
178 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
179 author_values += users.collect{|s| [s.name, s.id.to_s] }
179 author_values += users.collect{|s| [s.name, s.id.to_s] }
180 add_available_filter("author_id",
180 add_available_filter("author_id",
181 :type => :list, :values => author_values
181 :type => :list, :values => author_values
182 ) unless author_values.empty?
182 ) unless author_values.empty?
183
183
184 assigned_to_values = []
184 assigned_to_values = []
185 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
185 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
186 assigned_to_values += (Setting.issue_group_assignment? ?
186 assigned_to_values += (Setting.issue_group_assignment? ?
187 principals : users).collect{|s| [s.name, s.id.to_s] }
187 principals : users).collect{|s| [s.name, s.id.to_s] }
188 add_available_filter("assigned_to_id",
188 add_available_filter("assigned_to_id",
189 :type => :list_optional, :values => assigned_to_values
189 :type => :list_optional, :values => assigned_to_values
190 ) unless assigned_to_values.empty?
190 ) unless assigned_to_values.empty?
191
191
192 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
192 group_values = Group.givable.visible.collect {|g| [g.name, g.id.to_s] }
193 add_available_filter("member_of_group",
193 add_available_filter("member_of_group",
194 :type => :list_optional, :values => group_values
194 :type => :list_optional, :values => group_values
195 ) unless group_values.empty?
195 ) unless group_values.empty?
196
196
197 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
197 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
198 add_available_filter("assigned_to_role",
198 add_available_filter("assigned_to_role",
199 :type => :list_optional, :values => role_values
199 :type => :list_optional, :values => role_values
200 ) unless role_values.empty?
200 ) unless role_values.empty?
201
201
202 add_available_filter "fixed_version_id",
202 add_available_filter "fixed_version_id",
203 :type => :list_optional,
203 :type => :list_optional,
204 :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
204 :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
205
205
206 add_available_filter "category_id",
206 add_available_filter "category_id",
207 :type => :list_optional,
207 :type => :list_optional,
208 :values => categories.collect{|s| [s.name, s.id.to_s] }
208 :values => categories.collect{|s| [s.name, s.id.to_s] }
209
209
210 add_available_filter "subject", :type => :text
210 add_available_filter "subject", :type => :text
211 add_available_filter "description", :type => :text
211 add_available_filter "description", :type => :text
212 add_available_filter "created_on", :type => :date_past
212 add_available_filter "created_on", :type => :date_past
213 add_available_filter "updated_on", :type => :date_past
213 add_available_filter "updated_on", :type => :date_past
214 add_available_filter "closed_on", :type => :date_past
214 add_available_filter "closed_on", :type => :date_past
215 add_available_filter "start_date", :type => :date
215 add_available_filter "start_date", :type => :date
216 add_available_filter "due_date", :type => :date
216 add_available_filter "due_date", :type => :date
217 add_available_filter "estimated_hours", :type => :float
217 add_available_filter "estimated_hours", :type => :float
218 add_available_filter "done_ratio", :type => :integer
218 add_available_filter "done_ratio", :type => :integer
219
219
220 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
220 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
221 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
221 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
222 add_available_filter "is_private",
222 add_available_filter "is_private",
223 :type => :list,
223 :type => :list,
224 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
224 :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]]
225 end
225 end
226
226
227 if User.current.logged?
227 if User.current.logged?
228 add_available_filter "watcher_id",
228 add_available_filter "watcher_id",
229 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
229 :type => :list, :values => [["<< #{l(:label_me)} >>", "me"]]
230 end
230 end
231
231
232 if subprojects.any?
232 if subprojects.any?
233 add_available_filter "subproject_id",
233 add_available_filter "subproject_id",
234 :type => :list_subprojects,
234 :type => :list_subprojects,
235 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
235 :values => subprojects.collect{|s| [s.name, s.id.to_s] }
236 end
236 end
237
237
238 add_custom_fields_filters(issue_custom_fields)
238 add_custom_fields_filters(issue_custom_fields)
239
239
240 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
240 add_associations_custom_fields_filters :project, :author, :assigned_to, :fixed_version
241
241
242 IssueRelation::TYPES.each do |relation_type, options|
242 IssueRelation::TYPES.each do |relation_type, options|
243 add_available_filter relation_type, :type => :relation, :label => options[:name]
243 add_available_filter relation_type, :type => :relation, :label => options[:name]
244 end
244 end
245 add_available_filter "parent_id", :type => :tree, :label => :field_parent_issue
245 add_available_filter "parent_id", :type => :tree, :label => :field_parent_issue
246 add_available_filter "child_id", :type => :tree, :label => :label_subtask_plural
246 add_available_filter "child_id", :type => :tree, :label => :label_subtask_plural
247
247
248 add_available_filter "issue_id", :type => :integer, :label => :label_issue
248 add_available_filter "issue_id", :type => :integer, :label => :label_issue
249
249
250 Tracker.disabled_core_fields(trackers).each {|field|
250 Tracker.disabled_core_fields(trackers).each {|field|
251 delete_available_filter field
251 delete_available_filter field
252 }
252 }
253 end
253 end
254
254
255 def available_columns
255 def available_columns
256 return @available_columns if @available_columns
256 return @available_columns if @available_columns
257 @available_columns = self.class.available_columns.dup
257 @available_columns = self.class.available_columns.dup
258 @available_columns += (project ?
258 @available_columns += (project ?
259 project.all_issue_custom_fields :
259 project.all_issue_custom_fields :
260 IssueCustomField
260 IssueCustomField
261 ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
261 ).visible.collect {|cf| QueryCustomFieldColumn.new(cf) }
262
262
263 if User.current.allowed_to?(:view_time_entries, project, :global => true)
263 if User.current.allowed_to?(:view_time_entries, project, :global => true)
264 index = @available_columns.find_index {|column| column.name == :total_estimated_hours}
264 index = @available_columns.find_index {|column| column.name == :total_estimated_hours}
265 index = (index ? index + 1 : -1)
265 index = (index ? index + 1 : -1)
266 # insert the column after total_estimated_hours or at the end
266 # insert the column after total_estimated_hours or at the end
267 @available_columns.insert index, QueryColumn.new(:spent_hours,
267 @available_columns.insert index, QueryColumn.new(:spent_hours,
268 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
268 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} WHERE #{TimeEntry.table_name}.issue_id = #{Issue.table_name}.id), 0)",
269 :default_order => 'desc',
269 :default_order => 'desc',
270 :caption => :label_spent_time,
270 :caption => :label_spent_time,
271 :totalable => true
271 :totalable => true
272 )
272 )
273 @available_columns.insert index+1, QueryColumn.new(:total_spent_hours,
273 @available_columns.insert index+1, QueryColumn.new(:total_spent_hours,
274 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" +
274 :sortable => "COALESCE((SELECT SUM(hours) FROM #{TimeEntry.table_name} JOIN #{Issue.table_name} subtasks ON subtasks.id = #{TimeEntry.table_name}.issue_id" +
275 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
275 " WHERE subtasks.root_id = #{Issue.table_name}.root_id AND subtasks.lft >= #{Issue.table_name}.lft AND subtasks.rgt <= #{Issue.table_name}.rgt), 0)",
276 :default_order => 'desc',
276 :default_order => 'desc',
277 :caption => :label_total_spent_time
277 :caption => :label_total_spent_time
278 )
278 )
279 end
279 end
280
280
281 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
281 if User.current.allowed_to?(:set_issues_private, nil, :global => true) ||
282 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
282 User.current.allowed_to?(:set_own_issues_private, nil, :global => true)
283 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
283 @available_columns << QueryColumn.new(:is_private, :sortable => "#{Issue.table_name}.is_private")
284 end
284 end
285
285
286 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
286 disabled_fields = Tracker.disabled_core_fields(trackers).map {|field| field.sub(/_id$/, '')}
287 @available_columns.reject! {|column|
287 @available_columns.reject! {|column|
288 disabled_fields.include?(column.name.to_s)
288 disabled_fields.include?(column.name.to_s)
289 }
289 }
290
290
291 @available_columns
291 @available_columns
292 end
292 end
293
293
294 def default_columns_names
294 def default_columns_names
295 @default_columns_names ||= begin
295 @default_columns_names ||= begin
296 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
296 default_columns = Setting.issue_list_default_columns.map(&:to_sym)
297
297
298 project.present? ? default_columns : [:project] | default_columns
298 project.present? ? default_columns : [:project] | default_columns
299 end
299 end
300 end
300 end
301
301
302 def base_scope
302 def base_scope
303 Issue.visible.joins(:status, :project).where(statement)
303 Issue.visible.joins(:status, :project).where(statement)
304 end
304 end
305
305
306 # Returns the issue count
306 # Returns the issue count
307 def issue_count
307 def issue_count
308 base_scope.count
308 base_scope.count
309 rescue ::ActiveRecord::StatementInvalid => e
309 rescue ::ActiveRecord::StatementInvalid => e
310 raise StatementInvalid.new(e.message)
310 raise StatementInvalid.new(e.message)
311 end
311 end
312
312
313 # Returns the issue count by group or nil if query is not grouped
313 # Returns the issue count by group or nil if query is not grouped
314 def issue_count_by_group
314 def issue_count_by_group
315 grouped_query do |scope|
315 grouped_query do |scope|
316 scope.count
316 scope.count
317 end
317 end
318 end
318 end
319
319
320 # Returns sum of all the issue's estimated_hours
320 # Returns sum of all the issue's estimated_hours
321 def total_for_estimated_hours(scope)
321 def total_for_estimated_hours(scope)
322 map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
322 map_total(scope.sum(:estimated_hours)) {|t| t.to_f.round(2)}
323 end
323 end
324
324
325 # Returns sum of all the issue's time entries hours
325 # Returns sum of all the issue's time entries hours
326 def total_for_spent_hours(scope)
326 def total_for_spent_hours(scope)
327 total = if group_by_column.try(:name) == :project
327 total = if group_by_column.try(:name) == :project
328 # TODO: remove this when https://github.com/rails/rails/issues/21922 is fixed
328 # TODO: remove this when https://github.com/rails/rails/issues/21922 is fixed
329 # We have to do a custom join without the time_entries.project_id column
329 # We have to do a custom join without the time_entries.project_id column
330 # that would trigger a ambiguous column name error
330 # that would trigger a ambiguous column name error
331 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").
331 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").
332 sum("joined_time_entries.hours")
332 sum("joined_time_entries.hours")
333 else
333 else
334 scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours")
334 scope.joins(:time_entries).sum("#{TimeEntry.table_name}.hours")
335 end
335 end
336 map_total(total) {|t| t.to_f.round(2)}
336 map_total(total) {|t| t.to_f.round(2)}
337 end
337 end
338
338
339 # Returns the issues
339 # Returns the issues
340 # Valid options are :order, :offset, :limit, :include, :conditions
340 # Valid options are :order, :offset, :limit, :include, :conditions
341 def issues(options={})
341 def issues(options={})
342 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
342 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
343
343
344 scope = Issue.visible.
344 scope = Issue.visible.
345 joins(:status, :project).
345 joins(:status, :project).
346 where(statement).
346 where(statement).
347 includes(([:status, :project] + (options[:include] || [])).uniq).
347 includes(([:status, :project] + (options[:include] || [])).uniq).
348 where(options[:conditions]).
348 where(options[:conditions]).
349 order(order_option).
349 order(order_option).
350 joins(joins_for_order_statement(order_option.join(','))).
350 joins(joins_for_order_statement(order_option.join(','))).
351 limit(options[:limit]).
351 limit(options[:limit]).
352 offset(options[:offset])
352 offset(options[:offset])
353
353
354 scope = scope.preload(:custom_values)
354 scope = scope.preload(:custom_values)
355 if has_column?(:author)
355 if has_column?(:author)
356 scope = scope.preload(:author)
356 scope = scope.preload(:author)
357 end
357 end
358
358
359 issues = scope.to_a
359 issues = scope.to_a
360
360
361 if has_column?(:spent_hours)
361 if has_column?(:spent_hours)
362 Issue.load_visible_spent_hours(issues)
362 Issue.load_visible_spent_hours(issues)
363 end
363 end
364 if has_column?(:total_spent_hours)
364 if has_column?(:total_spent_hours)
365 Issue.load_visible_total_spent_hours(issues)
365 Issue.load_visible_total_spent_hours(issues)
366 end
366 end
367 if has_column?(:relations)
367 if has_column?(:relations)
368 Issue.load_visible_relations(issues)
368 Issue.load_visible_relations(issues)
369 end
369 end
370 issues
370 issues
371 rescue ::ActiveRecord::StatementInvalid => e
371 rescue ::ActiveRecord::StatementInvalid => e
372 raise StatementInvalid.new(e.message)
372 raise StatementInvalid.new(e.message)
373 end
373 end
374
374
375 # Returns the issues ids
375 # Returns the issues ids
376 def issue_ids(options={})
376 def issue_ids(options={})
377 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
377 order_option = [group_by_sort_order, options[:order]].flatten.reject(&:blank?)
378
378
379 Issue.visible.
379 Issue.visible.
380 joins(:status, :project).
380 joins(:status, :project).
381 where(statement).
381 where(statement).
382 includes(([:status, :project] + (options[:include] || [])).uniq).
382 includes(([:status, :project] + (options[:include] || [])).uniq).
383 references(([:status, :project] + (options[:include] || [])).uniq).
383 references(([:status, :project] + (options[:include] || [])).uniq).
384 where(options[:conditions]).
384 where(options[:conditions]).
385 order(order_option).
385 order(order_option).
386 joins(joins_for_order_statement(order_option.join(','))).
386 joins(joins_for_order_statement(order_option.join(','))).
387 limit(options[:limit]).
387 limit(options[:limit]).
388 offset(options[:offset]).
388 offset(options[:offset]).
389 pluck(:id)
389 pluck(:id)
390 rescue ::ActiveRecord::StatementInvalid => e
390 rescue ::ActiveRecord::StatementInvalid => e
391 raise StatementInvalid.new(e.message)
391 raise StatementInvalid.new(e.message)
392 end
392 end
393
393
394 # Returns the journals
394 # Returns the journals
395 # Valid options are :order, :offset, :limit
395 # Valid options are :order, :offset, :limit
396 def journals(options={})
396 def journals(options={})
397 Journal.visible.
397 Journal.visible.
398 joins(:issue => [:project, :status]).
398 joins(:issue => [:project, :status]).
399 where(statement).
399 where(statement).
400 order(options[:order]).
400 order(options[:order]).
401 limit(options[:limit]).
401 limit(options[:limit]).
402 offset(options[:offset]).
402 offset(options[:offset]).
403 preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}).
403 preload(:details, :user, {:issue => [:project, :author, :tracker, :status]}).
404 to_a
404 to_a
405 rescue ::ActiveRecord::StatementInvalid => e
405 rescue ::ActiveRecord::StatementInvalid => e
406 raise StatementInvalid.new(e.message)
406 raise StatementInvalid.new(e.message)
407 end
407 end
408
408
409 # Returns the versions
409 # Returns the versions
410 # Valid options are :conditions
410 # Valid options are :conditions
411 def versions(options={})
411 def versions(options={})
412 Version.visible.
412 Version.visible.
413 where(project_statement).
413 where(project_statement).
414 where(options[:conditions]).
414 where(options[:conditions]).
415 includes(:project).
415 includes(:project).
416 references(:project).
416 references(:project).
417 to_a
417 to_a
418 rescue ::ActiveRecord::StatementInvalid => e
418 rescue ::ActiveRecord::StatementInvalid => e
419 raise StatementInvalid.new(e.message)
419 raise StatementInvalid.new(e.message)
420 end
420 end
421
421
422 def sql_for_watcher_id_field(field, operator, value)
422 def sql_for_watcher_id_field(field, operator, value)
423 db_table = Watcher.table_name
423 db_table = Watcher.table_name
424 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
424 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
425 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
425 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
426 end
426 end
427
427
428 def sql_for_member_of_group_field(field, operator, value)
428 def sql_for_member_of_group_field(field, operator, value)
429 if operator == '*' # Any group
429 if operator == '*' # Any group
430 groups = Group.givable
430 groups = Group.givable
431 operator = '=' # Override the operator since we want to find by assigned_to
431 operator = '=' # Override the operator since we want to find by assigned_to
432 elsif operator == "!*"
432 elsif operator == "!*"
433 groups = Group.givable
433 groups = Group.givable
434 operator = '!' # Override the operator since we want to find by assigned_to
434 operator = '!' # Override the operator since we want to find by assigned_to
435 else
435 else
436 groups = Group.where(:id => value).to_a
436 groups = Group.where(:id => value).to_a
437 end
437 end
438 groups ||= []
438 groups ||= []
439
439
440 members_of_groups = groups.inject([]) {|user_ids, group|
440 members_of_groups = groups.inject([]) {|user_ids, group|
441 user_ids + group.user_ids + [group.id]
441 user_ids + group.user_ids + [group.id]
442 }.uniq.compact.sort.collect(&:to_s)
442 }.uniq.compact.sort.collect(&:to_s)
443
443
444 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
444 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
445 end
445 end
446
446
447 def sql_for_assigned_to_role_field(field, operator, value)
447 def sql_for_assigned_to_role_field(field, operator, value)
448 case operator
448 case operator
449 when "*", "!*" # Member / Not member
449 when "*", "!*" # Member / Not member
450 sw = operator == "!*" ? 'NOT' : ''
450 sw = operator == "!*" ? 'NOT' : ''
451 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
451 nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
452 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
452 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" +
453 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
453 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))"
454 when "=", "!"
454 when "=", "!"
455 role_cond = value.any? ?
455 role_cond = value.any? ?
456 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
456 "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{self.class.connection.quote_string(val)}'"}.join(",") + ")" :
457 "1=0"
457 "1=0"
458
458
459 sw = operator == "!" ? 'NOT' : ''
459 sw = operator == "!" ? 'NOT' : ''
460 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
460 nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : ''
461 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
461 "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" +
462 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
462 " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))"
463 end
463 end
464 end
464 end
465
465
466 def sql_for_is_private_field(field, operator, value)
466 def sql_for_is_private_field(field, operator, value)
467 op = (operator == "=" ? 'IN' : 'NOT IN')
467 op = (operator == "=" ? 'IN' : 'NOT IN')
468 va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',')
468 va = value.map {|v| v == '0' ? self.class.connection.quoted_false : self.class.connection.quoted_true}.uniq.join(',')
469
469
470 "#{Issue.table_name}.is_private #{op} (#{va})"
470 "#{Issue.table_name}.is_private #{op} (#{va})"
471 end
471 end
472
472
473 def sql_for_parent_id_field(field, operator, value)
473 def sql_for_parent_id_field(field, operator, value)
474 case operator
474 case operator
475 when "="
475 when "="
476 "#{Issue.table_name}.parent_id = #{value.first.to_i}"
476 "#{Issue.table_name}.parent_id = #{value.first.to_i}"
477 when "~"
477 when "~"
478 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
478 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
479 if root_id && lft && rgt
479 if root_id && lft && rgt
480 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft > #{lft} AND #{Issue.table_name}.rgt < #{rgt}"
480 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft > #{lft} AND #{Issue.table_name}.rgt < #{rgt}"
481 else
481 else
482 "1=0"
482 "1=0"
483 end
483 end
484 when "!*"
484 when "!*"
485 "#{Issue.table_name}.parent_id IS NULL"
485 "#{Issue.table_name}.parent_id IS NULL"
486 when "*"
486 when "*"
487 "#{Issue.table_name}.parent_id IS NOT NULL"
487 "#{Issue.table_name}.parent_id IS NOT NULL"
488 end
488 end
489 end
489 end
490
490
491 def sql_for_child_id_field(field, operator, value)
491 def sql_for_child_id_field(field, operator, value)
492 case operator
492 case operator
493 when "="
493 when "="
494 parent_id = Issue.where(:id => value.first.to_i).pluck(:parent_id).first
494 parent_id = Issue.where(:id => value.first.to_i).pluck(:parent_id).first
495 if parent_id
495 if parent_id
496 "#{Issue.table_name}.id = #{parent_id}"
496 "#{Issue.table_name}.id = #{parent_id}"
497 else
497 else
498 "1=0"
498 "1=0"
499 end
499 end
500 when "~"
500 when "~"
501 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
501 root_id, lft, rgt = Issue.where(:id => value.first.to_i).pluck(:root_id, :lft, :rgt).first
502 if root_id && lft && rgt
502 if root_id && lft && rgt
503 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft < #{lft} AND #{Issue.table_name}.rgt > #{rgt}"
503 "#{Issue.table_name}.root_id = #{root_id} AND #{Issue.table_name}.lft < #{lft} AND #{Issue.table_name}.rgt > #{rgt}"
504 else
504 else
505 "1=0"
505 "1=0"
506 end
506 end
507 when "!*"
507 when "!*"
508 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1"
508 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft = 1"
509 when "*"
509 when "*"
510 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft > 1"
510 "#{Issue.table_name}.rgt - #{Issue.table_name}.lft > 1"
511 end
511 end
512 end
512 end
513
513
514 def sql_for_issue_id_field(field, operator, value)
514 def sql_for_issue_id_field(field, operator, value)
515 ids = value.first.to_s.scan(/\d+/).map(&:to_i).join(",")
515 if operator == "="
516 if ids.present?
516 # accepts a comma separated list of ids
517 "#{Issue.table_name}.id IN (#{ids})"
517 ids = value.first.to_s.scan(/\d+/).map(&:to_i)
518 if ids.present?
519 "#{Issue.table_name}.id IN (#{ids.join(",")})"
520 else
521 "1=0"
522 end
518 else
523 else
519 "1=0"
524 sql_for_field("id", operator, value, Issue.table_name, "id")
520 end
525 end
521 end
526 end
522
527
523 def sql_for_relations(field, operator, value, options={})
528 def sql_for_relations(field, operator, value, options={})
524 relation_options = IssueRelation::TYPES[field]
529 relation_options = IssueRelation::TYPES[field]
525 return relation_options unless relation_options
530 return relation_options unless relation_options
526
531
527 relation_type = field
532 relation_type = field
528 join_column, target_join_column = "issue_from_id", "issue_to_id"
533 join_column, target_join_column = "issue_from_id", "issue_to_id"
529 if relation_options[:reverse] || options[:reverse]
534 if relation_options[:reverse] || options[:reverse]
530 relation_type = relation_options[:reverse] || relation_type
535 relation_type = relation_options[:reverse] || relation_type
531 join_column, target_join_column = target_join_column, join_column
536 join_column, target_join_column = target_join_column, join_column
532 end
537 end
533
538
534 sql = case operator
539 sql = case operator
535 when "*", "!*"
540 when "*", "!*"
536 op = (operator == "*" ? 'IN' : 'NOT IN')
541 op = (operator == "*" ? 'IN' : 'NOT IN')
537 "#{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)}')"
542 "#{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)}')"
538 when "=", "!"
543 when "=", "!"
539 op = (operator == "=" ? 'IN' : 'NOT IN')
544 op = (operator == "=" ? 'IN' : 'NOT IN')
540 "#{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})"
545 "#{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})"
541 when "=p", "=!p", "!p"
546 when "=p", "=!p", "!p"
542 op = (operator == "!p" ? 'NOT IN' : 'IN')
547 op = (operator == "!p" ? 'NOT IN' : 'IN')
543 comp = (operator == "=!p" ? '<>' : '=')
548 comp = (operator == "=!p" ? '<>' : '=')
544 "#{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})"
549 "#{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})"
545 when "*o", "!o"
550 when "*o", "!o"
546 op = (operator == "!o" ? 'NOT IN' : 'IN')
551 op = (operator == "!o" ? 'NOT IN' : 'IN')
547 "#{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}))"
552 "#{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}))"
548 end
553 end
549
554
550 if relation_options[:sym] == field && !options[:reverse]
555 if relation_options[:sym] == field && !options[:reverse]
551 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
556 sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
552 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
557 sql = sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ")
553 end
558 end
554 "(#{sql})"
559 "(#{sql})"
555 end
560 end
556
561
557 def find_assigned_to_id_filter_values(values)
562 def find_assigned_to_id_filter_values(values)
558 Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
563 Principal.visible.where(:id => values).map {|p| [p.name, p.id.to_s]}
559 end
564 end
560 alias :find_author_id_filter_values :find_assigned_to_id_filter_values
565 alias :find_author_id_filter_values :find_assigned_to_id_filter_values
561
566
562 IssueRelation::TYPES.keys.each do |relation_type|
567 IssueRelation::TYPES.keys.each do |relation_type|
563 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
568 alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
564 end
569 end
565 end
570 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.last == '2'}
98 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '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(&:last)
149 assert_include subproject_version.id.to_s, filter[:values].map(&:last)
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