##// END OF EJS Templates
Merged r3952 from trunk....
Eric Davis -
r3888:ccbc9f8ff963
parent child
Show More
@@ -1,262 +1,264
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006 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 IssuesHelper
18 module IssuesHelper
19 include ApplicationHelper
19 include ApplicationHelper
20
20
21 def issue_list(issues, &block)
21 def issue_list(issues, &block)
22 ancestors = []
22 ancestors = []
23 issues.each do |issue|
23 issues.each do |issue|
24 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
24 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
25 ancestors.pop
25 ancestors.pop
26 end
26 end
27 yield issue, ancestors.size
27 yield issue, ancestors.size
28 ancestors << issue unless issue.leaf?
28 ancestors << issue unless issue.leaf?
29 end
29 end
30 end
30 end
31
31
32 def render_issue_tooltip(issue)
32 def render_issue_tooltip(issue)
33 @cached_label_status ||= l(:field_status)
33 @cached_label_start_date ||= l(:field_start_date)
34 @cached_label_start_date ||= l(:field_start_date)
34 @cached_label_due_date ||= l(:field_due_date)
35 @cached_label_due_date ||= l(:field_due_date)
35 @cached_label_assigned_to ||= l(:field_assigned_to)
36 @cached_label_assigned_to ||= l(:field_assigned_to)
36 @cached_label_priority ||= l(:field_priority)
37 @cached_label_priority ||= l(:field_priority)
37
38
38 link_to_issue(issue) + "<br /><br />" +
39 link_to_issue(issue) + "<br /><br />" +
40 "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
39 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
41 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
40 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
42 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
41 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
43 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
42 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
44 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
43 end
45 end
44
46
45 def render_issue_subject_with_tree(issue)
47 def render_issue_subject_with_tree(issue)
46 s = ''
48 s = ''
47 issue.ancestors.each do |ancestor|
49 issue.ancestors.each do |ancestor|
48 s << '<div>' + content_tag('p', link_to_issue(ancestor))
50 s << '<div>' + content_tag('p', link_to_issue(ancestor))
49 end
51 end
50 s << '<div>' + content_tag('h3', h(issue.subject))
52 s << '<div>' + content_tag('h3', h(issue.subject))
51 s << '</div>' * (issue.ancestors.size + 1)
53 s << '</div>' * (issue.ancestors.size + 1)
52 s
54 s
53 end
55 end
54
56
55 def render_descendants_tree(issue)
57 def render_descendants_tree(issue)
56 s = '<form><table class="list issues">'
58 s = '<form><table class="list issues">'
57 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
59 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
58 s << content_tag('tr',
60 s << content_tag('tr',
59 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
61 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
60 content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
62 content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
61 content_tag('td', h(child.status)) +
63 content_tag('td', h(child.status)) +
62 content_tag('td', link_to_user(child.assigned_to)) +
64 content_tag('td', link_to_user(child.assigned_to)) +
63 content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
65 content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
64 :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
66 :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
65 end
67 end
66 s << '</form></table>'
68 s << '</form></table>'
67 s
69 s
68 end
70 end
69
71
70 def render_custom_fields_rows(issue)
72 def render_custom_fields_rows(issue)
71 return if issue.custom_field_values.empty?
73 return if issue.custom_field_values.empty?
72 ordered_values = []
74 ordered_values = []
73 half = (issue.custom_field_values.size / 2.0).ceil
75 half = (issue.custom_field_values.size / 2.0).ceil
74 half.times do |i|
76 half.times do |i|
75 ordered_values << issue.custom_field_values[i]
77 ordered_values << issue.custom_field_values[i]
76 ordered_values << issue.custom_field_values[i + half]
78 ordered_values << issue.custom_field_values[i + half]
77 end
79 end
78 s = "<tr>\n"
80 s = "<tr>\n"
79 n = 0
81 n = 0
80 ordered_values.compact.each do |value|
82 ordered_values.compact.each do |value|
81 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
83 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
82 s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
84 s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
83 n += 1
85 n += 1
84 end
86 end
85 s << "</tr>\n"
87 s << "</tr>\n"
86 s
88 s
87 end
89 end
88
90
89 def sidebar_queries
91 def sidebar_queries
90 unless @sidebar_queries
92 unless @sidebar_queries
91 # User can see public queries and his own queries
93 # User can see public queries and his own queries
92 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
94 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
93 # Project specific queries and global queries
95 # Project specific queries and global queries
94 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
96 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
95 @sidebar_queries = Query.find(:all,
97 @sidebar_queries = Query.find(:all,
96 :select => 'id, name',
98 :select => 'id, name',
97 :order => "name ASC",
99 :order => "name ASC",
98 :conditions => visible.conditions)
100 :conditions => visible.conditions)
99 end
101 end
100 @sidebar_queries
102 @sidebar_queries
101 end
103 end
102
104
103 def show_detail(detail, no_html=false)
105 def show_detail(detail, no_html=false)
104 case detail.property
106 case detail.property
105 when 'attr'
107 when 'attr'
106 field = detail.prop_key.to_s.gsub(/\_id$/, "")
108 field = detail.prop_key.to_s.gsub(/\_id$/, "")
107 label = l(("field_" + field).to_sym)
109 label = l(("field_" + field).to_sym)
108 case
110 case
109 when ['due_date', 'start_date'].include?(detail.prop_key)
111 when ['due_date', 'start_date'].include?(detail.prop_key)
110 value = format_date(detail.value.to_date) if detail.value
112 value = format_date(detail.value.to_date) if detail.value
111 old_value = format_date(detail.old_value.to_date) if detail.old_value
113 old_value = format_date(detail.old_value.to_date) if detail.old_value
112
114
113 when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
115 when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
114 value = find_name_by_reflection(field, detail.value)
116 value = find_name_by_reflection(field, detail.value)
115 old_value = find_name_by_reflection(field, detail.old_value)
117 old_value = find_name_by_reflection(field, detail.old_value)
116
118
117 when detail.prop_key == 'estimated_hours'
119 when detail.prop_key == 'estimated_hours'
118 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
120 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
119 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
121 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
120
122
121 when detail.prop_key == 'parent_id'
123 when detail.prop_key == 'parent_id'
122 label = l(:field_parent_issue)
124 label = l(:field_parent_issue)
123 value = "##{detail.value}" unless detail.value.blank?
125 value = "##{detail.value}" unless detail.value.blank?
124 old_value = "##{detail.old_value}" unless detail.old_value.blank?
126 old_value = "##{detail.old_value}" unless detail.old_value.blank?
125 end
127 end
126 when 'cf'
128 when 'cf'
127 custom_field = CustomField.find_by_id(detail.prop_key)
129 custom_field = CustomField.find_by_id(detail.prop_key)
128 if custom_field
130 if custom_field
129 label = custom_field.name
131 label = custom_field.name
130 value = format_value(detail.value, custom_field.field_format) if detail.value
132 value = format_value(detail.value, custom_field.field_format) if detail.value
131 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
133 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
132 end
134 end
133 when 'attachment'
135 when 'attachment'
134 label = l(:label_attachment)
136 label = l(:label_attachment)
135 end
137 end
136 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
138 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
137
139
138 label ||= detail.prop_key
140 label ||= detail.prop_key
139 value ||= detail.value
141 value ||= detail.value
140 old_value ||= detail.old_value
142 old_value ||= detail.old_value
141
143
142 unless no_html
144 unless no_html
143 label = content_tag('strong', label)
145 label = content_tag('strong', label)
144 old_value = content_tag("i", h(old_value)) if detail.old_value
146 old_value = content_tag("i", h(old_value)) if detail.old_value
145 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
147 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
146 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
148 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
147 # Link to the attachment if it has not been removed
149 # Link to the attachment if it has not been removed
148 value = link_to_attachment(a)
150 value = link_to_attachment(a)
149 else
151 else
150 value = content_tag("i", h(value)) if value
152 value = content_tag("i", h(value)) if value
151 end
153 end
152 end
154 end
153
155
154 if !detail.value.blank?
156 if !detail.value.blank?
155 case detail.property
157 case detail.property
156 when 'attr', 'cf'
158 when 'attr', 'cf'
157 if !detail.old_value.blank?
159 if !detail.old_value.blank?
158 l(:text_journal_changed, :label => label, :old => old_value, :new => value)
160 l(:text_journal_changed, :label => label, :old => old_value, :new => value)
159 else
161 else
160 l(:text_journal_set_to, :label => label, :value => value)
162 l(:text_journal_set_to, :label => label, :value => value)
161 end
163 end
162 when 'attachment'
164 when 'attachment'
163 l(:text_journal_added, :label => label, :value => value)
165 l(:text_journal_added, :label => label, :value => value)
164 end
166 end
165 else
167 else
166 l(:text_journal_deleted, :label => label, :old => old_value)
168 l(:text_journal_deleted, :label => label, :old => old_value)
167 end
169 end
168 end
170 end
169
171
170 # Find the name of an associated record stored in the field attribute
172 # Find the name of an associated record stored in the field attribute
171 def find_name_by_reflection(field, id)
173 def find_name_by_reflection(field, id)
172 association = Issue.reflect_on_association(field.to_sym)
174 association = Issue.reflect_on_association(field.to_sym)
173 if association
175 if association
174 record = association.class_name.constantize.find_by_id(id)
176 record = association.class_name.constantize.find_by_id(id)
175 return record.name if record
177 return record.name if record
176 end
178 end
177 end
179 end
178
180
179 def issues_to_csv(issues, project = nil)
181 def issues_to_csv(issues, project = nil)
180 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
182 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
181 decimal_separator = l(:general_csv_decimal_separator)
183 decimal_separator = l(:general_csv_decimal_separator)
182 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
184 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
183 # csv header fields
185 # csv header fields
184 headers = [ "#",
186 headers = [ "#",
185 l(:field_status),
187 l(:field_status),
186 l(:field_project),
188 l(:field_project),
187 l(:field_tracker),
189 l(:field_tracker),
188 l(:field_priority),
190 l(:field_priority),
189 l(:field_subject),
191 l(:field_subject),
190 l(:field_assigned_to),
192 l(:field_assigned_to),
191 l(:field_category),
193 l(:field_category),
192 l(:field_fixed_version),
194 l(:field_fixed_version),
193 l(:field_author),
195 l(:field_author),
194 l(:field_start_date),
196 l(:field_start_date),
195 l(:field_due_date),
197 l(:field_due_date),
196 l(:field_done_ratio),
198 l(:field_done_ratio),
197 l(:field_estimated_hours),
199 l(:field_estimated_hours),
198 l(:field_parent_issue),
200 l(:field_parent_issue),
199 l(:field_created_on),
201 l(:field_created_on),
200 l(:field_updated_on)
202 l(:field_updated_on)
201 ]
203 ]
202 # Export project custom fields if project is given
204 # Export project custom fields if project is given
203 # otherwise export custom fields marked as "For all projects"
205 # otherwise export custom fields marked as "For all projects"
204 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
206 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
205 custom_fields.each {|f| headers << f.name}
207 custom_fields.each {|f| headers << f.name}
206 # Description in the last column
208 # Description in the last column
207 headers << l(:field_description)
209 headers << l(:field_description)
208 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
210 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
209 # csv lines
211 # csv lines
210 issues.each do |issue|
212 issues.each do |issue|
211 fields = [issue.id,
213 fields = [issue.id,
212 issue.status.name,
214 issue.status.name,
213 issue.project.name,
215 issue.project.name,
214 issue.tracker.name,
216 issue.tracker.name,
215 issue.priority.name,
217 issue.priority.name,
216 issue.subject,
218 issue.subject,
217 issue.assigned_to,
219 issue.assigned_to,
218 issue.category,
220 issue.category,
219 issue.fixed_version,
221 issue.fixed_version,
220 issue.author.name,
222 issue.author.name,
221 format_date(issue.start_date),
223 format_date(issue.start_date),
222 format_date(issue.due_date),
224 format_date(issue.due_date),
223 issue.done_ratio,
225 issue.done_ratio,
224 issue.estimated_hours.to_s.gsub('.', decimal_separator),
226 issue.estimated_hours.to_s.gsub('.', decimal_separator),
225 issue.parent_id,
227 issue.parent_id,
226 format_time(issue.created_on),
228 format_time(issue.created_on),
227 format_time(issue.updated_on)
229 format_time(issue.updated_on)
228 ]
230 ]
229 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
231 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
230 fields << issue.description
232 fields << issue.description
231 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
233 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
232 end
234 end
233 end
235 end
234 export
236 export
235 end
237 end
236
238
237 def gantt_zoom_link(gantt, in_or_out)
239 def gantt_zoom_link(gantt, in_or_out)
238 img_attributes = {:style => 'height:1.4em; width:1.4em; margin-left: 3px;'} # em for accessibility
240 img_attributes = {:style => 'height:1.4em; width:1.4em; margin-left: 3px;'} # em for accessibility
239
241
240 case in_or_out
242 case in_or_out
241 when :in
243 when :in
242 if gantt.zoom < 4
244 if gantt.zoom < 4
243 link_to_remote(l(:text_zoom_in) + image_tag('zoom_in.png', img_attributes.merge(:alt => l(:text_zoom_in))),
245 link_to_remote(l(:text_zoom_in) + image_tag('zoom_in.png', img_attributes.merge(:alt => l(:text_zoom_in))),
244 {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :update => 'content'},
246 {:url => gantt.params.merge(:zoom => (gantt.zoom+1)), :update => 'content'},
245 {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1)))})
247 {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom+1)))})
246 else
248 else
247 l(:text_zoom_in) +
249 l(:text_zoom_in) +
248 image_tag('zoom_in_g.png', img_attributes.merge(:alt => l(:text_zoom_in)))
250 image_tag('zoom_in_g.png', img_attributes.merge(:alt => l(:text_zoom_in)))
249 end
251 end
250
252
251 when :out
253 when :out
252 if gantt.zoom > 1
254 if gantt.zoom > 1
253 link_to_remote(l(:text_zoom_out) + image_tag('zoom_out.png', img_attributes.merge(:alt => l(:text_zoom_out))),
255 link_to_remote(l(:text_zoom_out) + image_tag('zoom_out.png', img_attributes.merge(:alt => l(:text_zoom_out))),
254 {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :update => 'content'},
256 {:url => gantt.params.merge(:zoom => (gantt.zoom-1)), :update => 'content'},
255 {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1)))})
257 {:href => url_for(gantt.params.merge(:zoom => (gantt.zoom-1)))})
256 else
258 else
257 l(:text_zoom_out) +
259 l(:text_zoom_out) +
258 image_tag('zoom_out_g.png', img_attributes.merge(:alt => l(:text_zoom_out)))
260 image_tag('zoom_out_g.png', img_attributes.merge(:alt => l(:text_zoom_out)))
259 end
261 end
260 end
262 end
261 end
263 end
262 end
264 end
General Comments 0
You need to be logged in to leave comments. Login now