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