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