42 |
42 |
|
43 |
43 |
def initialize(options={})
|
44 |
44 |
options = options.dup
|
45 |
|
if options[:year] && options[:year].to_i >0
|
|
45 |
|
|
46 |
if options[:year] && options[:year].to_i > 0
|
46 |
47 |
@year_from = options[:year].to_i
|
47 |
|
if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
|
|
48 |
if options[:month] && options[:month].to_i >= 1 && options[:month].to_i <= 12
|
48 |
49 |
@month_from = options[:month].to_i
|
49 |
50 |
else
|
50 |
51 |
@month_from = 1
|
51 |
52 |
end
|
52 |
53 |
else
|
53 |
|
@month_from ||= Date.today.month
|
|
54 |
@month_from ||= Date.today.month - 1
|
54 |
55 |
@year_from ||= Date.today.year
|
|
56 |
@calculate_date_range = true
|
55 |
57 |
end
|
|
58 |
|
|
59 |
if @month_from <= 0
|
|
60 |
@month_from = 12
|
|
61 |
@year_from -= 1
|
|
62 |
end
|
|
63 |
if @month_from > 12
|
|
64 |
@month_from = 1
|
|
65 |
@year_from += 1
|
|
66 |
end
|
|
67 |
|
|
68 |
months = (options[:months] || User.current.pref[:gantt_months]).to_i
|
|
69 |
@months = (months > 0 && months < 48) ? months : 48
|
|
70 |
|
56 |
71 |
zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
|
57 |
72 |
@zoom = (zoom > 0 && zoom < 5) ? zoom : 2
|
58 |
|
months = (options[:months] || User.current.pref[:gantt_months]).to_i
|
59 |
|
@months = (months > 0 && months < 25) ? months : 6
|
|
73 |
|
60 |
74 |
# Save gantt parameters as user preference (zoom and months count)
|
61 |
75 |
if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
|
62 |
76 |
@months != User.current.pref[:gantt_months]))
|
63 |
77 |
User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
|
64 |
78 |
User.current.preference.save
|
65 |
79 |
end
|
|
80 |
|
66 |
81 |
@date_from = Date.civil(@year_from, @month_from, 1)
|
67 |
82 |
@date_to = (@date_from >> @months) - 1
|
68 |
83 |
@subjects = ''
|
... | ... | |
70 |
85 |
@number_of_rows = nil
|
71 |
86 |
@issue_ancestors = []
|
72 |
87 |
@truncated = false
|
|
88 |
|
73 |
89 |
if options.has_key?(:max_rows)
|
74 |
90 |
@max_rows = options[:max_rows]
|
75 |
91 |
else
|
... | ... | |
77 |
93 |
end
|
78 |
94 |
end
|
79 |
95 |
|
|
96 |
def setup_dates
|
|
97 |
if @calculate_date_range
|
|
98 |
dates = issues_date_range
|
|
99 |
|
|
100 |
@date_from = dates[0]
|
|
101 |
if @date_from.day < 10
|
|
102 |
@date_from = dates[0] << 1
|
|
103 |
end
|
|
104 |
@date_to = dates[1]
|
|
105 |
if @date_to.day > 20
|
|
106 |
@date_to = dates[1] >> 1
|
|
107 |
end
|
|
108 |
@year_from = @date_from.year
|
|
109 |
@month_from = @date_from.month
|
|
110 |
months = (@date_to.year*12+@date_to.month) - (@date_from.year*12+@date_from.month) + 1
|
|
111 |
@months = months > 48 ? 48 : months
|
|
112 |
@date_from = Date.civil(@year_from, @month_from, 1)
|
|
113 |
@date_to = (@date_from >> @months) - 1
|
|
114 |
end
|
|
115 |
end
|
|
116 |
|
80 |
117 |
def common_params
|
81 |
118 |
{ :controller => 'gantts', :action => 'show', :project_id => @project }
|
82 |
119 |
end
|
... | ... | |
209 |
246 |
|
210 |
247 |
def render_issues(issues, options={})
|
211 |
248 |
@issue_ancestors = []
|
212 |
|
issues.each do |i|
|
213 |
|
subject_for_issue(i, options) unless options[:only] == :lines
|
214 |
|
line_for_issue(i, options) unless options[:only] == :subjects
|
215 |
|
options[:top] += options[:top_increment]
|
216 |
|
@number_of_rows += 1
|
217 |
|
break if abort?
|
|
249 |
if @query.group_by == nil || @query.group_by == ""
|
|
250 |
issues.each do |i|
|
|
251 |
subject_for_issue(i, options) unless options[:only] == :lines
|
|
252 |
line_for_issue(i, options) unless options[:only] == :subjects
|
|
253 |
options[:top] += options[:top_increment]
|
|
254 |
@number_of_rows += 1
|
|
255 |
break if abort?
|
|
256 |
end
|
|
257 |
options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
|
|
258 |
else
|
|
259 |
new_group = true
|
|
260 |
group_start = options[:top] - options[:top_increment]
|
|
261 |
group_max_line = 0
|
|
262 |
group_write_line = group_max_line
|
|
263 |
dates_in_line = [[]]
|
|
264 |
last_shown_index = -1
|
|
265 |
|
|
266 |
issues.each_with_index do |i, index|
|
|
267 |
if i.start_date != nil
|
|
268 |
if i.due_before == nil
|
|
269 |
i.due_date = i.start_date
|
|
270 |
end
|
|
271 |
|
|
272 |
if ((i.start_date >= @date_from || i.due_before >= @date_from) && i.start_date <= @date_to)
|
|
273 |
new_group = last_shown_index < 0 || new_group?(i, issues[last_shown_index]) || !i.leaf? || !issues[last_shown_index].leaf?
|
|
274 |
|
|
275 |
if new_group
|
|
276 |
options[:top] = group_start + ((group_max_line+1) * options[:top_increment])
|
|
277 |
group_start = options[:top]
|
|
278 |
group_max_line = 0
|
|
279 |
group_write_line = group_max_line
|
|
280 |
dates_in_line = [[]]
|
|
281 |
@number_of_rows += 1
|
|
282 |
else
|
|
283 |
group_write_line = -1
|
|
284 |
dates_in_line.each_with_index do |dates, line|
|
|
285 |
if dates.find {|e| (i.start_date >= e[0] && i.start_date <= e[1]) || (i.start_date <= e[0] && i.due_before >= e[1]) } == nil
|
|
286 |
group_write_line = line
|
|
287 |
break
|
|
288 |
end
|
|
289 |
end
|
|
290 |
if group_write_line == -1
|
|
291 |
group_max_line += 1
|
|
292 |
group_write_line = group_max_line
|
|
293 |
dates_in_line.push([])
|
|
294 |
@number_of_rows += 1
|
|
295 |
end
|
|
296 |
end
|
|
297 |
|
|
298 |
options[:top] = group_start + (group_write_line * options[:top_increment])
|
|
299 |
options[:no_title] = !new_group;
|
|
300 |
|
|
301 |
subject_for_issue(i, options) unless options[:only] == :lines
|
|
302 |
line_for_issue(i, options) unless options[:only] == :subjects
|
|
303 |
|
|
304 |
last_shown_index = index
|
|
305 |
dates_in_line[group_write_line].push([i.start_date, i.due_before])
|
|
306 |
end
|
|
307 |
end
|
|
308 |
break if abort?
|
|
309 |
end
|
|
310 |
options[:top] = group_start + ((group_max_line+1) * options[:top_increment])
|
|
311 |
options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
|
218 |
312 |
end
|
219 |
|
options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
|
220 |
313 |
end
|
221 |
314 |
|
222 |
315 |
def render_version(project, version, options={})
|
... | ... | |
330 |
423 |
@issue_ancestors.pop
|
331 |
424 |
options[:indent] -= options[:indent_increment]
|
332 |
425 |
end
|
|
426 |
|
|
427 |
is_not_leaf_or_grouped = !issue.leaf? || @query.group_by == nil || @query.group_by == ""
|
|
428 |
group_name = group_name(issue)
|
|
429 |
|
|
430 |
if is_not_leaf_or_grouped
|
|
431 |
subject_title = issue.subject
|
|
432 |
else
|
|
433 |
subject_title = group_name
|
|
434 |
end
|
|
435 |
|
|
436 |
if options[:no_title]
|
|
437 |
subject_title = " "
|
|
438 |
end
|
|
439 |
|
333 |
440 |
output = case options[:format]
|
334 |
441 |
when :html
|
335 |
442 |
css_classes = ''
|
336 |
443 |
css_classes << ' issue-overdue' if issue.overdue?
|
337 |
444 |
css_classes << ' issue-behind-schedule' if issue.behind_schedule?
|
338 |
|
css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
|
|
445 |
css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to || options[:no_title]
|
|
446 |
|
339 |
447 |
s = "".html_safe
|
340 |
|
if issue.assigned_to.present?
|
|
448 |
if !options[:no_title] && issue.assigned_to.present?
|
341 |
449 |
assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
|
342 |
450 |
s << view.avatar(issue.assigned_to,
|
343 |
451 |
:class => 'gravatar icon-gravatar',
|
344 |
452 |
:size => 10,
|
345 |
453 |
:title => assigned_string).to_s.html_safe
|
346 |
454 |
end
|
347 |
|
s << view.link_to_issue(issue).html_safe
|
|
455 |
|
|
456 |
if is_not_leaf_or_grouped
|
|
457 |
s << view.link_to_issue(issue).html_safe
|
|
458 |
else
|
|
459 |
s << subject_title.html_safe
|
|
460 |
end
|
|
461 |
|
348 |
462 |
subject = view.content_tag(:span, s, :class => css_classes).html_safe
|
349 |
463 |
html_subject(options, subject, :css => "issue-subject",
|
350 |
|
:title => issue.subject) + "\n"
|
|
464 |
:title => subject_title) + "\n"
|
351 |
465 |
when :image
|
352 |
|
image_subject(options, issue.subject)
|
|
466 |
image_subject(options, subject_title)
|
353 |
467 |
when :pdf
|
354 |
468 |
pdf_new_page?(options)
|
355 |
|
pdf_subject(options, issue.subject)
|
|
469 |
pdf_subject(options, subject_title)
|
356 |
470 |
end
|
|
471 |
|
357 |
472 |
unless issue.leaf?
|
358 |
473 |
@issue_ancestors << issue
|
359 |
474 |
options[:indent] += options[:indent_increment]
|
360 |
475 |
end
|
|
476 |
|
361 |
477 |
output
|
362 |
478 |
end
|
363 |
479 |
|
364 |
480 |
def line_for_issue(issue, options)
|
365 |
|
# Skip issues that don't have a due_before (due_date or version's due_date)
|
366 |
|
if issue.is_a?(Issue) && issue.due_before
|
367 |
|
coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
|
368 |
|
label = "#{issue.status.name} #{issue.done_ratio}%"
|
|
481 |
if issue.is_a?(Issue)
|
|
482 |
due_date = issue.due_before
|
|
483 |
if !due_date
|
|
484 |
due_date = issue.start_date
|
|
485 |
end
|
|
486 |
|
|
487 |
coords = coordinates(issue.start_date, due_date, issue.done_ratio, options[:zoom])
|
|
488 |
|
|
489 |
label = " "
|
|
490 |
if @query.group_by == nil || @query.group_by == ""
|
|
491 |
label = "#{issue.status.name} #{issue.done_ratio}%"
|
|
492 |
end
|
|
493 |
|
369 |
494 |
case options[:format]
|
370 |
495 |
when :html
|
371 |
496 |
html_task(options, coords,
|
... | ... | |
376 |
501 |
image_task(options, coords, :label => label)
|
377 |
502 |
when :pdf
|
378 |
503 |
pdf_task(options, coords, :label => label)
|
379 |
|
end
|
|
504 |
end
|
380 |
505 |
else
|
381 |
|
ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
|
|
506 |
ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue"
|
382 |
507 |
''
|
383 |
508 |
end
|
384 |
509 |
end
|
... | ... | |
640 |
765 |
|
641 |
766 |
# Sorts a collection of issues by start_date, due_date, id for gantt rendering
|
642 |
767 |
def sort_issues!(issues)
|
643 |
|
issues.sort! { |a, b| gantt_issue_compare(a, b) }
|
|
768 |
issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
|
644 |
769 |
end
|
645 |
770 |
|
646 |
|
# TODO: top level issues should be sorted by start date
|
647 |
|
def gantt_issue_compare(x, y)
|
648 |
|
if x.root_id == y.root_id
|
649 |
|
x.lft <=> y.lft
|
|
771 |
def gantt_issue_compare(x, y, issues)
|
|
772 |
if @query.group_by == nil || @query.group_by == ""
|
|
773 |
[(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
|
|
774 |
elsif x.leaf? && !y.leaf?
|
|
775 |
1
|
|
776 |
elsif !x.leaf? && y.leaf?
|
|
777 |
-1
|
650 |
778 |
else
|
651 |
|
x.root_id <=> y.root_id
|
|
779 |
sort_group(x, y, issues)
|
652 |
780 |
end
|
653 |
781 |
end
|
|
782 |
|
|
783 |
def sort_group(x, y, issues)
|
|
784 |
value_x = @query.group_by_column.value(x)
|
|
785 |
value_y = @query.group_by_column.value(y)
|
654 |
786 |
|
|
787 |
if value_x == nil && value_y == nil
|
|
788 |
[(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
|
|
789 |
elsif value_x == nil
|
|
790 |
-1
|
|
791 |
elsif value_y == nil
|
|
792 |
1
|
|
793 |
elsif value_x == value_y
|
|
794 |
[(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
|
|
795 |
else
|
|
796 |
value_x <=> value_y
|
|
797 |
end
|
|
798 |
end
|
|
799 |
|
|
800 |
def issues_date_range
|
|
801 |
min = nil
|
|
802 |
max = nil
|
|
803 |
projects.each do |p|
|
|
804 |
all_issues = project_issues(p)
|
|
805 |
|
|
806 |
all_issues.each do |i|
|
|
807 |
if min == nil || (i.start_date != nil && min > i.start_date)
|
|
808 |
min = i.start_date
|
|
809 |
end
|
|
810 |
if min == nil || (i.due_before != nil && min > i.due_before)
|
|
811 |
min = i.due_before
|
|
812 |
end
|
|
813 |
if max == nil || (i.start_date != nil && max < i.start_date)
|
|
814 |
max = i.start_date
|
|
815 |
end
|
|
816 |
if max == nil || (i.due_before != nil && max < i.due_before)
|
|
817 |
max = i.due_before
|
|
818 |
end
|
|
819 |
end
|
|
820 |
end
|
|
821 |
|
|
822 |
if min == nil && max == nil
|
|
823 |
min = Date.today
|
|
824 |
max = Date.today
|
|
825 |
end
|
|
826 |
|
|
827 |
[min, max]
|
|
828 |
end
|
|
829 |
|
|
830 |
def new_group?(x, y)
|
|
831 |
if @query.group_by == nil || @query.group_by == ""
|
|
832 |
true
|
|
833 |
else
|
|
834 |
value_x = @query.group_by_column.value(x)
|
|
835 |
value_y = @query.group_by_column.value(y)
|
|
836 |
|
|
837 |
value_x != value_y
|
|
838 |
end
|
|
839 |
end
|
|
840 |
|
|
841 |
def group_name(issue)
|
|
842 |
if @query.group_by == nil || @query.group_by == ""
|
|
843 |
issue.subject
|
|
844 |
else
|
|
845 |
result = @query.group_by_column.value(issue)
|
|
846 |
|
|
847 |
if result == nil || result == ''
|
|
848 |
'None'
|
|
849 |
else
|
|
850 |
result.to_s
|
|
851 |
end
|
|
852 |
end
|
|
853 |
end
|
|
854 |
|
655 |
855 |
def current_limit
|
656 |
856 |
if @max_rows
|
657 |
857 |
@max_rows - @number_of_rows
|
... | ... | |
790 |
990 |
|
791 |
991 |
def pdf_task(params, coords, options={})
|
792 |
992 |
height = options[:height] || 2
|
|
993 |
|
793 |
994 |
# Renders the task bar, with progress and late
|
794 |
995 |
if coords[:bar_start] && coords[:bar_end]
|
795 |
|
params[:pdf].SetY(params[:top] + 1.5)
|
796 |
|
params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
|
|
996 |
top = params[:top] + 1.5
|
|
997 |
width = params[:subject_width] + coords[:bar_start]
|
|
998 |
|
|
999 |
length = coords[:bar_end] - coords[:bar_start]
|
|
1000 |
if length <= 0
|
|
1001 |
length = 1
|
|
1002 |
end
|
|
1003 |
params[:pdf].SetY(top)
|
|
1004 |
params[:pdf].SetX(width)
|
797 |
1005 |
params[:pdf].SetFillColor(200, 200, 200)
|
798 |
|
params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
|
|
1006 |
params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
|
|
1007 |
|
799 |
1008 |
if coords[:bar_late_end]
|
800 |
|
params[:pdf].SetY(params[:top] + 1.5)
|
801 |
|
params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
|
|
1009 |
length = coords[:bar_late_end] - coords[:bar_start]
|
|
1010 |
if length <= 0
|
|
1011 |
length = 1
|
|
1012 |
end
|
|
1013 |
params[:pdf].SetY(top)
|
|
1014 |
params[:pdf].SetX(width)
|
802 |
1015 |
params[:pdf].SetFillColor(255, 100, 100)
|
803 |
|
params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
|
|
1016 |
params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
|
804 |
1017 |
end
|
|
1018 |
|
805 |
1019 |
if coords[:bar_progress_end]
|
806 |
|
params[:pdf].SetY(params[:top] + 1.5)
|
807 |
|
params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
|
|
1020 |
length = coords[:bar_progress_end] - coords[:bar_start]
|
|
1021 |
if length <= 0
|
|
1022 |
length = 1
|
|
1023 |
end
|
|
1024 |
params[:pdf].SetY(top)
|
|
1025 |
params[:pdf].SetX(width)
|
808 |
1026 |
params[:pdf].SetFillColor(90, 200, 90)
|
809 |
|
params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
|
|
1027 |
params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
|
810 |
1028 |
end
|
811 |
1029 |
end
|
|
1030 |
|
812 |
1031 |
# Renders the markers
|
813 |
1032 |
if options[:markers]
|
814 |
1033 |
if coords[:start]
|
... | ... | |
824 |
1043 |
params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
|
825 |
1044 |
end
|
826 |
1045 |
end
|
|
1046 |
|
827 |
1047 |
# Renders the label on the right
|
828 |
1048 |
if options[:label]
|
829 |
1049 |
params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
|