Gantt: do not ignore project filter (#7000, #7352), do not display empty projects/versions, and display shared versions used in other projects (#5817, #6476, #6604).

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@5077 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2011-03-10 18:07:09 +00:00
parent b1cc8511c7
commit ec8d9a7911
2 changed files with 104 additions and 97 deletions

View File

@ -1,5 +1,5 @@
# Redmine - project management software # Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -98,51 +98,25 @@ module Redmine
common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months }) common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
end end
### Extracted from the HTML view/helpers
# Returns the number of rows that will be rendered on the Gantt chart # Returns the number of rows that will be rendered on the Gantt chart
def number_of_rows def number_of_rows
return @number_of_rows if @number_of_rows return @number_of_rows if @number_of_rows
rows = if @project rows = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)}
number_of_rows_on_project(@project)
else
Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
total += number_of_rows_on_project(project)
end
end
rows > @max_rows ? @max_rows : rows rows > @max_rows ? @max_rows : rows
end end
# Returns the number of rows that will be used to list a project on # Returns the number of rows that will be used to list a project on
# the Gantt chart. This will recurse for each subproject. # the Gantt chart. This will recurse for each subproject.
def number_of_rows_on_project(project) def number_of_rows_on_project(project)
# Remove the project requirement for Versions because it will return 0 unless projects.include?(project)
# restrict issues to only be on the current project. This
# ends up missing issues which are assigned to shared versions.
@query.project = nil if @query.project
# One Root project
count = 1 count = 1
# Issues without a Version count += project_issues(project).size
count += project.issues.for_gantt.without_version.with_query(@query).count count += project_versions(project).size
# Versions
count += project.versions.count
# Issues on the Versions
project.versions.each do |version|
count += version.fixed_issues.for_gantt.with_query(@query).count
end
# Subprojects
project.children.visible.has_module('issue_tracking').each do |subproject|
count += number_of_rows_on_project(subproject)
end
count count
end end
# Renders the subjects of the Gantt chart, the left side. # Renders the subjects of the Gantt chart, the left side.
def subjects(options={}) def subjects(options={})
render(options.merge(:only => :subjects)) unless @subjects_rendered render(options.merge(:only => :subjects)) unless @subjects_rendered
@ -155,20 +129,60 @@ module Redmine
@lines @lines
end end
# Returns issues that will be rendered
def issues
@issues ||= @query.issues(
:include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
:order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
:limit => @max_rows
)
end
# Return all the project nodes that will be displayed
def projects
return @projects if @projects
ids = issues.collect(&:project).uniq.collect(&:id)
if ids.any?
# All issues projects and their visible ancestors
@projects = Project.visible.all(
:joins => "LEFT JOIN #{Project.table_name} child ON #{Project.table_name}.lft <= child.lft AND #{Project.table_name}.rgt >= child.rgt",
:conditions => ["child.id IN (?)", ids],
:order => "#{Project.table_name}.lft ASC"
).uniq
else
@projects = []
end
end
# Returns the issues that belong to +project+
def project_issues(project)
@issues_by_project ||= issues.group_by(&:project)
@issues_by_project[project] || []
end
# Returns the distinct versions of the issues that belong to +project+
def project_versions(project)
project_issues(project).collect(&:fixed_version).compact.uniq
end
# Returns the issues that belong to +project+ and are assigned to +version+
def version_issues(project, version)
project_issues(project).select {|issue| issue.fixed_version == version}
end
def render(options={}) def render(options={})
options = {:indent => 4, :render => :subject, :format => :html}.merge(options) options = {:top => 0, :top_increment => 20, :indent_increment => 20, :render => :subject, :format => :html}.merge(options)
indent = options[:indent] || 4
@subjects = '' unless options[:only] == :lines @subjects = '' unless options[:only] == :lines
@lines = '' unless options[:only] == :subjects @lines = '' unless options[:only] == :subjects
@number_of_rows = 0 @number_of_rows = 0
if @project Project.project_tree(projects) do |project, level|
render_project(@project, options) options[:indent] = indent + level * options[:indent_increment]
else render_project(project, options)
Project.roots.visible.has_module('issue_tracking').each do |project| break if abort?
render_project(project, options)
break if abort?
end
end end
@subjects_rendered = true unless options[:only] == :lines @subjects_rendered = true unless options[:only] == :lines
@ -178,10 +192,6 @@ module Redmine
end end
def render_project(project, options={}) def render_project(project, options={})
options[:top] = 0 unless options.key? :top
options[:indent_increment] = 20 unless options.key? :indent_increment
options[:top_increment] = 20 unless options.key? :top_increment
subject_for_project(project, options) unless options[:only] == :lines subject_for_project(project, options) unless options[:only] == :lines
line_for_project(project, options) unless options[:only] == :subjects line_for_project(project, options) unless options[:only] == :subjects
@ -190,26 +200,18 @@ module Redmine
@number_of_rows += 1 @number_of_rows += 1
return if abort? return if abort?
# Second, Issues without a version issues = project_issues(project).select {|i| i.fixed_version.nil?}
issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
sort_issues!(issues) sort_issues!(issues)
if issues if issues
render_issues(issues, options) render_issues(issues, options)
return if abort? return if abort?
end end
# Third, Versions versions = project_versions(project)
project.versions.sort.each do |version| versions.each do |version|
render_version(version, options) render_version(project, version, options)
return if abort?
end end
# Fourth, subprojects
project.children.visible.has_module('issue_tracking').each do |project|
render_project(project, options)
return if abort?
end unless project.leaf?
# Remove indent to hit the next sibling # Remove indent to hit the next sibling
options[:indent] -= options[:indent_increment] options[:indent] -= options[:indent_increment]
end end
@ -229,7 +231,7 @@ module Redmine
options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
end end
def render_version(version, options={}) def render_version(project, version, options={})
# Version header # Version header
subject_for_version(version, options) unless options[:only] == :lines subject_for_version(version, options) unless options[:only] == :lines
line_for_version(version, options) unless options[:only] == :subjects line_for_version(version, options) unless options[:only] == :subjects
@ -238,12 +240,7 @@ module Redmine
@number_of_rows += 1 @number_of_rows += 1
return if abort? return if abort?
# Remove the project requirement for Versions because it will issues = version_issues(project, version)
# restrict issues to only be on the current project. This
# ends up missing issues which are assigned to shared versions.
@query.project = nil if @query.project
issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
if issues if issues
sort_issues!(issues) sort_issues!(issues)
# Indent issues # Indent issues

