summaryrefslogtreecommitdiffstats
path: root/app/models/query.rb
diff options
context:
space:
mode:
authorJean-Philippe Lang <jp_lang@yahoo.fr>2012-09-29 12:57:38 +0000
committerJean-Philippe Lang <jp_lang@yahoo.fr>2012-09-29 12:57:38 +0000
commit1b6da80e16dd0cb200588422dfc5a655cb3b94f0 (patch)
tree5a4bedc3760b89680f96e989fa606fcba92e6250 /app/models/query.rb
parent69b8931e92e37feceb7353d5baa6be6a51492bf0 (diff)
downloadredmine-1b6da80e16dd0cb200588422dfc5a655cb3b94f0.tar.gz
redmine-1b6da80e16dd0cb200588422dfc5a655cb3b94f0.zip
Makes related issues available for display and filtering on the issue list (#3239, #3265).
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10513 e93f8b46-1217-0410-a6f0-8f06a7374b81
Diffstat (limited to 'app/models/query.rb')
-rw-r--r--app/models/query.rb73
1 files changed, 65 insertions, 8 deletions
diff --git a/app/models/query.rb b/app/models/query.rb
index ddf4d87d9..92a568119 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -113,7 +113,9 @@ class Query < ActiveRecord::Base
"<t-" => :label_more_than_ago,
"t-" => :label_ago,
"~" => :label_contains,
- "!~" => :label_not_contains }
+ "!~" => :label_not_contains,
+ "=p" => :label_any_issues_in_project,
+ "=!p" => :label_any_issues_not_in_project}
cattr_reader :operators
@@ -126,7 +128,8 @@ class Query < ActiveRecord::Base
:string => [ "=", "~", "!", "!~", "!*", "*" ],
:text => [ "~", "!~", "!*", "*" ],
:integer => [ "=", ">=", "<=", "><", "!*", "*" ],
- :float => [ "=", ">=", "<=", "><", "!*", "*" ] }
+ :float => [ "=", ">=", "<=", "><", "!*", "*" ],
+ :relation => ["=", "=p", "=!p", "!*", "*"]}
cattr_reader :operators_by_filter_type
@@ -147,6 +150,7 @@ class Query < ActiveRecord::Base
QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
+ QueryColumn.new(:relations, :caption => :label_related_issues)
]
cattr_reader :available_columns
@@ -233,6 +237,10 @@ class Query < ActiveRecord::Base
"estimated_hours" => { :type => :float, :order => 13 },
"done_ratio" => { :type => :integer, :order => 14 }}
+ IssueRelation::TYPES.each do |relation_type, options|
+ @available_filters[relation_type] = {:type => :relation, :order => @available_filters.size + 100, :label => options[:name]}
+ end
+
principals = []
if project
principals += project.principals.sort
@@ -244,7 +252,6 @@ class Query < ActiveRecord::Base
end
end
else
- all_projects = Project.visible.all
if all_projects.any?
# members of visible projects
principals += Principal.member_of(all_projects)
@@ -254,10 +261,7 @@ class Query < ActiveRecord::Base
if User.current.logged? && User.current.memberships.any?
project_values << ["<< #{l(:label_my_projects).downcase} >>", "mine"]
end
- Project.project_tree(all_projects) do |p, level|
- prefix = (level > 0 ? ('--' * level + ' ') : '')
- project_values << ["#{prefix}#{p.name}", p.id.to_s]
- end
+ project_values += all_projects_values
@available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
end
end
@@ -317,7 +321,7 @@ class Query < ActiveRecord::Base
}
@available_filters.each do |field, options|
- options[:name] ||= l("field_#{field}".gsub(/_id$/, ''))
+ options[:name] ||= l(options[:label] || "field_#{field}".gsub(/_id$/, ''))
end
@available_filters
@@ -332,6 +336,21 @@ class Query < ActiveRecord::Base
json
end
+ def all_projects
+ @all_projects ||= Project.visible.all
+ end
+
+ def all_projects_values
+ return @all_projects_values if @all_projects_values
+
+ values = []
+ Project.project_tree(all_projects) do |p, level|
+ prefix = (level > 0 ? ('--' * level + ' ') : '')
+ values << ["#{prefix}#{p.name}", p.id.to_s]
+ end
+ @all_projects_values = values
+ end
+
def add_filter(field, operator, values)
# values must be an array
return unless values.nil? || values.is_a?(Array)
@@ -635,6 +654,9 @@ class Query < ActiveRecord::Base
if has_column?(:spent_hours)
Issue.load_visible_spent_hours(issues)
end
+ if has_column?(:relations)
+ Issue.load_visible_relations(issues)
+ end
issues
rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
@@ -729,6 +751,41 @@ class Query < ActiveRecord::Base
"#{Issue.table_name}.is_private #{op} (#{va})"
end
+ def sql_for_relations(field, operator, value, options={})
+ relation_options = IssueRelation::TYPES[field]
+ return relation_options unless relation_options
+
+ relation_type = field
+ join_column, target_join_column = "issue_from_id", "issue_to_id"
+ if relation_options[:reverse] || options[:reverse]
+ relation_type = relation_options[:reverse] || relation_type
+ join_column, target_join_column = target_join_column, join_column
+ end
+
+ sql = case operator
+ when "*", "!*"
+ op = (operator == "*" ? 'IN' : 'NOT IN')
+ "#{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)}')"
+ when "=", "!"
+ op = (operator == "=" ? 'IN' : 'NOT IN')
+ "#{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})"
+ when "=p", "=!p"
+ op = (operator == "=p" ? '=' : '<>')
+ "#{Issue.table_name}.id IN (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 #{op} #{value.first.to_i})"
+ end
+
+ if relation_options[:sym] == field && !options[:reverse]
+ sqls = [sql, sql_for_relations(field, operator, value, :reverse => true)]
+ sqls.join(["!", "!*"].include?(operator) ? " AND " : " OR ")
+ else
+ sql
+ end
+ end
+
+ IssueRelation::TYPES.keys.each do |relation_type|
+ alias_method "sql_for_#{relation_type}_field".to_sym, :sql_for_relations
+ end
+
private
def sql_for_custom_field(field, operator, value, custom_field_id)