##// END OF EJS Templates
Disable project completion display on the gantt (#7127)....
Jean-Philippe Lang -
r4468:ad620607539c
parent child
Show More
@@ -1,858 +1,858
1 1 # Redmine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 module Redmine
19 19 module Helpers
20 20 # Simple class to handle gantt chart data
21 21 class Gantt
22 22 include ERB::Util
23 23 include Redmine::I18n
24 24
25 25 # :nodoc:
26 26 # Some utility methods for the PDF export
27 27 class PDF
28 28 MaxCharactorsForSubject = 45
29 29 TotalWidth = 280
30 30 LeftPaneWidth = 100
31 31
32 32 def self.right_pane_width
33 33 TotalWidth - LeftPaneWidth
34 34 end
35 35 end
36 36
37 37 attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
38 38 attr_accessor :query
39 39 attr_accessor :project
40 40 attr_accessor :view
41 41
42 42 def initialize(options={})
43 43 options = options.dup
44 44
45 45 if options[:year] && options[:year].to_i >0
46 46 @year_from = options[:year].to_i
47 47 if options[:month] && options[:month].to_i >=1 && options[:month].to_i <= 12
48 48 @month_from = options[:month].to_i
49 49 else
50 50 @month_from = 1
51 51 end
52 52 else
53 53 @month_from ||= Date.today.month
54 54 @year_from ||= Date.today.year
55 55 end
56 56
57 57 zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
58 58 @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
59 59 months = (options[:months] || User.current.pref[:gantt_months]).to_i
60 60 @months = (months > 0 && months < 25) ? months : 6
61 61
62 62 # Save gantt parameters as user preference (zoom and months count)
63 63 if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] || @months != User.current.pref[:gantt_months]))
64 64 User.current.pref[:gantt_zoom], User.current.pref[:gantt_months] = @zoom, @months
65 65 User.current.preference.save
66 66 end
67 67
68 68 @date_from = Date.civil(@year_from, @month_from, 1)
69 69 @date_to = (@date_from >> @months) - 1
70 70
71 71 @subjects = ''
72 72 @lines = ''
73 73 @number_of_rows = nil
74 74
75 75 @issue_ancestors = []
76 76
77 77 @truncated = false
78 78 if options.has_key?(:max_rows)
79 79 @max_rows = options[:max_rows]
80 80 else
81 81 @max_rows = Setting.gantt_items_limit.blank? ? nil : Setting.gantt_items_limit.to_i
82 82 end
83 83 end
84 84
85 85 def common_params
86 86 { :controller => 'gantts', :action => 'show', :project_id => @project }
87 87 end
88 88
89 89 def params
90 90 common_params.merge({ :zoom => zoom, :year => year_from, :month => month_from, :months => months })
91 91 end
92 92
93 93 def params_previous
94 94 common_params.merge({:year => (date_from << months).year, :month => (date_from << months).month, :zoom => zoom, :months => months })
95 95 end
96 96
97 97 def params_next
98 98 common_params.merge({:year => (date_from >> months).year, :month => (date_from >> months).month, :zoom => zoom, :months => months })
99 99 end
100 100
101 101 ### Extracted from the HTML view/helpers
102 102 # Returns the number of rows that will be rendered on the Gantt chart
103 103 def number_of_rows
104 104 return @number_of_rows if @number_of_rows
105 105
106 106 rows = if @project
107 107 number_of_rows_on_project(@project)
108 108 else
109 109 Project.roots.visible.has_module('issue_tracking').inject(0) do |total, project|
110 110 total += number_of_rows_on_project(project)
111 111 end
112 112 end
113 113
114 114 rows > @max_rows ? @max_rows : rows
115 115 end
116 116
117 117 # Returns the number of rows that will be used to list a project on
118 118 # the Gantt chart. This will recurse for each subproject.
119 119 def number_of_rows_on_project(project)
120 120 # Remove the project requirement for Versions because it will
121 121 # restrict issues to only be on the current project. This
122 122 # ends up missing issues which are assigned to shared versions.
123 123 @query.project = nil if @query.project
124 124
125 125 # One Root project
126 126 count = 1
127 127 # Issues without a Version
128 128 count += project.issues.for_gantt.without_version.with_query(@query).count
129 129
130 130 # Versions
131 131 count += project.versions.count
132 132
133 133 # Issues on the Versions
134 134 project.versions.each do |version|
135 135 count += version.fixed_issues.for_gantt.with_query(@query).count
136 136 end
137 137
138 138 # Subprojects
139 139 project.children.visible.has_module('issue_tracking').each do |subproject|
140 140 count += number_of_rows_on_project(subproject)
141 141 end
142 142
143 143 count
144 144 end
145 145
146 146 # Renders the subjects of the Gantt chart, the left side.
147 147 def subjects(options={})
148 148 render(options.merge(:only => :subjects)) unless @subjects_rendered
149 149 @subjects
150 150 end
151 151
152 152 # Renders the lines of the Gantt chart, the right side
153 153 def lines(options={})
154 154 render(options.merge(:only => :lines)) unless @lines_rendered
155 155 @lines
156 156 end
157 157
158 158 def render(options={})
159 159 options = {:indent => 4, :render => :subject, :format => :html}.merge(options)
160 160
161 161 @subjects = '' unless options[:only] == :lines
162 162 @lines = '' unless options[:only] == :subjects
163 163 @number_of_rows = 0
164 164
165 165 if @project
166 166 render_project(@project, options)
167 167 else
168 168 Project.roots.visible.has_module('issue_tracking').each do |project|
169 169 render_project(project, options)
170 170 break if abort?
171 171 end
172 172 end
173 173
174 174 @subjects_rendered = true unless options[:only] == :lines
175 175 @lines_rendered = true unless options[:only] == :subjects
176 176
177 177 render_end(options)
178 178 end
179 179
180 180 def render_project(project, options={})
181 181 options[:top] = 0 unless options.key? :top
182 182 options[:indent_increment] = 20 unless options.key? :indent_increment
183 183 options[:top_increment] = 20 unless options.key? :top_increment
184 184
185 185 subject_for_project(project, options) unless options[:only] == :lines
186 186 line_for_project(project, options) unless options[:only] == :subjects
187 187
188 188 options[:top] += options[:top_increment]
189 189 options[:indent] += options[:indent_increment]
190 190 @number_of_rows += 1
191 191 return if abort?
192 192
193 193 # Second, Issues without a version
194 194 issues = project.issues.for_gantt.without_version.with_query(@query).all(:limit => current_limit)
195 195 sort_issues!(issues)
196 196 if issues
197 197 render_issues(issues, options)
198 198 return if abort?
199 199 end
200 200
201 201 # Third, Versions
202 202 project.versions.sort.each do |version|
203 203 render_version(version, options)
204 204 return if abort?
205 205 end
206 206
207 207 # Fourth, subprojects
208 208 project.children.visible.has_module('issue_tracking').each do |project|
209 209 render_project(project, options)
210 210 return if abort?
211 211 end unless project.leaf?
212 212
213 213 # Remove indent to hit the next sibling
214 214 options[:indent] -= options[:indent_increment]
215 215 end
216 216
217 217 def render_issues(issues, options={})
218 218 @issue_ancestors = []
219 219
220 220 issues.each do |i|
221 221 subject_for_issue(i, options) unless options[:only] == :lines
222 222 line_for_issue(i, options) unless options[:only] == :subjects
223 223
224 224 options[:top] += options[:top_increment]
225 225 @number_of_rows += 1
226 226 break if abort?
227 227 end
228 228
229 229 options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
230 230 end
231 231
232 232 def render_version(version, options={})
233 233 # Version header
234 234 subject_for_version(version, options) unless options[:only] == :lines
235 235 line_for_version(version, options) unless options[:only] == :subjects
236 236
237 237 options[:top] += options[:top_increment]
238 238 @number_of_rows += 1
239 239 return if abort?
240 240
241 241 # Remove the project requirement for Versions because it will
242 242 # restrict issues to only be on the current project. This
243 243 # ends up missing issues which are assigned to shared versions.
244 244 @query.project = nil if @query.project
245 245
246 246 issues = version.fixed_issues.for_gantt.with_query(@query).all(:limit => current_limit)
247 247 if issues
248 248 sort_issues!(issues)
249 249 # Indent issues
250 250 options[:indent] += options[:indent_increment]
251 251 render_issues(issues, options)
252 252 options[:indent] -= options[:indent_increment]
253 253 end
254 254 end
255 255
256 256 def render_end(options={})
257 257 case options[:format]
258 258 when :pdf
259 259 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
260 260 end
261 261 end
262 262
263 263 def subject_for_project(project, options)
264 264 case options[:format]
265 265 when :html
266 266 subject = "<span class='icon icon-projects #{project.overdue? ? 'project-overdue' : ''}'>"
267 267 subject << view.link_to_project(project)
268 268 subject << '</span>'
269 269 html_subject(options, subject, :css => "project-name")
270 270 when :image
271 271 image_subject(options, project.name)
272 272 when :pdf
273 273 pdf_new_page?(options)
274 274 pdf_subject(options, project.name)
275 275 end
276 276 end
277 277
278 278 def line_for_project(project, options)
279 279 # Skip versions that don't have a start_date or due date
280 280 if project.is_a?(Project) && project.start_date && project.due_date
281 281 options[:zoom] ||= 1
282 282 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
283 283
284 coords = coordinates(project.start_date, project.due_date, project.completed_percent(:include_subprojects => true), options[:zoom])
285 label = "#{h project } #{h project.completed_percent(:include_subprojects => true).to_i.to_s}%"
284 coords = coordinates(project.start_date, project.due_date, nil, options[:zoom])
285 label = h(project)
286 286
287 287 case options[:format]
288 288 when :html
289 289 html_task(options, coords, :css => "project task", :label => label, :markers => true)
290 290 when :image
291 291 image_task(options, coords, :label => label, :markers => true, :height => 3)
292 292 when :pdf
293 293 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
294 294 end
295 295 else
296 296 ActiveRecord::Base.logger.debug "Gantt#line_for_project was not given a project with a start_date"
297 297 ''
298 298 end
299 299 end
300 300
301 301 def subject_for_version(version, options)
302 302 case options[:format]
303 303 when :html
304 304 subject = "<span class='icon icon-package #{version.behind_schedule? ? 'version-behind-schedule' : ''} #{version.overdue? ? 'version-overdue' : ''}'>"
305 305 subject << view.link_to_version(version)
306 306 subject << '</span>'
307 307 html_subject(options, subject, :css => "version-name")
308 308 when :image
309 309 image_subject(options, version.to_s_with_project)
310 310 when :pdf
311 311 pdf_new_page?(options)
312 312 pdf_subject(options, version.to_s_with_project)
313 313 end
314 314 end
315 315
316 316 def line_for_version(version, options)
317 317 # Skip versions that don't have a start_date
318 318 if version.is_a?(Version) && version.start_date && version.due_date
319 319 options[:zoom] ||= 1
320 320 options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
321 321
322 322 coords = coordinates(version.start_date, version.due_date, version.completed_pourcent, options[:zoom])
323 323 label = "#{h version } #{h version.completed_pourcent.to_i.to_s}%"
324 324 label = h("#{version.project} -") + label unless @project && @project == version.project
325 325
326 326 case options[:format]
327 327 when :html
328 328 html_task(options, coords, :css => "version task", :label => label, :markers => true)
329 329 when :image
330 330 image_task(options, coords, :label => label, :markers => true, :height => 3)
331 331 when :pdf
332 332 pdf_task(options, coords, :label => label, :markers => true, :height => 0.8)
333 333 end
334 334 else
335 335 ActiveRecord::Base.logger.debug "Gantt#line_for_version was not given a version with a start_date"
336 336 ''
337 337 end
338 338 end
339 339
340 340 def subject_for_issue(issue, options)
341 341 while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
342 342 @issue_ancestors.pop
343 343 options[:indent] -= options[:indent_increment]
344 344 end
345 345
346 346 output = case options[:format]
347 347 when :html
348 348 css_classes = ''
349 349 css_classes << ' issue-overdue' if issue.overdue?
350 350 css_classes << ' issue-behind-schedule' if issue.behind_schedule?
351 351 css_classes << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to
352 352
353 353 subject = "<span class='#{css_classes}'>"
354 354 if issue.assigned_to.present?
355 355 assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
356 356 subject << view.avatar(issue.assigned_to, :class => 'gravatar icon-gravatar', :size => 10, :title => assigned_string)
357 357 end
358 358 subject << view.link_to_issue(issue)
359 359 subject << '</span>'
360 360 html_subject(options, subject, :css => "issue-subject") + "\n"
361 361 when :image
362 362 image_subject(options, issue.subject)
363 363 when :pdf
364 364 pdf_new_page?(options)
365 365 pdf_subject(options, issue.subject)
366 366 end
367 367
368 368 unless issue.leaf?
369 369 @issue_ancestors << issue
370 370 options[:indent] += options[:indent_increment]
371 371 end
372 372
373 373 output
374 374 end
375 375
376 376 def line_for_issue(issue, options)
377 377 # Skip issues that don't have a due_before (due_date or version's due_date)
378 378 if issue.is_a?(Issue) && issue.due_before
379 379 coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
380 380 label = "#{ issue.status.name } #{ issue.done_ratio }%"
381 381
382 382 case options[:format]
383 383 when :html
384 384 html_task(options, coords, :css => "task " + (issue.leaf? ? 'leaf' : 'parent'), :label => label, :issue => issue, :markers => !issue.leaf?)
385 385 when :image
386 386 image_task(options, coords, :label => label)
387 387 when :pdf
388 388 pdf_task(options, coords, :label => label)
389 389 end
390 390 else
391 391 ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
392 392 ''
393 393 end
394 394 end
395 395
396 396 # Generates a gantt image
397 397 # Only defined if RMagick is avalaible
398 398 def to_image(format='PNG')
399 399 date_to = (@date_from >> @months)-1
400 400 show_weeks = @zoom > 1
401 401 show_days = @zoom > 2
402 402
403 403 subject_width = 400
404 404 header_heigth = 18
405 405 # width of one day in pixels
406 406 zoom = @zoom*2
407 407 g_width = (@date_to - @date_from + 1)*zoom
408 408 g_height = 20 * number_of_rows + 30
409 409 headers_heigth = (show_weeks ? 2*header_heigth : header_heigth)
410 410 height = g_height + headers_heigth
411 411
412 412 imgl = Magick::ImageList.new
413 413 imgl.new_image(subject_width+g_width+1, height)
414 414 gc = Magick::Draw.new
415 415
416 416 # Subjects
417 417 gc.stroke('transparent')
418 418 subjects(:image => gc, :top => (headers_heigth + 20), :indent => 4, :format => :image)
419 419
420 420 # Months headers
421 421 month_f = @date_from
422 422 left = subject_width
423 423 @months.times do
424 424 width = ((month_f >> 1) - month_f) * zoom
425 425 gc.fill('white')
426 426 gc.stroke('grey')
427 427 gc.stroke_width(1)
428 428 gc.rectangle(left, 0, left + width, height)
429 429 gc.fill('black')
430 430 gc.stroke('transparent')
431 431 gc.stroke_width(1)
432 432 gc.text(left.round + 8, 14, "#{month_f.year}-#{month_f.month}")
433 433 left = left + width
434 434 month_f = month_f >> 1
435 435 end
436 436
437 437 # Weeks headers
438 438 if show_weeks
439 439 left = subject_width
440 440 height = header_heigth
441 441 if @date_from.cwday == 1
442 442 # date_from is monday
443 443 week_f = date_from
444 444 else
445 445 # find next monday after date_from
446 446 week_f = @date_from + (7 - @date_from.cwday + 1)
447 447 width = (7 - @date_from.cwday + 1) * zoom
448 448 gc.fill('white')
449 449 gc.stroke('grey')
450 450 gc.stroke_width(1)
451 451 gc.rectangle(left, header_heigth, left + width, 2*header_heigth + g_height-1)
452 452 left = left + width
453 453 end
454 454 while week_f <= date_to
455 455 width = (week_f + 6 <= date_to) ? 7 * zoom : (date_to - week_f + 1) * zoom
456 456 gc.fill('white')
457 457 gc.stroke('grey')
458 458 gc.stroke_width(1)
459 459 gc.rectangle(left.round, header_heigth, left.round + width, 2*header_heigth + g_height-1)
460 460 gc.fill('black')
461 461 gc.stroke('transparent')
462 462 gc.stroke_width(1)
463 463 gc.text(left.round + 2, header_heigth + 14, week_f.cweek.to_s)
464 464 left = left + width
465 465 week_f = week_f+7
466 466 end
467 467 end
468 468
469 469 # Days details (week-end in grey)
470 470 if show_days
471 471 left = subject_width
472 472 height = g_height + header_heigth - 1
473 473 wday = @date_from.cwday
474 474 (date_to - @date_from + 1).to_i.times do
475 475 width = zoom
476 476 gc.fill(wday == 6 || wday == 7 ? '#eee' : 'white')
477 477 gc.stroke('grey')
478 478 gc.stroke_width(1)
479 479 gc.rectangle(left, 2*header_heigth, left + width, 2*header_heigth + g_height-1)
480 480 left = left + width
481 481 wday = wday + 1
482 482 wday = 1 if wday > 7
483 483 end
484 484 end
485 485
486 486 # border
487 487 gc.fill('transparent')
488 488 gc.stroke('grey')
489 489 gc.stroke_width(1)
490 490 gc.rectangle(0, 0, subject_width+g_width, headers_heigth)
491 491 gc.stroke('black')
492 492 gc.rectangle(0, 0, subject_width+g_width, g_height+ headers_heigth-1)
493 493
494 494 # content
495 495 top = headers_heigth + 20
496 496
497 497 gc.stroke('transparent')
498 498 lines(:image => gc, :top => top, :zoom => zoom, :subject_width => subject_width, :format => :image)
499 499
500 500 # today red line
501 501 if Date.today >= @date_from and Date.today <= date_to
502 502 gc.stroke('red')
503 503 x = (Date.today-@date_from+1)*zoom + subject_width
504 504 gc.line(x, headers_heigth, x, headers_heigth + g_height-1)
505 505 end
506 506
507 507 gc.draw(imgl)
508 508 imgl.format = format
509 509 imgl.to_blob
510 510 end if Object.const_defined?(:Magick)
511 511
512 512 def to_pdf
513 513 pdf = ::Redmine::Export::PDF::IFPDF.new(current_language)
514 514 pdf.SetTitle("#{l(:label_gantt)} #{project}")
515 515 pdf.AliasNbPages
516 516 pdf.footer_date = format_date(Date.today)
517 517 pdf.AddPage("L")
518 518 pdf.SetFontStyle('B',12)
519 519 pdf.SetX(15)
520 520 pdf.Cell(PDF::LeftPaneWidth, 20, project.to_s)
521 521 pdf.Ln
522 522 pdf.SetFontStyle('B',9)
523 523
524 524 subject_width = PDF::LeftPaneWidth
525 525 header_heigth = 5
526 526
527 527 headers_heigth = header_heigth
528 528 show_weeks = false
529 529 show_days = false
530 530
531 531 if self.months < 7
532 532 show_weeks = true
533 533 headers_heigth = 2*header_heigth
534 534 if self.months < 3
535 535 show_days = true
536 536 headers_heigth = 3*header_heigth
537 537 end
538 538 end
539 539
540 540 g_width = PDF.right_pane_width
541 541 zoom = (g_width) / (self.date_to - self.date_from + 1)
542 542 g_height = 120
543 543 t_height = g_height + headers_heigth
544 544
545 545 y_start = pdf.GetY
546 546
547 547 # Months headers
548 548 month_f = self.date_from
549 549 left = subject_width
550 550 height = header_heigth
551 551 self.months.times do
552 552 width = ((month_f >> 1) - month_f) * zoom
553 553 pdf.SetY(y_start)
554 554 pdf.SetX(left)
555 555 pdf.Cell(width, height, "#{month_f.year}-#{month_f.month}", "LTR", 0, "C")
556 556 left = left + width
557 557 month_f = month_f >> 1
558 558 end
559 559
560 560 # Weeks headers
561 561 if show_weeks
562 562 left = subject_width
563 563 height = header_heigth
564 564 if self.date_from.cwday == 1
565 565 # self.date_from is monday
566 566 week_f = self.date_from
567 567 else
568 568 # find next monday after self.date_from
569 569 week_f = self.date_from + (7 - self.date_from.cwday + 1)
570 570 width = (7 - self.date_from.cwday + 1) * zoom-1
571 571 pdf.SetY(y_start + header_heigth)
572 572 pdf.SetX(left)
573 573 pdf.Cell(width + 1, height, "", "LTR")
574 574 left = left + width+1
575 575 end
576 576 while week_f <= self.date_to
577 577 width = (week_f + 6 <= self.date_to) ? 7 * zoom : (self.date_to - week_f + 1) * zoom
578 578 pdf.SetY(y_start + header_heigth)
579 579 pdf.SetX(left)
580 580 pdf.Cell(width, height, (width >= 5 ? week_f.cweek.to_s : ""), "LTR", 0, "C")
581 581 left = left + width
582 582 week_f = week_f+7
583 583 end
584 584 end
585 585
586 586 # Days headers
587 587 if show_days
588 588 left = subject_width
589 589 height = header_heigth
590 590 wday = self.date_from.cwday
591 591 pdf.SetFontStyle('B',7)
592 592 (self.date_to - self.date_from + 1).to_i.times do
593 593 width = zoom
594 594 pdf.SetY(y_start + 2 * header_heigth)
595 595 pdf.SetX(left)
596 596 pdf.Cell(width, height, day_name(wday).first, "LTR", 0, "C")
597 597 left = left + width
598 598 wday = wday + 1
599 599 wday = 1 if wday > 7
600 600 end
601 601 end
602 602
603 603 pdf.SetY(y_start)
604 604 pdf.SetX(15)
605 605 pdf.Cell(subject_width+g_width-15, headers_heigth, "", 1)
606 606
607 607 # Tasks
608 608 top = headers_heigth + y_start
609 609 options = {
610 610 :top => top,
611 611 :zoom => zoom,
612 612 :subject_width => subject_width,
613 613 :g_width => g_width,
614 614 :indent => 0,
615 615 :indent_increment => 5,
616 616 :top_increment => 5,
617 617 :format => :pdf,
618 618 :pdf => pdf
619 619 }
620 620 render(options)
621 621 pdf.Output
622 622 end
623 623
624 624 private
625 625
626 626 def coordinates(start_date, end_date, progress, zoom=nil)
627 627 zoom ||= @zoom
628 628
629 629 coords = {}
630 630 if start_date && end_date && start_date < self.date_to && end_date > self.date_from
631 631 if start_date > self.date_from
632 632 coords[:start] = start_date - self.date_from
633 633 coords[:bar_start] = start_date - self.date_from
634 634 else
635 635 coords[:bar_start] = 0
636 636 end
637 637 if end_date < self.date_to
638 638 coords[:end] = end_date - self.date_from
639 639 coords[:bar_end] = end_date - self.date_from + 1
640 640 else
641 641 coords[:bar_end] = self.date_to - self.date_from + 1
642 642 end
643 643
644 644 if progress
645 645 progress_date = start_date + (end_date - start_date) * (progress / 100.0)
646 646 if progress_date > self.date_from && progress_date > start_date
647 647 if progress_date < self.date_to
648 648 coords[:bar_progress_end] = progress_date - self.date_from + 1
649 649 else
650 650 coords[:bar_progress_end] = self.date_to - self.date_from + 1
651 651 end
652 652 end
653 653
654 654 if progress_date < Date.today
655 655 late_date = [Date.today, end_date].min
656 656 if late_date > self.date_from && late_date > start_date
657 657 if late_date < self.date_to
658 658 coords[:bar_late_end] = late_date - self.date_from + 1
659 659 else
660 660 coords[:bar_late_end] = self.date_to - self.date_from + 1
661 661 end
662 662 end
663 663 end
664 664 end
665 665 end
666 666
667 667 # Transforms dates into pixels witdh
668 668 coords.keys.each do |key|
669 669 coords[key] = (coords[key] * zoom).floor
670 670 end
671 671 coords
672 672 end
673 673
674 674 # Sorts a collection of issues by start_date, due_date, id for gantt rendering
675 675 def sort_issues!(issues)
676 676 issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
677 677 end
678 678
679 679 # TODO: top level issues should be sorted by start date
680 680 def gantt_issue_compare(x, y, issues)
681 681 if x.root_id == y.root_id
682 682 x.lft <=> y.lft
683 683 else
684 684 x.root_id <=> y.root_id
685 685 end
686 686 end
687 687
688 688 def current_limit
689 689 if @max_rows
690 690 @max_rows - @number_of_rows
691 691 else
692 692 nil
693 693 end
694 694 end
695 695
696 696 def abort?
697 697 if @max_rows && @number_of_rows >= @max_rows
698 698 @truncated = true
699 699 end
700 700 end
701 701
702 702 def pdf_new_page?(options)
703 703 if options[:top] > 180
704 704 options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])
705 705 options[:pdf].AddPage("L")
706 706 options[:top] = 15
707 707 options[:pdf].Line(15, options[:top] - 0.1, PDF::TotalWidth, options[:top] - 0.1)
708 708 end
709 709 end
710 710
711 711 def html_subject(params, subject, options={})
712 712 output = "<div class=' #{options[:css] }' style='position: absolute;line-height:1.2em;height:16px;top:#{params[:top]}px;left:#{params[:indent]}px;overflow:hidden;'>"
713 713 output << subject
714 714 output << "</div>"
715 715 @subjects << output
716 716 output
717 717 end
718 718
719 719 def pdf_subject(params, subject, options={})
720 720 params[:pdf].SetY(params[:top])
721 721 params[:pdf].SetX(15)
722 722
723 723 char_limit = PDF::MaxCharactorsForSubject - params[:indent]
724 724 params[:pdf].Cell(params[:subject_width]-15, 5, (" " * params[:indent]) + subject.to_s.sub(/^(.{#{char_limit}}[^\s]*\s).*$/, '\1 (...)'), "LR")
725 725
726 726 params[:pdf].SetY(params[:top])
727 727 params[:pdf].SetX(params[:subject_width])
728 728 params[:pdf].Cell(params[:g_width], 5, "", "LR")
729 729 end
730 730
731 731 def image_subject(params, subject, options={})
732 732 params[:image].fill('black')
733 733 params[:image].stroke('transparent')
734 734 params[:image].stroke_width(1)
735 735 params[:image].text(params[:indent], params[:top] + 2, subject)
736 736 end
737 737
738 738 def html_task(params, coords, options={})
739 739 output = ''
740 740 # Renders the task bar, with progress and late
741 741 if coords[:bar_start] && coords[:bar_end]
742 742 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_todo'>&nbsp;</div>"
743 743
744 744 if coords[:bar_late_end]
745 745 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_late_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_late'>&nbsp;</div>"
746 746 end
747 747 if coords[:bar_progress_end]
748 748 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_progress_end] - coords[:bar_start] - 2}px;' class='#{options[:css]} task_done'>&nbsp;</div>"
749 749 end
750 750 end
751 751 # Renders the markers
752 752 if options[:markers]
753 753 if coords[:start]
754 754 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:start] }px;width:15px;' class='#{options[:css]} marker starting'>&nbsp;</div>"
755 755 end
756 756 if coords[:end]
757 757 output << "<div style='top:#{ params[:top] }px;left:#{ coords[:end] + params[:zoom] }px;width:15px;' class='#{options[:css]} marker ending'>&nbsp;</div>"
758 758 end
759 759 end
760 760 # Renders the label on the right
761 761 if options[:label]
762 762 output << "<div style='top:#{ params[:top] }px;left:#{ (coords[:bar_end] || 0) + 8 }px;' class='#{options[:css]} label'>"
763 763 output << options[:label]
764 764 output << "</div>"
765 765 end
766 766 # Renders the tooltip
767 767 if options[:issue] && coords[:bar_start] && coords[:bar_end]
768 768 output << "<div class='tooltip' style='position: absolute;top:#{ params[:top] }px;left:#{ coords[:bar_start] }px;width:#{ coords[:bar_end] - coords[:bar_start] }px;height:12px;'>"
769 769 output << '<span class="tip">'
770 770 output << view.render_issue_tooltip(options[:issue])
771 771 output << "</span></div>"
772 772 end
773 773 @lines << output
774 774 output
775 775 end
776 776
777 777 def pdf_task(params, coords, options={})
778 778 height = options[:height] || 2
779 779
780 780 # Renders the task bar, with progress and late
781 781 if coords[:bar_start] && coords[:bar_end]
782 782 params[:pdf].SetY(params[:top]+1.5)
783 783 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
784 784 params[:pdf].SetFillColor(200,200,200)
785 785 params[:pdf].Cell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
786 786
787 787 if coords[:bar_late_end]
788 788 params[:pdf].SetY(params[:top]+1.5)
789 789 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
790 790 params[:pdf].SetFillColor(255,100,100)
791 791 params[:pdf].Cell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
792 792 end
793 793 if coords[:bar_progress_end]
794 794 params[:pdf].SetY(params[:top]+1.5)
795 795 params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
796 796 params[:pdf].SetFillColor(90,200,90)
797 797 params[:pdf].Cell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
798 798 end
799 799 end
800 800 # Renders the markers
801 801 if options[:markers]
802 802 if coords[:start]
803 803 params[:pdf].SetY(params[:top] + 1)
804 804 params[:pdf].SetX(params[:subject_width] + coords[:start] - 1)
805 805 params[:pdf].SetFillColor(50,50,200)
806 806 params[:pdf].Cell(2, 2, "", 0, 0, "", 1)
807 807 end
808 808 if coords[:end]
809 809 params[:pdf].SetY(params[:top] + 1)
810 810 params[:pdf].SetX(params[:subject_width] + coords[:end] - 1)
811 811 params[:pdf].SetFillColor(50,50,200)
812 812 params[:pdf].Cell(2, 2, "", 0, 0, "", 1)
813 813 end
814 814 end
815 815 # Renders the label on the right
816 816 if options[:label]
817 817 params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
818 818 params[:pdf].Cell(30, 2, options[:label])
819 819 end
820 820 end
821 821
822 822 def image_task(params, coords, options={})
823 823 height = options[:height] || 6
824 824
825 825 # Renders the task bar, with progress and late
826 826 if coords[:bar_start] && coords[:bar_end]
827 827 params[:image].fill('grey')
828 828 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_end], params[:top] - height)
829 829
830 830 if coords[:bar_late_end]
831 831 params[:image].fill('red')
832 832 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_late_end], params[:top] - height)
833 833 end
834 834 if coords[:bar_progress_end]
835 835 params[:image].fill('green')
836 836 params[:image].rectangle(params[:subject_width] + coords[:bar_start], params[:top], params[:subject_width] + coords[:bar_progress_end], params[:top] - height)
837 837 end
838 838 end
839 839 # Renders the markers
840 840 if options[:markers]
841 841 if coords[:start]
842 842 params[:image].fill('blue')
843 843 params[:image].rectangle(params[:subject_width] + coords[:start], params[:top] + 1, params[:subject_width] + coords[:start] + 4, params[:top] - 4)
844 844 end
845 845 if coords[:end]
846 846 params[:image].fill('blue')
847 847 params[:image].rectangle(params[:subject_width] + coords[:end], params[:top] + 1, params[:subject_width] + coords[:end] + 4, params[:top] - 4)
848 848 end
849 849 end
850 850 # Renders the label on the right
851 851 if options[:label]
852 852 params[:image].fill('black')
853 853 params[:image].text(params[:subject_width] + (coords[:bar_end] || 0) + 5,params[:top] + 1, options[:label])
854 854 end
855 855 end
856 856 end
857 857 end
858 858 end
@@ -1,751 +1,751
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2008 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require File.expand_path('../../../../../test_helper', __FILE__)
19 19
20 20 class Redmine::Helpers::GanttTest < ActiveSupport::TestCase
21 21 # Utility methods and classes so assert_select can be used.
22 22 class GanttViewTest < ActionView::Base
23 23 include ActionView::Helpers::UrlHelper
24 24 include ActionView::Helpers::TextHelper
25 25 include ActionController::UrlWriter
26 26 include ApplicationHelper
27 27 include ProjectsHelper
28 28 include IssuesHelper
29 29
30 30 def self.default_url_options
31 31 {:only_path => true }
32 32 end
33 33
34 34 end
35 35
36 36 include ActionController::Assertions::SelectorAssertions
37 37
38 38 def setup
39 39 @response = ActionController::TestResponse.new
40 40 # Fixtures
41 41 ProjectCustomField.delete_all
42 42 Project.destroy_all
43 43
44 44 User.current = User.find(1)
45 45 end
46 46
47 47 def build_view
48 48 @view = GanttViewTest.new
49 49 end
50 50
51 51 def html_document
52 52 HTML::Document.new(@response.body)
53 53 end
54 54
55 55 # Creates a Gantt chart for a 4 week span
56 56 def create_gantt(project=Project.generate!, options={})
57 57 @project = project
58 58 @gantt = Redmine::Helpers::Gantt.new(options)
59 59 @gantt.project = @project
60 60 @gantt.query = Query.generate_default!(:project => @project)
61 61 @gantt.view = build_view
62 62 @gantt.instance_variable_set('@date_from', options[:date_from] || 2.weeks.ago.to_date)
63 63 @gantt.instance_variable_set('@date_to', options[:date_to] || 2.weeks.from_now.to_date)
64 64 end
65 65
66 66 context "#number_of_rows" do
67 67
68 68 context "with one project" do
69 69 should "return the number of rows just for that project"
70 70 end
71 71
72 72 context "with no project" do
73 73 should "return the total number of rows for all the projects, resursively"
74 74 end
75 75
76 76 should "not exceed max_rows option" do
77 77 p = Project.generate!
78 78 5.times do
79 79 Issue.generate_for_project!(p)
80 80 end
81 81
82 82 create_gantt(p)
83 83 @gantt.render
84 84 assert_equal 6, @gantt.number_of_rows
85 85 assert !@gantt.truncated
86 86
87 87 create_gantt(p, :max_rows => 3)
88 88 @gantt.render
89 89 assert_equal 3, @gantt.number_of_rows
90 90 assert @gantt.truncated
91 91 end
92 92 end
93 93
94 94 context "#number_of_rows_on_project" do
95 95 setup do
96 96 create_gantt
97 97 end
98 98
99 99 should "clear the @query.project so cross-project issues and versions can be counted" do
100 100 assert @gantt.query.project
101 101 @gantt.number_of_rows_on_project(@project)
102 102 assert_nil @gantt.query.project
103 103 end
104 104
105 105 should "count 1 for the project itself" do
106 106 assert_equal 1, @gantt.number_of_rows_on_project(@project)
107 107 end
108 108
109 109 should "count the number of issues without a version" do
110 110 @project.issues << Issue.generate_for_project!(@project, :fixed_version => nil)
111 111 assert_equal 2, @gantt.number_of_rows_on_project(@project)
112 112 end
113 113
114 114 should "count the number of versions" do
115 115 @project.versions << Version.generate!
116 116 @project.versions << Version.generate!
117 117 assert_equal 3, @gantt.number_of_rows_on_project(@project)
118 118 end
119 119
120 120 should "count the number of issues on versions, including cross-project" do
121 121 version = Version.generate!
122 122 @project.versions << version
123 123 @project.issues << Issue.generate_for_project!(@project, :fixed_version => version)
124 124
125 125 assert_equal 3, @gantt.number_of_rows_on_project(@project)
126 126 end
127 127
128 128 should "recursive and count the number of rows on each subproject" do
129 129 @project.versions << Version.generate! # +1
130 130
131 131 @subproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
132 132 @subproject.set_parent!(@project)
133 133 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
134 134 @subproject.issues << Issue.generate_for_project!(@subproject) # +1
135 135
136 136 @subsubproject = Project.generate!(:enabled_module_names => ['issue_tracking']) # +1
137 137 @subsubproject.set_parent!(@subproject)
138 138 @subsubproject.issues << Issue.generate_for_project!(@subsubproject) # +1
139 139
140 140 assert_equal 7, @gantt.number_of_rows_on_project(@project) # +1 for self
141 141 end
142 142 end
143 143
144 144 # TODO: more of an integration test
145 145 context "#subjects" do
146 146 setup do
147 147 create_gantt
148 148 @project.enabled_module_names = [:issue_tracking]
149 149 @tracker = Tracker.generate!
150 150 @project.trackers << @tracker
151 151 @version = Version.generate!(:effective_date => 1.week.from_now.to_date, :sharing => 'none')
152 152 @project.versions << @version
153 153
154 154 @issue = Issue.generate!(:fixed_version => @version,
155 155 :subject => "gantt#line_for_project",
156 156 :tracker => @tracker,
157 157 :project => @project,
158 158 :done_ratio => 30,
159 159 :start_date => Date.yesterday,
160 160 :due_date => 1.week.from_now.to_date)
161 161 @project.issues << @issue
162 162 end
163 163
164 164 context "project" do
165 165 should "be rendered" do
166 166 @response.body = @gantt.subjects
167 167 assert_select "div.project-name a", /#{@project.name}/
168 168 end
169 169
170 170 should "have an indent of 4" do
171 171 @response.body = @gantt.subjects
172 172 assert_select "div.project-name[style*=left:4px]"
173 173 end
174 174 end
175 175
176 176 context "version" do
177 177 should "be rendered" do
178 178 @response.body = @gantt.subjects
179 179 assert_select "div.version-name a", /#{@version.name}/
180 180 end
181 181
182 182 should "be indented 24 (one level)" do
183 183 @response.body = @gantt.subjects
184 184 assert_select "div.version-name[style*=left:24px]"
185 185 end
186 186 end
187 187
188 188 context "issue" do
189 189 should "be rendered" do
190 190 @response.body = @gantt.subjects
191 191 assert_select "div.issue-subject", /#{@issue.subject}/
192 192 end
193 193
194 194 should "be indented 44 (two levels)" do
195 195 @response.body = @gantt.subjects
196 196 assert_select "div.issue-subject[style*=left:44px]"
197 197 end
198 198
199 199 context "with subtasks" do
200 200 setup do
201 201 attrs = {:project => @project, :tracker => @tracker, :fixed_version => @version}
202 202 @child1 = Issue.generate!(attrs.merge(:subject => 'child1', :parent_issue_id => @issue.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date))
203 203 @child2 = Issue.generate!(attrs.merge(:subject => 'child2', :parent_issue_id => @issue.id, :start_date => Date.today, :due_date => 1.week.from_now.to_date))
204 204 @grandchild = Issue.generate!(attrs.merge(:subject => 'grandchild', :parent_issue_id => @child1.id, :start_date => Date.yesterday, :due_date => 2.day.from_now.to_date))
205 205 end
206 206
207 207 should "indent subtasks" do
208 208 @response.body = @gantt.subjects
209 209 # parent task 44px
210 210 assert_select "div.issue-subject[style*=left:44px]", /#{@issue.subject}/
211 211 # children 64px
212 212 assert_select "div.issue-subject[style*=left:64px]", /child1/
213 213 assert_select "div.issue-subject[style*=left:64px]", /child2/
214 214 # grandchild 84px
215 215 assert_select "div.issue-subject[style*=left:84px]", /grandchild/, @response.body
216 216 end
217 217 end
218 218 end
219 219 end
220 220
221 221 context "#lines" do
222 222 setup do
223 223 create_gantt
224 224 @project.enabled_module_names = [:issue_tracking]
225 225 @tracker = Tracker.generate!
226 226 @project.trackers << @tracker
227 227 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
228 228 @project.versions << @version
229 229 @issue = Issue.generate!(:fixed_version => @version,
230 230 :subject => "gantt#line_for_project",
231 231 :tracker => @tracker,
232 232 :project => @project,
233 233 :done_ratio => 30,
234 234 :start_date => Date.yesterday,
235 235 :due_date => 1.week.from_now.to_date)
236 236 @project.issues << @issue
237 237
238 238 @response.body = @gantt.lines
239 239 end
240 240
241 241 context "project" do
242 242 should "be rendered" do
243 243 assert_select "div.project.task_todo"
244 244 assert_select "div.project.starting"
245 245 assert_select "div.project.ending"
246 246 assert_select "div.label.project", /#{@project.name}/
247 247 end
248 248 end
249 249
250 250 context "version" do
251 251 should "be rendered" do
252 252 assert_select "div.version.task_todo"
253 253 assert_select "div.version.starting"
254 254 assert_select "div.version.ending"
255 255 assert_select "div.label.version", /#{@version.name}/
256 256 end
257 257 end
258 258
259 259 context "issue" do
260 260 should "be rendered" do
261 261 assert_select "div.task_todo"
262 262 assert_select "div.task.label", /#{@issue.done_ratio}/
263 263 assert_select "div.tooltip", /#{@issue.subject}/
264 264 end
265 265 end
266 266 end
267 267
268 268 context "#render_project" do
269 269 should "be tested"
270 270 end
271 271
272 272 context "#render_issues" do
273 273 should "be tested"
274 274 end
275 275
276 276 context "#render_version" do
277 277 should "be tested"
278 278 end
279 279
280 280 context "#subject_for_project" do
281 281 setup do
282 282 create_gantt
283 283 end
284 284
285 285 context ":html format" do
286 286 should "add an absolute positioned div" do
287 287 @response.body = @gantt.subject_for_project(@project, {:format => :html})
288 288 assert_select "div[style*=absolute]"
289 289 end
290 290
291 291 should "use the indent option to move the div to the right" do
292 292 @response.body = @gantt.subject_for_project(@project, {:format => :html, :indent => 40})
293 293 assert_select "div[style*=left:40]"
294 294 end
295 295
296 296 should "include the project name" do
297 297 @response.body = @gantt.subject_for_project(@project, {:format => :html})
298 298 assert_select 'div', :text => /#{@project.name}/
299 299 end
300 300
301 301 should "include a link to the project" do
302 302 @response.body = @gantt.subject_for_project(@project, {:format => :html})
303 303 assert_select 'a[href=?]', "/projects/#{@project.identifier}", :text => /#{@project.name}/
304 304 end
305 305
306 306 should "style overdue projects" do
307 307 @project.enabled_module_names = [:issue_tracking]
308 308 @project.versions << Version.generate!(:effective_date => Date.yesterday)
309 309
310 310 assert @project.overdue?, "Need an overdue project for this test"
311 311 @response.body = @gantt.subject_for_project(@project, {:format => :html})
312 312
313 313 assert_select 'div span.project-overdue'
314 314 end
315 315
316 316
317 317 end
318 318
319 319 should "test the PNG format"
320 320 should "test the PDF format"
321 321 end
322 322
323 323 context "#line_for_project" do
324 324 setup do
325 325 create_gantt
326 326 @project.enabled_module_names = [:issue_tracking]
327 327 @tracker = Tracker.generate!
328 328 @project.trackers << @tracker
329 329 @version = Version.generate!(:effective_date => Date.yesterday)
330 330 @project.versions << @version
331 331
332 332 @project.issues << Issue.generate!(:fixed_version => @version,
333 333 :subject => "gantt#line_for_project",
334 334 :tracker => @tracker,
335 335 :project => @project,
336 336 :done_ratio => 30,
337 337 :start_date => 1.week.ago.to_date,
338 338 :due_date => 1.week.from_now.to_date)
339 339 end
340 340
341 341 context ":html format" do
342 342 context "todo line" do
343 343 should "start from the starting point on the left" do
344 344 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
345 345 assert_select "div.project.task_todo[style*=left:28px]", true, @response.body
346 346 end
347 347
348 348 should "be the total width of the project" do
349 349 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
350 350 assert_select "div.project.task_todo[style*=width:58px]", true, @response.body
351 351 end
352 352
353 353 end
354 354
355 355 context "late line" do
356 should "start from the starting point on the left" do
356 should_eventually "start from the starting point on the left" do
357 357 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
358 358 assert_select "div.project.task_late[style*=left:28px]", true, @response.body
359 359 end
360 360
361 should "be the total delayed width of the project" do
361 should_eventually "be the total delayed width of the project" do
362 362 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
363 363 assert_select "div.project.task_late[style*=width:30px]", true, @response.body
364 364 end
365 365 end
366 366
367 367 context "done line" do
368 should "start from the starting point on the left" do
368 should_eventually "start from the starting point on the left" do
369 369 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
370 370 assert_select "div.project.task_done[style*=left:28px]", true, @response.body
371 371 end
372 372
373 should "Be the total done width of the project" do
373 should_eventually "Be the total done width of the project" do
374 374 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
375 375 assert_select "div.project.task_done[style*=width:18px]", true, @response.body
376 376 end
377 377 end
378 378
379 379 context "starting marker" do
380 380 should "not appear if the starting point is off the gantt chart" do
381 381 # Shift the date range of the chart
382 382 @gantt.instance_variable_set('@date_from', Date.today)
383 383
384 384 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
385 385 assert_select "div.project.starting", false, @response.body
386 386 end
387 387
388 388 should "appear at the starting point" do
389 389 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
390 390 assert_select "div.project.starting[style*=left:28px]", true, @response.body
391 391 end
392 392 end
393 393
394 394 context "ending marker" do
395 395 should "not appear if the starting point is off the gantt chart" do
396 396 # Shift the date range of the chart
397 397 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
398 398
399 399 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
400 400 assert_select "div.project.ending", false, @response.body
401 401
402 402 end
403 403
404 404 should "appear at the end of the date range" do
405 405 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
406 406 assert_select "div.project.ending[style*=left:88px]", true, @response.body
407 407 end
408 408 end
409 409
410 410 context "status content" do
411 411 should "appear at the far left, even if it's far in the past" do
412 412 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
413 413
414 414 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
415 415 assert_select "div.project.label", /#{@project.name}/
416 416 end
417 417
418 418 should "show the project name" do
419 419 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
420 420 assert_select "div.project.label", /#{@project.name}/
421 421 end
422 422
423 should "show the percent complete" do
423 should_eventually "show the percent complete" do
424 424 @response.body = @gantt.line_for_project(@project, {:format => :html, :zoom => 4})
425 425 assert_select "div.project.label", /0%/
426 426 end
427 427 end
428 428 end
429 429
430 430 should "test the PNG format"
431 431 should "test the PDF format"
432 432 end
433 433
434 434 context "#subject_for_version" do
435 435 setup do
436 436 create_gantt
437 437 @project.enabled_module_names = [:issue_tracking]
438 438 @tracker = Tracker.generate!
439 439 @project.trackers << @tracker
440 440 @version = Version.generate!(:effective_date => Date.yesterday)
441 441 @project.versions << @version
442 442
443 443 @project.issues << Issue.generate!(:fixed_version => @version,
444 444 :subject => "gantt#subject_for_version",
445 445 :tracker => @tracker,
446 446 :project => @project,
447 447 :start_date => Date.today)
448 448
449 449 end
450 450
451 451 context ":html format" do
452 452 should "add an absolute positioned div" do
453 453 @response.body = @gantt.subject_for_version(@version, {:format => :html})
454 454 assert_select "div[style*=absolute]"
455 455 end
456 456
457 457 should "use the indent option to move the div to the right" do
458 458 @response.body = @gantt.subject_for_version(@version, {:format => :html, :indent => 40})
459 459 assert_select "div[style*=left:40]"
460 460 end
461 461
462 462 should "include the version name" do
463 463 @response.body = @gantt.subject_for_version(@version, {:format => :html})
464 464 assert_select 'div', :text => /#{@version.name}/
465 465 end
466 466
467 467 should "include a link to the version" do
468 468 @response.body = @gantt.subject_for_version(@version, {:format => :html})
469 469 assert_select 'a[href=?]', Regexp.escape("/versions/show/#{@version.to_param}"), :text => /#{@version.name}/
470 470 end
471 471
472 472 should "style late versions" do
473 473 assert @version.overdue?, "Need an overdue version for this test"
474 474 @response.body = @gantt.subject_for_version(@version, {:format => :html})
475 475
476 476 assert_select 'div span.version-behind-schedule'
477 477 end
478 478
479 479 should "style behind schedule versions" do
480 480 assert @version.behind_schedule?, "Need a behind schedule version for this test"
481 481 @response.body = @gantt.subject_for_version(@version, {:format => :html})
482 482
483 483 assert_select 'div span.version-behind-schedule'
484 484 end
485 485 end
486 486 should "test the PNG format"
487 487 should "test the PDF format"
488 488 end
489 489
490 490 context "#line_for_version" do
491 491 setup do
492 492 create_gantt
493 493 @project.enabled_module_names = [:issue_tracking]
494 494 @tracker = Tracker.generate!
495 495 @project.trackers << @tracker
496 496 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
497 497 @project.versions << @version
498 498
499 499 @project.issues << Issue.generate!(:fixed_version => @version,
500 500 :subject => "gantt#line_for_project",
501 501 :tracker => @tracker,
502 502 :project => @project,
503 503 :done_ratio => 30,
504 504 :start_date => 1.week.ago.to_date,
505 505 :due_date => 1.week.from_now.to_date)
506 506 end
507 507
508 508 context ":html format" do
509 509 context "todo line" do
510 510 should "start from the starting point on the left" do
511 511 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
512 512 assert_select "div.version.task_todo[style*=left:28px]", true, @response.body
513 513 end
514 514
515 515 should "be the total width of the version" do
516 516 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
517 517 assert_select "div.version.task_todo[style*=width:58px]", true, @response.body
518 518 end
519 519
520 520 end
521 521
522 522 context "late line" do
523 523 should "start from the starting point on the left" do
524 524 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
525 525 assert_select "div.version.task_late[style*=left:28px]", true, @response.body
526 526 end
527 527
528 528 should "be the total delayed width of the version" do
529 529 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
530 530 assert_select "div.version.task_late[style*=width:30px]", true, @response.body
531 531 end
532 532 end
533 533
534 534 context "done line" do
535 535 should "start from the starting point on the left" do
536 536 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
537 537 assert_select "div.version.task_done[style*=left:28px]", true, @response.body
538 538 end
539 539
540 540 should "Be the total done width of the version" do
541 541 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
542 542 assert_select "div.version.task_done[style*=width:18px]", true, @response.body
543 543 end
544 544 end
545 545
546 546 context "starting marker" do
547 547 should "not appear if the starting point is off the gantt chart" do
548 548 # Shift the date range of the chart
549 549 @gantt.instance_variable_set('@date_from', Date.today)
550 550
551 551 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
552 552 assert_select "div.version.starting", false
553 553 end
554 554
555 555 should "appear at the starting point" do
556 556 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
557 557 assert_select "div.version.starting[style*=left:28px]", true, @response.body
558 558 end
559 559 end
560 560
561 561 context "ending marker" do
562 562 should "not appear if the starting point is off the gantt chart" do
563 563 # Shift the date range of the chart
564 564 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
565 565
566 566 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
567 567 assert_select "div.version.ending", false
568 568
569 569 end
570 570
571 571 should "appear at the end of the date range" do
572 572 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
573 573 assert_select "div.version.ending[style*=left:88px]", true, @response.body
574 574 end
575 575 end
576 576
577 577 context "status content" do
578 578 should "appear at the far left, even if it's far in the past" do
579 579 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
580 580
581 581 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
582 582 assert_select "div.version.label", /#{@version.name}/
583 583 end
584 584
585 585 should "show the version name" do
586 586 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
587 587 assert_select "div.version.label", /#{@version.name}/
588 588 end
589 589
590 590 should "show the percent complete" do
591 591 @response.body = @gantt.line_for_version(@version, {:format => :html, :zoom => 4})
592 592 assert_select "div.version.label", /30%/
593 593 end
594 594 end
595 595 end
596 596
597 597 should "test the PNG format"
598 598 should "test the PDF format"
599 599 end
600 600
601 601 context "#subject_for_issue" do
602 602 setup do
603 603 create_gantt
604 604 @project.enabled_module_names = [:issue_tracking]
605 605 @tracker = Tracker.generate!
606 606 @project.trackers << @tracker
607 607
608 608 @issue = Issue.generate!(:subject => "gantt#subject_for_issue",
609 609 :tracker => @tracker,
610 610 :project => @project,
611 611 :start_date => 3.days.ago.to_date,
612 612 :due_date => Date.yesterday)
613 613 @project.issues << @issue
614 614
615 615 end
616 616
617 617 context ":html format" do
618 618 should "add an absolute positioned div" do
619 619 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
620 620 assert_select "div[style*=absolute]"
621 621 end
622 622
623 623 should "use the indent option to move the div to the right" do
624 624 @response.body = @gantt.subject_for_issue(@issue, {:format => :html, :indent => 40})
625 625 assert_select "div[style*=left:40]"
626 626 end
627 627
628 628 should "include the issue subject" do
629 629 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
630 630 assert_select 'div', :text => /#{@issue.subject}/
631 631 end
632 632
633 633 should "include a link to the issue" do
634 634 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
635 635 assert_select 'a[href=?]', Regexp.escape("/issues/#{@issue.to_param}"), :text => /#{@tracker.name} ##{@issue.id}/
636 636 end
637 637
638 638 should "style overdue issues" do
639 639 assert @issue.overdue?, "Need an overdue issue for this test"
640 640 @response.body = @gantt.subject_for_issue(@issue, {:format => :html})
641 641
642 642 assert_select 'div span.issue-overdue'
643 643 end
644 644
645 645 end
646 646 should "test the PNG format"
647 647 should "test the PDF format"
648 648 end
649 649
650 650 context "#line_for_issue" do
651 651 setup do
652 652 create_gantt
653 653 @project.enabled_module_names = [:issue_tracking]
654 654 @tracker = Tracker.generate!
655 655 @project.trackers << @tracker
656 656 @version = Version.generate!(:effective_date => 1.week.from_now.to_date)
657 657 @project.versions << @version
658 658 @issue = Issue.generate!(:fixed_version => @version,
659 659 :subject => "gantt#line_for_project",
660 660 :tracker => @tracker,
661 661 :project => @project,
662 662 :done_ratio => 30,
663 663 :start_date => 1.week.ago.to_date,
664 664 :due_date => 1.week.from_now.to_date)
665 665 @project.issues << @issue
666 666 end
667 667
668 668 context ":html format" do
669 669 context "todo line" do
670 670 should "start from the starting point on the left" do
671 671 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
672 672 assert_select "div.task_todo[style*=left:28px]", true, @response.body
673 673 end
674 674
675 675 should "be the total width of the issue" do
676 676 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
677 677 assert_select "div.task_todo[style*=width:58px]", true, @response.body
678 678 end
679 679
680 680 end
681 681
682 682 context "late line" do
683 683 should "start from the starting point on the left" do
684 684 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
685 685 assert_select "div.task_late[style*=left:28px]", true, @response.body
686 686 end
687 687
688 688 should "be the total delayed width of the issue" do
689 689 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
690 690 assert_select "div.task_late[style*=width:30px]", true, @response.body
691 691 end
692 692 end
693 693
694 694 context "done line" do
695 695 should "start from the starting point on the left" do
696 696 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
697 697 assert_select "div.task_done[style*=left:28px]", true, @response.body
698 698 end
699 699
700 700 should "Be the total done width of the issue" do
701 701 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
702 702 assert_select "div.task_done[style*=width:18px]", true, @response.body
703 703 end
704 704
705 705 should "not be the total done width if the chart starts after issue start date" do
706 706 create_gantt(@project, :date_from => 5.days.ago.to_date)
707 707
708 708 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
709 709 assert_select "div.task_done[style*=left:0px]", true, @response.body
710 710 assert_select "div.task_done[style*=width:10px]", true, @response.body
711 711 end
712 712 end
713 713
714 714 context "status content" do
715 715 should "appear at the far left, even if it's far in the past" do
716 716 @gantt.instance_variable_set('@date_to', 2.weeks.ago.to_date)
717 717
718 718 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
719 719 assert_select "div.task.label", true, @response.body
720 720 end
721 721
722 722 should "show the issue status" do
723 723 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
724 724 assert_select "div.task.label", /#{@issue.status.name}/
725 725 end
726 726
727 727 should "show the percent complete" do
728 728 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
729 729 assert_select "div.task.label", /30%/
730 730 end
731 731 end
732 732 end
733 733
734 734 should "have an issue tooltip" do
735 735 @response.body = @gantt.line_for_issue(@issue, {:format => :html, :zoom => 4})
736 736 assert_select "div.tooltip", /#{@issue.subject}/
737 737 end
738 738
739 739 should "test the PNG format"
740 740 should "test the PDF format"
741 741 end
742 742
743 743 context "#to_image" do
744 744 should "be tested"
745 745 end
746 746
747 747 context "#to_pdf" do
748 748 should "be tested"
749 749 end
750 750
751 751 end
General Comments 0
You need to be logged in to leave comments. Login now