View File

@ -1,5 +1,5 @@
# redMine - project management software # Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang # Copyright (C) 2006-2011 Jean-Philippe Lang
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # modify it under the terms of the GNU General Public License
@ -95,15 +95,9 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
setup do setup do
create_gantt create_gantt
end end
should "clear the @query.project so cross-project issues and versions can be counted" do
assert @gantt.query.project
@gantt.number_of_rows_on_project(@project)
assert_nil @gantt.query.project
end
should "count 1 for the project itself" do should "count 0 for an empty the project" do
assert_equal 1, @gantt.number_of_rows_on_project(@project) assert_equal 0, @gantt.number_of_rows_on_project(@project)
end end
should "count the number of issues without a version" do should "count the number of issues without a version" do
@ -111,12 +105,6 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
assert_equal 2, @gantt.number_of_rows_on_project(@project) assert_equal 2, @gantt.number_of_rows_on_project(@project)
end end
should "count the number of versions" do
@project.versions << Version.generate!
@project.versions << Version.generate!
assert_equal 3, @gantt.number_of_rows_on_project(@project)
end
should "count the number of issues on versions, including cross-project" do should "count the number of issues on versions, including cross-project" do
version = Version.generate! version = Version.generate!
@project.versions << version @project.versions << version
@ -124,21 +112,6 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
assert_equal 3, @gantt.number_of_rows_on_project(@project) assert_equal 3, @gantt.number_of_rows_on_project(@project)
end end
should "recursive and count the number of rows on each subproject" do
@project.versions << Version.generate! # +1
@subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
@subproject.set_parent!(@project)
@subproject.issues << Issue.generate_for_project!(@subproject) # +1
@subproject.issues << Issue.generate_for_project!(@subproject) # +1
@subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
@subsubproject.set_parent!(@subproject)
@subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1
assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self
end
end end
# TODO: more of an integration test # TODO: more of an integration test
@ -183,6 +156,18 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
@response.body = @gantt.subjects @response.body = @gantt.subjects
assert_select "div.version-name[style*=left:24px]" assert_select "div.version-name[style*=left:24px]"
end end
context "without assigned issues" do
setup do
@version = Version.generate!(:effective_date => 2.week.from_now.to_date, :sharing => 'none', :name => 'empty_version')
@project.versions << @version
end
should "not be rendered" do
@response.body = @gantt.subjects
assert_select "div.version-name a", :text => /#{@version.name}/, :count => 0
end
end
end end
context "issue" do context "issue" do
@ -196,6 +181,31 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
assert_select "div.issue-subject[style*=left:44px]" assert_select "div.issue-subject[style*=left:44px]"
end end
context "assigned to a shared version of another project" do
setup do
p = Project.generate!
p.trackers << @tracker
p.enabled_module_names = [:issue_tracking]
@shared_version = Version.generate!(:sharing => 'system')
p.versions << @shared_version
# Reassign the issue to a shared version of another project
@issue = Issue.generate!(:fixed_version => @shared_version,
:subject => "gantt#assigned_to_shared_version",
:tracker => @tracker,
:project => @project,
:done_ratio => 30,
:start_date => Date.yesterday,
:due_date => 1.week.from_now.to_date)
@project.issues << @issue
end
should "be rendered" do
@response.body = @gantt.subjects
assert_select "div.issue-subject", /#{@issue.subject}/
end
end
context "with subtasks" do context "with subtasks" do
setup do setup do
attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version} attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version}