git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@10513 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
+20
-1
@@ -752,7 +752,7 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def relations
|
||||
@relations ||= (relations_from + relations_to).sort
|
||||
@relations ||= IssueRelations.new(self, (relations_from + relations_to).sort)
|
||||
end
|
||||
|
||||
# Preloads relations for a collection of issues
|
||||
@@ -775,6 +775,25 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# Preloads visible relations for a collection of issues
|
||||
def self.load_visible_relations(issues, user=User.current)
|
||||
if issues.any?
|
||||
issue_ids = issues.map(&:id)
|
||||
# Relations with issue_from in given issues and visible issue_to
|
||||
relations_from = IssueRelation.includes(:issue_to => [:status, :project]).where(visible_condition(user)).where(:issue_from_id => issue_ids).all
|
||||
# Relations with issue_to in given issues and visible issue_from
|
||||
relations_to = IssueRelation.includes(:issue_from => [:status, :project]).where(visible_condition(user)).where(:issue_to_id => issue_ids).all
|
||||
|
||||
issues.each do |issue|
|
||||
relations =
|
||||
relations_from.select {|relation| relation.issue_from_id == issue.id} +
|
||||
relations_to.select {|relation| relation.issue_to_id == issue.id}
|
||||
|
||||
issue.instance_variable_set "@relations", IssueRelations.new(issue, relations.sort)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Finds an issue relation given its id.
|
||||
def find_relation(relation_id)
|
||||
IssueRelation.find(relation_id, :conditions => ["issue_to_id = ? OR issue_from_id = ?", id, id])
|
||||
|
||||
@@ -15,6 +15,20 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
# Class used to represent the relations of an issue
|
||||
class IssueRelations < Array
|
||||
include Redmine::I18n
|
||||
|
||||
def initialize(issue, *args)
|
||||
@issue = issue
|
||||
super(*args)
|
||||
end
|
||||
|
||||
def to_s(*args)
|
||||
map {|relation| "#{l(relation.label_for(@issue))} ##{relation.other_issue(@issue).id}"}.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
class IssueRelation < ActiveRecord::Base
|
||||
belongs_to :issue_from, :class_name => 'Issue', :foreign_key => 'issue_from_id'
|
||||
belongs_to :issue_to, :class_name => 'Issue', :foreign_key => 'issue_to_id'
|
||||
@@ -103,6 +117,10 @@ class IssueRelation < ActiveRecord::Base
|
||||
TYPES[relation_type] ? TYPES[relation_type][(self.issue_from_id == issue.id) ? :name : :sym_name] : :unknow
|
||||
end
|
||||
|
||||
def css_classes_for(issue)
|
||||
"rel-#{relation_type_for(issue)}"
|
||||
end
|
||||
|
||||
def handle_issue_order
|
||||
reverse_if_needed
|
||||
|
||||
@@ -128,7 +146,8 @@ class IssueRelation < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def <=>(relation)
|
||||
TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
|
||||
r = TYPES[self.relation_type][:order] <=> TYPES[relation.relation_type][:order]
|
||||
r == 0 ? id <=> relation.id : r
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
+65
-8
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user