From 525656a4900f01fb1032e7ca92a3deac16386c5b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Sat, 18 Dec 2010 16:06:20 +0000 Subject: [PATCH] Ported subtasks display with indentation to the new gantt (#7128) and fixed markers alignment. git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4534 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- lib/redmine/helpers/gantt.rb | 64 ++++++++++++++++---- public/images/task_parent_end.png | Bin 165 -> 224 bytes public/stylesheets/application.css | 10 +-- test/unit/lib/redmine/helpers/gantt_test.rb | 46 ++++++++++---- 4 files changed, 92 insertions(+), 28 deletions(-) diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb index 6ce98f775..af0fc4c72 100644 --- a/lib/redmine/helpers/gantt.rb +++ b/lib/redmine/helpers/gantt.rb @@ -72,6 +72,8 @@ module Redmine @lines = '' @number_of_rows = nil + @issue_ancestors = [] + @truncated = false if options.has_key?(:max_rows) @max_rows = options[:max_rows] @@ -213,14 +215,18 @@ module Redmine end def render_issues(issues, options={}) + @issue_ancestors = [] + issues.each do |i| subject_for_issue(i, options) unless options[:only] == :lines line_for_issue(i, options) unless options[:only] == :subjects options[:top] += options[:top_increment] @number_of_rows += 1 - return if abort? + break if abort? end + + options[:indent] -= (options[:indent_increment] * @issue_ancestors.size) end def render_version(version, options={}) @@ -332,7 +338,12 @@ module Redmine end def subject_for_issue(issue, options) - case options[:format] + while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last) + @issue_ancestors.pop + options[:indent] -= options[:indent_increment] + end + + output = case options[:format] when :html css_classes = '' css_classes << ' issue-overdue' if issue.overdue? @@ -346,13 +357,20 @@ module Redmine end subject << view.link_to_issue(issue) subject << '' - html_subject(options, subject, :css => "issue-subject") + html_subject(options, subject, :css => "issue-subject") + "\n" when :image image_subject(options, issue.subject) when :pdf pdf_new_page?(options) pdf_subject(options, issue.subject) end + + unless issue.leaf? + @issue_ancestors << issue + options[:indent] += options[:indent_increment] + end + + output end def line_for_issue(issue, options) @@ -363,7 +381,7 @@ module Redmine case options[:format] when :html - html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue) + html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?) when :image image_task(options, coords, :label => label) when :pdf @@ -655,12 +673,34 @@ module Redmine # Sorts a collection of issues by start_date, due_date, id for gantt rendering def sort_issues!(issues) - issues.sort! do |a, b| - cmp = 0 - cmp = (a.start_date <=> b.start_date) if a.start_date? && b.start_date? - cmp = (a.due_date <=> b.due_date) if cmp == 0 && a.due_date? && b.due_date? - cmp = (a.id <=> b.id) if cmp == 0 - cmp + issues.sort! { |a, b| gantt_issue_compare(a, b, issues) } + end + + def gantt_issue_compare(x, y, issues) + if x.parent_id == y.parent_id + gantt_start_compare(x, y) + elsif x.is_ancestor_of?(y) + -1 + elsif y.is_ancestor_of?(x) + 1 + else + ax = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(x) && !i.is_ancestor_of?(y) }.sort_by(&:lft).first + ay = issues.select {|i| i.is_a?(Issue) && i.is_ancestor_of?(y) && !i.is_ancestor_of?(x) }.sort_by(&:lft).first + if ax.nil? && ay.nil? + gantt_start_compare(x, y) + else + gantt_issue_compare(ax || x, ay || y, issues) + end + end + end + + def gantt_start_compare(x, y) + if x.start_date.nil? + -1 + elsif y.start_date.nil? + 1 + else + x.start_date <=> y.start_date end end @@ -733,12 +773,12 @@ module Redmine output << "
 
" end if coords[:end] - output << "
 
" + output << "
 
" end end # Renders the label on the right if options[:label] - output << "
" + output << "
" output << options[:label] output << "
" end diff --git a/public/images/task_parent_end.png b/public/images/task_parent_end.png index fc920564345aa04a2572bf729daac9e635d4cc04..9442b86a5f3392fa023d38a49c78c94a64337e39 100644 GIT binary patch delta 123 zcmZ3=_<(VOiYa@Er>`sfEgm6$C7B;}yVfx4nJa0`Jj5jgR z3=A9lx&I`xPE_=@~ diff --git a/public/stylesheets/application.css b/public/stylesheets/application.css index ce9b346f6..4ff5c28bd 100644 --- a/public/stylesheets/application.css +++ b/public/stylesheets/application.css @@ -799,20 +799,20 @@ background-image:url('../images/close_hl.png'); .task_done { background:#00c600 url(../images/task_done.png); border: 1px solid #00c600; } .task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } -.task_todo.parent { background: #888; border: 1px solid #888; height: 6px;} +.task_todo.parent { background: #888; border: 1px solid #888; height: 3px;} .task_late.parent, .task_done.parent { height: 3px;} -.task_todo.parent .left { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -5px; left: 0px; top: -1px;} -.task_todo.parent .right { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-right: -5px; right: 0px; top: -1px;} +.task.parent.marker.starting { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; left: 0px; top: -1px;} +.task.parent.marker.ending { position: absolute; background: url(../images/task_parent_end.png) no-repeat 0 0; width: 8px; height: 16px; margin-left: -4px; right: 0px; top: -1px;} .version.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} .version.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} .version.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} -.version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; } +.version.marker { background-image:url(../images/version_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; } .project.task_late { background:#f66 url(../images/milestone_late.png); border: 1px solid #f66; height: 2px; margin-top: 3px;} .project.task_done { background:#00c600 url(../images/milestone_done.png); border: 1px solid #00c600; height: 2px; margin-top: 3px;} .project.task_todo { background:#fff url(../images/milestone_todo.png); border: 1px solid #fff; height: 2px; margin-top: 3px;} -.project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; } +.project.marker { background-image:url(../images/project_marker.png); background-repeat: no-repeat; border: 0; margin-left: -4px; margin-top: 1px; } .version-behind-schedule a, .issue-behind-schedule a {color: #f66914;} .version-overdue a, .issue-overdue a, .project-overdue a {color: #f00;} diff --git a/test/unit/lib/redmine/helpers/gantt_test.rb b/test/unit/lib/redmine/helpers/gantt_test.rb index 526679df7..02d69120a 100644 --- a/test/unit/lib/redmine/helpers/gantt_test.rb +++ b/test/unit/lib/redmine/helpers/gantt_test.rb @@ -158,39 +158,63 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase :done_ratio => 30, :start_date => Date.yesterday, :due_date => 1.week.from_now.to_date) - @project.issues << @issue - - @response.body = @gantt.subjects + @project.issues << @issue end - + context "project" do should "be rendered" do + @response.body = @gantt.subjects assert_select "div.project-name a", /#{@project.name}/ end - + should "have an indent of 4" do + @response.body = @gantt.subjects assert_select "div.project-name[style*=left:4px]" end end - + context "version" do should "be rendered" do + @response.body = @gantt.subjects assert_select "div.version-name a", /#{@version.name}/ end - + should "be indented 24 (one level)" do + @response.body = @gantt.subjects assert_select "div.version-name[style*=left:24px]" end end - + context "issue" do should "be rendered" do + @response.body = @gantt.subjects assert_select "div.issue-subject", /#{@issue.subject}/ end - + should "be indented 44 (two levels)" do + @response.body = @gantt.subjects assert_select "div.issue-subject[style*=left:44px]" end + + context "with subtasks" do + setup do + attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version} + @child1 = Issue.generate!(attrs.merge(:subject => 'child1', :parent_issue_id => @issue.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date)) + @child2 = Issue.generate!(attrs.merge(:subject => 'child2', :parent_issue_id => @issue.id, :start_date => Date.today, :due_date => 1.week.from_now.to_date)) + @grandchild = Issue.generate!(attrs.merge(:subject => 'grandchild', :parent_issue_id => @child1.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date)) + end + + should "indent subtasks" do + @response.body = @gantt.subjects + # parent task 44px + assert_select "div.issue-subject[style*=left:44px]", /#{@issue.subject}/ + # children 64px + assert_select "div.issue-subject[style*=left:64px]", /child1/ + assert_select "div.issue-subject[style*=left:64px]", /child2/ + # grandchild 84px + assert_select "div.issue-subject[style*=left:84px]", /grandchild/, @response.body + end + end end end @@ -379,7 +403,7 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase should "appear at the end of the date range" do @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4}) - assert_select "div.project.ending[style*=left:84px]", true, @response.body + assert_select "div.project.ending[style*=left:88px]", true, @response.body end end @@ -546,7 +570,7 @@ class Redmine::Helpers::GanttTest < ActiveSupport::TestCase should "appear at the end of the date range" do @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4}) - assert_select "div.version.ending[style*=left:84px]", true, @response.body + assert_select "div.version.ending[style*=left:88px]", true, @response.body end end