Merged r11827 to r11833 from trunk (#14015).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/2.3-stable@11996 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2013-07-11 18:28:54 +00:00
parent 9874a61ec3
commit 7375dff6d6
2 changed files with 220 additions and 9 deletions

View File

@ -854,18 +854,99 @@ class Issue < ActiveRecord::Base
end
# Returns all the other issues that depend on the issue
# The algorithm is a modified breadth first search (bfs)
def all_dependent_issues(except=[])
except << self
# The found dependencies
dependencies = []
dependencies += relations_from.map(&:issue_to)
dependencies += children unless leaf?
dependencies.compact!
# The visited flag for every node (issue) used by the breadth first search
eNOT_DISCOVERED = 0 # The issue is "new" to the algorithm, it has not seen it before.
ePROCESS_ALL = 1 # The issue is added to the queue. Process both children and relations of
# the issue when it is processed.
ePROCESS_RELATIONS_ONLY = 2 # The issue was added to the queue and will be output as dependent issue,
# but its children will not be added to the queue when it is processed.
eRELATIONS_PROCESSED = 3 # The related issues, the parent issue and the issue itself have been added to
# the queue, but its children have not been added.
ePROCESS_CHILDREN_ONLY = 4 # The relations and the parent of the issue have been added to the queue, but
# the children still need to be processed.
eALL_PROCESSED = 5 # The issue and all its children, its parent and its related issues have been
# added as dependent issues. It needs no further processing.
issue_status = Hash.new(eNOT_DISCOVERED)
# The queue
queue = []
# Initialize the bfs, add start node (self) to the queue
queue << self
issue_status[self] = ePROCESS_ALL
while (!queue.empty?) do
current_issue = queue.shift
current_issue_status = issue_status[current_issue]
dependencies << current_issue
# Add parent to queue, if not already in it.
parent = current_issue.parent
parent_status = issue_status[parent]
if parent && (parent_status == eNOT_DISCOVERED) && !except.include?(parent)
queue << parent
issue_status[parent] = ePROCESS_RELATIONS_ONLY
end
# Add children to queue, but only if they are not already in it and
# the children of the current node need to be processed.
if (current_issue_status == ePROCESS_CHILDREN_ONLY || current_issue_status == ePROCESS_ALL)
current_issue.children.each do |child|
next if except.include?(child)
if (issue_status[child] == eNOT_DISCOVERED)
queue << child
issue_status[child] = ePROCESS_ALL
elsif (issue_status[child] == eRELATIONS_PROCESSED)
queue << child
issue_status[child] = ePROCESS_CHILDREN_ONLY
elsif (issue_status[child] == ePROCESS_RELATIONS_ONLY)
queue << child
issue_status[child] = ePROCESS_ALL
end
end
end
# Add related issues to the queue, if they are not already in it.
current_issue.relations_from.map(&:issue_to).each do |related_issue|
next if except.include?(related_issue)
if (issue_status[related_issue] == eNOT_DISCOVERED)
queue << related_issue
issue_status[related_issue] = ePROCESS_ALL
elsif (issue_status[related_issue] == eRELATIONS_PROCESSED)
queue << related_issue
issue_status[related_issue] = ePROCESS_CHILDREN_ONLY
elsif (issue_status[related_issue] == ePROCESS_RELATIONS_ONLY)
queue << related_issue
issue_status[related_issue] = ePROCESS_ALL
end
end
# Set new status for current issue
if (current_issue_status == ePROCESS_ALL) || (current_issue_status == ePROCESS_CHILDREN_ONLY)
issue_status[current_issue] = eALL_PROCESSED
elsif (current_issue_status == ePROCESS_RELATIONS_ONLY)
issue_status[current_issue] = eRELATIONS_PROCESSED
end
end # while
# Remove the issues from the "except" parameter from the result array
dependencies -= except
dependencies += dependencies.map {|issue| issue.all_dependent_issues(except)}.flatten
if parent
dependencies << parent
dependencies += parent.all_dependent_issues(except + parent.descendants)
end
dependencies.delete(self)
dependencies
end

View File

@ -1792,6 +1792,136 @@ class IssueTest < ActiveSupport::TestCase
assert_equal [2, 3, 8], Issue.find(1).all_dependent_issues.collect(&:id).sort
end
def test_all_dependent_issues_with_subtask
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue = Issue.generate!(:project => project)
childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
assert_equal [childIssue1.id, childIssue2.id].sort, parentIssue.all_dependent_issues.collect(&:id).uniq.sort
end
def test_all_dependent_issues_does_not_include_self
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue = Issue.generate!(:project => project)
childIssue = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
assert_equal [childIssue.id], parentIssue.all_dependent_issues.collect(&:id)
end
def test_all_dependent_issues_with_parenttask_and_sibling
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue = Issue.generate!(:project => project)
childIssue1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
childIssue2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue.id)
assert_equal [parentIssue.id].sort, childIssue1.all_dependent_issues.collect(&:id)
end
def test_all_dependent_issues_with_relation_to_leaf_in_other_tree
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue1 = Issue.generate!(:project => project)
childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
parentIssue2 = Issue.generate!(:project => project)
childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
assert IssueRelation.create(:issue_from => parentIssue1,
:issue_to => childIssue2_2,
:relation_type => IssueRelation::TYPE_BLOCKS)
assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_2.id].sort,
parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
end
def test_all_dependent_issues_with_relation_to_parent_in_other_tree
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue1 = Issue.generate!(:project => project)
childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
childIssue1_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
parentIssue2 = Issue.generate!(:project => project)
childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
childIssue2_2 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
assert IssueRelation.create(:issue_from => parentIssue1,
:issue_to => parentIssue2,
:relation_type => IssueRelation::TYPE_BLOCKS)
assert_equal [childIssue1_1.id, childIssue1_2.id, parentIssue2.id, childIssue2_1.id, childIssue2_2.id].sort,
parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
end
def test_all_dependent_issues_with_transitive_relation
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue1 = Issue.generate!(:project => project)
childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
parentIssue2 = Issue.generate!(:project => project)
childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
independentIssue = Issue.generate!(:project => project)
assert IssueRelation.create(:issue_from => parentIssue1,
:issue_to => childIssue2_1,
:relation_type => IssueRelation::TYPE_RELATES)
assert IssueRelation.create(:issue_from => childIssue2_1,
:issue_to => independentIssue,
:relation_type => IssueRelation::TYPE_RELATES)
assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
end
def test_all_dependent_issues_with_transitive_relation2
IssueRelation.delete_all
project = Project.generate!(:name => "testproject")
parentIssue1 = Issue.generate!(:project => project)
childIssue1_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue1.id)
parentIssue2 = Issue.generate!(:project => project)
childIssue2_1 = Issue.generate!(:project => project, :parent_issue_id => parentIssue2.id)
independentIssue = Issue.generate!(:project => project)
assert IssueRelation.create(:issue_from => parentIssue1,
:issue_to => independentIssue,
:relation_type => IssueRelation::TYPE_RELATES)
assert IssueRelation.create(:issue_from => independentIssue,
:issue_to => childIssue2_1,
:relation_type => IssueRelation::TYPE_RELATES)
assert_equal [childIssue1_1.id, parentIssue2.id, childIssue2_1.id, independentIssue.id].sort,
parentIssue1.all_dependent_issues.collect(&:id).uniq.sort
end
def test_all_dependent_issues_with_persistent_circular_dependency
IssueRelation.delete_all
assert IssueRelation.create!(:issue_from => Issue.find(1),