##// END OF EJS Templates
Adds a helper for issue heading (#7647)....
Jean-Philippe Lang -
r5327:1cd4f5b353f0
parent child
Show More
@@ -1,292 +1,296
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 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 # Renders a HTML/CSS tooltip
32 # Renders a HTML/CSS tooltip
33 #
33 #
34 # To use, a trigger div is needed. This is a div with the class of "tooltip"
34 # To use, a trigger div is needed. This is a div with the class of "tooltip"
35 # that contains this method wrapped in a span with the class of "tip"
35 # that contains this method wrapped in a span with the class of "tip"
36 #
36 #
37 # <div class="tooltip"><%= link_to_issue(issue) %>
37 # <div class="tooltip"><%= link_to_issue(issue) %>
38 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
38 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
39 # </div>
39 # </div>
40 #
40 #
41 def render_issue_tooltip(issue)
41 def render_issue_tooltip(issue)
42 @cached_label_status ||= l(:field_status)
42 @cached_label_status ||= l(:field_status)
43 @cached_label_start_date ||= l(:field_start_date)
43 @cached_label_start_date ||= l(:field_start_date)
44 @cached_label_due_date ||= l(:field_due_date)
44 @cached_label_due_date ||= l(:field_due_date)
45 @cached_label_assigned_to ||= l(:field_assigned_to)
45 @cached_label_assigned_to ||= l(:field_assigned_to)
46 @cached_label_priority ||= l(:field_priority)
46 @cached_label_priority ||= l(:field_priority)
47 @cached_label_project ||= l(:field_project)
47 @cached_label_project ||= l(:field_project)
48
48
49 link_to_issue(issue) + "<br /><br />" +
49 link_to_issue(issue) + "<br /><br />" +
50 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
50 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />" +
51 "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
51 "<strong>#{@cached_label_status}</strong>: #{issue.status.name}<br />" +
52 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
52 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
53 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
53 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
54 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
54 "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
55 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
55 "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
56 end
56 end
57
58 def issue_heading(issue)
59 h("#{issue.tracker} ##{issue.id}")
60 end
57
61
58 def render_issue_subject_with_tree(issue)
62 def render_issue_subject_with_tree(issue)
59 s = ''
63 s = ''
60 ancestors = issue.root? ? [] : issue.ancestors.all
64 ancestors = issue.root? ? [] : issue.ancestors.all
61 ancestors.each do |ancestor|
65 ancestors.each do |ancestor|
62 s << '<div>' + content_tag('p', link_to_issue(ancestor))
66 s << '<div>' + content_tag('p', link_to_issue(ancestor))
63 end
67 end
64 s << '<div>' + content_tag('h3', h(issue.subject))
68 s << '<div>' + content_tag('h3', h(issue.subject))
65 s << '</div>' * (ancestors.size + 1)
69 s << '</div>' * (ancestors.size + 1)
66 s
70 s
67 end
71 end
68
72
69 def render_descendants_tree(issue)
73 def render_descendants_tree(issue)
70 s = '<form><table class="list issues">'
74 s = '<form><table class="list issues">'
71 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
75 issue_list(issue.descendants.sort_by(&:lft)) do |child, level|
72 s << content_tag('tr',
76 s << content_tag('tr',
73 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
77 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
74 content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
78 content_tag('td', link_to_issue(child, :truncate => 60), :class => 'subject') +
75 content_tag('td', h(child.status)) +
79 content_tag('td', h(child.status)) +
76 content_tag('td', link_to_user(child.assigned_to)) +
80 content_tag('td', link_to_user(child.assigned_to)) +
77 content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
81 content_tag('td', progress_bar(child.done_ratio, :width => '80px')),
78 :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
82 :class => "issue issue-#{child.id} hascontextmenu #{level > 0 ? "idnt idnt-#{level}" : nil}")
79 end
83 end
80 s << '</form></table>'
84 s << '</form></table>'
81 s
85 s
82 end
86 end
83
87
84 def render_custom_fields_rows(issue)
88 def render_custom_fields_rows(issue)
85 return if issue.custom_field_values.empty?
89 return if issue.custom_field_values.empty?
86 ordered_values = []
90 ordered_values = []
87 half = (issue.custom_field_values.size / 2.0).ceil
91 half = (issue.custom_field_values.size / 2.0).ceil
88 half.times do |i|
92 half.times do |i|
89 ordered_values << issue.custom_field_values[i]
93 ordered_values << issue.custom_field_values[i]
90 ordered_values << issue.custom_field_values[i + half]
94 ordered_values << issue.custom_field_values[i + half]
91 end
95 end
92 s = "<tr>\n"
96 s = "<tr>\n"
93 n = 0
97 n = 0
94 ordered_values.compact.each do |value|
98 ordered_values.compact.each do |value|
95 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
99 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
96 s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
100 s << "\t<th>#{ h(value.custom_field.name) }:</th><td>#{ simple_format_without_paragraph(h(show_value(value))) }</td>\n"
97 n += 1
101 n += 1
98 end
102 end
99 s << "</tr>\n"
103 s << "</tr>\n"
100 s
104 s
101 end
105 end
102
106
103 def sidebar_queries
107 def sidebar_queries
104 unless @sidebar_queries
108 unless @sidebar_queries
105 # User can see public queries and his own queries
109 # User can see public queries and his own queries
106 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
110 visible = ARCondition.new(["is_public = ? OR user_id = ?", true, (User.current.logged? ? User.current.id : 0)])
107 # Project specific queries and global queries
111 # Project specific queries and global queries
108 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
112 visible << (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id])
109 @sidebar_queries = Query.find(:all,
113 @sidebar_queries = Query.find(:all,
110 :select => 'id, name, is_public',
114 :select => 'id, name, is_public',
111 :order => "name ASC",
115 :order => "name ASC",
112 :conditions => visible.conditions)
116 :conditions => visible.conditions)
113 end
117 end
114 @sidebar_queries
118 @sidebar_queries
115 end
119 end
116
120
117 def query_links(title, queries)
121 def query_links(title, queries)
118 # links to #index on issues/show
122 # links to #index on issues/show
119 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
123 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
120
124
121 content_tag('h3', title) +
125 content_tag('h3', title) +
122 queries.collect {|query|
126 queries.collect {|query|
123 link_to(h(query.name), url_params.merge(:query_id => query))
127 link_to(h(query.name), url_params.merge(:query_id => query))
124 }.join('<br />')
128 }.join('<br />')
125 end
129 end
126
130
127 def render_sidebar_queries
131 def render_sidebar_queries
128 out = ''
132 out = ''
129 queries = sidebar_queries.select {|q| !q.is_public?}
133 queries = sidebar_queries.select {|q| !q.is_public?}
130 out << query_links(l(:label_my_queries), queries) if queries.any?
134 out << query_links(l(:label_my_queries), queries) if queries.any?
131 queries = sidebar_queries.select {|q| q.is_public?}
135 queries = sidebar_queries.select {|q| q.is_public?}
132 out << query_links(l(:label_query_plural), queries) if queries.any?
136 out << query_links(l(:label_query_plural), queries) if queries.any?
133 out
137 out
134 end
138 end
135
139
136 def show_detail(detail, no_html=false)
140 def show_detail(detail, no_html=false)
137 case detail.property
141 case detail.property
138 when 'attr'
142 when 'attr'
139 field = detail.prop_key.to_s.gsub(/\_id$/, "")
143 field = detail.prop_key.to_s.gsub(/\_id$/, "")
140 label = l(("field_" + field).to_sym)
144 label = l(("field_" + field).to_sym)
141 case
145 case
142 when ['due_date', 'start_date'].include?(detail.prop_key)
146 when ['due_date', 'start_date'].include?(detail.prop_key)
143 value = format_date(detail.value.to_date) if detail.value
147 value = format_date(detail.value.to_date) if detail.value
144 old_value = format_date(detail.old_value.to_date) if detail.old_value
148 old_value = format_date(detail.old_value.to_date) if detail.old_value
145
149
146 when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
150 when ['project_id', 'status_id', 'tracker_id', 'assigned_to_id', 'priority_id', 'category_id', 'fixed_version_id'].include?(detail.prop_key)
147 value = find_name_by_reflection(field, detail.value)
151 value = find_name_by_reflection(field, detail.value)
148 old_value = find_name_by_reflection(field, detail.old_value)
152 old_value = find_name_by_reflection(field, detail.old_value)
149
153
150 when detail.prop_key == 'estimated_hours'
154 when detail.prop_key == 'estimated_hours'
151 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
155 value = "%0.02f" % detail.value.to_f unless detail.value.blank?
152 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
156 old_value = "%0.02f" % detail.old_value.to_f unless detail.old_value.blank?
153
157
154 when detail.prop_key == 'parent_id'
158 when detail.prop_key == 'parent_id'
155 label = l(:field_parent_issue)
159 label = l(:field_parent_issue)
156 value = "##{detail.value}" unless detail.value.blank?
160 value = "##{detail.value}" unless detail.value.blank?
157 old_value = "##{detail.old_value}" unless detail.old_value.blank?
161 old_value = "##{detail.old_value}" unless detail.old_value.blank?
158 end
162 end
159 when 'cf'
163 when 'cf'
160 custom_field = CustomField.find_by_id(detail.prop_key)
164 custom_field = CustomField.find_by_id(detail.prop_key)
161 if custom_field
165 if custom_field
162 label = custom_field.name
166 label = custom_field.name
163 value = format_value(detail.value, custom_field.field_format) if detail.value
167 value = format_value(detail.value, custom_field.field_format) if detail.value
164 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
168 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
165 end
169 end
166 when 'attachment'
170 when 'attachment'
167 label = l(:label_attachment)
171 label = l(:label_attachment)
168 end
172 end
169 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
173 call_hook(:helper_issues_show_detail_after_setting, {:detail => detail, :label => label, :value => value, :old_value => old_value })
170
174
171 label ||= detail.prop_key
175 label ||= detail.prop_key
172 value ||= detail.value
176 value ||= detail.value
173 old_value ||= detail.old_value
177 old_value ||= detail.old_value
174
178
175 unless no_html
179 unless no_html
176 label = content_tag('strong', label)
180 label = content_tag('strong', label)
177 old_value = content_tag("i", h(old_value)) if detail.old_value
181 old_value = content_tag("i", h(old_value)) if detail.old_value
178 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
182 old_value = content_tag("strike", old_value) if detail.old_value and (!detail.value or detail.value.empty?)
179 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
183 if detail.property == 'attachment' && !value.blank? && a = Attachment.find_by_id(detail.prop_key)
180 # Link to the attachment if it has not been removed
184 # Link to the attachment if it has not been removed
181 value = link_to_attachment(a)
185 value = link_to_attachment(a)
182 else
186 else
183 value = content_tag("i", h(value)) if value
187 value = content_tag("i", h(value)) if value
184 end
188 end
185 end
189 end
186
190
187 if detail.property == 'attr' && detail.prop_key == 'description'
191 if detail.property == 'attr' && detail.prop_key == 'description'
188 s = l(:text_journal_changed_no_detail, :label => label)
192 s = l(:text_journal_changed_no_detail, :label => label)
189 unless no_html
193 unless no_html
190 diff_link = link_to 'diff',
194 diff_link = link_to 'diff',
191 {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
195 {:controller => 'journals', :action => 'diff', :id => detail.journal_id, :detail_id => detail.id},
192 :title => l(:label_view_diff)
196 :title => l(:label_view_diff)
193 s << " (#{ diff_link })"
197 s << " (#{ diff_link })"
194 end
198 end
195 s
199 s
196 elsif !detail.value.blank?
200 elsif !detail.value.blank?
197 case detail.property
201 case detail.property
198 when 'attr', 'cf'
202 when 'attr', 'cf'
199 if !detail.old_value.blank?
203 if !detail.old_value.blank?
200 l(:text_journal_changed, :label => label, :old => old_value, :new => value)
204 l(:text_journal_changed, :label => label, :old => old_value, :new => value)
201 else
205 else
202 l(:text_journal_set_to, :label => label, :value => value)
206 l(:text_journal_set_to, :label => label, :value => value)
203 end
207 end
204 when 'attachment'
208 when 'attachment'
205 l(:text_journal_added, :label => label, :value => value)
209 l(:text_journal_added, :label => label, :value => value)
206 end
210 end
207 else
211 else
208 l(:text_journal_deleted, :label => label, :old => old_value)
212 l(:text_journal_deleted, :label => label, :old => old_value)
209 end
213 end
210 end
214 end
211
215
212 # Find the name of an associated record stored in the field attribute
216 # Find the name of an associated record stored in the field attribute
213 def find_name_by_reflection(field, id)
217 def find_name_by_reflection(field, id)
214 association = Issue.reflect_on_association(field.to_sym)
218 association = Issue.reflect_on_association(field.to_sym)
215 if association
219 if association
216 record = association.class_name.constantize.find_by_id(id)
220 record = association.class_name.constantize.find_by_id(id)
217 return record.name if record
221 return record.name if record
218 end
222 end
219 end
223 end
220
224
221 # Renders issue children recursively
225 # Renders issue children recursively
222 def render_api_issue_children(issue, api)
226 def render_api_issue_children(issue, api)
223 return if issue.leaf?
227 return if issue.leaf?
224 api.array :children do
228 api.array :children do
225 issue.children.each do |child|
229 issue.children.each do |child|
226 api.issue(:id => child.id) do
230 api.issue(:id => child.id) do
227 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
231 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
228 api.subject child.subject
232 api.subject child.subject
229 render_api_issue_children(child, api)
233 render_api_issue_children(child, api)
230 end
234 end
231 end
235 end
232 end
236 end
233 end
237 end
234
238
235 def issues_to_csv(issues, project = nil)
239 def issues_to_csv(issues, project = nil)
236 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
240 ic = Iconv.new(l(:general_csv_encoding), 'UTF-8')
237 decimal_separator = l(:general_csv_decimal_separator)
241 decimal_separator = l(:general_csv_decimal_separator)
238 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
242 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
239 # csv header fields
243 # csv header fields
240 headers = [ "#",
244 headers = [ "#",
241 l(:field_status),
245 l(:field_status),
242 l(:field_project),
246 l(:field_project),
243 l(:field_tracker),
247 l(:field_tracker),
244 l(:field_priority),
248 l(:field_priority),
245 l(:field_subject),
249 l(:field_subject),
246 l(:field_assigned_to),
250 l(:field_assigned_to),
247 l(:field_category),
251 l(:field_category),
248 l(:field_fixed_version),
252 l(:field_fixed_version),
249 l(:field_author),
253 l(:field_author),
250 l(:field_start_date),
254 l(:field_start_date),
251 l(:field_due_date),
255 l(:field_due_date),
252 l(:field_done_ratio),
256 l(:field_done_ratio),
253 l(:field_estimated_hours),
257 l(:field_estimated_hours),
254 l(:field_parent_issue),
258 l(:field_parent_issue),
255 l(:field_created_on),
259 l(:field_created_on),
256 l(:field_updated_on)
260 l(:field_updated_on)
257 ]
261 ]
258 # Export project custom fields if project is given
262 # Export project custom fields if project is given
259 # otherwise export custom fields marked as "For all projects"
263 # otherwise export custom fields marked as "For all projects"
260 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
264 custom_fields = project.nil? ? IssueCustomField.for_all : project.all_issue_custom_fields
261 custom_fields.each {|f| headers << f.name}
265 custom_fields.each {|f| headers << f.name}
262 # Description in the last column
266 # Description in the last column
263 headers << l(:field_description)
267 headers << l(:field_description)
264 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
268 csv << headers.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
265 # csv lines
269 # csv lines
266 issues.each do |issue|
270 issues.each do |issue|
267 fields = [issue.id,
271 fields = [issue.id,
268 issue.status.name,
272 issue.status.name,
269 issue.project.name,
273 issue.project.name,
270 issue.tracker.name,
274 issue.tracker.name,
271 issue.priority.name,
275 issue.priority.name,
272 issue.subject,
276 issue.subject,
273 issue.assigned_to,
277 issue.assigned_to,
274 issue.category,
278 issue.category,
275 issue.fixed_version,
279 issue.fixed_version,
276 issue.author.name,
280 issue.author.name,
277 format_date(issue.start_date),
281 format_date(issue.start_date),
278 format_date(issue.due_date),
282 format_date(issue.due_date),
279 issue.done_ratio,
283 issue.done_ratio,
280 issue.estimated_hours.to_s.gsub('.', decimal_separator),
284 issue.estimated_hours.to_s.gsub('.', decimal_separator),
281 issue.parent_id,
285 issue.parent_id,
282 format_time(issue.created_on),
286 format_time(issue.created_on),
283 format_time(issue.updated_on)
287 format_time(issue.updated_on)
284 ]
288 ]
285 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
289 custom_fields.each {|f| fields << show_value(issue.custom_value_for(f)) }
286 fields << issue.description
290 fields << issue.description
287 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
291 csv << fields.collect {|c| begin; ic.iconv(c.to_s); rescue; c.to_s; end }
288 end
292 end
289 end
293 end
290 export
294 export
291 end
295 end
292 end
296 end
@@ -1,137 +1,137
1 <%= render :partial => 'action_menu' %>
1 <%= render :partial => 'action_menu' %>
2
2
3 <h2><%= @issue.tracker.name %> #<%= @issue.id %></h2>
3 <h2><%= issue_heading(@issue) %></h2>
4
4
5 <div class="<%= @issue.css_classes %> details">
5 <div class="<%= @issue.css_classes %> details">
6 <%= avatar(@issue.author, :size => "50") %>
6 <%= avatar(@issue.author, :size => "50") %>
7
7
8 <div class="subject">
8 <div class="subject">
9 <%= render_issue_subject_with_tree(@issue) %>
9 <%= render_issue_subject_with_tree(@issue) %>
10 </div>
10 </div>
11 <p class="author">
11 <p class="author">
12 <%= authoring @issue.created_on, @issue.author %>.
12 <%= authoring @issue.created_on, @issue.author %>.
13 <% if @issue.created_on != @issue.updated_on %>
13 <% if @issue.created_on != @issue.updated_on %>
14 <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
14 <%= l(:label_updated_time, time_tag(@issue.updated_on)) %>.
15 <% end %>
15 <% end %>
16 </p>
16 </p>
17
17
18 <table class="attributes">
18 <table class="attributes">
19 <tr>
19 <tr>
20 <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
20 <th class="status"><%=l(:field_status)%>:</th><td class="status"><%= @issue.status.name %></td>
21 <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
21 <th class="start-date"><%=l(:field_start_date)%>:</th><td class="start-date"><%= format_date(@issue.start_date) %></td>
22 </tr>
22 </tr>
23 <tr>
23 <tr>
24 <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
24 <th class="priority"><%=l(:field_priority)%>:</th><td class="priority"><%= @issue.priority.name %></td>
25 <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
25 <th class="due-date"><%=l(:field_due_date)%>:</th><td class="due-date"><%= format_date(@issue.due_date) %></td>
26 </tr>
26 </tr>
27 <tr>
27 <tr>
28 <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
28 <th class="assigned-to"><%=l(:field_assigned_to)%>:</th><td class="assigned-to"><%= avatar(@issue.assigned_to, :size => "14") %><%= @issue.assigned_to ? link_to_user(@issue.assigned_to) : "-" %></td>
29 <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
29 <th class="progress"><%=l(:field_done_ratio)%>:</th><td class="progress"><%= progress_bar @issue.done_ratio, :width => '80px', :legend => "#{@issue.done_ratio}%" %></td>
30 </tr>
30 </tr>
31 <tr>
31 <tr>
32 <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
32 <th class="category"><%=l(:field_category)%>:</th><td class="category"><%=h @issue.category ? @issue.category.name : "-" %></td>
33 <% if User.current.allowed_to?(:view_time_entries, @project) %>
33 <% if User.current.allowed_to?(:view_time_entries, @project) %>
34 <th class="spent-time"><%=l(:label_spent_time)%>:</th>
34 <th class="spent-time"><%=l(:label_spent_time)%>:</th>
35 <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
35 <td class="spent-time"><%= @issue.spent_hours > 0 ? (link_to l_hours(@issue.spent_hours), {:controller => 'timelog', :action => 'index', :project_id => @project, :issue_id => @issue}) : "-" %></td>
36 <% end %>
36 <% end %>
37 </tr>
37 </tr>
38 <tr>
38 <tr>
39 <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
39 <th class="fixed-version"><%=l(:field_fixed_version)%>:</th><td class="fixed-version"><%= @issue.fixed_version ? link_to_version(@issue.fixed_version) : "-" %></td>
40 <% if @issue.estimated_hours %>
40 <% if @issue.estimated_hours %>
41 <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
41 <th class="estimated-hours"><%=l(:field_estimated_hours)%>:</th><td class="estimated-hours"><%= l_hours(@issue.estimated_hours) %></td>
42 <% end %>
42 <% end %>
43 </tr>
43 </tr>
44 <%= render_custom_fields_rows(@issue) %>
44 <%= render_custom_fields_rows(@issue) %>
45 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
45 <%= call_hook(:view_issues_show_details_bottom, :issue => @issue) %>
46 </table>
46 </table>
47
47
48 <% if @issue.description? || @issue.attachments.any? -%>
48 <% if @issue.description? || @issue.attachments.any? -%>
49 <hr />
49 <hr />
50 <% if @issue.description? %>
50 <% if @issue.description? %>
51 <div class="contextual">
51 <div class="contextual">
52 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
52 <%= link_to_remote_if_authorized(l(:button_quote), { :url => {:controller => 'journals', :action => 'new', :id => @issue} }, :class => 'icon icon-comment') %>
53 </div>
53 </div>
54
54
55 <p><strong><%=l(:field_description)%></strong></p>
55 <p><strong><%=l(:field_description)%></strong></p>
56 <div class="wiki">
56 <div class="wiki">
57 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
57 <%= textilizable @issue, :description, :attachments => @issue.attachments %>
58 </div>
58 </div>
59 <% end %>
59 <% end %>
60 <%= link_to_attachments @issue %>
60 <%= link_to_attachments @issue %>
61 <% end -%>
61 <% end -%>
62
62
63 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
63 <%= call_hook(:view_issues_show_description_bottom, :issue => @issue) %>
64
64
65 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
65 <% if !@issue.leaf? || User.current.allowed_to?(:manage_subtasks, @project) %>
66 <hr />
66 <hr />
67 <div id="issue_tree">
67 <div id="issue_tree">
68 <div class="contextual">
68 <div class="contextual">
69 <%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
69 <%= link_to(l(:button_add), {:controller => 'issues', :action => 'new', :project_id => @project, :issue => {:parent_issue_id => @issue}}) if User.current.allowed_to?(:manage_subtasks, @project) %>
70 </div>
70 </div>
71 <p><strong><%=l(:label_subtask_plural)%></strong></p>
71 <p><strong><%=l(:label_subtask_plural)%></strong></p>
72 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
72 <%= render_descendants_tree(@issue) unless @issue.leaf? %>
73 </div>
73 </div>
74 <% end %>
74 <% end %>
75
75
76 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
76 <% if @relations.present? || User.current.allowed_to?(:manage_issue_relations, @project) %>
77 <hr />
77 <hr />
78 <div id="relations">
78 <div id="relations">
79 <%= render :partial => 'relations' %>
79 <%= render :partial => 'relations' %>
80 </div>
80 </div>
81 <% end %>
81 <% end %>
82
82
83 </div>
83 </div>
84
84
85 <% if @changesets.present? %>
85 <% if @changesets.present? %>
86 <div id="issue-changesets">
86 <div id="issue-changesets">
87 <h3><%=l(:label_associated_revisions)%></h3>
87 <h3><%=l(:label_associated_revisions)%></h3>
88 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
88 <%= render :partial => 'changesets', :locals => { :changesets => @changesets} %>
89 </div>
89 </div>
90 <% end %>
90 <% end %>
91
91
92 <% if @journals.present? %>
92 <% if @journals.present? %>
93 <div id="history">
93 <div id="history">
94 <h3><%=l(:label_history)%></h3>
94 <h3><%=l(:label_history)%></h3>
95 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
95 <%= render :partial => 'history', :locals => { :issue => @issue, :journals => @journals } %>
96 </div>
96 </div>
97 <% end %>
97 <% end %>
98
98
99
99
100 <div style="clear: both;"></div>
100 <div style="clear: both;"></div>
101 <%= render :partial => 'action_menu' %>
101 <%= render :partial => 'action_menu' %>
102
102
103 <div style="clear: both;"></div>
103 <div style="clear: both;"></div>
104 <% if authorize_for('issues', 'edit') %>
104 <% if authorize_for('issues', 'edit') %>
105 <div id="update" style="display:none;">
105 <div id="update" style="display:none;">
106 <h3><%= l(:button_update) %></h3>
106 <h3><%= l(:button_update) %></h3>
107 <%= render :partial => 'edit' %>
107 <%= render :partial => 'edit' %>
108 </div>
108 </div>
109 <% end %>
109 <% end %>
110
110
111 <% other_formats_links do |f| %>
111 <% other_formats_links do |f| %>
112 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
112 <%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
113 <%= f.link_to 'PDF' %>
113 <%= f.link_to 'PDF' %>
114 <% end %>
114 <% end %>
115
115
116 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
116 <% html_title "#{@issue.tracker.name} ##{@issue.id}: #{@issue.subject}" %>
117
117
118 <% content_for :sidebar do %>
118 <% content_for :sidebar do %>
119 <%= render :partial => 'issues/sidebar' %>
119 <%= render :partial => 'issues/sidebar' %>
120
120
121 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
121 <% if User.current.allowed_to?(:add_issue_watchers, @project) ||
122 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
122 (@issue.watchers.present? && User.current.allowed_to?(:view_issue_watchers, @project)) %>
123 <div id="watchers">
123 <div id="watchers">
124 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
124 <%= render :partial => 'watchers/watchers', :locals => {:watched => @issue} %>
125 </div>
125 </div>
126 <% end %>
126 <% end %>
127 <% end %>
127 <% end %>
128
128
129 <% content_for :header_tags do %>
129 <% content_for :header_tags do %>
130 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
130 <%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@issue.project} - #{@issue.tracker} ##{@issue.id}: #{@issue.subject}") %>
131 <%= stylesheet_link_tag 'scm' %>
131 <%= stylesheet_link_tag 'scm' %>
132 <%= javascript_include_tag 'context_menu' %>
132 <%= javascript_include_tag 'context_menu' %>
133 <%= stylesheet_link_tag 'context_menu' %>
133 <%= stylesheet_link_tag 'context_menu' %>
134 <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
134 <%= stylesheet_link_tag 'context_menu_rtl' if l(:direction) == 'rtl' %>
135 <% end %>
135 <% end %>
136 <div id="context-menu" style="display: none;"></div>
136 <div id="context-menu" style="display: none;"></div>
137 <%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>
137 <%= javascript_tag "new ContextMenu('#{issues_context_menu_path}')" %>
@@ -1,159 +1,163
1 # Redmine - project management software
1 # Redmine - project management software
2 # Copyright (C) 2006-2010 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 IssuesHelperTest < HelperTestCase
20 class IssuesHelperTest < HelperTestCase
21 include ApplicationHelper
21 include ApplicationHelper
22 include IssuesHelper
22 include IssuesHelper
23
23
24 include ActionController::Assertions::SelectorAssertions
24 include ActionController::Assertions::SelectorAssertions
25 fixtures :all
25 fixtures :all
26
26
27 # Used by assert_select
27 # Used by assert_select
28 def html_document
28 def html_document
29 HTML::Document.new(@response.body)
29 HTML::Document.new(@response.body)
30 end
30 end
31
31
32 def setup
32 def setup
33 super
33 super
34 set_language_if_valid('en')
34 set_language_if_valid('en')
35 User.current = nil
35 User.current = nil
36 @response = ActionController::TestResponse.new
36 @response = ActionController::TestResponse.new
37 end
37 end
38
38
39 def controller
39 def controller
40 @controller ||= IssuesController.new
40 @controller ||= IssuesController.new
41 end
41 end
42
42
43 def request
43 def request
44 @request ||= ActionController::TestRequest.new
44 @request ||= ActionController::TestRequest.new
45 end
45 end
46
47 def test_issue_heading
48 assert_equal "Bug #1", issue_heading(Issue.find(1))
49 end
46
50
47 context "IssuesHelper#show_detail" do
51 context "IssuesHelper#show_detail" do
48 context "with no_html" do
52 context "with no_html" do
49 should 'show a changing attribute' do
53 should 'show a changing attribute' do
50 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
54 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
51 assert_equal "% Done changed from 40 to 100", show_detail(@detail, true)
55 assert_equal "% Done changed from 40 to 100", show_detail(@detail, true)
52 end
56 end
53
57
54 should 'show a new attribute' do
58 should 'show a new attribute' do
55 @detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
59 @detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
56 assert_equal "% Done set to 100", show_detail(@detail, true)
60 assert_equal "% Done set to 100", show_detail(@detail, true)
57 end
61 end
58
62
59 should 'show a deleted attribute' do
63 should 'show a deleted attribute' do
60 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
64 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
61 assert_equal "% Done deleted (50)", show_detail(@detail, true)
65 assert_equal "% Done deleted (50)", show_detail(@detail, true)
62 end
66 end
63 end
67 end
64
68
65 context "with html" do
69 context "with html" do
66 should 'show a changing attribute with HTML highlights' do
70 should 'show a changing attribute with HTML highlights' do
67 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
71 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '40', :value => '100', :prop_key => 'done_ratio')
68 @response.body = show_detail(@detail, false)
72 @response.body = show_detail(@detail, false)
69
73
70 assert_select 'strong', :text => '% Done'
74 assert_select 'strong', :text => '% Done'
71 assert_select 'i', :text => '40'
75 assert_select 'i', :text => '40'
72 assert_select 'i', :text => '100'
76 assert_select 'i', :text => '100'
73 end
77 end
74
78
75 should 'show a new attribute with HTML highlights' do
79 should 'show a new attribute with HTML highlights' do
76 @detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
80 @detail = JournalDetail.generate!(:property => 'attr', :old_value => nil, :value => '100', :prop_key => 'done_ratio')
77 @response.body = show_detail(@detail, false)
81 @response.body = show_detail(@detail, false)
78
82
79 assert_select 'strong', :text => '% Done'
83 assert_select 'strong', :text => '% Done'
80 assert_select 'i', :text => '100'
84 assert_select 'i', :text => '100'
81 end
85 end
82
86
83 should 'show a deleted attribute with HTML highlights' do
87 should 'show a deleted attribute with HTML highlights' do
84 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
88 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '50', :value => nil, :prop_key => 'done_ratio')
85 @response.body = show_detail(@detail, false)
89 @response.body = show_detail(@detail, false)
86
90
87 assert_select 'strong', :text => '% Done'
91 assert_select 'strong', :text => '% Done'
88 assert_select 'strike' do
92 assert_select 'strike' do
89 assert_select 'i', :text => '50'
93 assert_select 'i', :text => '50'
90 end
94 end
91 end
95 end
92 end
96 end
93
97
94 context "with a start_date attribute" do
98 context "with a start_date attribute" do
95 should "format the current date" do
99 should "format the current date" do
96 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date')
100 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date')
97 assert_match "01/31/2010", show_detail(@detail, true)
101 assert_match "01/31/2010", show_detail(@detail, true)
98 end
102 end
99
103
100 should "format the old date" do
104 should "format the old date" do
101 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date')
105 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'start_date')
102 assert_match "01/01/2010", show_detail(@detail, true)
106 assert_match "01/01/2010", show_detail(@detail, true)
103 end
107 end
104 end
108 end
105
109
106 context "with a due_date attribute" do
110 context "with a due_date attribute" do
107 should "format the current date" do
111 should "format the current date" do
108 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date')
112 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date')
109 assert_match "01/31/2010", show_detail(@detail, true)
113 assert_match "01/31/2010", show_detail(@detail, true)
110 end
114 end
111
115
112 should "format the old date" do
116 should "format the old date" do
113 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date')
117 @detail = JournalDetail.generate!(:property => 'attr', :old_value => '2010-01-01', :value => '2010-01-31', :prop_key => 'due_date')
114 assert_match "01/01/2010", show_detail(@detail, true)
118 assert_match "01/01/2010", show_detail(@detail, true)
115 end
119 end
116 end
120 end
117
121
118 context "with a project attribute" do
122 context "with a project attribute" do
119 should_show_the_old_and_new_values_for('project_id', Project)
123 should_show_the_old_and_new_values_for('project_id', Project)
120 end
124 end
121
125
122 context "with a issue status attribute" do
126 context "with a issue status attribute" do
123 should_show_the_old_and_new_values_for('status_id', IssueStatus)
127 should_show_the_old_and_new_values_for('status_id', IssueStatus)
124 end
128 end
125
129
126 context "with a tracker attribute" do
130 context "with a tracker attribute" do
127 should_show_the_old_and_new_values_for('tracker_id', Tracker)
131 should_show_the_old_and_new_values_for('tracker_id', Tracker)
128 end
132 end
129
133
130 context "with a assigned to attribute" do
134 context "with a assigned to attribute" do
131 should_show_the_old_and_new_values_for('assigned_to_id', User)
135 should_show_the_old_and_new_values_for('assigned_to_id', User)
132 end
136 end
133
137
134 context "with a priority attribute" do
138 context "with a priority attribute" do
135 should_show_the_old_and_new_values_for('priority_id', IssuePriority) do
139 should_show_the_old_and_new_values_for('priority_id', IssuePriority) do
136 @old_value = IssuePriority.generate!(:type => 'IssuePriority')
140 @old_value = IssuePriority.generate!(:type => 'IssuePriority')
137 @new_value = IssuePriority.generate!(:type => 'IssuePriority')
141 @new_value = IssuePriority.generate!(:type => 'IssuePriority')
138 end
142 end
139 end
143 end
140
144
141 context "with a category attribute" do
145 context "with a category attribute" do
142 should_show_the_old_and_new_values_for('category_id', IssueCategory)
146 should_show_the_old_and_new_values_for('category_id', IssueCategory)
143 end
147 end
144
148
145 context "with a fixed version attribute" do
149 context "with a fixed version attribute" do
146 should_show_the_old_and_new_values_for('fixed_version_id', Version)
150 should_show_the_old_and_new_values_for('fixed_version_id', Version)
147 end
151 end
148
152
149 context "with a estimated hours attribute" do
153 context "with a estimated hours attribute" do
150 should "format the time into two decimal places"
154 should "format the time into two decimal places"
151 should "format the old time into two decimal places"
155 should "format the old time into two decimal places"
152 end
156 end
153
157
154 should "test custom fields"
158 should "test custom fields"
155 should "test attachments"
159 should "test attachments"
156
160
157 end
161 end
158
162
159 end
163 end
General Comments 0
You need to be logged in to leave comments. Login now