@@ -17,6 +17,8 | |||
|
17 | 17 | |
|
18 | 18 | class IssueQuery < Query |
|
19 | 19 | |
|
20 | self.queried_class = Issue | |
|
21 | ||
|
20 | 22 | self.available_columns = [ |
|
21 | 23 | QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), |
|
22 | 24 | QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), |
@@ -226,6 +228,18 class IssueQuery < Query | |||
|
226 | 228 | @available_columns |
|
227 | 229 | end |
|
228 | 230 | |
|
231 | def sortable_columns | |
|
232 | {'id' => "#{Issue.table_name}.id"}.merge(super) | |
|
233 | end | |
|
234 | ||
|
235 | def default_columns_names | |
|
236 | @default_columns_names ||= begin | |
|
237 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) | |
|
238 | ||
|
239 | project.present? ? default_columns : [:project] | default_columns | |
|
240 | end | |
|
241 | end | |
|
242 | ||
|
229 | 243 | # Returns the issue count |
|
230 | 244 | def issue_count |
|
231 | 245 | Issue.visible.count(:include => [:status, :project], :conditions => statement) |
@@ -318,4 +332,94 class IssueQuery < Query | |||
|
318 | 332 | rescue ::ActiveRecord::StatementInvalid => e |
|
319 | 333 | raise StatementInvalid.new(e.message) |
|
320 | 334 | end |
|
335 | ||
|
336 | def sql_for_watcher_id_field(field, operator, value) | |
|
337 | db_table = Watcher.table_name | |
|
338 | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + | |
|
339 | sql_for_field(field, '=', value, db_table, 'user_id') + ')' | |
|
340 | end | |
|
341 | ||
|
342 | def sql_for_member_of_group_field(field, operator, value) | |
|
343 | if operator == '*' # Any group | |
|
344 | groups = Group.all | |
|
345 | operator = '=' # Override the operator since we want to find by assigned_to | |
|
346 | elsif operator == "!*" | |
|
347 | groups = Group.all | |
|
348 | operator = '!' # Override the operator since we want to find by assigned_to | |
|
349 | else | |
|
350 | groups = Group.find_all_by_id(value) | |
|
351 | end | |
|
352 | groups ||= [] | |
|
353 | ||
|
354 | members_of_groups = groups.inject([]) {|user_ids, group| | |
|
355 | if group && group.user_ids.present? | |
|
356 | user_ids << group.user_ids | |
|
357 | end | |
|
358 | user_ids.flatten.uniq.compact | |
|
359 | }.sort.collect(&:to_s) | |
|
360 | ||
|
361 | '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' | |
|
362 | end | |
|
363 | ||
|
364 | def sql_for_assigned_to_role_field(field, operator, value) | |
|
365 | case operator | |
|
366 | when "*", "!*" # Member / Not member | |
|
367 | sw = operator == "!*" ? 'NOT' : '' | |
|
368 | nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
|
369 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + | |
|
370 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" | |
|
371 | when "=", "!" | |
|
372 | role_cond = value.any? ? | |
|
373 | "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : | |
|
374 | "1=0" | |
|
375 | ||
|
376 | sw = operator == "!" ? 'NOT' : '' | |
|
377 | nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
|
378 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + | |
|
379 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" | |
|
380 | end | |
|
381 | end | |
|
382 | ||
|
383 | def sql_for_is_private_field(field, operator, value) | |
|
384 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
|
385 | va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') | |
|
386 | ||
|
387 | "#{Issue.table_name}.is_private #{op} (#{va})" | |
|
388 | end | |
|
389 | ||
|
390 | def sql_for_relations(field, operator, value, options={}) | |
|
391 | relation_options = IssueRelation::TYPES[field] | |
|
392 | return relation_options unless relation_options | |
|
393 | ||
|
394 | relation_type = field | |
|
395 | join_column, target_join_column = "issue_from_id", "issue_to_id" | |
|
396 | if relation_options[:reverse] || options[:reverse] | |
|
397 | relation_type = relation_options[:reverse] || relation_type | |
|
398 | join_column, target_join_column = target_join_column, join_column | |
|
399 | end | |
|
400 | ||
|
401 | sql = case operator | |
|
402 | when "*", "!*" | |
|
403 | op = (operator == "*" ? 'IN' : 'NOT IN') | |
|
404 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" | |
|
405 | when "=", "!" | |
|
406 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
|
407 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" | |
|
408 | when "=p", "=!p", "!p" | |
|
409 | op = (operator == "!p" ? 'NOT IN' : 'IN') | |
|
410 | comp = (operator == "=!p" ? '<>' : '=') | |
|
411 | "#{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 = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" | |
|
412 | end | |
|
413 | ||
|
414 | if relation_options[:sym] == field && !options[:reverse] | |
|
415 | sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] | |
|
416 | sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") | |
|
417 | else | |
|
418 | sql | |
|
419 | end | |
|
420 | end | |
|
421 | ||
|
422 | IssueRelation::TYPES.keys.each do |relation_type| | |
|
423 | alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations | |
|
424 | end | |
|
321 | 425 | end |
@@ -48,8 +48,8 class QueryColumn | |||
|
48 | 48 | @inline |
|
49 | 49 | end |
|
50 | 50 | |
|
51 |
def value( |
|
|
52 |
|
|
|
51 | def value(object) | |
|
52 | object.send name | |
|
53 | 53 | end |
|
54 | 54 | |
|
55 | 55 | def css_classes |
@@ -75,8 +75,8 class QueryCustomFieldColumn < QueryColumn | |||
|
75 | 75 | @cf |
|
76 | 76 | end |
|
77 | 77 | |
|
78 |
def value( |
|
|
79 |
cv = |
|
|
78 | def value(object) | |
|
79 | cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} | |
|
80 | 80 | cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first |
|
81 | 81 | end |
|
82 | 82 | |
@@ -147,6 +147,12 class Query < ActiveRecord::Base | |||
|
147 | 147 | class_attribute :available_columns |
|
148 | 148 | self.available_columns = [] |
|
149 | 149 | |
|
150 | class_attribute :queried_class | |
|
151 | ||
|
152 | def queried_table_name | |
|
153 | @queried_table_name ||= self.class.queried_class.table_name | |
|
154 | end | |
|
155 | ||
|
150 | 156 | def initialize(attributes=nil, *args) |
|
151 | 157 | super attributes |
|
152 | 158 | @is_for_all = project.nil? |
@@ -288,10 +294,10 class Query < ActiveRecord::Base | |||
|
288 | 294 | |
|
289 | 295 | # Returns a Hash of columns and the key for sorting |
|
290 | 296 | def sortable_columns |
|
291 |
|
|
|
292 |
|
|
|
293 | h | |
|
294 | }) | |
|
297 | available_columns.inject({}) {|h, column| | |
|
298 | h[column.name.to_s] = column.sortable | |
|
299 | h | |
|
300 | } | |
|
295 | 301 | end |
|
296 | 302 | |
|
297 | 303 | def columns |
@@ -318,11 +324,7 class Query < ActiveRecord::Base | |||
|
318 | 324 | end |
|
319 | 325 | |
|
320 | 326 | def default_columns_names |
|
321 | @default_columns_names ||= begin | |
|
322 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) | |
|
323 | ||
|
324 | project.present? ? default_columns : [:project] | default_columns | |
|
325 | end | |
|
327 | [] | |
|
326 | 328 | end |
|
327 | 329 | |
|
328 | 330 | def column_names=(names) |
@@ -453,7 +455,7 class Query < ActiveRecord::Base | |||
|
453 | 455 | filters_clauses << send("sql_for_#{field}_field", field, operator, v) |
|
454 | 456 | else |
|
455 | 457 | # regular field |
|
456 |
filters_clauses << '(' + sql_for_field(field, operator, v, |
|
|
458 | filters_clauses << '(' + sql_for_field(field, operator, v, queried_table_name, field) + ')' | |
|
457 | 459 | end |
|
458 | 460 | end if filters and valid? |
|
459 | 461 | |
@@ -463,96 +465,6 class Query < ActiveRecord::Base | |||
|
463 | 465 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil |
|
464 | 466 | end |
|
465 | 467 | |
|
466 | def sql_for_watcher_id_field(field, operator, value) | |
|
467 | db_table = Watcher.table_name | |
|
468 | "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND " + | |
|
469 | sql_for_field(field, '=', value, db_table, 'user_id') + ')' | |
|
470 | end | |
|
471 | ||
|
472 | def sql_for_member_of_group_field(field, operator, value) | |
|
473 | if operator == '*' # Any group | |
|
474 | groups = Group.all | |
|
475 | operator = '=' # Override the operator since we want to find by assigned_to | |
|
476 | elsif operator == "!*" | |
|
477 | groups = Group.all | |
|
478 | operator = '!' # Override the operator since we want to find by assigned_to | |
|
479 | else | |
|
480 | groups = Group.find_all_by_id(value) | |
|
481 | end | |
|
482 | groups ||= [] | |
|
483 | ||
|
484 | members_of_groups = groups.inject([]) {|user_ids, group| | |
|
485 | if group && group.user_ids.present? | |
|
486 | user_ids << group.user_ids | |
|
487 | end | |
|
488 | user_ids.flatten.uniq.compact | |
|
489 | }.sort.collect(&:to_s) | |
|
490 | ||
|
491 | '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' | |
|
492 | end | |
|
493 | ||
|
494 | def sql_for_assigned_to_role_field(field, operator, value) | |
|
495 | case operator | |
|
496 | when "*", "!*" # Member / Not member | |
|
497 | sw = operator == "!*" ? 'NOT' : '' | |
|
498 | nl = operator == "!*" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
|
499 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}" + | |
|
500 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id))" | |
|
501 | when "=", "!" | |
|
502 | role_cond = value.any? ? | |
|
503 | "#{MemberRole.table_name}.role_id IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" : | |
|
504 | "1=0" | |
|
505 | ||
|
506 | sw = operator == "!" ? 'NOT' : '' | |
|
507 | nl = operator == "!" ? "#{Issue.table_name}.assigned_to_id IS NULL OR" : '' | |
|
508 | "(#{nl} #{Issue.table_name}.assigned_to_id #{sw} IN (SELECT DISTINCT #{Member.table_name}.user_id FROM #{Member.table_name}, #{MemberRole.table_name}" + | |
|
509 | " WHERE #{Member.table_name}.project_id = #{Issue.table_name}.project_id AND #{Member.table_name}.id = #{MemberRole.table_name}.member_id AND #{role_cond}))" | |
|
510 | end | |
|
511 | end | |
|
512 | ||
|
513 | def sql_for_is_private_field(field, operator, value) | |
|
514 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
|
515 | va = value.map {|v| v == '0' ? connection.quoted_false : connection.quoted_true}.uniq.join(',') | |
|
516 | ||
|
517 | "#{Issue.table_name}.is_private #{op} (#{va})" | |
|
518 | end | |
|
519 | ||
|
520 | def sql_for_relations(field, operator, value, options={}) | |
|
521 | relation_options = IssueRelation::TYPES[field] | |
|
522 | return relation_options unless relation_options | |
|
523 | ||
|
524 | relation_type = field | |
|
525 | join_column, target_join_column = "issue_from_id", "issue_to_id" | |
|
526 | if relation_options[:reverse] || options[:reverse] | |
|
527 | relation_type = relation_options[:reverse] || relation_type | |
|
528 | join_column, target_join_column = target_join_column, join_column | |
|
529 | end | |
|
530 | ||
|
531 | sql = case operator | |
|
532 | when "*", "!*" | |
|
533 | op = (operator == "*" ? 'IN' : 'NOT IN') | |
|
534 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}')" | |
|
535 | when "=", "!" | |
|
536 | op = (operator == "=" ? 'IN' : 'NOT IN') | |
|
537 | "#{Issue.table_name}.id #{op} (SELECT DISTINCT #{IssueRelation.table_name}.#{join_column} FROM #{IssueRelation.table_name} WHERE #{IssueRelation.table_name}.relation_type = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = #{value.first.to_i})" | |
|
538 | when "=p", "=!p", "!p" | |
|
539 | op = (operator == "!p" ? 'NOT IN' : 'IN') | |
|
540 | comp = (operator == "=!p" ? '<>' : '=') | |
|
541 | "#{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 = '#{connection.quote_string(relation_type)}' AND #{IssueRelation.table_name}.#{target_join_column} = relissues.id AND relissues.project_id #{comp} #{value.first.to_i})" | |
|
542 | end | |
|
543 | ||
|
544 | if relation_options[:sym] == field && !options[:reverse] | |
|
545 | sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)] | |
|
546 | sqls.join(["!", "!*", "!p"].include?(operator) ? " AND " : " OR ") | |
|
547 | else | |
|
548 | sql | |
|
549 | end | |
|
550 | end | |
|
551 | ||
|
552 | IssueRelation::TYPES.keys.each do |relation_type| | |
|
553 | alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations | |
|
554 | end | |
|
555 | ||
|
556 | 468 | private |
|
557 | 469 | |
|
558 | 470 | def sql_for_custom_field(field, operator, value, custom_field_id) |
@@ -572,14 +484,14 class Query < ActiveRecord::Base | |||
|
572 | 484 | not_in = 'NOT' |
|
573 | 485 | end |
|
574 | 486 | customized_key = "id" |
|
575 |
customized_class = |
|
|
487 | customized_class = queried_class | |
|
576 | 488 | if field =~ /^(.+)\.cf_/ |
|
577 | 489 | assoc = $1 |
|
578 | 490 | customized_key = "#{assoc}_id" |
|
579 |
customized_class = |
|
|
580 |
raise "Unknown |
|
|
491 | customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil | |
|
492 | raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class | |
|
581 | 493 | end |
|
582 |
"#{ |
|
|
494 | "#{queried_table_name}.#{customized_key} #{not_in} IN (SELECT #{customized_class.table_name}.id FROM #{customized_class.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='#{customized_class}' AND #{db_table}.customized_id=#{customized_class.table_name}.id AND #{db_table}.custom_field_id=#{custom_field_id} WHERE " + | |
|
583 | 495 | sql_for_field(field, operator, value, db_table, db_field, true) + ')' |
|
584 | 496 | end |
|
585 | 497 | |
@@ -655,9 +567,9 class Query < ActiveRecord::Base | |||
|
655 | 567 | end |
|
656 | 568 | end |
|
657 | 569 | when "o" |
|
658 |
sql = "#{ |
|
|
570 | sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_false})" if field == "status_id" | |
|
659 | 571 | when "c" |
|
660 |
sql = "#{ |
|
|
572 | sql = "#{queried_table_name}.status_id IN (SELECT id FROM #{IssueStatus.table_name} WHERE is_closed=#{connection.quoted_true})" if field == "status_id" | |
|
661 | 573 | when "><t-" |
|
662 | 574 | # between today - n days and today |
|
663 | 575 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) |
@@ -747,7 +659,7 class Query < ActiveRecord::Base | |||
|
747 | 659 | def add_associations_custom_fields_filters(*associations) |
|
748 | 660 | fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) |
|
749 | 661 | associations.each do |assoc| |
|
750 |
association_klass = |
|
|
662 | association_klass = queried_class.reflect_on_association(assoc).klass | |
|
751 | 663 | fields_by_class.each do |field_class, fields| |
|
752 | 664 | if field_class.customized_class <= association_klass |
|
753 | 665 | add_custom_fields_filters(fields, assoc) |
@@ -788,7 +700,7 class Query < ActiveRecord::Base | |||
|
788 | 700 | |
|
789 | 701 | if order_options |
|
790 | 702 | if order_options.include?('authors') |
|
791 |
joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{ |
|
|
703 | joins << "LEFT OUTER JOIN #{User.table_name} authors ON authors.id = #{queried_table_name}.author_id" | |
|
792 | 704 | end |
|
793 | 705 | order_options.scan(/cf_\d+/).uniq.each do |name| |
|
794 | 706 | column = available_columns.detect {|c| c.name.to_s == name} |
General Comments 0
You need to be logged in to leave comments.
Login now