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