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