@@ -17,6 +17,8 | |||||
17 |
|
17 | |||
18 | class IssueQuery < Query |
|
18 | class IssueQuery < Query | |
19 |
|
19 | |||
|
20 | self.queried_class = Issue | |||
|
21 | ||||
20 | self.available_columns = [ |
|
22 | self.available_columns = [ | |
21 | QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), |
|
23 | QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true), | |
22 | QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), |
|
24 | QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true), | |
@@ -226,6 +228,18 class IssueQuery < Query | |||||
226 | @available_columns |
|
228 | @available_columns | |
227 | end |
|
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 | # Returns the issue count |
|
243 | # Returns the issue count | |
230 | def issue_count |
|
244 | def issue_count | |
231 | Issue.visible.count(:include => [:status, :project], :conditions => statement) |
|
245 | Issue.visible.count(:include => [:status, :project], :conditions => statement) | |
@@ -318,4 +332,94 class IssueQuery < Query | |||||
318 | rescue ::ActiveRecord::StatementInvalid => e |
|
332 | rescue ::ActiveRecord::StatementInvalid => e | |
319 | raise StatementInvalid.new(e.message) |
|
333 | raise StatementInvalid.new(e.message) | |
320 | end |
|
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 | end |
|
425 | end |
@@ -48,8 +48,8 class QueryColumn | |||||
48 | @inline |
|
48 | @inline | |
49 | end |
|
49 | end | |
50 |
|
50 | |||
51 |
def value( |
|
51 | def value(object) | |
52 |
|
|
52 | object.send name | |
53 | end |
|
53 | end | |
54 |
|
54 | |||
55 | def css_classes |
|
55 | def css_classes | |
@@ -75,8 +75,8 class QueryCustomFieldColumn < QueryColumn | |||||
75 | @cf |
|
75 | @cf | |
76 | end |
|
76 | end | |
77 |
|
77 | |||
78 |
def value( |
|
78 | def value(object) | |
79 |
cv = |
|
79 | cv = object.custom_values.select {|v| v.custom_field_id == @cf.id}.collect {|v| @cf.cast_value(v.value)} | |
80 | cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first |
|
80 | cv.size > 1 ? cv.sort {|a,b| a.to_s <=> b.to_s} : cv.first | |
81 | end |
|
81 | end | |
82 |
|
82 | |||
@@ -147,6 +147,12 class Query < ActiveRecord::Base | |||||
147 | class_attribute :available_columns |
|
147 | class_attribute :available_columns | |
148 | self.available_columns = [] |
|
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 | def initialize(attributes=nil, *args) |
|
156 | def initialize(attributes=nil, *args) | |
151 | super attributes |
|
157 | super attributes | |
152 | @is_for_all = project.nil? |
|
158 | @is_for_all = project.nil? | |
@@ -288,10 +294,10 class Query < ActiveRecord::Base | |||||
288 |
|
294 | |||
289 | # Returns a Hash of columns and the key for sorting |
|
295 | # Returns a Hash of columns and the key for sorting | |
290 | def sortable_columns |
|
296 | def sortable_columns | |
291 |
|
|
297 | available_columns.inject({}) {|h, column| | |
292 |
|
|
298 | h[column.name.to_s] = column.sortable | |
293 | h |
|
299 | h | |
294 | }) |
|
300 | } | |
295 | end |
|
301 | end | |
296 |
|
302 | |||
297 | def columns |
|
303 | def columns | |
@@ -318,11 +324,7 class Query < ActiveRecord::Base | |||||
318 | end |
|
324 | end | |
319 |
|
325 | |||
320 | def default_columns_names |
|
326 | def default_columns_names | |
321 | @default_columns_names ||= begin |
|
327 | [] | |
322 | default_columns = Setting.issue_list_default_columns.map(&:to_sym) |
|
|||
323 |
|
||||
324 | project.present? ? default_columns : [:project] | default_columns |
|
|||
325 | end |
|
|||
326 | end |
|
328 | end | |
327 |
|
329 | |||
328 | def column_names=(names) |
|
330 | def column_names=(names) | |
@@ -453,7 +455,7 class Query < ActiveRecord::Base | |||||
453 | filters_clauses << send("sql_for_#{field}_field", field, operator, v) |
|
455 | filters_clauses << send("sql_for_#{field}_field", field, operator, v) | |
454 | else |
|
456 | else | |
455 | # regular field |
|
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 | end |
|
459 | end | |
458 | end if filters and valid? |
|
460 | end if filters and valid? | |
459 |
|
461 | |||
@@ -463,96 +465,6 class Query < ActiveRecord::Base | |||||
463 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil |
|
465 | filters_clauses.any? ? filters_clauses.join(' AND ') : nil | |
464 | end |
|
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 | private |
|
468 | private | |
557 |
|
469 | |||
558 | def sql_for_custom_field(field, operator, value, custom_field_id) |
|
470 | def sql_for_custom_field(field, operator, value, custom_field_id) | |
@@ -572,14 +484,14 class Query < ActiveRecord::Base | |||||
572 | not_in = 'NOT' |
|
484 | not_in = 'NOT' | |
573 | end |
|
485 | end | |
574 | customized_key = "id" |
|
486 | customized_key = "id" | |
575 |
customized_class = |
|
487 | customized_class = queried_class | |
576 | if field =~ /^(.+)\.cf_/ |
|
488 | if field =~ /^(.+)\.cf_/ | |
577 | assoc = $1 |
|
489 | assoc = $1 | |
578 | customized_key = "#{assoc}_id" |
|
490 | customized_key = "#{assoc}_id" | |
579 |
customized_class = |
|
491 | customized_class = queried_class.reflect_on_association(assoc.to_sym).klass.base_class rescue nil | |
580 |
raise "Unknown |
|
492 | raise "Unknown #{queried_class.name} association #{assoc}" unless customized_class | |
581 | end |
|
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 | sql_for_field(field, operator, value, db_table, db_field, true) + ')' |
|
495 | sql_for_field(field, operator, value, db_table, db_field, true) + ')' | |
584 | end |
|
496 | end | |
585 |
|
497 | |||
@@ -655,9 +567,9 class Query < ActiveRecord::Base | |||||
655 | end |
|
567 | end | |
656 | end |
|
568 | end | |
657 | when "o" |
|
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 | when "c" |
|
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 | when "><t-" |
|
573 | when "><t-" | |
662 | # between today - n days and today |
|
574 | # between today - n days and today | |
663 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) |
|
575 | sql = relative_date_clause(db_table, db_field, - value.first.to_i, 0) | |
@@ -747,7 +659,7 class Query < ActiveRecord::Base | |||||
747 | def add_associations_custom_fields_filters(*associations) |
|
659 | def add_associations_custom_fields_filters(*associations) | |
748 | fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) |
|
660 | fields_by_class = CustomField.where(:is_filter => true).group_by(&:class) | |
749 | associations.each do |assoc| |
|
661 | associations.each do |assoc| | |
750 |
association_klass = |
|
662 | association_klass = queried_class.reflect_on_association(assoc).klass | |
751 | fields_by_class.each do |field_class, fields| |
|
663 | fields_by_class.each do |field_class, fields| | |
752 | if field_class.customized_class <= association_klass |
|
664 | if field_class.customized_class <= association_klass | |
753 | add_custom_fields_filters(fields, assoc) |
|
665 | add_custom_fields_filters(fields, assoc) | |
@@ -788,7 +700,7 class Query < ActiveRecord::Base | |||||
788 |
|
700 | |||
789 | if order_options |
|
701 | if order_options | |
790 | if order_options.include?('authors') |
|
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 | end |
|
704 | end | |
793 | order_options.scan(/cf_\d+/).uniq.each do |name| |
|
705 | order_options.scan(/cf_\d+/).uniq.each do |name| | |
794 | column = available_columns.detect {|c| c.name.to_s == name} |
|
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