comparison app/helpers/issues_helper.rb @ 1464:261b3d9a4903 redmine-2.4

Update to Redmine 2.4 branch rev 12663
author Chris Cannam
date Tue, 14 Jan 2014 14:37:42 +0000
parents 3e4c3460b6ca
children e248c7af89ec
comparison
equal deleted inserted replaced
1296:038ba2d95de8 1464:261b3d9a4903
1 # encoding: utf-8 1 # encoding: utf-8
2 # 2 #
3 # Redmine - project management software 3 # Redmine - project management software
4 # Copyright (C) 2006-2012 Jean-Philippe Lang 4 # Copyright (C) 2006-2013 Jean-Philippe Lang
5 # 5 #
6 # This program is free software; you can redistribute it and/or 6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License 7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2 8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version. 9 # of the License, or (at your option) any later version.
92 end 92 end
93 s << '</table></form>' 93 s << '</table></form>'
94 s.html_safe 94 s.html_safe
95 end 95 end
96 96
97 # Returns an array of error messages for bulk edited issues
98 def bulk_edit_error_messages(issues)
99 messages = {}
100 issues.each do |issue|
101 issue.errors.full_messages.each do |message|
102 messages[message] ||= []
103 messages[message] << issue
104 end
105 end
106 messages.map { |message, issues|
107 "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
108 }
109 end
110
97 # Returns a link for adding a new subtask to the given issue 111 # Returns a link for adding a new subtask to the given issue
98 def link_to_new_subtask(issue) 112 def link_to_new_subtask(issue)
99 attrs = { 113 attrs = {
100 :tracker_id => issue.tracker, 114 :tracker_id => issue.tracker,
101 :parent_issue_id => issue 115 :parent_issue_id => issue
144 yield r 158 yield r
145 r.to_html 159 r.to_html
146 end 160 end
147 161
148 def render_custom_fields_rows(issue) 162 def render_custom_fields_rows(issue)
149 return if issue.custom_field_values.empty? 163 values = issue.visible_custom_field_values
164 return if values.empty?
150 ordered_values = [] 165 ordered_values = []
151 half = (issue.custom_field_values.size / 2.0).ceil 166 half = (values.size / 2.0).ceil
152 half.times do |i| 167 half.times do |i|
153 ordered_values << issue.custom_field_values[i] 168 ordered_values << values[i]
154 ordered_values << issue.custom_field_values[i + half] 169 ordered_values << values[i + half]
155 end 170 end
156 s = "<tr>\n" 171 s = "<tr>\n"
157 n = 0 172 n = 0
158 ordered_values.compact.each do |value| 173 ordered_values.compact.each do |value|
159 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0 174 s << "</tr>\n<tr>\n" if n > 0 && (n % 2) == 0
182 message 197 message
183 end 198 end
184 199
185 def sidebar_queries 200 def sidebar_queries
186 unless @sidebar_queries 201 unless @sidebar_queries
187 @sidebar_queries = Query.visible.all( 202 @sidebar_queries = IssueQuery.visible.
188 :order => "#{Query.table_name}.name ASC", 203 order("#{Query.table_name}.name ASC").
189 # Project specific queries and global queries 204 # Project specific queries and global queries
190 :conditions => (@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]) 205 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
191 ) 206 all
192 end 207 end
193 @sidebar_queries 208 @sidebar_queries
194 end 209 end
195 210
196 def query_links(title, queries) 211 def query_links(title, queries)
212 return '' if queries.empty?
197 # links to #index on issues/show 213 # links to #index on issues/show
198 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params 214 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : params
199 215
200 content_tag('h3', h(title)) + 216 content_tag('h3', title) + "\n" +
201 queries.collect {|query| 217 content_tag('ul',
202 css = 'query' 218 queries.collect {|query|
203 css << ' selected' if query == @query 219 css = 'query'
204 link_to(h(query.name), url_params.merge(:query_id => query), :class => css) 220 css << ' selected' if query == @query
205 }.join('<br />').html_safe 221 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
222 }.join("\n").html_safe,
223 :class => 'queries'
224 ) + "\n"
206 end 225 end
207 226
208 def render_sidebar_queries 227 def render_sidebar_queries
209 out = ''.html_safe 228 out = ''.html_safe
210 queries = sidebar_queries.select {|q| !q.is_public?} 229 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
211 out << query_links(l(:label_my_queries), queries) if queries.any? 230 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
212 queries = sidebar_queries.select {|q| q.is_public?}
213 out << query_links(l(:label_query_plural), queries) if queries.any?
214 out 231 out
232 end
233
234 def email_issue_attributes(issue, user)
235 items = []
236 %w(author status priority assigned_to category fixed_version).each do |attribute|
237 unless issue.disabled_core_fields.include?(attribute+"_id")
238 items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
239 end
240 end
241 issue.visible_custom_field_values(user).each do |value|
242 items << "#{value.custom_field.name}: #{show_value(value)}"
243 end
244 items
245 end
246
247 def render_email_issue_attributes(issue, user, html=false)
248 items = email_issue_attributes(issue, user)
249 if html
250 content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
251 else
252 items.map{|s| "* #{s}"}.join("\n")
253 end
215 end 254 end
216 255
217 # Returns the textual representation of a journal details 256 # Returns the textual representation of a journal details
218 # as an array of strings 257 # as an array of strings
219 def details_to_strings(details, no_html=false, options={}) 258 def details_to_strings(details, no_html=false, options={})
220 options[:only_path] = (options[:only_path] == false ? false : true) 259 options[:only_path] = (options[:only_path] == false ? false : true)
221 strings = [] 260 strings = []
222 values_by_field = {} 261 values_by_field = {}
223 details.each do |detail| 262 details.each do |detail|
224 if detail.property == 'cf' 263 if detail.property == 'cf'
225 field_id = detail.prop_key 264 field = detail.custom_field
226 field = CustomField.find_by_id(field_id)
227 if field && field.multiple? 265 if field && field.multiple?
228 values_by_field[field_id] ||= {:added => [], :deleted => []} 266 values_by_field[field] ||= {:added => [], :deleted => []}
229 if detail.old_value 267 if detail.old_value
230 values_by_field[field_id][:deleted] << detail.old_value 268 values_by_field[field][:deleted] << detail.old_value
231 end 269 end
232 if detail.value 270 if detail.value
233 values_by_field[field_id][:added] << detail.value 271 values_by_field[field][:added] << detail.value
234 end 272 end
235 next 273 next
236 end 274 end
237 end 275 end
238 strings << show_detail(detail, no_html, options) 276 strings << show_detail(detail, no_html, options)
239 end 277 end
240 values_by_field.each do |field_id, changes| 278 values_by_field.each do |field, changes|
241 detail = JournalDetail.new(:property => 'cf', :prop_key => field_id) 279 detail = JournalDetail.new(:property => 'cf', :prop_key => field.id.to_s)
280 detail.instance_variable_set "@custom_field", field
242 if changes[:added].any? 281 if changes[:added].any?
243 detail.value = changes[:added] 282 detail.value = changes[:added]
244 strings << show_detail(detail, no_html, options) 283 strings << show_detail(detail, no_html, options)
245 elsif changes[:deleted].any? 284 elsif changes[:deleted].any?
246 detail.old_value = changes[:deleted] 285 detail.old_value = changes[:deleted]
279 when 'is_private' 318 when 'is_private'
280 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank? 319 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
281 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank? 320 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
282 end 321 end
283 when 'cf' 322 when 'cf'
284 custom_field = CustomField.find_by_id(detail.prop_key) 323 custom_field = detail.custom_field
285 if custom_field 324 if custom_field
286 multiple = custom_field.multiple? 325 multiple = custom_field.multiple?
287 label = custom_field.name 326 label = custom_field.name
288 value = format_value(detail.value, custom_field.field_format) if detail.value 327 value = format_value(detail.value, custom_field.field_format) if detail.value
289 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value 328 old_value = format_value(detail.old_value, custom_field.field_format) if detail.old_value
290 end 329 end
291 when 'attachment' 330 when 'attachment'
292 label = l(:label_attachment) 331 label = l(:label_attachment)
332 when 'relation'
333 if detail.value && !detail.old_value
334 rel_issue = Issue.visible.find_by_id(detail.value)
335 value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
336 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
337 elsif detail.old_value && !detail.value
338 rel_issue = Issue.visible.find_by_id(detail.old_value)
339 old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
340 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
341 end
342 label = l(detail.prop_key.to_sym)
293 end 343 end
294 call_hook(:helper_issues_show_detail_after_setting, 344 call_hook(:helper_issues_show_detail_after_setting,
295 {:detail => detail, :label => label, :value => value, :old_value => old_value }) 345 {:detail => detail, :label => label, :value => value, :old_value => old_value })
296 346
297 label ||= detail.prop_key 347 label ||= detail.prop_key
299 old_value ||= detail.old_value 349 old_value ||= detail.old_value
300 350
301 unless no_html 351 unless no_html
302 label = content_tag('strong', label) 352 label = content_tag('strong', label)
303 old_value = content_tag("i", h(old_value)) if detail.old_value 353 old_value = content_tag("i", h(old_value)) if detail.old_value
304 old_value = content_tag("del", old_value) if detail.old_value and detail.value.blank? 354 if detail.old_value && detail.value.blank? && detail.property != 'relation'
355 old_value = content_tag("del", old_value)
356 end
305 if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key) 357 if detail.property == 'attachment' && !value.blank? && atta = Attachment.find_by_id(detail.prop_key)
306 # Link to the attachment if it has not been removed 358 # Link to the attachment if it has not been removed
307 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path]) 359 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
308 if options[:only_path] != false && atta.is_text? 360 if options[:only_path] != false && atta.is_text?
309 value += link_to( 361 value += link_to(
335 elsif multiple 387 elsif multiple
336 l(:text_journal_added, :label => label, :value => value).html_safe 388 l(:text_journal_added, :label => label, :value => value).html_safe
337 else 389 else
338 l(:text_journal_set_to, :label => label, :value => value).html_safe 390 l(:text_journal_set_to, :label => label, :value => value).html_safe
339 end 391 end
340 when 'attachment' 392 when 'attachment', 'relation'
341 l(:text_journal_added, :label => label, :value => value).html_safe 393 l(:text_journal_added, :label => label, :value => value).html_safe
342 end 394 end
343 else 395 else
344 l(:text_journal_deleted, :label => label, :old => old_value).html_safe 396 l(:text_journal_deleted, :label => label, :old => old_value).html_safe
345 end 397 end
346 end 398 end
347 399
348 # Find the name of an associated record stored in the field attribute 400 # Find the name of an associated record stored in the field attribute
349 def find_name_by_reflection(field, id) 401 def find_name_by_reflection(field, id)
402 unless id.present?
403 return nil
404 end
350 association = Issue.reflect_on_association(field.to_sym) 405 association = Issue.reflect_on_association(field.to_sym)
351 if association 406 if association
352 record = association.class_name.constantize.find_by_id(id) 407 record = association.class_name.constantize.find_by_id(id)
353 if record 408 if record
354 record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding) 409 record.name.force_encoding('UTF-8') if record.name.respond_to?(:force_encoding)
368 render_api_issue_children(child, api) 423 render_api_issue_children(child, api)
369 end 424 end
370 end 425 end
371 end 426 end
372 end 427 end
373
374 def issues_to_csv(issues, project, query, options={})
375 decimal_separator = l(:general_csv_decimal_separator)
376 encoding = l(:general_csv_encoding)
377 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
378 if options[:description]
379 if description = query.available_columns.detect {|q| q.name == :description}
380 columns << description
381 end
382 end
383
384 export = FCSV.generate(:col_sep => l(:general_csv_separator)) do |csv|
385 # csv header fields
386 csv << [ "#" ] + columns.collect {|c| Redmine::CodesetUtil.from_utf8(c.caption.to_s, encoding) }
387
388 # csv lines
389 issues.each do |issue|
390 col_values = columns.collect do |column|
391 s = if column.is_a?(QueryCustomFieldColumn)
392 cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
393 show_value(cv)
394 else
395 value = column.value(issue)
396 if value.is_a?(Date)
397 format_date(value)
398 elsif value.is_a?(Time)
399 format_time(value)
400 elsif value.is_a?(Float)
401 ("%.2f" % value).gsub('.', decimal_separator)
402 else
403 value
404 end
405 end
406 s.to_s
407 end
408 csv << [ issue.id.to_s ] + col_values.collect {|c| Redmine::CodesetUtil.from_utf8(c.to_s, encoding) }
409 end
410 end
411 export
412 end
413 end 428 end