comparison lib/redmine/helpers/gantt.rb @ 1295:622f24f53b42 redmine-2.3

Update to Redmine SVN revision 11972 on 2.3-stable branch
author Chris Cannam
date Fri, 14 Jun 2013 09:02:21 +0100
parents 433d4f72a19b
children
comparison
equal deleted inserted replaced
1294:3e4c3460b6ca 1295:622f24f53b42
1 # Redmine - project management software 1 # Redmine - project management software
2 # Copyright (C) 2006-2012 Jean-Philippe Lang 2 # Copyright (C) 2006-2013 Jean-Philippe Lang
3 # 3 #
4 # This program is free software; you can redistribute it and/or 4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License 5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2 6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version. 7 # of the License, or (at your option) any later version.
21 class Gantt 21 class Gantt
22 include ERB::Util 22 include ERB::Util
23 include Redmine::I18n 23 include Redmine::I18n
24 include Redmine::Utils::DateCalculation 24 include Redmine::Utils::DateCalculation
25 25
26 # Relation types that are rendered
27 DRAW_TYPES = {
28 IssueRelation::TYPE_BLOCKS => { :landscape_margin => 16, :color => '#F34F4F' },
29 IssueRelation::TYPE_PRECEDES => { :landscape_margin => 20, :color => '#628FEA' }
30 }.freeze
31
26 # :nodoc: 32 # :nodoc:
27 # Some utility methods for the PDF export 33 # Some utility methods for the PDF export
28 class PDF 34 class PDF
29 MaxCharactorsForSubject = 45 35 MaxCharactorsForSubject = 45
30 TotalWidth = 280 36 TotalWidth = 280
132 @issues ||= @query.issues( 138 @issues ||= @query.issues(
133 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version], 139 :include => [:assigned_to, :tracker, :priority, :category, :fixed_version],
134 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC", 140 :order => "#{Project.table_name}.lft ASC, #{Issue.table_name}.id ASC",
135 :limit => @max_rows 141 :limit => @max_rows
136 ) 142 )
143 end
144
145 # Returns a hash of the relations between the issues that are present on the gantt
146 # and that should be displayed, grouped by issue ids.
147 def relations
148 return @relations if @relations
149 if issues.any?
150 issue_ids = issues.map(&:id)
151 @relations = IssueRelation.
152 where(:issue_from_id => issue_ids, :issue_to_id => issue_ids, :relation_type => DRAW_TYPES.keys).
153 group_by(&:issue_from_id)
154 else
155 @relations = {}
156 end
137 end 157 end
138 158
139 # Return all the project nodes that will be displayed 159 # Return all the project nodes that will be displayed
140 def projects 160 def projects
141 return @projects if @projects 161 return @projects if @projects
275 image_task(options, coords, :label => label, :markers => true, :height => 3) 295 image_task(options, coords, :label => label, :markers => true, :height => 3)
276 when :pdf 296 when :pdf
277 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) 297 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
278 end 298 end
279 else 299 else
280 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
281 '' 300 ''
282 end 301 end
283 end 302 end
284 303
285 def subject_for_version(version, options) 304 def subject_for_version(version, options)
287 when :html 306 when :html
288 html_class = "" 307 html_class = ""
289 html_class << 'icon icon-package ' 308 html_class << 'icon icon-package '
290 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " " 309 html_class << (version.behind_schedule? ? 'version-behind-schedule' : '') << " "
291 html_class << (version.overdue? ? 'version-overdue' : '') 310 html_class << (version.overdue? ? 'version-overdue' : '')
311 html_class << ' version-closed' unless version.open?
312 if version.start_date && version.due_date && version.completed_pourcent
313 progress_date = calc_progress_date(version.start_date,
314 version.due_date, version.completed_pourcent)
315 html_class << ' behind-start-date' if progress_date < self.date_from
316 html_class << ' over-end-date' if progress_date > self.date_to
317 end
292 s = view.link_to_version(version).html_safe 318 s = view.link_to_version(version).html_safe
293 subject = view.content_tag(:span, s, 319 subject = view.content_tag(:span, s,
294 :class => html_class).html_safe 320 :class => html_class).html_safe
295 html_subject(options, subject, :css => "version-name") 321 html_subject(options, subject, :css => "version-name",
322 :id => "version-#{version.id}")
296 when :image 323 when :image
297 image_subject(options, version.to_s_with_project) 324 image_subject(options, version.to_s_with_project)
298 when :pdf 325 when :pdf
299 pdf_new_page?(options) 326 pdf_new_page?(options)
300 pdf_subject(options, version.to_s_with_project) 327 pdf_subject(options, version.to_s_with_project)
301 end 328 end
302 end 329 end
303 330
304 def line_for_version(version, options) 331 def line_for_version(version, options)
305 # Skip versions that don't have a start_date 332 # Skip versions that don't have a start_date
306 if version.is_a?(Version) && version.start_date && version.due_date 333 if version.is_a?(Version) && version.due_date && version.start_date
307 options[:zoom] ||= 1 334 options[:zoom] ||= 1
308 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom] 335 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
309 coords = coordinates(version.start_date, 336 coords = coordinates(version.start_date,
310 version.due_date, version.completed_pourcent, 337 version.due_date, version.completed_percent,
311 options[:zoom]) 338 options[:zoom])
312 label = "#{h version} #{h version.completed_pourcent.to_i.to_s}%" 339 label = "#{h version} #{h version.completed_percent.to_i.to_s}%"
313 label = h("#{version.project} -") + label unless @project && @project == version.project 340 label = h("#{version.project} -") + label unless @project && @project == version.project
314 case options[:format] 341 case options[:format]
315 when :html 342 when :html
316 html_task(options, coords, :css => "version task", :label => label, :markers => true) 343 html_task(options, coords, :css => "version task",
344 :label => label, :markers => true, :version => version)
317 when :image 345 when :image
318 image_task(options, coords, :label => label, :markers => true, :height => 3) 346 image_task(options, coords, :label => label, :markers => true, :height => 3)
319 when :pdf 347 when :pdf
320 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8) 348 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
321 end 349 end
322 else 350 else
323 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
324 '' 351 ''
325 end 352 end
326 end 353 end
327 354
328 def subject_for_issue(issue, options) 355 def subject_for_issue(issue, options)
334 when :html 361 when :html
335 css_classes = '' 362 css_classes = ''
336 css_classes << ' issue-overdue' if issue.overdue? 363 css_classes << ' issue-overdue' if issue.overdue?
337 css_classes << ' issue-behind-schedule' if issue.behind_schedule? 364 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
338 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to 365 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
366 css_classes << ' issue-closed' if issue.closed?
367 if issue.start_date && issue.due_before && issue.done_ratio
368 progress_date = calc_progress_date(issue.start_date,
369 issue.due_before, issue.done_ratio)
370 css_classes << ' behind-start-date' if progress_date < self.date_from
371 css_classes << ' over-end-date' if progress_date > self.date_to
372 end
339 s = "".html_safe 373 s = "".html_safe
340 if issue.assigned_to.present? 374 if issue.assigned_to.present?
341 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name 375 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
342 s << view.avatar(issue.assigned_to, 376 s << view.avatar(issue.assigned_to,
343 :class => 'gravatar icon-gravatar', 377 :class => 'gravatar icon-gravatar',
345 :title => assigned_string).to_s.html_safe 379 :title => assigned_string).to_s.html_safe
346 end 380 end
347 s << view.link_to_issue(issue).html_safe 381 s << view.link_to_issue(issue).html_safe
348 subject = view.content_tag(:span, s, :class => css_classes).html_safe 382 subject = view.content_tag(:span, s, :class => css_classes).html_safe
349 html_subject(options, subject, :css => "issue-subject", 383 html_subject(options, subject, :css => "issue-subject",
350 :title => issue.subject) + "\n" 384 :title => issue.subject, :id => "issue-#{issue.id}") + "\n"
351 when :image 385 when :image
352 image_subject(options, issue.subject) 386 image_subject(options, issue.subject)
353 when :pdf 387 when :pdf
354 pdf_new_page?(options) 388 pdf_new_page?(options)
355 pdf_subject(options, issue.subject) 389 pdf_subject(options, issue.subject)
376 image_task(options, coords, :label => label) 410 image_task(options, coords, :label => label)
377 when :pdf 411 when :pdf
378 pdf_task(options, coords, :label => label) 412 pdf_task(options, coords, :label => label)
379 end 413 end
380 else 414 else
381 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
382 '' 415 ''
383 end 416 end
384 end 417 end
385 418
386 # Generates a gantt image 419 # Generates a gantt image
609 coords[:bar_end] = end_date - self.date_from + 1 642 coords[:bar_end] = end_date - self.date_from + 1
610 else 643 else
611 coords[:bar_end] = self.date_to - self.date_from + 1 644 coords[:bar_end] = self.date_to - self.date_from + 1
612 end 645 end
613 if progress 646 if progress
614 progress_date = start_date + (end_date - start_date + 1) * (progress / 100.0) 647 progress_date = calc_progress_date(start_date, end_date, progress)
615 if progress_date > self.date_from && progress_date > start_date 648 if progress_date > self.date_from && progress_date > start_date
616 if progress_date < self.date_to 649 if progress_date < self.date_to
617 coords[:bar_progress_end] = progress_date - self.date_from 650 coords[:bar_progress_end] = progress_date - self.date_from
618 else 651 else
619 coords[:bar_progress_end] = self.date_to - self.date_from + 1 652 coords[:bar_progress_end] = self.date_to - self.date_from + 1
636 coords[key] = (coords[key] * zoom).floor 669 coords[key] = (coords[key] * zoom).floor
637 end 670 end
638 coords 671 coords
639 end 672 end
640 673
641 # Sorts a collection of issues by start_date, due_date, id for gantt rendering 674 def calc_progress_date(start_date, end_date, progress)
675 start_date + (end_date - start_date + 1) * (progress / 100.0)
676 end
677
678 # TODO: Sorts a collection of issues by start_date, due_date, id for gantt rendering
642 def sort_issues!(issues) 679 def sort_issues!(issues)
643 issues.sort! { |a, b| gantt_issue_compare(a, b) } 680 issues.sort! { |a, b| gantt_issue_compare(a, b) }
644 end 681 end
645 682
646 # TODO: top level issues should be sorted by start date 683 # TODO: top level issues should be sorted by start date
676 end 713 end
677 714
678 def html_subject(params, subject, options={}) 715 def html_subject(params, subject, options={})
679 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;" 716 style = "position: absolute;top:#{params[:top]}px;left:#{params[:indent]}px;"
680 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width] 717 style << "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
681 output = view.content_tag('div', subject, 718 output = view.content_tag(:div, subject,
682 :class => options[:css], :style => style, 719 :class => options[:css], :style => style,
683 :title => options[:title]) 720 :title => options[:title],
721 :id => options[:id])
684 @subjects << output 722 @subjects << output
685 output 723 output
686 end 724 end
687 725
688 def pdf_subject(params, subject, options={}) 726 def pdf_subject(params, subject, options={})
703 params[:image].stroke('transparent') 741 params[:image].stroke('transparent')
704 params[:image].stroke_width(1) 742 params[:image].stroke_width(1)
705 params[:image].text(params[:indent], params[:top] + 2, subject) 743 params[:image].text(params[:indent], params[:top] + 2, subject)
706 end 744 end
707 745
746 def issue_relations(issue)
747 rels = {}
748 if relations[issue.id]
749 relations[issue.id].each do |relation|
750 (rels[relation.relation_type] ||= []) << relation.issue_to_id
751 end
752 end
753 rels
754 end
755
708 def html_task(params, coords, options={}) 756 def html_task(params, coords, options={})
709 output = '' 757 output = ''
710 # Renders the task bar, with progress and late 758 # Renders the task bar, with progress and late
711 if coords[:bar_start] && coords[:bar_end] 759 if coords[:bar_start] && coords[:bar_end]
712 width = coords[:bar_end] - coords[:bar_start] - 2 760 width = coords[:bar_end] - coords[:bar_start] - 2
713 style = "" 761 style = ""
714 style << "top:#{params[:top]}px;" 762 style << "top:#{params[:top]}px;"
715 style << "left:#{coords[:bar_start]}px;" 763 style << "left:#{coords[:bar_start]}px;"
716 style << "width:#{width}px;" 764 style << "width:#{width}px;"
717 output << view.content_tag(:div, '&nbsp;'.html_safe, 765 html_id = "task-todo-issue-#{options[:issue].id}" if options[:issue]
718 :style => style, 766 html_id = "task-todo-version-#{options[:version].id}" if options[:version]
719 :class => "#{options[:css]} task_todo") 767 content_opt = {:style => style,
768 :class => "#{options[:css]} task_todo",
769 :id => html_id}
770 if options[:issue]
771 rels = issue_relations(options[:issue])
772 if rels.present?
773 content_opt[:data] = {"rels" => rels.to_json}
774 end
775 end
776 output << view.content_tag(:div, '&nbsp;'.html_safe, content_opt)
720 if coords[:bar_late_end] 777 if coords[:bar_late_end]
721 width = coords[:bar_late_end] - coords[:bar_start] - 2 778 width = coords[:bar_late_end] - coords[:bar_start] - 2
722 style = "" 779 style = ""
723 style << "top:#{params[:top]}px;" 780 style << "top:#{params[:top]}px;"
724 style << "left:#{coords[:bar_start]}px;" 781 style << "left:#{coords[:bar_start]}px;"
731 width = coords[:bar_progress_end] - coords[:bar_start] - 2 788 width = coords[:bar_progress_end] - coords[:bar_start] - 2
732 style = "" 789 style = ""
733 style << "top:#{params[:top]}px;" 790 style << "top:#{params[:top]}px;"
734 style << "left:#{coords[:bar_start]}px;" 791 style << "left:#{coords[:bar_start]}px;"
735 style << "width:#{width}px;" 792 style << "width:#{width}px;"
793 html_id = "task-done-issue-#{options[:issue].id}" if options[:issue]
794 html_id = "task-done-version-#{options[:version].id}" if options[:version]
736 output << view.content_tag(:div, '&nbsp;'.html_safe, 795 output << view.content_tag(:div, '&nbsp;'.html_safe,
737 :style => style, 796 :style => style,
738 :class => "#{options[:css]} task_done") 797 :class => "#{options[:css]} task_done",
798 :id => html_id)
739 end 799 end
740 end 800 end
741 # Renders the markers 801 # Renders the markers
742 if options[:markers] 802 if options[:markers]
743 if coords[:start] 803 if coords[:start]