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