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