##// END OF EJS Templates
Moves sidebar queries rendering to QueriesHelper (#14790)....
Jean-Philippe Lang -
r15258:b5d2ddedfa5e
parent child
Show More
@@ -1,526 +1,492
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module IssuesHelper
21 21 include ApplicationHelper
22 22 include Redmine::Export::PDF::IssuesPdfHelper
23 23
24 24 def issue_list(issues, &block)
25 25 ancestors = []
26 26 issues.each do |issue|
27 27 while (ancestors.any? && !issue.is_descendant_of?(ancestors.last))
28 28 ancestors.pop
29 29 end
30 30 yield issue, ancestors.size
31 31 ancestors << issue unless issue.leaf?
32 32 end
33 33 end
34 34
35 35 def grouped_issue_list(issues, query, issue_count_by_group, &block)
36 36 previous_group, first = false, true
37 37 totals_by_group = query.totalable_columns.inject({}) do |h, column|
38 38 h[column] = query.total_by_group_for(column)
39 39 h
40 40 end
41 41 issue_list(issues) do |issue, level|
42 42 group_name = group_count = nil
43 43 if query.grouped?
44 44 group = query.group_by_column.value(issue)
45 45 if first || group != previous_group
46 46 if group.blank? && group != false
47 47 group_name = "(#{l(:label_blank_value)})"
48 48 else
49 49 group_name = format_object(group)
50 50 end
51 51 group_name ||= ""
52 52 group_count = issue_count_by_group[group]
53 53 group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
54 54 end
55 55 end
56 56 yield issue, level, group_name, group_count, group_totals
57 57 previous_group, first = group, false
58 58 end
59 59 end
60 60
61 61 # Renders a HTML/CSS tooltip
62 62 #
63 63 # To use, a trigger div is needed. This is a div with the class of "tooltip"
64 64 # that contains this method wrapped in a span with the class of "tip"
65 65 #
66 66 # <div class="tooltip"><%= link_to_issue(issue) %>
67 67 # <span class="tip"><%= render_issue_tooltip(issue) %></span>
68 68 # </div>
69 69 #
70 70 def render_issue_tooltip(issue)
71 71 @cached_label_status ||= l(:field_status)
72 72 @cached_label_start_date ||= l(:field_start_date)
73 73 @cached_label_due_date ||= l(:field_due_date)
74 74 @cached_label_assigned_to ||= l(:field_assigned_to)
75 75 @cached_label_priority ||= l(:field_priority)
76 76 @cached_label_project ||= l(:field_project)
77 77
78 78 link_to_issue(issue) + "<br /><br />".html_safe +
79 79 "<strong>#{@cached_label_project}</strong>: #{link_to_project(issue.project)}<br />".html_safe +
80 80 "<strong>#{@cached_label_status}</strong>: #{h(issue.status.name)}<br />".html_safe +
81 81 "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />".html_safe +
82 82 "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />".html_safe +
83 83 "<strong>#{@cached_label_assigned_to}</strong>: #{h(issue.assigned_to)}<br />".html_safe +
84 84 "<strong>#{@cached_label_priority}</strong>: #{h(issue.priority.name)}".html_safe
85 85 end
86 86
87 87 def issue_heading(issue)
88 88 h("#{issue.tracker} ##{issue.id}")
89 89 end
90 90
91 91 def render_issue_subject_with_tree(issue)
92 92 s = ''
93 93 ancestors = issue.root? ? [] : issue.ancestors.visible.to_a
94 94 ancestors.each do |ancestor|
95 95 s << '<div>' + content_tag('p', link_to_issue(ancestor, :project => (issue.project_id != ancestor.project_id)))
96 96 end
97 97 s << '<div>'
98 98 subject = h(issue.subject)
99 99 if issue.is_private?
100 100 subject = content_tag('span', l(:field_is_private), :class => 'private') + ' ' + subject
101 101 end
102 102 s << content_tag('h3', subject)
103 103 s << '</div>' * (ancestors.size + 1)
104 104 s.html_safe
105 105 end
106 106
107 107 def render_descendants_tree(issue)
108 108 s = '<form><table class="list issues">'
109 109 issue_list(issue.descendants.visible.preload(:status, :priority, :tracker, :assigned_to).sort_by(&:lft)) do |child, level|
110 110 css = "issue issue-#{child.id} hascontextmenu #{issue.css_classes}"
111 111 css << " idnt idnt-#{level}" if level > 0
112 112 s << content_tag('tr',
113 113 content_tag('td', check_box_tag("ids[]", child.id, false, :id => nil), :class => 'checkbox') +
114 114 content_tag('td', link_to_issue(child, :project => (issue.project_id != child.project_id)), :class => 'subject', :style => 'width: 50%') +
115 115 content_tag('td', h(child.status), :class => 'status') +
116 116 content_tag('td', link_to_user(child.assigned_to), :class => 'assigned_to') +
117 117 content_tag('td', child.disabled_core_fields.include?('done_ratio') ? '' : progress_bar(child.done_ratio), :class=> 'done_ratio'),
118 118 :class => css)
119 119 end
120 120 s << '</table></form>'
121 121 s.html_safe
122 122 end
123 123
124 124 def issue_estimated_hours_details(issue)
125 125 if issue.total_estimated_hours.present?
126 126 if issue.total_estimated_hours == issue.estimated_hours
127 127 l_hours_short(issue.estimated_hours)
128 128 else
129 129 s = issue.estimated_hours.present? ? l_hours_short(issue.estimated_hours) : ""
130 130 s << " (#{l(:label_total)}: #{l_hours_short(issue.total_estimated_hours)})"
131 131 s.html_safe
132 132 end
133 133 end
134 134 end
135 135
136 136 def issue_spent_hours_details(issue)
137 137 if issue.total_spent_hours > 0
138 138 if issue.total_spent_hours == issue.spent_hours
139 139 link_to(l_hours_short(issue.spent_hours), issue_time_entries_path(issue))
140 140 else
141 141 s = issue.spent_hours > 0 ? l_hours_short(issue.spent_hours) : ""
142 142 s << " (#{l(:label_total)}: #{link_to l_hours_short(issue.total_spent_hours), issue_time_entries_path(issue)})"
143 143 s.html_safe
144 144 end
145 145 end
146 146 end
147 147
148 148 # Returns an array of error messages for bulk edited issues
149 149 def bulk_edit_error_messages(issues)
150 150 messages = {}
151 151 issues.each do |issue|
152 152 issue.errors.full_messages.each do |message|
153 153 messages[message] ||= []
154 154 messages[message] << issue
155 155 end
156 156 end
157 157 messages.map { |message, issues|
158 158 "#{message}: " + issues.map {|i| "##{i.id}"}.join(', ')
159 159 }
160 160 end
161 161
162 162 # Returns a link for adding a new subtask to the given issue
163 163 def link_to_new_subtask(issue)
164 164 attrs = {
165 165 :parent_issue_id => issue
166 166 }
167 167 attrs[:tracker_id] = issue.tracker unless issue.tracker.disabled_core_fields.include?('parent_issue_id')
168 168 link_to(l(:button_add), new_project_issue_path(issue.project, :issue => attrs))
169 169 end
170 170
171 171 def trackers_options_for_select(issue)
172 172 trackers = issue.allowed_target_trackers
173 173 if issue.new_record? && issue.parent_issue_id.present?
174 174 trackers = trackers.reject do |tracker|
175 175 issue.tracker_id != tracker.id && tracker.disabled_core_fields.include?('parent_issue_id')
176 176 end
177 177 end
178 178 trackers.collect {|t| [t.name, t.id]}
179 179 end
180 180
181 181 class IssueFieldsRows
182 182 include ActionView::Helpers::TagHelper
183 183
184 184 def initialize
185 185 @left = []
186 186 @right = []
187 187 end
188 188
189 189 def left(*args)
190 190 args.any? ? @left << cells(*args) : @left
191 191 end
192 192
193 193 def right(*args)
194 194 args.any? ? @right << cells(*args) : @right
195 195 end
196 196
197 197 def size
198 198 @left.size > @right.size ? @left.size : @right.size
199 199 end
200 200
201 201 def to_html
202 202 content =
203 203 content_tag('div', @left.reduce(&:+), :class => 'splitcontentleft') +
204 204 content_tag('div', @right.reduce(&:+), :class => 'splitcontentleft')
205 205
206 206 content_tag('div', content, :class => 'splitcontent')
207 207 end
208 208
209 209 def cells(label, text, options={})
210 210 options[:class] = [options[:class] || "", 'attribute'].join(' ')
211 211 content_tag 'div',
212 212 content_tag('div', label + ":", :class => 'label') + content_tag('div', text, :class => 'value'),
213 213 options
214 214 end
215 215 end
216 216
217 217 def issue_fields_rows
218 218 r = IssueFieldsRows.new
219 219 yield r
220 220 r.to_html
221 221 end
222 222
223 223 def render_custom_fields_rows(issue)
224 224 values = issue.visible_custom_field_values
225 225 return if values.empty?
226 226 half = (values.size / 2.0).ceil
227 227 issue_fields_rows do |rows|
228 228 values.each_with_index do |value, i|
229 229 css = "cf_#{value.custom_field.id}"
230 230 m = (i < half ? :left : :right)
231 231 rows.send m, custom_field_name_tag(value.custom_field), show_value(value), :class => css
232 232 end
233 233 end
234 234 end
235 235
236 236 # Returns the path for updating the issue form
237 237 # with project as the current project
238 238 def update_issue_form_path(project, issue)
239 239 options = {:format => 'js'}
240 240 if issue.new_record?
241 241 if project
242 242 new_project_issue_path(project, options)
243 243 else
244 244 new_issue_path(options)
245 245 end
246 246 else
247 247 edit_issue_path(issue, options)
248 248 end
249 249 end
250 250
251 251 # Returns the number of descendants for an array of issues
252 252 def issues_descendant_count(issues)
253 253 ids = issues.reject(&:leaf?).map {|issue| issue.descendants.ids}.flatten.uniq
254 254 ids -= issues.map(&:id)
255 255 ids.size
256 256 end
257 257
258 258 def issues_destroy_confirmation_message(issues)
259 259 issues = [issues] unless issues.is_a?(Array)
260 260 message = l(:text_issues_destroy_confirmation)
261 261
262 262 descendant_count = issues_descendant_count(issues)
263 263 if descendant_count > 0
264 264 message << "\n" + l(:text_issues_destroy_descendants_confirmation, :count => descendant_count)
265 265 end
266 266 message
267 267 end
268 268
269 269 # Returns an array of users that are proposed as watchers
270 270 # on the new issue form
271 271 def users_for_new_issue_watchers(issue)
272 272 users = issue.watcher_users
273 273 if issue.project.users.count <= 20
274 274 users = (users + issue.project.users.sort).uniq
275 275 end
276 276 users
277 277 end
278 278
279 def sidebar_queries
280 unless @sidebar_queries
281 @sidebar_queries = IssueQuery.visible.
282 order("#{Query.table_name}.name ASC").
283 # Project specific queries and global queries
284 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
285 to_a
286 end
287 @sidebar_queries
288 end
289
290 def query_links(title, queries)
291 return '' if queries.empty?
292 # links to #index on issues/show
293 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : {}
294
295 content_tag('h3', title) + "\n" +
296 content_tag('ul',
297 queries.collect {|query|
298 css = 'query'
299 css << ' selected' if query == @query
300 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
301 }.join("\n").html_safe,
302 :class => 'queries'
303 ) + "\n"
304 end
305
306 def render_sidebar_queries
307 out = ''.html_safe
308 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
309 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
310 out
311 end
312
313 279 def email_issue_attributes(issue, user)
314 280 items = []
315 281 %w(author status priority assigned_to category fixed_version).each do |attribute|
316 282 unless issue.disabled_core_fields.include?(attribute+"_id")
317 283 items << "#{l("field_#{attribute}")}: #{issue.send attribute}"
318 284 end
319 285 end
320 286 issue.visible_custom_field_values(user).each do |value|
321 287 items << "#{value.custom_field.name}: #{show_value(value, false)}"
322 288 end
323 289 items
324 290 end
325 291
326 292 def render_email_issue_attributes(issue, user, html=false)
327 293 items = email_issue_attributes(issue, user)
328 294 if html
329 295 content_tag('ul', items.map{|s| content_tag('li', s)}.join("\n").html_safe)
330 296 else
331 297 items.map{|s| "* #{s}"}.join("\n")
332 298 end
333 299 end
334 300
335 301 # Returns the textual representation of a journal details
336 302 # as an array of strings
337 303 def details_to_strings(details, no_html=false, options={})
338 304 options[:only_path] = (options[:only_path] == false ? false : true)
339 305 strings = []
340 306 values_by_field = {}
341 307 details.each do |detail|
342 308 if detail.property == 'cf'
343 309 field = detail.custom_field
344 310 if field && field.multiple?
345 311 values_by_field[field] ||= {:added => [], :deleted => []}
346 312 if detail.old_value
347 313 values_by_field[field][:deleted] << detail.old_value
348 314 end
349 315 if detail.value
350 316 values_by_field[field][:added] << detail.value
351 317 end
352 318 next
353 319 end
354 320 end
355 321 strings << show_detail(detail, no_html, options)
356 322 end
357 323 if values_by_field.present?
358 324 multiple_values_detail = Struct.new(:property, :prop_key, :custom_field, :old_value, :value)
359 325 values_by_field.each do |field, changes|
360 326 if changes[:added].any?
361 327 detail = multiple_values_detail.new('cf', field.id.to_s, field)
362 328 detail.value = changes[:added]
363 329 strings << show_detail(detail, no_html, options)
364 330 end
365 331 if changes[:deleted].any?
366 332 detail = multiple_values_detail.new('cf', field.id.to_s, field)
367 333 detail.old_value = changes[:deleted]
368 334 strings << show_detail(detail, no_html, options)
369 335 end
370 336 end
371 337 end
372 338 strings
373 339 end
374 340
375 341 # Returns the textual representation of a single journal detail
376 342 def show_detail(detail, no_html=false, options={})
377 343 multiple = false
378 344 show_diff = false
379 345
380 346 case detail.property
381 347 when 'attr'
382 348 field = detail.prop_key.to_s.gsub(/\_id$/, "")
383 349 label = l(("field_" + field).to_sym)
384 350 case detail.prop_key
385 351 when 'due_date', 'start_date'
386 352 value = format_date(detail.value.to_date) if detail.value
387 353 old_value = format_date(detail.old_value.to_date) if detail.old_value
388 354
389 355 when 'project_id', 'status_id', 'tracker_id', 'assigned_to_id',
390 356 'priority_id', 'category_id', 'fixed_version_id'
391 357 value = find_name_by_reflection(field, detail.value)
392 358 old_value = find_name_by_reflection(field, detail.old_value)
393 359
394 360 when 'estimated_hours'
395 361 value = l_hours_short(detail.value.to_f) unless detail.value.blank?
396 362 old_value = l_hours_short(detail.old_value.to_f) unless detail.old_value.blank?
397 363
398 364 when 'parent_id'
399 365 label = l(:field_parent_issue)
400 366 value = "##{detail.value}" unless detail.value.blank?
401 367 old_value = "##{detail.old_value}" unless detail.old_value.blank?
402 368
403 369 when 'is_private'
404 370 value = l(detail.value == "0" ? :general_text_No : :general_text_Yes) unless detail.value.blank?
405 371 old_value = l(detail.old_value == "0" ? :general_text_No : :general_text_Yes) unless detail.old_value.blank?
406 372
407 373 when 'description'
408 374 show_diff = true
409 375 end
410 376 when 'cf'
411 377 custom_field = detail.custom_field
412 378 if custom_field
413 379 label = custom_field.name
414 380 if custom_field.format.class.change_as_diff
415 381 show_diff = true
416 382 else
417 383 multiple = custom_field.multiple?
418 384 value = format_value(detail.value, custom_field) if detail.value
419 385 old_value = format_value(detail.old_value, custom_field) if detail.old_value
420 386 end
421 387 end
422 388 when 'attachment'
423 389 label = l(:label_attachment)
424 390 when 'relation'
425 391 if detail.value && !detail.old_value
426 392 rel_issue = Issue.visible.find_by_id(detail.value)
427 393 value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.value}" :
428 394 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
429 395 elsif detail.old_value && !detail.value
430 396 rel_issue = Issue.visible.find_by_id(detail.old_value)
431 397 old_value = rel_issue.nil? ? "#{l(:label_issue)} ##{detail.old_value}" :
432 398 (no_html ? rel_issue : link_to_issue(rel_issue, :only_path => options[:only_path]))
433 399 end
434 400 relation_type = IssueRelation::TYPES[detail.prop_key]
435 401 label = l(relation_type[:name]) if relation_type
436 402 end
437 403 call_hook(:helper_issues_show_detail_after_setting,
438 404 {:detail => detail, :label => label, :value => value, :old_value => old_value })
439 405
440 406 label ||= detail.prop_key
441 407 value ||= detail.value
442 408 old_value ||= detail.old_value
443 409
444 410 unless no_html
445 411 label = content_tag('strong', label)
446 412 old_value = content_tag("i", h(old_value)) if detail.old_value
447 413 if detail.old_value && detail.value.blank? && detail.property != 'relation'
448 414 old_value = content_tag("del", old_value)
449 415 end
450 416 if detail.property == 'attachment' && value.present? &&
451 417 atta = detail.journal.journalized.attachments.detect {|a| a.id == detail.prop_key.to_i}
452 418 # Link to the attachment if it has not been removed
453 419 value = link_to_attachment(atta, :download => true, :only_path => options[:only_path])
454 420 if options[:only_path] != false && (atta.is_text? || atta.is_image?)
455 421 value += ' '
456 422 value += link_to(l(:button_view),
457 423 { :controller => 'attachments', :action => 'show',
458 424 :id => atta, :filename => atta.filename },
459 425 :class => 'icon-only icon-magnifier',
460 426 :title => l(:button_view))
461 427 end
462 428 else
463 429 value = content_tag("i", h(value)) if value
464 430 end
465 431 end
466 432
467 433 if show_diff
468 434 s = l(:text_journal_changed_no_detail, :label => label)
469 435 unless no_html
470 436 diff_link = link_to 'diff',
471 437 diff_journal_url(detail.journal_id, :detail_id => detail.id, :only_path => options[:only_path]),
472 438 :title => l(:label_view_diff)
473 439 s << " (#{ diff_link })"
474 440 end
475 441 s.html_safe
476 442 elsif detail.value.present?
477 443 case detail.property
478 444 when 'attr', 'cf'
479 445 if detail.old_value.present?
480 446 l(:text_journal_changed, :label => label, :old => old_value, :new => value).html_safe
481 447 elsif multiple
482 448 l(:text_journal_added, :label => label, :value => value).html_safe
483 449 else
484 450 l(:text_journal_set_to, :label => label, :value => value).html_safe
485 451 end
486 452 when 'attachment', 'relation'
487 453 l(:text_journal_added, :label => label, :value => value).html_safe
488 454 end
489 455 else
490 456 l(:text_journal_deleted, :label => label, :old => old_value).html_safe
491 457 end
492 458 end
493 459
494 460 # Find the name of an associated record stored in the field attribute
495 461 def find_name_by_reflection(field, id)
496 462 unless id.present?
497 463 return nil
498 464 end
499 465 @detail_value_name_by_reflection ||= Hash.new do |hash, key|
500 466 association = Issue.reflect_on_association(key.first.to_sym)
501 467 name = nil
502 468 if association
503 469 record = association.klass.find_by_id(key.last)
504 470 if record
505 471 name = record.name.force_encoding('UTF-8')
506 472 end
507 473 end
508 474 hash[key] = name
509 475 end
510 476 @detail_value_name_by_reflection[[field, id]]
511 477 end
512 478
513 479 # Renders issue children recursively
514 480 def render_api_issue_children(issue, api)
515 481 return if issue.leaf?
516 482 api.array :children do
517 483 issue.children.each do |child|
518 484 api.issue(:id => child.id) do
519 485 api.tracker(:id => child.tracker_id, :name => child.tracker.name) unless child.tracker.nil?
520 486 api.subject child.subject
521 487 render_api_issue_children(child, api)
522 488 end
523 489 end
524 490 end
525 491 end
526 492 end
@@ -1,280 +1,314
1 1 # encoding: utf-8
2 2 #
3 3 # Redmine - project management software
4 4 # Copyright (C) 2006-2016 Jean-Philippe Lang
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; either version 2
9 9 # of the License, or (at your option) any later version.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 19
20 20 module QueriesHelper
21 21 include ApplicationHelper
22 22
23 23 def filters_options_for_select(query)
24 24 ungrouped = []
25 25 grouped = {}
26 26 query.available_filters.map do |field, field_options|
27 27 if [:tree, :relation].include?(field_options[:type])
28 28 group = :label_relations
29 29 elsif field =~ /^(.+)\./
30 30 # association filters
31 31 group = "field_#{$1}"
32 32 elsif %w(member_of_group assigned_to_role).include?(field)
33 33 group = :field_assigned_to
34 34 elsif field_options[:type] == :date_past || field_options[:type] == :date
35 35 group = :label_date
36 36 end
37 37 if group
38 38 (grouped[group] ||= []) << [field_options[:name], field]
39 39 else
40 40 ungrouped << [field_options[:name], field]
41 41 end
42 42 end
43 43 # Don't group dates if there's only one (eg. time entries filters)
44 44 if grouped[:label_date].try(:size) == 1
45 45 ungrouped << grouped.delete(:label_date).first
46 46 end
47 47 s = options_for_select([[]] + ungrouped)
48 48 if grouped.present?
49 49 localized_grouped = grouped.map {|k,v| [l(k), v]}
50 50 s << grouped_options_for_select(localized_grouped)
51 51 end
52 52 s
53 53 end
54 54
55 55 def query_filters_hidden_tags(query)
56 56 tags = ''.html_safe
57 57 query.filters.each do |field, options|
58 58 tags << hidden_field_tag("f[]", field, :id => nil)
59 59 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
60 60 options[:values].each do |value|
61 61 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
62 62 end
63 63 end
64 64 tags
65 65 end
66 66
67 67 def query_columns_hidden_tags(query)
68 68 tags = ''.html_safe
69 69 query.columns.each do |column|
70 70 tags << hidden_field_tag("c[]", column.name, :id => nil)
71 71 end
72 72 tags
73 73 end
74 74
75 75 def query_hidden_tags(query)
76 76 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
77 77 end
78 78
79 79 def available_block_columns_tags(query)
80 80 tags = ''.html_safe
81 81 query.available_block_columns.each do |column|
82 82 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
83 83 end
84 84 tags
85 85 end
86 86
87 87 def available_totalable_columns_tags(query)
88 88 tags = ''.html_safe
89 89 query.available_totalable_columns.each do |column|
90 90 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
91 91 end
92 92 tags << hidden_field_tag('t[]', '')
93 93 tags
94 94 end
95 95
96 96 def query_available_inline_columns_options(query)
97 97 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
98 98 end
99 99
100 100 def query_selected_inline_columns_options(query)
101 101 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
102 102 end
103 103
104 104 def render_query_columns_selection(query, options={})
105 105 tag_name = (options[:name] || 'c') + '[]'
106 106 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
107 107 end
108 108
109 109 def render_query_totals(query)
110 110 return unless query.totalable_columns.present?
111 111 totals = query.totalable_columns.map do |column|
112 112 total_tag(column, query.total_for(column))
113 113 end
114 114 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
115 115 end
116 116
117 117 def total_tag(column, value)
118 118 label = content_tag('span', "#{column.caption}:")
119 119 value = content_tag('span', format_object(value), :class => 'value')
120 120 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
121 121 end
122 122
123 123 def column_header(column)
124 124 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
125 125 :default_order => column.default_order) :
126 126 content_tag('th', h(column.caption))
127 127 end
128 128
129 129 def column_content(column, issue)
130 130 value = column.value_object(issue)
131 131 if value.is_a?(Array)
132 132 value.collect {|v| column_value(column, issue, v)}.compact.join(', ').html_safe
133 133 else
134 134 column_value(column, issue, value)
135 135 end
136 136 end
137 137
138 138 def column_value(column, issue, value)
139 139 case column.name
140 140 when :id
141 141 link_to value, issue_path(issue)
142 142 when :subject
143 143 link_to value, issue_path(issue)
144 144 when :parent
145 145 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
146 146 when :description
147 147 issue.description? ? content_tag('div', textilizable(issue, :description), :class => "wiki") : ''
148 148 when :done_ratio
149 149 progress_bar(value)
150 150 when :relations
151 151 content_tag('span',
152 152 value.to_s(issue) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
153 153 :class => value.css_classes_for(issue))
154 154 else
155 155 format_object(value)
156 156 end
157 157 end
158 158
159 159 def csv_content(column, issue)
160 160 value = column.value_object(issue)
161 161 if value.is_a?(Array)
162 162 value.collect {|v| csv_value(column, issue, v)}.compact.join(', ')
163 163 else
164 164 csv_value(column, issue, value)
165 165 end
166 166 end
167 167
168 168 def csv_value(column, object, value)
169 169 format_object(value, false) do |value|
170 170 case value.class.name
171 171 when 'Float'
172 172 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
173 173 when 'IssueRelation'
174 174 value.to_s(object)
175 175 when 'Issue'
176 176 if object.is_a?(TimeEntry)
177 177 "#{value.tracker} ##{value.id}: #{value.subject}"
178 178 else
179 179 value.id
180 180 end
181 181 else
182 182 value
183 183 end
184 184 end
185 185 end
186 186
187 187 def query_to_csv(items, query, options={})
188 188 options ||= {}
189 189 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
190 190 query.available_block_columns.each do |column|
191 191 if options[column.name].present?
192 192 columns << column
193 193 end
194 194 end
195 195
196 196 Redmine::Export::CSV.generate do |csv|
197 197 # csv header fields
198 198 csv << columns.map {|c| c.caption.to_s}
199 199 # csv lines
200 200 items.each do |item|
201 201 csv << columns.map {|c| csv_content(c, item)}
202 202 end
203 203 end
204 204 end
205 205
206 206 # Retrieve query from session or build a new query
207 207 def retrieve_query(klass=IssueQuery, use_session=true)
208 208 session_key = klass.name.underscore.to_sym
209 209
210 210 if params[:query_id].present?
211 211 cond = "project_id IS NULL"
212 212 cond << " OR project_id = #{@project.id}" if @project
213 213 @query = klass.where(cond).find(params[:query_id])
214 214 raise ::Unauthorized unless @query.visible?
215 215 @query.project = @project
216 216 session[session_key] = {:id => @query.id, :project_id => @query.project_id} if use_session
217 217 sort_clear
218 218 elsif api_request? || params[:set_filter] || !use_session || session[session_key].nil? || session[session_key][:project_id] != (@project ? @project.id : nil)
219 219 # Give it a name, required to be valid
220 220 @query = klass.new(:name => "_", :project => @project)
221 221 @query.build_from_params(params)
222 222 session[session_key] = {:project_id => @query.project_id, :filters => @query.filters, :group_by => @query.group_by, :column_names => @query.column_names, :totalable_names => @query.totalable_names} if use_session
223 223 else
224 224 # retrieve from session
225 225 @query = nil
226 226 @query = klass.find_by_id(session[session_key][:id]) if session[session_key][:id]
227 227 @query ||= klass.new(:name => "_", :filters => session[session_key][:filters], :group_by => session[session_key][:group_by], :column_names => session[session_key][:column_names], :totalable_names => session[session_key][:totalable_names])
228 228 @query.project = @project
229 229 end
230 230 end
231 231
232 232 def retrieve_query_from_session
233 233 if session[:query]
234 234 if session[:query][:id]
235 235 @query = IssueQuery.find_by_id(session[:query][:id])
236 236 return unless @query
237 237 else
238 238 @query = IssueQuery.new(:name => "_", :filters => session[:query][:filters], :group_by => session[:query][:group_by], :column_names => session[:query][:column_names], :totalable_names => session[:query][:totalable_names])
239 239 end
240 240 if session[:query].has_key?(:project_id)
241 241 @query.project_id = session[:query][:project_id]
242 242 else
243 243 @query.project = @project
244 244 end
245 245 @query
246 246 end
247 247 end
248 248
249 249 # Returns the query definition as hidden field tags
250 250 def query_as_hidden_field_tags(query)
251 251 tags = hidden_field_tag("set_filter", "1", :id => nil)
252 252
253 253 if query.filters.present?
254 254 query.filters.each do |field, filter|
255 255 tags << hidden_field_tag("f[]", field, :id => nil)
256 256 tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
257 257 filter[:values].each do |value|
258 258 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
259 259 end
260 260 end
261 261 else
262 262 tags << hidden_field_tag("f[]", "", :id => nil)
263 263 end
264 264 if query.column_names.present?
265 265 query.column_names.each do |name|
266 266 tags << hidden_field_tag("c[]", name, :id => nil)
267 267 end
268 268 end
269 269 if query.totalable_names.present?
270 270 query.totalable_names.each do |name|
271 271 tags << hidden_field_tag("t[]", name, :id => nil)
272 272 end
273 273 end
274 274 if query.group_by.present?
275 275 tags << hidden_field_tag("group_by", query.group_by, :id => nil)
276 276 end
277 277
278 278 tags
279 279 end
280
281 def sidebar_queries
282 unless @sidebar_queries
283 @sidebar_queries = IssueQuery.visible.
284 order("#{Query.table_name}.name ASC").
285 # Project specific queries and global queries
286 where(@project.nil? ? ["project_id IS NULL"] : ["project_id IS NULL OR project_id = ?", @project.id]).
287 to_a
288 end
289 @sidebar_queries
290 end
291
292 def query_links(title, queries)
293 return '' if queries.empty?
294 # links to #index on issues/show
295 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : {}
296
297 content_tag('h3', title) + "\n" +
298 content_tag('ul',
299 queries.collect {|query|
300 css = 'query'
301 css << ' selected' if query == @query
302 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
303 }.join("\n").html_safe,
304 :class => 'queries'
305 ) + "\n"
306 end
307
308 def render_sidebar_queries
309 out = ''.html_safe
310 out << query_links(l(:label_my_queries), sidebar_queries.select(&:is_private?))
311 out << query_links(l(:label_query_plural), sidebar_queries.reject(&:is_private?))
312 out
313 end
280 314 end
General Comments 0
You need to be logged in to leave comments. Login now