diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index f551f1c1a..68f3dfaec 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -1,5 +1,5 @@ # 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 # 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 }) end - ### Extracted from the HTML view/helpers # Returns the number of rows that will be rendered on the Gantt chart def number_of_rows return @number_of_rows if @number_of_rows - rows = if @project - 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 = projects.inject(0) {|total, p| total += number_of_rows_on_project(p)} rows > @max_rows ? @max_rows : rows end - + # Returns the number of rows that will be used to list a project on # the Gantt chart. This will recurse for each subproject. def number_of_rows_on_project(project) - # Remove the project requirement for Versions because it will - # 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 + return 0 unless projects.include?(project) + count = 1 - # Issues without a Version - count += project.issues.for_gantt.without_version.with_query(@query).count - - # 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 += project_issues(project).size + count += project_versions(project).size count end - + # Renders the subjects of the Gantt chart, the left side. def subjects(options={}) render(options.merge(:only => :subjects)) unless @subjects_rendered @@ -155,20 +129,60 @@ module Redmine @lines 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={}) - 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 @lines = '' unless options[:only] == :subjects @number_of_rows = 0 - if @project - render_project(@project, options) - else - Project.roots.visible.has_module('issue_tracking').each do |project| - render_project(project, options) - break if abort? - end + Project.project_tree(projects) do |project, level| + options[:indent] = indent + level * options[:indent_increment] + render_project(project, options) + break if abort? end @subjects_rendered = true unless options[:only] == :lines @@ -178,10 +192,6 @@ module Redmine end 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 line_for_project(project, options) unless options[:only] == :subjects @@ -190,26 +200,18 @@ module Redmine @number_of_rows += 1 return if abort? - # Second, Issues without a version - issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit) + issues = project_issues(project).select {|i| i.fixed_version.nil?} sort_issues!(issues) if issues render_issues(issues, options) return if abort? end - - # Third, Versions - project.versions.sort.each do |version| - render_version(version, options) - return if abort? + + versions = project_versions(project) + versions.each do |version| + render_version(project, version, options) 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 options[:indent] -= options[:indent_increment] end @@ -229,7 +231,7 @@ module Redmine options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) end - def render_version(version, options={}) + def render_version(project, version, options={}) # Version header subject_for_version(version, options) unless options[:only] == :lines line_for_version(version, options) unless options[:only] == :subjects @@ -238,12 +240,7 @@ module Redmine @number_of_rows += 1 return if abort? - # Remove the project requirement for Versions because it will - # 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) + issues = version_issues(project, version) if issues sort_issues!(issues) # Indent issues diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb index ec9b0651b..1f6d507b7 100644 --- a/test/unit/lib/redmine/helpers/gantt_test.rb +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -1,5 +1,5 @@ -# redMine - project management software -# Copyright (C) 2006-2008 Jean-Philippe Lang +# Redmine - project management software +# Copyright (C) 2006-2011 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -95,15 +95,9 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase setup do create_gantt 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 - assert_equal 1, @gantt.number_of_rows_on_project(@project) + should "count 0 for an empty the project" do + assert_equal 0, @gantt.number_of_rows_on_project(@project) end 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) 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 version = Version.generate! @project.versions << version @@ -124,21 +112,6 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_equal 3, @gantt.number_of_rows_on_project(@project) 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 # TODO: more of an integration test @@ -183,6 +156,18 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase @response.body = @gantt.subjects assert_select "div.version-name[style*=left:24px]" 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 context "issue" do @@ -196,6 +181,31 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase assert_select "div.issue-subject[style*=left:44px]" 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 setup do attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version}