##// END OF EJS Templates
Include issues asigned to user's groups when using "assigned to me" filter (#2964)....
Jean-Philippe Lang -
r6212:7f77744e86e0
parent child
Show More
@@ -1,773 +1,780
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 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 QueryColumn
18 class QueryColumn
19 attr_accessor :name, :sortable, :groupable, :default_order
19 attr_accessor :name, :sortable, :groupable, :default_order
20 include Redmine::I18n
20 include Redmine::I18n
21
21
22 def initialize(name, options={})
22 def initialize(name, options={})
23 self.name = name
23 self.name = name
24 self.sortable = options[:sortable]
24 self.sortable = options[:sortable]
25 self.groupable = options[:groupable] || false
25 self.groupable = options[:groupable] || false
26 if groupable == true
26 if groupable == true
27 self.groupable = name.to_s
27 self.groupable = name.to_s
28 end
28 end
29 self.default_order = options[:default_order]
29 self.default_order = options[:default_order]
30 @caption_key = options[:caption] || "field_#{name}"
30 @caption_key = options[:caption] || "field_#{name}"
31 end
31 end
32
32
33 def caption
33 def caption
34 l(@caption_key)
34 l(@caption_key)
35 end
35 end
36
36
37 # Returns true if the column is sortable, otherwise false
37 # Returns true if the column is sortable, otherwise false
38 def sortable?
38 def sortable?
39 !sortable.nil?
39 !sortable.nil?
40 end
40 end
41
41
42 def value(issue)
42 def value(issue)
43 issue.send name
43 issue.send name
44 end
44 end
45
45
46 def css_classes
46 def css_classes
47 name
47 name
48 end
48 end
49 end
49 end
50
50
51 class QueryCustomFieldColumn < QueryColumn
51 class QueryCustomFieldColumn < QueryColumn
52
52
53 def initialize(custom_field)
53 def initialize(custom_field)
54 self.name = "cf_#{custom_field.id}".to_sym
54 self.name = "cf_#{custom_field.id}".to_sym
55 self.sortable = custom_field.order_statement || false
55 self.sortable = custom_field.order_statement || false
56 if %w(list date bool int).include?(custom_field.field_format)
56 if %w(list date bool int).include?(custom_field.field_format)
57 self.groupable = custom_field.order_statement
57 self.groupable = custom_field.order_statement
58 end
58 end
59 self.groupable ||= false
59 self.groupable ||= false
60 @cf = custom_field
60 @cf = custom_field
61 end
61 end
62
62
63 def caption
63 def caption
64 @cf.name
64 @cf.name
65 end
65 end
66
66
67 def custom_field
67 def custom_field
68 @cf
68 @cf
69 end
69 end
70
70
71 def value(issue)
71 def value(issue)
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
73 cv && @cf.cast_value(cv.value)
73 cv && @cf.cast_value(cv.value)
74 end
74 end
75
75
76 def css_classes
76 def css_classes
77 @css_classes ||= "#{name} #{@cf.field_format}"
77 @css_classes ||= "#{name} #{@cf.field_format}"
78 end
78 end
79 end
79 end
80
80
81 class Query < ActiveRecord::Base
81 class Query < ActiveRecord::Base
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
82 class StatementInvalid < ::ActiveRecord::StatementInvalid
83 end
83 end
84
84
85 belongs_to :project
85 belongs_to :project
86 belongs_to :user
86 belongs_to :user
87 serialize :filters
87 serialize :filters
88 serialize :column_names
88 serialize :column_names
89 serialize :sort_criteria, Array
89 serialize :sort_criteria, Array
90
90
91 attr_protected :project_id, :user_id
91 attr_protected :project_id, :user_id
92
92
93 validates_presence_of :name, :on => :save
93 validates_presence_of :name, :on => :save
94 validates_length_of :name, :maximum => 255
94 validates_length_of :name, :maximum => 255
95
95
96 @@operators = { "=" => :label_equals,
96 @@operators = { "=" => :label_equals,
97 "!" => :label_not_equals,
97 "!" => :label_not_equals,
98 "o" => :label_open_issues,
98 "o" => :label_open_issues,
99 "c" => :label_closed_issues,
99 "c" => :label_closed_issues,
100 "!*" => :label_none,
100 "!*" => :label_none,
101 "*" => :label_all,
101 "*" => :label_all,
102 ">=" => :label_greater_or_equal,
102 ">=" => :label_greater_or_equal,
103 "<=" => :label_less_or_equal,
103 "<=" => :label_less_or_equal,
104 "><" => :label_between,
104 "><" => :label_between,
105 "<t+" => :label_in_less_than,
105 "<t+" => :label_in_less_than,
106 ">t+" => :label_in_more_than,
106 ">t+" => :label_in_more_than,
107 "t+" => :label_in,
107 "t+" => :label_in,
108 "t" => :label_today,
108 "t" => :label_today,
109 "w" => :label_this_week,
109 "w" => :label_this_week,
110 ">t-" => :label_less_than_ago,
110 ">t-" => :label_less_than_ago,
111 "<t-" => :label_more_than_ago,
111 "<t-" => :label_more_than_ago,
112 "t-" => :label_ago,
112 "t-" => :label_ago,
113 "~" => :label_contains,
113 "~" => :label_contains,
114 "!~" => :label_not_contains }
114 "!~" => :label_not_contains }
115
115
116 cattr_reader :operators
116 cattr_reader :operators
117
117
118 @@operators_by_filter_type = { :list => [ "=", "!" ],
118 @@operators_by_filter_type = { :list => [ "=", "!" ],
119 :list_status => [ "o", "=", "!", "c", "*" ],
119 :list_status => [ "o", "=", "!", "c", "*" ],
120 :list_optional => [ "=", "!", "!*", "*" ],
120 :list_optional => [ "=", "!", "!*", "*" ],
121 :list_subprojects => [ "*", "!*", "=" ],
121 :list_subprojects => [ "*", "!*", "=" ],
122 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
122 :date => [ "=", ">=", "<=", "><", "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
123 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
123 :date_past => [ "=", ">=", "<=", "><", ">t-", "<t-", "t-", "t", "w" ],
124 :string => [ "=", "~", "!", "!~" ],
124 :string => [ "=", "~", "!", "!~" ],
125 :text => [ "~", "!~" ],
125 :text => [ "~", "!~" ],
126 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
126 :integer => [ "=", ">=", "<=", "><", "!*", "*" ],
127 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
127 :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
128
128
129 cattr_reader :operators_by_filter_type
129 cattr_reader :operators_by_filter_type
130
130
131 @@available_columns = [
131 @@available_columns = [
132 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
132 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
133 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
133 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
134 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
134 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
135 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
135 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
136 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
136 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
137 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
137 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
138 QueryColumn.new(:author),
138 QueryColumn.new(:author),
139 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
139 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
140 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
140 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
141 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
141 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
142 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
142 QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
143 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
143 QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
144 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
144 QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
145 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
145 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
146 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
146 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
147 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
147 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
148 ]
148 ]
149 cattr_reader :available_columns
149 cattr_reader :available_columns
150
150
151 named_scope :visible, lambda {|*args|
151 named_scope :visible, lambda {|*args|
152 user = args.shift || User.current
152 user = args.shift || User.current
153 base = Project.allowed_to_condition(user, :view_issues, *args)
153 base = Project.allowed_to_condition(user, :view_issues, *args)
154 user_id = user.logged? ? user.id : 0
154 user_id = user.logged? ? user.id : 0
155 {
155 {
156 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
156 :conditions => ["(#{table_name}.project_id IS NULL OR (#{base})) AND (#{table_name}.is_public = ? OR #{table_name}.user_id = ?)", true, user_id],
157 :include => :project
157 :include => :project
158 }
158 }
159 }
159 }
160
160
161 def initialize(attributes = nil)
161 def initialize(attributes = nil)
162 super attributes
162 super attributes
163 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
163 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
164 end
164 end
165
165
166 def after_initialize
166 def after_initialize
167 # Store the fact that project is nil (used in #editable_by?)
167 # Store the fact that project is nil (used in #editable_by?)
168 @is_for_all = project.nil?
168 @is_for_all = project.nil?
169 end
169 end
170
170
171 def validate
171 def validate
172 filters.each_key do |field|
172 filters.each_key do |field|
173 if values_for(field)
173 if values_for(field)
174 case type_for(field)
174 case type_for(field)
175 when :integer
175 when :integer
176 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
176 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
177 when :float
177 when :float
178 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+(\.\d*)?$/) }
178 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+(\.\d*)?$/) }
179 when :date, :date_past
179 when :date, :date_past
180 case operator_for(field)
180 case operator_for(field)
181 when "=", ">=", "<=", "><"
181 when "=", ">=", "<=", "><"
182 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
182 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && (!v.match(/^\d{4}-\d{2}-\d{2}$/) || (Date.parse(v) rescue nil).nil?) }
183 when ">t-", "<t-", "t-"
183 when ">t-", "<t-", "t-"
184 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
184 errors.add(label_for(field), :invalid) if values_for(field).detect {|v| v.present? && !v.match(/^\d+$/) }
185 end
185 end
186 end
186 end
187 end
187 end
188
188
189 errors.add label_for(field), :blank unless
189 errors.add label_for(field), :blank unless
190 # filter requires one or more values
190 # filter requires one or more values
191 (values_for(field) and !values_for(field).first.blank?) or
191 (values_for(field) and !values_for(field).first.blank?) or
192 # filter doesn't require any value
192 # filter doesn't require any value
193 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
193 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
194 end if filters
194 end if filters
195 end
195 end
196
196
197 # Returns true if the query is visible to +user+ or the current user.
197 # Returns true if the query is visible to +user+ or the current user.
198 def visible?(user=User.current)
198 def visible?(user=User.current)
199 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
199 (project.nil? || user.allowed_to?(:view_issues, project)) && (self.is_public? || self.user_id == user.id)
200 end
200 end
201
201
202 def editable_by?(user)
202 def editable_by?(user)
203 return false unless user
203 return false unless user
204 # Admin can edit them all and regular users can edit their private queries
204 # Admin can edit them all and regular users can edit their private queries
205 return true if user.admin? || (!is_public && self.user_id == user.id)
205 return true if user.admin? || (!is_public && self.user_id == user.id)
206 # Members can not edit public queries that are for all project (only admin is allowed to)
206 # Members can not edit public queries that are for all project (only admin is allowed to)
207 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
207 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
208 end
208 end
209
209
210 def available_filters
210 def available_filters
211 return @available_filters if @available_filters
211 return @available_filters if @available_filters
212
212
213 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
213 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
214
214
215 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
215 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
216 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
216 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
217 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
217 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
218 "subject" => { :type => :text, :order => 8 },
218 "subject" => { :type => :text, :order => 8 },
219 "created_on" => { :type => :date_past, :order => 9 },
219 "created_on" => { :type => :date_past, :order => 9 },
220 "updated_on" => { :type => :date_past, :order => 10 },
220 "updated_on" => { :type => :date_past, :order => 10 },
221 "start_date" => { :type => :date, :order => 11 },
221 "start_date" => { :type => :date, :order => 11 },
222 "due_date" => { :type => :date, :order => 12 },
222 "due_date" => { :type => :date, :order => 12 },
223 "estimated_hours" => { :type => :float, :order => 13 },
223 "estimated_hours" => { :type => :float, :order => 13 },
224 "done_ratio" => { :type => :integer, :order => 14 }}
224 "done_ratio" => { :type => :integer, :order => 14 }}
225
225
226 principals = []
226 principals = []
227 if project
227 if project
228 principals += project.principals.sort
228 principals += project.principals.sort
229 else
229 else
230 all_projects = Project.visible.all
230 all_projects = Project.visible.all
231 if all_projects.any?
231 if all_projects.any?
232 # members of visible projects
232 # members of visible projects
233 principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort
233 principals += Principal.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort
234
234
235 # project filter
235 # project filter
236 project_values = []
236 project_values = []
237 Project.project_tree(all_projects) do |p, level|
237 Project.project_tree(all_projects) do |p, level|
238 prefix = (level > 0 ? ('--' * level + ' ') : '')
238 prefix = (level > 0 ? ('--' * level + ' ') : '')
239 project_values << ["#{prefix}#{p.name}", p.id.to_s]
239 project_values << ["#{prefix}#{p.name}", p.id.to_s]
240 end
240 end
241 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
241 @available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
242 end
242 end
243 end
243 end
244 users = principals.select {|p| p.is_a?(User)}
244 users = principals.select {|p| p.is_a?(User)}
245
245
246 assigned_to_values = []
246 assigned_to_values = []
247 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
247 assigned_to_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
248 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
248 assigned_to_values += (Setting.issue_group_assignment? ? principals : users).collect{|s| [s.name, s.id.to_s] }
249 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
249 @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => assigned_to_values } unless assigned_to_values.empty?
250
250
251 author_values = []
251 author_values = []
252 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
252 author_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
253 author_values += users.collect{|s| [s.name, s.id.to_s] }
253 author_values += users.collect{|s| [s.name, s.id.to_s] }
254 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
254 @available_filters["author_id"] = { :type => :list, :order => 5, :values => author_values } unless author_values.empty?
255
255
256 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
256 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
257 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
257 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
258
258
259 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
259 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
260 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
260 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
261
261
262 if User.current.logged?
262 if User.current.logged?
263 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
263 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
264 end
264 end
265
265
266 if project
266 if project
267 # project specific filters
267 # project specific filters
268 categories = @project.issue_categories.all
268 categories = @project.issue_categories.all
269 unless categories.empty?
269 unless categories.empty?
270 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
270 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } }
271 end
271 end
272 versions = @project.shared_versions.all
272 versions = @project.shared_versions.all
273 unless versions.empty?
273 unless versions.empty?
274 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
274 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
275 end
275 end
276 unless @project.leaf?
276 unless @project.leaf?
277 subprojects = @project.descendants.visible.all
277 subprojects = @project.descendants.visible.all
278 unless subprojects.empty?
278 unless subprojects.empty?
279 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
279 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } }
280 end
280 end
281 end
281 end
282 add_custom_fields_filters(@project.all_issue_custom_fields)
282 add_custom_fields_filters(@project.all_issue_custom_fields)
283 else
283 else
284 # global filters for cross project issue list
284 # global filters for cross project issue list
285 system_shared_versions = Version.visible.find_all_by_sharing('system')
285 system_shared_versions = Version.visible.find_all_by_sharing('system')
286 unless system_shared_versions.empty?
286 unless system_shared_versions.empty?
287 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
287 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
288 end
288 end
289 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
289 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
290 end
290 end
291 @available_filters
291 @available_filters
292 end
292 end
293
293
294 def add_filter(field, operator, values)
294 def add_filter(field, operator, values)
295 # values must be an array
295 # values must be an array
296 return unless values.nil? || values.is_a?(Array)
296 return unless values.nil? || values.is_a?(Array)
297 # check if field is defined as an available filter
297 # check if field is defined as an available filter
298 if available_filters.has_key? field
298 if available_filters.has_key? field
299 filter_options = available_filters[field]
299 filter_options = available_filters[field]
300 # check if operator is allowed for that filter
300 # check if operator is allowed for that filter
301 #if @@operators_by_filter_type[filter_options[:type]].include? operator
301 #if @@operators_by_filter_type[filter_options[:type]].include? operator
302 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
302 # allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
303 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
303 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
304 #end
304 #end
305 filters[field] = {:operator => operator, :values => (values || [''])}
305 filters[field] = {:operator => operator, :values => (values || [''])}
306 end
306 end
307 end
307 end
308
308
309 def add_short_filter(field, expression)
309 def add_short_filter(field, expression)
310 return unless expression
310 return unless expression
311 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
311 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
312 add_filter field, (parms[0] || "="), [parms[1] || ""]
312 add_filter field, (parms[0] || "="), [parms[1] || ""]
313 end
313 end
314
314
315 # Add multiple filters using +add_filter+
315 # Add multiple filters using +add_filter+
316 def add_filters(fields, operators, values)
316 def add_filters(fields, operators, values)
317 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
317 if fields.is_a?(Array) && operators.is_a?(Hash) && (values.nil? || values.is_a?(Hash))
318 fields.each do |field|
318 fields.each do |field|
319 add_filter(field, operators[field], values && values[field])
319 add_filter(field, operators[field], values && values[field])
320 end
320 end
321 end
321 end
322 end
322 end
323
323
324 def has_filter?(field)
324 def has_filter?(field)
325 filters and filters[field]
325 filters and filters[field]
326 end
326 end
327
327
328 def type_for(field)
328 def type_for(field)
329 available_filters[field][:type] if available_filters.has_key?(field)
329 available_filters[field][:type] if available_filters.has_key?(field)
330 end
330 end
331
331
332 def operator_for(field)
332 def operator_for(field)
333 has_filter?(field) ? filters[field][:operator] : nil
333 has_filter?(field) ? filters[field][:operator] : nil
334 end
334 end
335
335
336 def values_for(field)
336 def values_for(field)
337 has_filter?(field) ? filters[field][:values] : nil
337 has_filter?(field) ? filters[field][:values] : nil
338 end
338 end
339
339
340 def value_for(field, index=0)
340 def value_for(field, index=0)
341 (values_for(field) || [])[index]
341 (values_for(field) || [])[index]
342 end
342 end
343
343
344 def label_for(field)
344 def label_for(field)
345 label = available_filters[field][:name] if available_filters.has_key?(field)
345 label = available_filters[field][:name] if available_filters.has_key?(field)
346 label ||= field.gsub(/\_id$/, "")
346 label ||= field.gsub(/\_id$/, "")
347 end
347 end
348
348
349 def available_columns
349 def available_columns
350 return @available_columns if @available_columns
350 return @available_columns if @available_columns
351 @available_columns = Query.available_columns
351 @available_columns = Query.available_columns
352 @available_columns += (project ?
352 @available_columns += (project ?
353 project.all_issue_custom_fields :
353 project.all_issue_custom_fields :
354 IssueCustomField.find(:all)
354 IssueCustomField.find(:all)
355 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
355 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
356 end
356 end
357
357
358 def self.available_columns=(v)
358 def self.available_columns=(v)
359 self.available_columns = (v)
359 self.available_columns = (v)
360 end
360 end
361
361
362 def self.add_available_column(column)
362 def self.add_available_column(column)
363 self.available_columns << (column) if column.is_a?(QueryColumn)
363 self.available_columns << (column) if column.is_a?(QueryColumn)
364 end
364 end
365
365
366 # Returns an array of columns that can be used to group the results
366 # Returns an array of columns that can be used to group the results
367 def groupable_columns
367 def groupable_columns
368 available_columns.select {|c| c.groupable}
368 available_columns.select {|c| c.groupable}
369 end
369 end
370
370
371 # Returns a Hash of columns and the key for sorting
371 # Returns a Hash of columns and the key for sorting
372 def sortable_columns
372 def sortable_columns
373 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
373 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
374 h[column.name.to_s] = column.sortable
374 h[column.name.to_s] = column.sortable
375 h
375 h
376 })
376 })
377 end
377 end
378
378
379 def columns
379 def columns
380 if has_default_columns?
380 if has_default_columns?
381 available_columns.select do |c|
381 available_columns.select do |c|
382 # Adds the project column by default for cross-project lists
382 # Adds the project column by default for cross-project lists
383 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
383 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
384 end
384 end
385 else
385 else
386 # preserve the column_names order
386 # preserve the column_names order
387 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
387 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
388 end
388 end
389 end
389 end
390
390
391 def column_names=(names)
391 def column_names=(names)
392 if names
392 if names
393 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
393 names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
394 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
394 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
395 # Set column_names to nil if default columns
395 # Set column_names to nil if default columns
396 if names.map(&:to_s) == Setting.issue_list_default_columns
396 if names.map(&:to_s) == Setting.issue_list_default_columns
397 names = nil
397 names = nil
398 end
398 end
399 end
399 end
400 write_attribute(:column_names, names)
400 write_attribute(:column_names, names)
401 end
401 end
402
402
403 def has_column?(column)
403 def has_column?(column)
404 column_names && column_names.include?(column.name)
404 column_names && column_names.include?(column.name)
405 end
405 end
406
406
407 def has_default_columns?
407 def has_default_columns?
408 column_names.nil? || column_names.empty?
408 column_names.nil? || column_names.empty?
409 end
409 end
410
410
411 def sort_criteria=(arg)
411 def sort_criteria=(arg)
412 c = []
412 c = []
413 if arg.is_a?(Hash)
413 if arg.is_a?(Hash)
414 arg = arg.keys.sort.collect {|k| arg[k]}
414 arg = arg.keys.sort.collect {|k| arg[k]}
415 end
415 end
416 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
416 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
417 write_attribute(:sort_criteria, c)
417 write_attribute(:sort_criteria, c)
418 end
418 end
419
419
420 def sort_criteria
420 def sort_criteria
421 read_attribute(:sort_criteria) || []
421 read_attribute(:sort_criteria) || []
422 end
422 end
423
423
424 def sort_criteria_key(arg)
424 def sort_criteria_key(arg)
425 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
425 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
426 end
426 end
427
427
428 def sort_criteria_order(arg)
428 def sort_criteria_order(arg)
429 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
429 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
430 end
430 end
431
431
432 # Returns the SQL sort order that should be prepended for grouping
432 # Returns the SQL sort order that should be prepended for grouping
433 def group_by_sort_order
433 def group_by_sort_order
434 if grouped? && (column = group_by_column)
434 if grouped? && (column = group_by_column)
435 column.sortable.is_a?(Array) ?
435 column.sortable.is_a?(Array) ?
436 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
436 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
437 "#{column.sortable} #{column.default_order}"
437 "#{column.sortable} #{column.default_order}"
438 end
438 end
439 end
439 end
440
440
441 # Returns true if the query is a grouped query
441 # Returns true if the query is a grouped query
442 def grouped?
442 def grouped?
443 !group_by_column.nil?
443 !group_by_column.nil?
444 end
444 end
445
445
446 def group_by_column
446 def group_by_column
447 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
447 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
448 end
448 end
449
449
450 def group_by_statement
450 def group_by_statement
451 group_by_column.try(:groupable)
451 group_by_column.try(:groupable)
452 end
452 end
453
453
454 def project_statement
454 def project_statement
455 project_clauses = []
455 project_clauses = []
456 if project && !@project.descendants.active.empty?
456 if project && !@project.descendants.active.empty?
457 ids = [project.id]
457 ids = [project.id]
458 if has_filter?("subproject_id")
458 if has_filter?("subproject_id")
459 case operator_for("subproject_id")
459 case operator_for("subproject_id")
460 when '='
460 when '='
461 # include the selected subprojects
461 # include the selected subprojects
462 ids += values_for("subproject_id").each(&:to_i)
462 ids += values_for("subproject_id").each(&:to_i)
463 when '!*'
463 when '!*'
464 # main project only
464 # main project only
465 else
465 else
466 # all subprojects
466 # all subprojects
467 ids += project.descendants.collect(&:id)
467 ids += project.descendants.collect(&:id)
468 end
468 end
469 elsif Setting.display_subprojects_issues?
469 elsif Setting.display_subprojects_issues?
470 ids += project.descendants.collect(&:id)
470 ids += project.descendants.collect(&:id)
471 end
471 end
472 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
472 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
473 elsif project
473 elsif project
474 project_clauses << "#{Project.table_name}.id = %d" % project.id
474 project_clauses << "#{Project.table_name}.id = %d" % project.id
475 end
475 end
476 project_clauses.any? ? project_clauses.join(' AND ') : nil
476 project_clauses.any? ? project_clauses.join(' AND ') : nil
477 end
477 end
478
478
479 def statement
479 def statement
480 # filters clauses
480 # filters clauses
481 filters_clauses = []
481 filters_clauses = []
482 filters.each_key do |field|
482 filters.each_key do |field|
483 next if field == "subproject_id"
483 next if field == "subproject_id"
484 v = values_for(field).clone
484 v = values_for(field).clone
485 next unless v and !v.empty?
485 next unless v and !v.empty?
486 operator = operator_for(field)
486 operator = operator_for(field)
487
487
488 # "me" value subsitution
488 # "me" value subsitution
489 if %w(assigned_to_id author_id watcher_id).include?(field)
489 if %w(assigned_to_id author_id watcher_id).include?(field)
490 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
490 if v.delete("me")
491 if User.current.logged?
492 v.push(User.current.id.to_s)
493 v += User.current.group_ids.map(&:to_s) if field == 'assigned_to_id'
494 else
495 v.push("0")
496 end
497 end
491 end
498 end
492
499
493 if field =~ /^cf_(\d+)$/
500 if field =~ /^cf_(\d+)$/
494 # custom field
501 # custom field
495 filters_clauses << sql_for_custom_field(field, operator, v, $1)
502 filters_clauses << sql_for_custom_field(field, operator, v, $1)
496 elsif respond_to?("sql_for_#{field}_field")
503 elsif respond_to?("sql_for_#{field}_field")
497 # specific statement
504 # specific statement
498 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
505 filters_clauses << send("sql_for_#{field}_field", field, operator, v)
499 else
506 else
500 # regular field
507 # regular field
501 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
508 filters_clauses << '(' + sql_for_field(field, operator, v, Issue.table_name, field) + ')'
502 end
509 end
503 end if filters and valid?
510 end if filters and valid?
504
511
505 filters_clauses << project_statement
512 filters_clauses << project_statement
506 filters_clauses.reject!(&:blank?)
513 filters_clauses.reject!(&:blank?)
507
514
508 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
515 filters_clauses.any? ? filters_clauses.join(' AND ') : nil
509 end
516 end
510
517
511 # Returns the issue count
518 # Returns the issue count
512 def issue_count
519 def issue_count
513 Issue.visible.count(:include => [:status, :project], :conditions => statement)
520 Issue.visible.count(:include => [:status, :project], :conditions => statement)
514 rescue ::ActiveRecord::StatementInvalid => e
521 rescue ::ActiveRecord::StatementInvalid => e
515 raise StatementInvalid.new(e.message)
522 raise StatementInvalid.new(e.message)
516 end
523 end
517
524
518 # Returns the issue count by group or nil if query is not grouped
525 # Returns the issue count by group or nil if query is not grouped
519 def issue_count_by_group
526 def issue_count_by_group
520 r = nil
527 r = nil
521 if grouped?
528 if grouped?
522 begin
529 begin
523 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
530 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
524 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
531 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
525 rescue ActiveRecord::RecordNotFound
532 rescue ActiveRecord::RecordNotFound
526 r = {nil => issue_count}
533 r = {nil => issue_count}
527 end
534 end
528 c = group_by_column
535 c = group_by_column
529 if c.is_a?(QueryCustomFieldColumn)
536 if c.is_a?(QueryCustomFieldColumn)
530 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
537 r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
531 end
538 end
532 end
539 end
533 r
540 r
534 rescue ::ActiveRecord::StatementInvalid => e
541 rescue ::ActiveRecord::StatementInvalid => e
535 raise StatementInvalid.new(e.message)
542 raise StatementInvalid.new(e.message)
536 end
543 end
537
544
538 # Returns the issues
545 # Returns the issues
539 # Valid options are :order, :offset, :limit, :include, :conditions
546 # Valid options are :order, :offset, :limit, :include, :conditions
540 def issues(options={})
547 def issues(options={})
541 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
548 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
542 order_option = nil if order_option.blank?
549 order_option = nil if order_option.blank?
543
550
544 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
551 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
545 :conditions => Query.merge_conditions(statement, options[:conditions]),
552 :conditions => Query.merge_conditions(statement, options[:conditions]),
546 :order => order_option,
553 :order => order_option,
547 :limit => options[:limit],
554 :limit => options[:limit],
548 :offset => options[:offset]
555 :offset => options[:offset]
549 rescue ::ActiveRecord::StatementInvalid => e
556 rescue ::ActiveRecord::StatementInvalid => e
550 raise StatementInvalid.new(e.message)
557 raise StatementInvalid.new(e.message)
551 end
558 end
552
559
553 # Returns the journals
560 # Returns the journals
554 # Valid options are :order, :offset, :limit
561 # Valid options are :order, :offset, :limit
555 def journals(options={})
562 def journals(options={})
556 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
563 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
557 :conditions => statement,
564 :conditions => statement,
558 :order => options[:order],
565 :order => options[:order],
559 :limit => options[:limit],
566 :limit => options[:limit],
560 :offset => options[:offset]
567 :offset => options[:offset]
561 rescue ::ActiveRecord::StatementInvalid => e
568 rescue ::ActiveRecord::StatementInvalid => e
562 raise StatementInvalid.new(e.message)
569 raise StatementInvalid.new(e.message)
563 end
570 end
564
571
565 # Returns the versions
572 # Returns the versions
566 # Valid options are :conditions
573 # Valid options are :conditions
567 def versions(options={})
574 def versions(options={})
568 Version.visible.find :all, :include => :project,
575 Version.visible.find :all, :include => :project,
569 :conditions => Query.merge_conditions(project_statement, options[:conditions])
576 :conditions => Query.merge_conditions(project_statement, options[:conditions])
570 rescue ::ActiveRecord::StatementInvalid => e
577 rescue ::ActiveRecord::StatementInvalid => e
571 raise StatementInvalid.new(e.message)
578 raise StatementInvalid.new(e.message)
572 end
579 end
573
580
574 def sql_for_watcher_id_field(field, operator, value)
581 def sql_for_watcher_id_field(field, operator, value)
575 db_table = Watcher.table_name
582 db_table = Watcher.table_name
576 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
583 "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " +
577 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
584 sql_for_field(field, '=', value, db_table, 'user_id') + ')'
578 end
585 end
579
586
580 def sql_for_member_of_group_field(field, operator, value)
587 def sql_for_member_of_group_field(field, operator, value)
581 if operator == '*' # Any group
588 if operator == '*' # Any group
582 groups = Group.all
589 groups = Group.all
583 operator = '=' # Override the operator since we want to find by assigned_to
590 operator = '=' # Override the operator since we want to find by assigned_to
584 elsif operator == "!*"
591 elsif operator == "!*"
585 groups = Group.all
592 groups = Group.all
586 operator = '!' # Override the operator since we want to find by assigned_to
593 operator = '!' # Override the operator since we want to find by assigned_to
587 else
594 else
588 groups = Group.find_all_by_id(value)
595 groups = Group.find_all_by_id(value)
589 end
596 end
590 groups ||= []
597 groups ||= []
591
598
592 members_of_groups = groups.inject([]) {|user_ids, group|
599 members_of_groups = groups.inject([]) {|user_ids, group|
593 if group && group.user_ids.present?
600 if group && group.user_ids.present?
594 user_ids << group.user_ids
601 user_ids << group.user_ids
595 end
602 end
596 user_ids.flatten.uniq.compact
603 user_ids.flatten.uniq.compact
597 }.sort.collect(&:to_s)
604 }.sort.collect(&:to_s)
598
605
599 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
606 '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
600 end
607 end
601
608
602 def sql_for_assigned_to_role_field(field, operator, value)
609 def sql_for_assigned_to_role_field(field, operator, value)
603 if operator == "*" # Any Role
610 if operator == "*" # Any Role
604 roles = Role.givable
611 roles = Role.givable
605 operator = '=' # Override the operator since we want to find by assigned_to
612 operator = '=' # Override the operator since we want to find by assigned_to
606 elsif operator == "!*" # No role
613 elsif operator == "!*" # No role
607 roles = Role.givable
614 roles = Role.givable
608 operator = '!' # Override the operator since we want to find by assigned_to
615 operator = '!' # Override the operator since we want to find by assigned_to
609 else
616 else
610 roles = Role.givable.find_all_by_id(value)
617 roles = Role.givable.find_all_by_id(value)
611 end
618 end
612 roles ||= []
619 roles ||= []
613
620
614 members_of_roles = roles.inject([]) {|user_ids, role|
621 members_of_roles = roles.inject([]) {|user_ids, role|
615 if role && role.members
622 if role && role.members
616 user_ids << role.members.collect(&:user_id)
623 user_ids << role.members.collect(&:user_id)
617 end
624 end
618 user_ids.flatten.uniq.compact
625 user_ids.flatten.uniq.compact
619 }.sort.collect(&:to_s)
626 }.sort.collect(&:to_s)
620
627
621 '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
628 '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
622 end
629 end
623
630
624 private
631 private
625
632
626 def sql_for_custom_field(field, operator, value, custom_field_id)
633 def sql_for_custom_field(field, operator, value, custom_field_id)
627 db_table = CustomValue.table_name
634 db_table = CustomValue.table_name
628 db_field = 'value'
635 db_field = 'value'
629 "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
636 "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " +
630 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
637 sql_for_field(field, operator, value, db_table, db_field, true) + ')'
631 end
638 end
632
639
633 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
640 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
634 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
641 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
635 sql = ''
642 sql = ''
636 case operator
643 case operator
637 when "="
644 when "="
638 if value.any?
645 if value.any?
639 case type_for(field)
646 case type_for(field)
640 when :date, :date_past
647 when :date, :date_past
641 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
648 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), (Date.parse(value.first) rescue nil))
642 when :integer
649 when :integer
643 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
650 sql = "#{db_table}.#{db_field} = #{value.first.to_i}"
644 when :float
651 when :float
645 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
652 sql = "#{db_table}.#{db_field} BETWEEN #{value.first.to_f - 1e-5} AND #{value.first.to_f + 1e-5}"
646 else
653 else
647 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
654 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
648 end
655 end
649 else
656 else
650 # IN an empty set
657 # IN an empty set
651 sql = "1=0"
658 sql = "1=0"
652 end
659 end
653 when "!"
660 when "!"
654 if value.any?
661 if value.any?
655 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
662 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
656 else
663 else
657 # NOT IN an empty set
664 # NOT IN an empty set
658 sql = "1=1"
665 sql = "1=1"
659 end
666 end
660 when "!*"
667 when "!*"
661 sql = "#{db_table}.#{db_field} IS NULL"
668 sql = "#{db_table}.#{db_field} IS NULL"
662 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
669 sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
663 when "*"
670 when "*"
664 sql = "#{db_table}.#{db_field} IS NOT NULL"
671 sql = "#{db_table}.#{db_field} IS NOT NULL"
665 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
672 sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
666 when ">="
673 when ">="
667 if [:date, :date_past].include?(type_for(field))
674 if [:date, :date_past].include?(type_for(field))
668 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
675 sql = date_clause(db_table, db_field, (Date.parse(value.first) rescue nil), nil)
669 else
676 else
670 if is_custom_filter
677 if is_custom_filter
671 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f}"
678 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) >= #{value.first.to_f}"
672 else
679 else
673 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
680 sql = "#{db_table}.#{db_field} >= #{value.first.to_f}"
674 end
681 end
675 end
682 end
676 when "<="
683 when "<="
677 if [:date, :date_past].include?(type_for(field))
684 if [:date, :date_past].include?(type_for(field))
678 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
685 sql = date_clause(db_table, db_field, nil, (Date.parse(value.first) rescue nil))
679 else
686 else
680 if is_custom_filter
687 if is_custom_filter
681 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f}"
688 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) <= #{value.first.to_f}"
682 else
689 else
683 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
690 sql = "#{db_table}.#{db_field} <= #{value.first.to_f}"
684 end
691 end
685 end
692 end
686 when "><"
693 when "><"
687 if [:date, :date_past].include?(type_for(field))
694 if [:date, :date_past].include?(type_for(field))
688 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
695 sql = date_clause(db_table, db_field, (Date.parse(value[0]) rescue nil), (Date.parse(value[1]) rescue nil))
689 else
696 else
690 if is_custom_filter
697 if is_custom_filter
691 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
698 sql = "CAST(#{db_table}.#{db_field} AS decimal(60,3)) BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
692 else
699 else
693 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
700 sql = "#{db_table}.#{db_field} BETWEEN #{value[0].to_f} AND #{value[1].to_f}"
694 end
701 end
695 end
702 end
696 when "o"
703 when "o"
697 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
704 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
698 when "c"
705 when "c"
699 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
706 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
700 when ">t-"
707 when ">t-"
701 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
708 sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0)
702 when "<t-"
709 when "<t-"
703 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
710 sql = relative_date_clause(db_table, db_field, nil, - value.first.to_i)
704 when "t-"
711 when "t-"
705 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
712 sql = relative_date_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
706 when ">t+"
713 when ">t+"
707 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
714 sql = relative_date_clause(db_table, db_field, value.first.to_i, nil)
708 when "<t+"
715 when "<t+"
709 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
716 sql = relative_date_clause(db_table, db_field, 0, value.first.to_i)
710 when "t+"
717 when "t+"
711 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
718 sql = relative_date_clause(db_table, db_field, value.first.to_i, value.first.to_i)
712 when "t"
719 when "t"
713 sql = relative_date_clause(db_table, db_field, 0, 0)
720 sql = relative_date_clause(db_table, db_field, 0, 0)
714 when "w"
721 when "w"
715 first_day_of_week = l(:general_first_day_of_week).to_i
722 first_day_of_week = l(:general_first_day_of_week).to_i
716 day_of_week = Date.today.cwday
723 day_of_week = Date.today.cwday
717 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
724 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
718 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
725 sql = relative_date_clause(db_table, db_field, - days_ago, - days_ago + 6)
719 when "~"
726 when "~"
720 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
727 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
721 when "!~"
728 when "!~"
722 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
729 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
723 else
730 else
724 raise "Unknown query operator #{operator}"
731 raise "Unknown query operator #{operator}"
725 end
732 end
726
733
727 return sql
734 return sql
728 end
735 end
729
736
730 def add_custom_fields_filters(custom_fields)
737 def add_custom_fields_filters(custom_fields)
731 @available_filters ||= {}
738 @available_filters ||= {}
732
739
733 custom_fields.select(&:is_filter?).each do |field|
740 custom_fields.select(&:is_filter?).each do |field|
734 case field.field_format
741 case field.field_format
735 when "text"
742 when "text"
736 options = { :type => :text, :order => 20 }
743 options = { :type => :text, :order => 20 }
737 when "list"
744 when "list"
738 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
745 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
739 when "date"
746 when "date"
740 options = { :type => :date, :order => 20 }
747 options = { :type => :date, :order => 20 }
741 when "bool"
748 when "bool"
742 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
749 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
743 when "int"
750 when "int"
744 options = { :type => :integer, :order => 20 }
751 options = { :type => :integer, :order => 20 }
745 when "float"
752 when "float"
746 options = { :type => :float, :order => 20 }
753 options = { :type => :float, :order => 20 }
747 when "user", "version"
754 when "user", "version"
748 next unless project
755 next unless project
749 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
756 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20}
750 else
757 else
751 options = { :type => :string, :order => 20 }
758 options = { :type => :string, :order => 20 }
752 end
759 end
753 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
760 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
754 end
761 end
755 end
762 end
756
763
757 # Returns a SQL clause for a date or datetime field.
764 # Returns a SQL clause for a date or datetime field.
758 def date_clause(table, field, from, to)
765 def date_clause(table, field, from, to)
759 s = []
766 s = []
760 if from
767 if from
761 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((from - 1).to_time.end_of_day)])
768 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((from - 1).to_time.end_of_day)])
762 end
769 end
763 if to
770 if to
764 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to.to_time.end_of_day)])
771 s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date(to.to_time.end_of_day)])
765 end
772 end
766 s.join(' AND ')
773 s.join(' AND ')
767 end
774 end
768
775
769 # Returns a SQL clause for a date or datetime field using relative dates.
776 # Returns a SQL clause for a date or datetime field using relative dates.
770 def relative_date_clause(table, field, days_from, days_to)
777 def relative_date_clause(table, field, days_from, days_to)
771 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
778 date_clause(table, field, (days_from ? Date.today + days_from : nil), (days_to ? Date.today + days_to : nil))
772 end
779 end
773 end
780 end
@@ -1,22 +1,22
1 <h3><%=l(:label_assigned_to_me_issues)%> (<%= Issue.visible.open.count(:conditions => {:assigned_to_id => User.current.id})%>)</h3>
1 <h3><%=l(:label_assigned_to_me_issues)%> (<%= Issue.visible.open.count(:conditions => {:assigned_to_id => ([User.current.id] + User.current.group_ids)})%>)</h3>
2
2
3 <% assigned_issues = Issue.visible.open.find(:all,
3 <% assigned_issues = Issue.visible.open.find(:all,
4 :conditions => {:assigned_to_id => User.current.id},
4 :conditions => {:assigned_to_id => ([User.current.id] + User.current.group_ids)},
5 :limit => 10,
5 :limit => 10,
6 :include => [ :status, :project, :tracker, :priority ],
6 :include => [ :status, :project, :tracker, :priority ],
7 :order => "#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC") %>
7 :order => "#{IssuePriority.table_name}.position DESC, #{Issue.table_name}.updated_on DESC") %>
8 <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
8 <%= render :partial => 'issues/list_simple', :locals => { :issues => assigned_issues } %>
9 <% if assigned_issues.length > 0 %>
9 <% if assigned_issues.length > 0 %>
10 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues',
10 <p class="small"><%= link_to l(:label_issue_view_all), :controller => 'issues',
11 :action => 'index',
11 :action => 'index',
12 :set_filter => 1,
12 :set_filter => 1,
13 :assigned_to_id => 'me',
13 :assigned_to_id => 'me',
14 :sort => 'priority:desc,updated_on:desc' %></p>
14 :sort => 'priority:desc,updated_on:desc' %></p>
15 <% end %>
15 <% end %>
16
16
17 <% content_for :header_tags do %>
17 <% content_for :header_tags do %>
18 <%= auto_discovery_link_tag(:atom,
18 <%= auto_discovery_link_tag(:atom,
19 {:controller => 'issues', :action => 'index', :set_filter => 1,
19 {:controller => 'issues', :action => 'index', :set_filter => 1,
20 :assigned_to_id => 'me', :format => 'atom', :key => User.current.rss_key},
20 :assigned_to_id => 'me', :format => 'atom', :key => User.current.rss_key},
21 {:title => l(:label_assigned_to_me_issues)}) %>
21 {:title => l(:label_assigned_to_me_issues)}) %>
22 <% end %>
22 <% end %>
@@ -1,746 +1,764
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
2 # Copyright (C) 2006-2011 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
17
18 require File.expand_path('../../test_helper', __FILE__)
18 require File.expand_path('../../test_helper', __FILE__)
19
19
20 class QueryTest < ActiveSupport::TestCase
20 class QueryTest < ActiveSupport::TestCase
21 fixtures :projects, :enabled_modules, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries
21 fixtures :projects, :enabled_modules, :users, :members, :member_roles, :roles, :trackers, :issue_statuses, :issue_categories, :enumerations, :issues, :watchers, :custom_fields, :custom_values, :versions, :queries
22
22
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
23 def test_custom_fields_for_all_projects_should_be_available_in_global_queries
24 query = Query.new(:project => nil, :name => '_')
24 query = Query.new(:project => nil, :name => '_')
25 assert query.available_filters.has_key?('cf_1')
25 assert query.available_filters.has_key?('cf_1')
26 assert !query.available_filters.has_key?('cf_3')
26 assert !query.available_filters.has_key?('cf_3')
27 end
27 end
28
28
29 def test_system_shared_versions_should_be_available_in_global_queries
29 def test_system_shared_versions_should_be_available_in_global_queries
30 Version.find(2).update_attribute :sharing, 'system'
30 Version.find(2).update_attribute :sharing, 'system'
31 query = Query.new(:project => nil, :name => '_')
31 query = Query.new(:project => nil, :name => '_')
32 assert query.available_filters.has_key?('fixed_version_id')
32 assert query.available_filters.has_key?('fixed_version_id')
33 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
33 assert query.available_filters['fixed_version_id'][:values].detect {|v| v.last == '2'}
34 end
34 end
35
35
36 def test_project_filter_in_global_queries
36 def test_project_filter_in_global_queries
37 query = Query.new(:project => nil, :name => '_')
37 query = Query.new(:project => nil, :name => '_')
38 project_filter = query.available_filters["project_id"]
38 project_filter = query.available_filters["project_id"]
39 assert_not_nil project_filter
39 assert_not_nil project_filter
40 project_ids = project_filter[:values].map{|p| p[1]}
40 project_ids = project_filter[:values].map{|p| p[1]}
41 assert project_ids.include?("1") #public project
41 assert project_ids.include?("1") #public project
42 assert !project_ids.include?("2") #private project user cannot see
42 assert !project_ids.include?("2") #private project user cannot see
43 end
43 end
44
44
45 def find_issues_with_query(query)
45 def find_issues_with_query(query)
46 Issue.find :all,
46 Issue.find :all,
47 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
47 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
48 :conditions => query.statement
48 :conditions => query.statement
49 end
49 end
50
50
51 def assert_find_issues_with_query_is_successful(query)
51 def assert_find_issues_with_query_is_successful(query)
52 assert_nothing_raised do
52 assert_nothing_raised do
53 find_issues_with_query(query)
53 find_issues_with_query(query)
54 end
54 end
55 end
55 end
56
56
57 def assert_query_statement_includes(query, condition)
57 def assert_query_statement_includes(query, condition)
58 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
58 assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
59 end
59 end
60
60
61 def test_query_should_allow_shared_versions_for_a_project_query
61 def test_query_should_allow_shared_versions_for_a_project_query
62 subproject_version = Version.find(4)
62 subproject_version = Version.find(4)
63 query = Query.new(:project => Project.find(1), :name => '_')
63 query = Query.new(:project => Project.find(1), :name => '_')
64 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
64 query.add_filter('fixed_version_id', '=', [subproject_version.id.to_s])
65
65
66 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
66 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IN ('4')")
67 end
67 end
68
68
69 def test_query_with_multiple_custom_fields
69 def test_query_with_multiple_custom_fields
70 query = Query.find(1)
70 query = Query.find(1)
71 assert query.valid?
71 assert query.valid?
72 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
72 assert query.statement.include?("#{CustomValue.table_name}.value IN ('MySQL')")
73 issues = find_issues_with_query(query)
73 issues = find_issues_with_query(query)
74 assert_equal 1, issues.length
74 assert_equal 1, issues.length
75 assert_equal Issue.find(3), issues.first
75 assert_equal Issue.find(3), issues.first
76 end
76 end
77
77
78 def test_operator_none
78 def test_operator_none
79 query = Query.new(:project => Project.find(1), :name => '_')
79 query = Query.new(:project => Project.find(1), :name => '_')
80 query.add_filter('fixed_version_id', '!*', [''])
80 query.add_filter('fixed_version_id', '!*', [''])
81 query.add_filter('cf_1', '!*', [''])
81 query.add_filter('cf_1', '!*', [''])
82 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
82 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NULL")
83 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
83 assert query.statement.include?("#{CustomValue.table_name}.value IS NULL OR #{CustomValue.table_name}.value = ''")
84 find_issues_with_query(query)
84 find_issues_with_query(query)
85 end
85 end
86
86
87 def test_operator_none_for_integer
87 def test_operator_none_for_integer
88 query = Query.new(:project => Project.find(1), :name => '_')
88 query = Query.new(:project => Project.find(1), :name => '_')
89 query.add_filter('estimated_hours', '!*', [''])
89 query.add_filter('estimated_hours', '!*', [''])
90 issues = find_issues_with_query(query)
90 issues = find_issues_with_query(query)
91 assert !issues.empty?
91 assert !issues.empty?
92 assert issues.all? {|i| !i.estimated_hours}
92 assert issues.all? {|i| !i.estimated_hours}
93 end
93 end
94
94
95 def test_operator_all
95 def test_operator_all
96 query = Query.new(:project => Project.find(1), :name => '_')
96 query = Query.new(:project => Project.find(1), :name => '_')
97 query.add_filter('fixed_version_id', '*', [''])
97 query.add_filter('fixed_version_id', '*', [''])
98 query.add_filter('cf_1', '*', [''])
98 query.add_filter('cf_1', '*', [''])
99 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
99 assert query.statement.include?("#{Issue.table_name}.fixed_version_id IS NOT NULL")
100 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
100 assert query.statement.include?("#{CustomValue.table_name}.value IS NOT NULL AND #{CustomValue.table_name}.value <> ''")
101 find_issues_with_query(query)
101 find_issues_with_query(query)
102 end
102 end
103
103
104 def test_numeric_filter_should_not_accept_non_numeric_values
104 def test_numeric_filter_should_not_accept_non_numeric_values
105 query = Query.new(:name => '_')
105 query = Query.new(:name => '_')
106 query.add_filter('estimated_hours', '=', ['a'])
106 query.add_filter('estimated_hours', '=', ['a'])
107
107
108 assert query.has_filter?('estimated_hours')
108 assert query.has_filter?('estimated_hours')
109 assert !query.valid?
109 assert !query.valid?
110 end
110 end
111
111
112 def test_operator_is_on_float
112 def test_operator_is_on_float
113 Issue.update_all("estimated_hours = 171.2", "id=2")
113 Issue.update_all("estimated_hours = 171.2", "id=2")
114
114
115 query = Query.new(:name => '_')
115 query = Query.new(:name => '_')
116 query.add_filter('estimated_hours', '=', ['171.20'])
116 query.add_filter('estimated_hours', '=', ['171.20'])
117 issues = find_issues_with_query(query)
117 issues = find_issues_with_query(query)
118 assert_equal 1, issues.size
118 assert_equal 1, issues.size
119 assert_equal 2, issues.first.id
119 assert_equal 2, issues.first.id
120 end
120 end
121
121
122 def test_operator_greater_than
122 def test_operator_greater_than
123 query = Query.new(:project => Project.find(1), :name => '_')
123 query = Query.new(:project => Project.find(1), :name => '_')
124 query.add_filter('done_ratio', '>=', ['40'])
124 query.add_filter('done_ratio', '>=', ['40'])
125 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
125 assert query.statement.include?("#{Issue.table_name}.done_ratio >= 40.0")
126 find_issues_with_query(query)
126 find_issues_with_query(query)
127 end
127 end
128
128
129 def test_operator_greater_than_a_float
129 def test_operator_greater_than_a_float
130 query = Query.new(:project => Project.find(1), :name => '_')
130 query = Query.new(:project => Project.find(1), :name => '_')
131 query.add_filter('estimated_hours', '>=', ['40.5'])
131 query.add_filter('estimated_hours', '>=', ['40.5'])
132 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
132 assert query.statement.include?("#{Issue.table_name}.estimated_hours >= 40.5")
133 find_issues_with_query(query)
133 find_issues_with_query(query)
134 end
134 end
135
135
136 def test_operator_greater_than_on_custom_field
136 def test_operator_greater_than_on_custom_field
137 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
137 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
138 query = Query.new(:project => Project.find(1), :name => '_')
138 query = Query.new(:project => Project.find(1), :name => '_')
139 query.add_filter("cf_#{f.id}", '>=', ['40'])
139 query.add_filter("cf_#{f.id}", '>=', ['40'])
140 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) >= 40.0")
140 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) >= 40.0")
141 find_issues_with_query(query)
141 find_issues_with_query(query)
142 end
142 end
143
143
144 def test_operator_lesser_than
144 def test_operator_lesser_than
145 query = Query.new(:project => Project.find(1), :name => '_')
145 query = Query.new(:project => Project.find(1), :name => '_')
146 query.add_filter('done_ratio', '<=', ['30'])
146 query.add_filter('done_ratio', '<=', ['30'])
147 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
147 assert query.statement.include?("#{Issue.table_name}.done_ratio <= 30.0")
148 find_issues_with_query(query)
148 find_issues_with_query(query)
149 end
149 end
150
150
151 def test_operator_lesser_than_on_custom_field
151 def test_operator_lesser_than_on_custom_field
152 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
152 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
153 query = Query.new(:project => Project.find(1), :name => '_')
153 query = Query.new(:project => Project.find(1), :name => '_')
154 query.add_filter("cf_#{f.id}", '<=', ['30'])
154 query.add_filter("cf_#{f.id}", '<=', ['30'])
155 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
155 assert query.statement.include?("CAST(custom_values.value AS decimal(60,3)) <= 30.0")
156 find_issues_with_query(query)
156 find_issues_with_query(query)
157 end
157 end
158
158
159 def test_operator_between
159 def test_operator_between
160 query = Query.new(:project => Project.find(1), :name => '_')
160 query = Query.new(:project => Project.find(1), :name => '_')
161 query.add_filter('done_ratio', '><', ['30', '40'])
161 query.add_filter('done_ratio', '><', ['30', '40'])
162 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
162 assert_include "#{Issue.table_name}.done_ratio BETWEEN 30.0 AND 40.0", query.statement
163 find_issues_with_query(query)
163 find_issues_with_query(query)
164 end
164 end
165
165
166 def test_operator_between_on_custom_field
166 def test_operator_between_on_custom_field
167 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
167 f = IssueCustomField.create!(:name => 'filter', :field_format => 'int', :is_filter => true, :is_for_all => true)
168 query = Query.new(:project => Project.find(1), :name => '_')
168 query = Query.new(:project => Project.find(1), :name => '_')
169 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
169 query.add_filter("cf_#{f.id}", '><', ['30', '40'])
170 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
170 assert_include "CAST(custom_values.value AS decimal(60,3)) BETWEEN 30.0 AND 40.0", query.statement
171 find_issues_with_query(query)
171 find_issues_with_query(query)
172 end
172 end
173
173
174 def test_date_filter_should_not_accept_non_date_values
174 def test_date_filter_should_not_accept_non_date_values
175 query = Query.new(:name => '_')
175 query = Query.new(:name => '_')
176 query.add_filter('created_on', '=', ['a'])
176 query.add_filter('created_on', '=', ['a'])
177
177
178 assert query.has_filter?('created_on')
178 assert query.has_filter?('created_on')
179 assert !query.valid?
179 assert !query.valid?
180 end
180 end
181
181
182 def test_date_filter_should_not_accept_invalid_date_values
182 def test_date_filter_should_not_accept_invalid_date_values
183 query = Query.new(:name => '_')
183 query = Query.new(:name => '_')
184 query.add_filter('created_on', '=', ['2011-01-34'])
184 query.add_filter('created_on', '=', ['2011-01-34'])
185
185
186 assert query.has_filter?('created_on')
186 assert query.has_filter?('created_on')
187 assert !query.valid?
187 assert !query.valid?
188 end
188 end
189
189
190 def test_relative_date_filter_should_not_accept_non_integer_values
190 def test_relative_date_filter_should_not_accept_non_integer_values
191 query = Query.new(:name => '_')
191 query = Query.new(:name => '_')
192 query.add_filter('created_on', '>t-', ['a'])
192 query.add_filter('created_on', '>t-', ['a'])
193
193
194 assert query.has_filter?('created_on')
194 assert query.has_filter?('created_on')
195 assert !query.valid?
195 assert !query.valid?
196 end
196 end
197
197
198 def test_operator_date_equals
198 def test_operator_date_equals
199 query = Query.new(:name => '_')
199 query = Query.new(:name => '_')
200 query.add_filter('due_date', '=', ['2011-07-10'])
200 query.add_filter('due_date', '=', ['2011-07-10'])
201 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
201 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
202 find_issues_with_query(query)
202 find_issues_with_query(query)
203 end
203 end
204
204
205 def test_operator_date_lesser_than
205 def test_operator_date_lesser_than
206 query = Query.new(:name => '_')
206 query = Query.new(:name => '_')
207 query.add_filter('due_date', '<=', ['2011-07-10'])
207 query.add_filter('due_date', '<=', ['2011-07-10'])
208 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
208 assert_match /issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
209 find_issues_with_query(query)
209 find_issues_with_query(query)
210 end
210 end
211
211
212 def test_operator_date_greater_than
212 def test_operator_date_greater_than
213 query = Query.new(:name => '_')
213 query = Query.new(:name => '_')
214 query.add_filter('due_date', '>=', ['2011-07-10'])
214 query.add_filter('due_date', '>=', ['2011-07-10'])
215 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
215 assert_match /issues\.due_date > '2011-07-09 23:59:59(\.9+)?'/, query.statement
216 find_issues_with_query(query)
216 find_issues_with_query(query)
217 end
217 end
218
218
219 def test_operator_date_between
219 def test_operator_date_between
220 query = Query.new(:name => '_')
220 query = Query.new(:name => '_')
221 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
221 query.add_filter('due_date', '><', ['2011-06-23', '2011-07-10'])
222 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
222 assert_match /issues\.due_date > '2011-06-22 23:59:59(\.9+)?' AND issues\.due_date <= '2011-07-10 23:59:59(\.9+)?/, query.statement
223 find_issues_with_query(query)
223 find_issues_with_query(query)
224 end
224 end
225
225
226 def test_operator_in_more_than
226 def test_operator_in_more_than
227 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
227 Issue.find(7).update_attribute(:due_date, (Date.today + 15))
228 query = Query.new(:project => Project.find(1), :name => '_')
228 query = Query.new(:project => Project.find(1), :name => '_')
229 query.add_filter('due_date', '>t+', ['15'])
229 query.add_filter('due_date', '>t+', ['15'])
230 issues = find_issues_with_query(query)
230 issues = find_issues_with_query(query)
231 assert !issues.empty?
231 assert !issues.empty?
232 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
232 issues.each {|issue| assert(issue.due_date >= (Date.today + 15))}
233 end
233 end
234
234
235 def test_operator_in_less_than
235 def test_operator_in_less_than
236 query = Query.new(:project => Project.find(1), :name => '_')
236 query = Query.new(:project => Project.find(1), :name => '_')
237 query.add_filter('due_date', '<t+', ['15'])
237 query.add_filter('due_date', '<t+', ['15'])
238 issues = find_issues_with_query(query)
238 issues = find_issues_with_query(query)
239 assert !issues.empty?
239 assert !issues.empty?
240 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
240 issues.each {|issue| assert(issue.due_date >= Date.today && issue.due_date <= (Date.today + 15))}
241 end
241 end
242
242
243 def test_operator_less_than_ago
243 def test_operator_less_than_ago
244 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
244 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
245 query = Query.new(:project => Project.find(1), :name => '_')
245 query = Query.new(:project => Project.find(1), :name => '_')
246 query.add_filter('due_date', '>t-', ['3'])
246 query.add_filter('due_date', '>t-', ['3'])
247 issues = find_issues_with_query(query)
247 issues = find_issues_with_query(query)
248 assert !issues.empty?
248 assert !issues.empty?
249 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
249 issues.each {|issue| assert(issue.due_date >= (Date.today - 3) && issue.due_date <= Date.today)}
250 end
250 end
251
251
252 def test_operator_more_than_ago
252 def test_operator_more_than_ago
253 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
253 Issue.find(7).update_attribute(:due_date, (Date.today - 10))
254 query = Query.new(:project => Project.find(1), :name => '_')
254 query = Query.new(:project => Project.find(1), :name => '_')
255 query.add_filter('due_date', '<t-', ['10'])
255 query.add_filter('due_date', '<t-', ['10'])
256 assert query.statement.include?("#{Issue.table_name}.due_date <=")
256 assert query.statement.include?("#{Issue.table_name}.due_date <=")
257 issues = find_issues_with_query(query)
257 issues = find_issues_with_query(query)
258 assert !issues.empty?
258 assert !issues.empty?
259 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
259 issues.each {|issue| assert(issue.due_date <= (Date.today - 10))}
260 end
260 end
261
261
262 def test_operator_in
262 def test_operator_in
263 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
263 Issue.find(7).update_attribute(:due_date, (Date.today + 2))
264 query = Query.new(:project => Project.find(1), :name => '_')
264 query = Query.new(:project => Project.find(1), :name => '_')
265 query.add_filter('due_date', 't+', ['2'])
265 query.add_filter('due_date', 't+', ['2'])
266 issues = find_issues_with_query(query)
266 issues = find_issues_with_query(query)
267 assert !issues.empty?
267 assert !issues.empty?
268 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
268 issues.each {|issue| assert_equal((Date.today + 2), issue.due_date)}
269 end
269 end
270
270
271 def test_operator_ago
271 def test_operator_ago
272 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
272 Issue.find(7).update_attribute(:due_date, (Date.today - 3))
273 query = Query.new(:project => Project.find(1), :name => '_')
273 query = Query.new(:project => Project.find(1), :name => '_')
274 query.add_filter('due_date', 't-', ['3'])
274 query.add_filter('due_date', 't-', ['3'])
275 issues = find_issues_with_query(query)
275 issues = find_issues_with_query(query)
276 assert !issues.empty?
276 assert !issues.empty?
277 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
277 issues.each {|issue| assert_equal((Date.today - 3), issue.due_date)}
278 end
278 end
279
279
280 def test_operator_today
280 def test_operator_today
281 query = Query.new(:project => Project.find(1), :name => '_')
281 query = Query.new(:project => Project.find(1), :name => '_')
282 query.add_filter('due_date', 't', [''])
282 query.add_filter('due_date', 't', [''])
283 issues = find_issues_with_query(query)
283 issues = find_issues_with_query(query)
284 assert !issues.empty?
284 assert !issues.empty?
285 issues.each {|issue| assert_equal Date.today, issue.due_date}
285 issues.each {|issue| assert_equal Date.today, issue.due_date}
286 end
286 end
287
287
288 def test_operator_this_week_on_date
288 def test_operator_this_week_on_date
289 query = Query.new(:project => Project.find(1), :name => '_')
289 query = Query.new(:project => Project.find(1), :name => '_')
290 query.add_filter('due_date', 'w', [''])
290 query.add_filter('due_date', 'w', [''])
291 find_issues_with_query(query)
291 find_issues_with_query(query)
292 end
292 end
293
293
294 def test_operator_this_week_on_datetime
294 def test_operator_this_week_on_datetime
295 query = Query.new(:project => Project.find(1), :name => '_')
295 query = Query.new(:project => Project.find(1), :name => '_')
296 query.add_filter('created_on', 'w', [''])
296 query.add_filter('created_on', 'w', [''])
297 find_issues_with_query(query)
297 find_issues_with_query(query)
298 end
298 end
299
299
300 def test_operator_contains
300 def test_operator_contains
301 query = Query.new(:project => Project.find(1), :name => '_')
301 query = Query.new(:project => Project.find(1), :name => '_')
302 query.add_filter('subject', '~', ['uNable'])
302 query.add_filter('subject', '~', ['uNable'])
303 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
303 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) LIKE '%unable%'")
304 result = find_issues_with_query(query)
304 result = find_issues_with_query(query)
305 assert result.empty?
305 assert result.empty?
306 result.each {|issue| assert issue.subject.downcase.include?('unable') }
306 result.each {|issue| assert issue.subject.downcase.include?('unable') }
307 end
307 end
308
308
309 def test_range_for_this_week_with_week_starting_on_monday
309 def test_range_for_this_week_with_week_starting_on_monday
310 I18n.locale = :fr
310 I18n.locale = :fr
311 assert_equal '1', I18n.t(:general_first_day_of_week)
311 assert_equal '1', I18n.t(:general_first_day_of_week)
312
312
313 Date.stubs(:today).returns(Date.parse('2011-04-29'))
313 Date.stubs(:today).returns(Date.parse('2011-04-29'))
314
314
315 query = Query.new(:project => Project.find(1), :name => '_')
315 query = Query.new(:project => Project.find(1), :name => '_')
316 query.add_filter('due_date', 'w', [''])
316 query.add_filter('due_date', 'w', [''])
317 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
317 assert query.statement.match(/issues\.due_date > '2011-04-24 23:59:59(\.9+)?' AND issues\.due_date <= '2011-05-01 23:59:59(\.9+)?/), "range not found in #{query.statement}"
318 I18n.locale = :en
318 I18n.locale = :en
319 end
319 end
320
320
321 def test_range_for_this_week_with_week_starting_on_sunday
321 def test_range_for_this_week_with_week_starting_on_sunday
322 I18n.locale = :en
322 I18n.locale = :en
323 assert_equal '7', I18n.t(:general_first_day_of_week)
323 assert_equal '7', I18n.t(:general_first_day_of_week)
324
324
325 Date.stubs(:today).returns(Date.parse('2011-04-29'))
325 Date.stubs(:today).returns(Date.parse('2011-04-29'))
326
326
327 query = Query.new(:project => Project.find(1), :name => '_')
327 query = Query.new(:project => Project.find(1), :name => '_')
328 query.add_filter('due_date', 'w', [''])
328 query.add_filter('due_date', 'w', [''])
329 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
329 assert query.statement.match(/issues\.due_date > '2011-04-23 23:59:59(\.9+)?' AND issues\.due_date <= '2011-04-30 23:59:59(\.9+)?/), "range not found in #{query.statement}"
330 end
330 end
331
331
332 def test_operator_does_not_contains
332 def test_operator_does_not_contains
333 query = Query.new(:project => Project.find(1), :name => '_')
333 query = Query.new(:project => Project.find(1), :name => '_')
334 query.add_filter('subject', '!~', ['uNable'])
334 query.add_filter('subject', '!~', ['uNable'])
335 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
335 assert query.statement.include?("LOWER(#{Issue.table_name}.subject) NOT LIKE '%unable%'")
336 find_issues_with_query(query)
336 find_issues_with_query(query)
337 end
337 end
338
338
339 def test_filter_assigned_to_me
340 user = User.find(2)
341 group = Group.find(10)
342 User.current = user
343 i1 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => user)
344 i2 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => group)
345 i3 = Issue.generate!(:project_id => 1, :tracker_id => 1, :assigned_to => Group.find(11))
346 group.users << user
347
348 query = Query.new(:name => '_', :filters => { 'assigned_to_id' => {:operator => '=', :values => ['me']}})
349 result = query.issues
350 assert_equal Issue.visible.all(:conditions => {:assigned_to_id => ([2] + user.reload.group_ids)}).sort_by(&:id), result.sort_by(&:id)
351
352 assert result.include?(i1)
353 assert result.include?(i2)
354 assert !result.include?(i3)
355 end
356
339 def test_filter_watched_issues
357 def test_filter_watched_issues
340 User.current = User.find(1)
358 User.current = User.find(1)
341 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
359 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '=', :values => ['me']}})
342 result = find_issues_with_query(query)
360 result = find_issues_with_query(query)
343 assert_not_nil result
361 assert_not_nil result
344 assert !result.empty?
362 assert !result.empty?
345 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
363 assert_equal Issue.visible.watched_by(User.current).sort_by(&:id), result.sort_by(&:id)
346 User.current = nil
364 User.current = nil
347 end
365 end
348
366
349 def test_filter_unwatched_issues
367 def test_filter_unwatched_issues
350 User.current = User.find(1)
368 User.current = User.find(1)
351 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
369 query = Query.new(:name => '_', :filters => { 'watcher_id' => {:operator => '!', :values => ['me']}})
352 result = find_issues_with_query(query)
370 result = find_issues_with_query(query)
353 assert_not_nil result
371 assert_not_nil result
354 assert !result.empty?
372 assert !result.empty?
355 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
373 assert_equal((Issue.visible - Issue.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
356 User.current = nil
374 User.current = nil
357 end
375 end
358
376
359 def test_statement_should_be_nil_with_no_filters
377 def test_statement_should_be_nil_with_no_filters
360 q = Query.new(:name => '_')
378 q = Query.new(:name => '_')
361 q.filters = {}
379 q.filters = {}
362
380
363 assert q.valid?
381 assert q.valid?
364 assert_nil q.statement
382 assert_nil q.statement
365 end
383 end
366
384
367 def test_default_columns
385 def test_default_columns
368 q = Query.new
386 q = Query.new
369 assert !q.columns.empty?
387 assert !q.columns.empty?
370 end
388 end
371
389
372 def test_set_column_names
390 def test_set_column_names
373 q = Query.new
391 q = Query.new
374 q.column_names = ['tracker', :subject, '', 'unknonw_column']
392 q.column_names = ['tracker', :subject, '', 'unknonw_column']
375 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
393 assert_equal [:tracker, :subject], q.columns.collect {|c| c.name}
376 c = q.columns.first
394 c = q.columns.first
377 assert q.has_column?(c)
395 assert q.has_column?(c)
378 end
396 end
379
397
380 def test_groupable_columns_should_include_custom_fields
398 def test_groupable_columns_should_include_custom_fields
381 q = Query.new
399 q = Query.new
382 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
400 assert q.groupable_columns.detect {|c| c.is_a? QueryCustomFieldColumn}
383 end
401 end
384
402
385 def test_grouped_with_valid_column
403 def test_grouped_with_valid_column
386 q = Query.new(:group_by => 'status')
404 q = Query.new(:group_by => 'status')
387 assert q.grouped?
405 assert q.grouped?
388 assert_not_nil q.group_by_column
406 assert_not_nil q.group_by_column
389 assert_equal :status, q.group_by_column.name
407 assert_equal :status, q.group_by_column.name
390 assert_not_nil q.group_by_statement
408 assert_not_nil q.group_by_statement
391 assert_equal 'status', q.group_by_statement
409 assert_equal 'status', q.group_by_statement
392 end
410 end
393
411
394 def test_grouped_with_invalid_column
412 def test_grouped_with_invalid_column
395 q = Query.new(:group_by => 'foo')
413 q = Query.new(:group_by => 'foo')
396 assert !q.grouped?
414 assert !q.grouped?
397 assert_nil q.group_by_column
415 assert_nil q.group_by_column
398 assert_nil q.group_by_statement
416 assert_nil q.group_by_statement
399 end
417 end
400
418
401 def test_default_sort
419 def test_default_sort
402 q = Query.new
420 q = Query.new
403 assert_equal [], q.sort_criteria
421 assert_equal [], q.sort_criteria
404 end
422 end
405
423
406 def test_set_sort_criteria_with_hash
424 def test_set_sort_criteria_with_hash
407 q = Query.new
425 q = Query.new
408 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
426 q.sort_criteria = {'0' => ['priority', 'desc'], '2' => ['tracker']}
409 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
427 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
410 end
428 end
411
429
412 def test_set_sort_criteria_with_array
430 def test_set_sort_criteria_with_array
413 q = Query.new
431 q = Query.new
414 q.sort_criteria = [['priority', 'desc'], 'tracker']
432 q.sort_criteria = [['priority', 'desc'], 'tracker']
415 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
433 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
416 end
434 end
417
435
418 def test_create_query_with_sort
436 def test_create_query_with_sort
419 q = Query.new(:name => 'Sorted')
437 q = Query.new(:name => 'Sorted')
420 q.sort_criteria = [['priority', 'desc'], 'tracker']
438 q.sort_criteria = [['priority', 'desc'], 'tracker']
421 assert q.save
439 assert q.save
422 q.reload
440 q.reload
423 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
441 assert_equal [['priority', 'desc'], ['tracker', 'asc']], q.sort_criteria
424 end
442 end
425
443
426 def test_sort_by_string_custom_field_asc
444 def test_sort_by_string_custom_field_asc
427 q = Query.new
445 q = Query.new
428 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
446 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
429 assert c
447 assert c
430 assert c.sortable
448 assert c.sortable
431 issues = Issue.find :all,
449 issues = Issue.find :all,
432 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
450 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
433 :conditions => q.statement,
451 :conditions => q.statement,
434 :order => "#{c.sortable} ASC"
452 :order => "#{c.sortable} ASC"
435 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
453 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
436 assert !values.empty?
454 assert !values.empty?
437 assert_equal values.sort, values
455 assert_equal values.sort, values
438 end
456 end
439
457
440 def test_sort_by_string_custom_field_desc
458 def test_sort_by_string_custom_field_desc
441 q = Query.new
459 q = Query.new
442 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
460 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'string' }
443 assert c
461 assert c
444 assert c.sortable
462 assert c.sortable
445 issues = Issue.find :all,
463 issues = Issue.find :all,
446 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
464 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
447 :conditions => q.statement,
465 :conditions => q.statement,
448 :order => "#{c.sortable} DESC"
466 :order => "#{c.sortable} DESC"
449 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
467 values = issues.collect {|i| i.custom_value_for(c.custom_field).to_s}
450 assert !values.empty?
468 assert !values.empty?
451 assert_equal values.sort.reverse, values
469 assert_equal values.sort.reverse, values
452 end
470 end
453
471
454 def test_sort_by_float_custom_field_asc
472 def test_sort_by_float_custom_field_asc
455 q = Query.new
473 q = Query.new
456 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
474 c = q.available_columns.find {|col| col.is_a?(QueryCustomFieldColumn) && col.custom_field.field_format == 'float' }
457 assert c
475 assert c
458 assert c.sortable
476 assert c.sortable
459 issues = Issue.find :all,
477 issues = Issue.find :all,
460 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
478 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
461 :conditions => q.statement,
479 :conditions => q.statement,
462 :order => "#{c.sortable} ASC"
480 :order => "#{c.sortable} ASC"
463 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
481 values = issues.collect {|i| begin; Kernel.Float(i.custom_value_for(c.custom_field).to_s); rescue; nil; end}.compact
464 assert !values.empty?
482 assert !values.empty?
465 assert_equal values.sort, values
483 assert_equal values.sort, values
466 end
484 end
467
485
468 def test_invalid_query_should_raise_query_statement_invalid_error
486 def test_invalid_query_should_raise_query_statement_invalid_error
469 q = Query.new
487 q = Query.new
470 assert_raise Query::StatementInvalid do
488 assert_raise Query::StatementInvalid do
471 q.issues(:conditions => "foo = 1")
489 q.issues(:conditions => "foo = 1")
472 end
490 end
473 end
491 end
474
492
475 def test_issue_count
493 def test_issue_count
476 q = Query.new(:name => '_')
494 q = Query.new(:name => '_')
477 issue_count = q.issue_count
495 issue_count = q.issue_count
478 assert_equal q.issues.size, issue_count
496 assert_equal q.issues.size, issue_count
479 end
497 end
480
498
481 def test_issue_count_with_archived_issues
499 def test_issue_count_with_archived_issues
482 p = Project.generate!( :status => Project::STATUS_ARCHIVED )
500 p = Project.generate!( :status => Project::STATUS_ARCHIVED )
483 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
501 i = Issue.generate!( :project => p, :tracker => p.trackers.first )
484 assert !i.visible?
502 assert !i.visible?
485
503
486 test_issue_count
504 test_issue_count
487 end
505 end
488
506
489 def test_issue_count_by_association_group
507 def test_issue_count_by_association_group
490 q = Query.new(:name => '_', :group_by => 'assigned_to')
508 q = Query.new(:name => '_', :group_by => 'assigned_to')
491 count_by_group = q.issue_count_by_group
509 count_by_group = q.issue_count_by_group
492 assert_kind_of Hash, count_by_group
510 assert_kind_of Hash, count_by_group
493 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
511 assert_equal %w(NilClass User), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
494 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
512 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
495 assert count_by_group.has_key?(User.find(3))
513 assert count_by_group.has_key?(User.find(3))
496 end
514 end
497
515
498 def test_issue_count_by_list_custom_field_group
516 def test_issue_count_by_list_custom_field_group
499 q = Query.new(:name => '_', :group_by => 'cf_1')
517 q = Query.new(:name => '_', :group_by => 'cf_1')
500 count_by_group = q.issue_count_by_group
518 count_by_group = q.issue_count_by_group
501 assert_kind_of Hash, count_by_group
519 assert_kind_of Hash, count_by_group
502 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
520 assert_equal %w(NilClass String), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
503 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
521 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
504 assert count_by_group.has_key?('MySQL')
522 assert count_by_group.has_key?('MySQL')
505 end
523 end
506
524
507 def test_issue_count_by_date_custom_field_group
525 def test_issue_count_by_date_custom_field_group
508 q = Query.new(:name => '_', :group_by => 'cf_8')
526 q = Query.new(:name => '_', :group_by => 'cf_8')
509 count_by_group = q.issue_count_by_group
527 count_by_group = q.issue_count_by_group
510 assert_kind_of Hash, count_by_group
528 assert_kind_of Hash, count_by_group
511 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
529 assert_equal %w(Date NilClass), count_by_group.keys.collect {|k| k.class.name}.uniq.sort
512 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
530 assert_equal %w(Fixnum), count_by_group.values.collect {|k| k.class.name}.uniq
513 end
531 end
514
532
515 def test_label_for
533 def test_label_for
516 q = Query.new
534 q = Query.new
517 assert_equal 'assigned_to', q.label_for('assigned_to_id')
535 assert_equal 'assigned_to', q.label_for('assigned_to_id')
518 end
536 end
519
537
520 def test_editable_by
538 def test_editable_by
521 admin = User.find(1)
539 admin = User.find(1)
522 manager = User.find(2)
540 manager = User.find(2)
523 developer = User.find(3)
541 developer = User.find(3)
524
542
525 # Public query on project 1
543 # Public query on project 1
526 q = Query.find(1)
544 q = Query.find(1)
527 assert q.editable_by?(admin)
545 assert q.editable_by?(admin)
528 assert q.editable_by?(manager)
546 assert q.editable_by?(manager)
529 assert !q.editable_by?(developer)
547 assert !q.editable_by?(developer)
530
548
531 # Private query on project 1
549 # Private query on project 1
532 q = Query.find(2)
550 q = Query.find(2)
533 assert q.editable_by?(admin)
551 assert q.editable_by?(admin)
534 assert !q.editable_by?(manager)
552 assert !q.editable_by?(manager)
535 assert q.editable_by?(developer)
553 assert q.editable_by?(developer)
536
554
537 # Private query for all projects
555 # Private query for all projects
538 q = Query.find(3)
556 q = Query.find(3)
539 assert q.editable_by?(admin)
557 assert q.editable_by?(admin)
540 assert !q.editable_by?(manager)
558 assert !q.editable_by?(manager)
541 assert q.editable_by?(developer)
559 assert q.editable_by?(developer)
542
560
543 # Public query for all projects
561 # Public query for all projects
544 q = Query.find(4)
562 q = Query.find(4)
545 assert q.editable_by?(admin)
563 assert q.editable_by?(admin)
546 assert !q.editable_by?(manager)
564 assert !q.editable_by?(manager)
547 assert !q.editable_by?(developer)
565 assert !q.editable_by?(developer)
548 end
566 end
549
567
550 def test_visible_scope
568 def test_visible_scope
551 query_ids = Query.visible(User.anonymous).map(&:id)
569 query_ids = Query.visible(User.anonymous).map(&:id)
552
570
553 assert query_ids.include?(1), 'public query on public project was not visible'
571 assert query_ids.include?(1), 'public query on public project was not visible'
554 assert query_ids.include?(4), 'public query for all projects was not visible'
572 assert query_ids.include?(4), 'public query for all projects was not visible'
555 assert !query_ids.include?(2), 'private query on public project was visible'
573 assert !query_ids.include?(2), 'private query on public project was visible'
556 assert !query_ids.include?(3), 'private query for all projects was visible'
574 assert !query_ids.include?(3), 'private query for all projects was visible'
557 assert !query_ids.include?(7), 'public query on private project was visible'
575 assert !query_ids.include?(7), 'public query on private project was visible'
558 end
576 end
559
577
560 context "#available_filters" do
578 context "#available_filters" do
561 setup do
579 setup do
562 @query = Query.new(:name => "_")
580 @query = Query.new(:name => "_")
563 end
581 end
564
582
565 should "include users of visible projects in cross-project view" do
583 should "include users of visible projects in cross-project view" do
566 users = @query.available_filters["assigned_to_id"]
584 users = @query.available_filters["assigned_to_id"]
567 assert_not_nil users
585 assert_not_nil users
568 assert users[:values].map{|u|u[1]}.include?("3")
586 assert users[:values].map{|u|u[1]}.include?("3")
569 end
587 end
570
588
571 should "include visible projects in cross-project view" do
589 should "include visible projects in cross-project view" do
572 projects = @query.available_filters["project_id"]
590 projects = @query.available_filters["project_id"]
573 assert_not_nil projects
591 assert_not_nil projects
574 assert projects[:values].map{|u|u[1]}.include?("1")
592 assert projects[:values].map{|u|u[1]}.include?("1")
575 end
593 end
576
594
577 context "'member_of_group' filter" do
595 context "'member_of_group' filter" do
578 should "be present" do
596 should "be present" do
579 assert @query.available_filters.keys.include?("member_of_group")
597 assert @query.available_filters.keys.include?("member_of_group")
580 end
598 end
581
599
582 should "be an optional list" do
600 should "be an optional list" do
583 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
601 assert_equal :list_optional, @query.available_filters["member_of_group"][:type]
584 end
602 end
585
603
586 should "have a list of the groups as values" do
604 should "have a list of the groups as values" do
587 Group.destroy_all # No fixtures
605 Group.destroy_all # No fixtures
588 group1 = Group.generate!.reload
606 group1 = Group.generate!.reload
589 group2 = Group.generate!.reload
607 group2 = Group.generate!.reload
590
608
591 expected_group_list = [
609 expected_group_list = [
592 [group1.name, group1.id.to_s],
610 [group1.name, group1.id.to_s],
593 [group2.name, group2.id.to_s]
611 [group2.name, group2.id.to_s]
594 ]
612 ]
595 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
613 assert_equal expected_group_list.sort, @query.available_filters["member_of_group"][:values].sort
596 end
614 end
597
615
598 end
616 end
599
617
600 context "'assigned_to_role' filter" do
618 context "'assigned_to_role' filter" do
601 should "be present" do
619 should "be present" do
602 assert @query.available_filters.keys.include?("assigned_to_role")
620 assert @query.available_filters.keys.include?("assigned_to_role")
603 end
621 end
604
622
605 should "be an optional list" do
623 should "be an optional list" do
606 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
624 assert_equal :list_optional, @query.available_filters["assigned_to_role"][:type]
607 end
625 end
608
626
609 should "have a list of the Roles as values" do
627 should "have a list of the Roles as values" do
610 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
628 assert @query.available_filters["assigned_to_role"][:values].include?(['Manager','1'])
611 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
629 assert @query.available_filters["assigned_to_role"][:values].include?(['Developer','2'])
612 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
630 assert @query.available_filters["assigned_to_role"][:values].include?(['Reporter','3'])
613 end
631 end
614
632
615 should "not include the built in Roles as values" do
633 should "not include the built in Roles as values" do
616 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
634 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Non member','4'])
617 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
635 assert ! @query.available_filters["assigned_to_role"][:values].include?(['Anonymous','5'])
618 end
636 end
619
637
620 end
638 end
621
639
622 end
640 end
623
641
624 context "#statement" do
642 context "#statement" do
625 context "with 'member_of_group' filter" do
643 context "with 'member_of_group' filter" do
626 setup do
644 setup do
627 Group.destroy_all # No fixtures
645 Group.destroy_all # No fixtures
628 @user_in_group = User.generate!
646 @user_in_group = User.generate!
629 @second_user_in_group = User.generate!
647 @second_user_in_group = User.generate!
630 @user_in_group2 = User.generate!
648 @user_in_group2 = User.generate!
631 @user_not_in_group = User.generate!
649 @user_not_in_group = User.generate!
632
650
633 @group = Group.generate!.reload
651 @group = Group.generate!.reload
634 @group.users << @user_in_group
652 @group.users << @user_in_group
635 @group.users << @second_user_in_group
653 @group.users << @second_user_in_group
636
654
637 @group2 = Group.generate!.reload
655 @group2 = Group.generate!.reload
638 @group2.users << @user_in_group2
656 @group2.users << @user_in_group2
639
657
640 end
658 end
641
659
642 should "search assigned to for users in the group" do
660 should "search assigned to for users in the group" do
643 @query = Query.new(:name => '_')
661 @query = Query.new(:name => '_')
644 @query.add_filter('member_of_group', '=', [@group.id.to_s])
662 @query.add_filter('member_of_group', '=', [@group.id.to_s])
645
663
646 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
664 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}')"
647 assert_find_issues_with_query_is_successful @query
665 assert_find_issues_with_query_is_successful @query
648 end
666 end
649
667
650 should "search not assigned to any group member (none)" do
668 should "search not assigned to any group member (none)" do
651 @query = Query.new(:name => '_')
669 @query = Query.new(:name => '_')
652 @query.add_filter('member_of_group', '!*', [''])
670 @query.add_filter('member_of_group', '!*', [''])
653
671
654 # Users not in a group
672 # Users not in a group
655 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
673 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
656 assert_find_issues_with_query_is_successful @query
674 assert_find_issues_with_query_is_successful @query
657 end
675 end
658
676
659 should "search assigned to any group member (all)" do
677 should "search assigned to any group member (all)" do
660 @query = Query.new(:name => '_')
678 @query = Query.new(:name => '_')
661 @query.add_filter('member_of_group', '*', [''])
679 @query.add_filter('member_of_group', '*', [''])
662
680
663 # Only users in a group
681 # Only users in a group
664 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
682 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@user_in_group.id}','#{@second_user_in_group.id}','#{@user_in_group2.id}')"
665 assert_find_issues_with_query_is_successful @query
683 assert_find_issues_with_query_is_successful @query
666 end
684 end
667
685
668 should "return an empty set with = empty group" do
686 should "return an empty set with = empty group" do
669 @empty_group = Group.generate!
687 @empty_group = Group.generate!
670 @query = Query.new(:name => '_')
688 @query = Query.new(:name => '_')
671 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
689 @query.add_filter('member_of_group', '=', [@empty_group.id.to_s])
672
690
673 assert_equal [], find_issues_with_query(@query)
691 assert_equal [], find_issues_with_query(@query)
674 end
692 end
675
693
676 should "return issues with ! empty group" do
694 should "return issues with ! empty group" do
677 @empty_group = Group.generate!
695 @empty_group = Group.generate!
678 @query = Query.new(:name => '_')
696 @query = Query.new(:name => '_')
679 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
697 @query.add_filter('member_of_group', '!', [@empty_group.id.to_s])
680
698
681 assert_find_issues_with_query_is_successful @query
699 assert_find_issues_with_query_is_successful @query
682 end
700 end
683 end
701 end
684
702
685 context "with 'assigned_to_role' filter" do
703 context "with 'assigned_to_role' filter" do
686 setup do
704 setup do
687 # No fixtures
705 # No fixtures
688 MemberRole.delete_all
706 MemberRole.delete_all
689 Member.delete_all
707 Member.delete_all
690 Role.delete_all
708 Role.delete_all
691
709
692 @manager_role = Role.generate!(:name => 'Manager')
710 @manager_role = Role.generate!(:name => 'Manager')
693 @developer_role = Role.generate!(:name => 'Developer')
711 @developer_role = Role.generate!(:name => 'Developer')
694
712
695 @project = Project.generate!
713 @project = Project.generate!
696 @manager = User.generate!
714 @manager = User.generate!
697 @developer = User.generate!
715 @developer = User.generate!
698 @boss = User.generate!
716 @boss = User.generate!
699 User.add_to_project(@manager, @project, @manager_role)
717 User.add_to_project(@manager, @project, @manager_role)
700 User.add_to_project(@developer, @project, @developer_role)
718 User.add_to_project(@developer, @project, @developer_role)
701 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
719 User.add_to_project(@boss, @project, [@manager_role, @developer_role])
702 end
720 end
703
721
704 should "search assigned to for users with the Role" do
722 should "search assigned to for users with the Role" do
705 @query = Query.new(:name => '_')
723 @query = Query.new(:name => '_')
706 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
724 @query.add_filter('assigned_to_role', '=', [@manager_role.id.to_s])
707
725
708 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')"
726 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@boss.id}')"
709 assert_find_issues_with_query_is_successful @query
727 assert_find_issues_with_query_is_successful @query
710 end
728 end
711
729
712 should "search assigned to for users not assigned to any Role (none)" do
730 should "search assigned to for users not assigned to any Role (none)" do
713 @query = Query.new(:name => '_')
731 @query = Query.new(:name => '_')
714 @query.add_filter('assigned_to_role', '!*', [''])
732 @query.add_filter('assigned_to_role', '!*', [''])
715
733
716 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
734 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IS NULL OR #{Issue.table_name}.assigned_to_id NOT IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
717 assert_find_issues_with_query_is_successful @query
735 assert_find_issues_with_query_is_successful @query
718 end
736 end
719
737
720 should "search assigned to for users assigned to any Role (all)" do
738 should "search assigned to for users assigned to any Role (all)" do
721 @query = Query.new(:name => '_')
739 @query = Query.new(:name => '_')
722 @query.add_filter('assigned_to_role', '*', [''])
740 @query.add_filter('assigned_to_role', '*', [''])
723
741
724 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
742 assert_query_statement_includes @query, "#{Issue.table_name}.assigned_to_id IN ('#{@manager.id}','#{@developer.id}','#{@boss.id}')"
725 assert_find_issues_with_query_is_successful @query
743 assert_find_issues_with_query_is_successful @query
726 end
744 end
727
745
728 should "return an empty set with empty role" do
746 should "return an empty set with empty role" do
729 @empty_role = Role.generate!
747 @empty_role = Role.generate!
730 @query = Query.new(:name => '_')
748 @query = Query.new(:name => '_')
731 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
749 @query.add_filter('assigned_to_role', '=', [@empty_role.id.to_s])
732
750
733 assert_equal [], find_issues_with_query(@query)
751 assert_equal [], find_issues_with_query(@query)
734 end
752 end
735
753
736 should "return issues with ! empty role" do
754 should "return issues with ! empty role" do
737 @empty_role = Role.generate!
755 @empty_role = Role.generate!
738 @query = Query.new(:name => '_')
756 @query = Query.new(:name => '_')
739 @query.add_filter('member_of_group', '!', [@empty_role.id.to_s])
757 @query.add_filter('member_of_group', '!', [@empty_role.id.to_s])
740
758
741 assert_find_issues_with_query_is_successful @query
759 assert_find_issues_with_query_is_successful @query
742 end
760 end
743 end
761 end
744 end
762 end
745
763
746 end
764 end
General Comments 0
You need to be logged in to leave comments. Login now