##// END OF EJS Templates
Make Spent time clickable in issue lists (#24649)....
Jean-Philippe Lang -
r15822:a12219034d06
parent child
Show More
@@ -1,357 +1,361
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 field_options[:type] == :relation
28 28 group = :label_relations
29 29 elsif field_options[:type] == :tree
30 30 group = query.is_a?(IssueQuery) ? :label_relations : nil
31 31 elsif field =~ /^cf_\d+\./
32 32 group = (field_options[:through] || field_options[:field]).try(:name)
33 33 elsif field =~ /^(.+)\./
34 34 # association filters
35 35 group = "field_#{$1}".to_sym
36 36 elsif %w(member_of_group assigned_to_role).include?(field)
37 37 group = :field_assigned_to
38 38 elsif field_options[:type] == :date_past || field_options[:type] == :date
39 39 group = :label_date
40 40 end
41 41 if group
42 42 (grouped[group] ||= []) << [field_options[:name], field]
43 43 else
44 44 ungrouped << [field_options[:name], field]
45 45 end
46 46 end
47 47 # Don't group dates if there's only one (eg. time entries filters)
48 48 if grouped[:label_date].try(:size) == 1
49 49 ungrouped << grouped.delete(:label_date).first
50 50 end
51 51 s = options_for_select([[]] + ungrouped)
52 52 if grouped.present?
53 53 localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]}
54 54 s << grouped_options_for_select(localized_grouped)
55 55 end
56 56 s
57 57 end
58 58
59 59 def query_filters_hidden_tags(query)
60 60 tags = ''.html_safe
61 61 query.filters.each do |field, options|
62 62 tags << hidden_field_tag("f[]", field, :id => nil)
63 63 tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
64 64 options[:values].each do |value|
65 65 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
66 66 end
67 67 end
68 68 tags
69 69 end
70 70
71 71 def query_columns_hidden_tags(query)
72 72 tags = ''.html_safe
73 73 query.columns.each do |column|
74 74 tags << hidden_field_tag("c[]", column.name, :id => nil)
75 75 end
76 76 tags
77 77 end
78 78
79 79 def query_hidden_tags(query)
80 80 query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
81 81 end
82 82
83 83 def group_by_column_select_tag(query)
84 84 options = [[]] + query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}
85 85 select_tag('group_by', options_for_select(options, @query.group_by))
86 86 end
87 87
88 88 def available_block_columns_tags(query)
89 89 tags = ''.html_safe
90 90 query.available_block_columns.each do |column|
91 91 tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
92 92 end
93 93 tags
94 94 end
95 95
96 96 def available_totalable_columns_tags(query)
97 97 tags = ''.html_safe
98 98 query.available_totalable_columns.each do |column|
99 99 tags << content_tag('label', check_box_tag('t[]', column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
100 100 end
101 101 tags << hidden_field_tag('t[]', '')
102 102 tags
103 103 end
104 104
105 105 def query_available_inline_columns_options(query)
106 106 (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
107 107 end
108 108
109 109 def query_selected_inline_columns_options(query)
110 110 (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
111 111 end
112 112
113 113 def render_query_columns_selection(query, options={})
114 114 tag_name = (options[:name] || 'c') + '[]'
115 115 render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
116 116 end
117 117
118 118 def grouped_query_results(items, query, item_count_by_group, &block)
119 119 previous_group, first = false, true
120 120 totals_by_group = query.totalable_columns.inject({}) do |h, column|
121 121 h[column] = query.total_by_group_for(column)
122 122 h
123 123 end
124 124 items.each do |item|
125 125 group_name = group_count = nil
126 126 if query.grouped?
127 127 group = query.group_by_column.value(item)
128 128 if first || group != previous_group
129 129 if group.blank? && group != false
130 130 group_name = "(#{l(:label_blank_value)})"
131 131 else
132 132 group_name = format_object(group)
133 133 end
134 134 group_name ||= ""
135 135 group_count = item_count_by_group ? item_count_by_group[group] : nil
136 136 group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
137 137 end
138 138 end
139 139 yield item, group_name, group_count, group_totals
140 140 previous_group, first = group, false
141 141 end
142 142 end
143 143
144 144 def render_query_totals(query)
145 145 return unless query.totalable_columns.present?
146 146 totals = query.totalable_columns.map do |column|
147 147 total_tag(column, query.total_for(column))
148 148 end
149 149 content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
150 150 end
151 151
152 152 def total_tag(column, value)
153 153 label = content_tag('span', "#{column.caption}:")
154 154 value = if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
155 155 format_hours(value)
156 156 else
157 157 format_object(value)
158 158 end
159 159 value = content_tag('span', value, :class => 'value')
160 160 content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
161 161 end
162 162
163 163 def column_header(column)
164 164 column.sortable ? sort_header_tag(column.name.to_s, :caption => column.caption,
165 165 :default_order => column.default_order) :
166 166 content_tag('th', h(column.caption))
167 167 end
168 168
169 169 def column_content(column, item)
170 170 value = column.value_object(item)
171 171 if value.is_a?(Array)
172 172 value.collect {|v| column_value(column, item, v)}.compact.join(', ').html_safe
173 173 else
174 174 column_value(column, item, value)
175 175 end
176 176 end
177 177
178 178 def column_value(column, item, value)
179 179 case column.name
180 180 when :id
181 181 link_to value, issue_path(item)
182 182 when :subject
183 183 link_to value, issue_path(item)
184 184 when :parent
185 185 value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
186 186 when :description
187 187 item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
188 188 when :done_ratio
189 189 progress_bar(value)
190 190 when :relations
191 191 content_tag('span',
192 192 value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
193 193 :class => value.css_classes_for(item))
194 when :hours, :spent_hours, :total_spent_hours, :estimated_hours
194 when :hours, :estimated_hours
195 195 format_hours(value)
196 when :spent_hours
197 link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
198 when :total_spent_hours
199 link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "~#{item.id}"))
196 200 else
197 201 format_object(value)
198 202 end
199 203 end
200 204
201 205 def csv_content(column, item)
202 206 value = column.value_object(item)
203 207 if value.is_a?(Array)
204 208 value.collect {|v| csv_value(column, item, v)}.compact.join(', ')
205 209 else
206 210 csv_value(column, item, value)
207 211 end
208 212 end
209 213
210 214 def csv_value(column, object, value)
211 215 format_object(value, false) do |value|
212 216 case value.class.name
213 217 when 'Float'
214 218 sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
215 219 when 'IssueRelation'
216 220 value.to_s(object)
217 221 when 'Issue'
218 222 if object.is_a?(TimeEntry)
219 223 "#{value.tracker} ##{value.id}: #{value.subject}"
220 224 else
221 225 value.id
222 226 end
223 227 else
224 228 value
225 229 end
226 230 end
227 231 end
228 232
229 233 def query_to_csv(items, query, options={})
230 234 options ||= {}
231 235 columns = (options[:columns] == 'all' ? query.available_inline_columns : query.inline_columns)
232 236 query.available_block_columns.each do |column|
233 237 if options[column.name].present?
234 238 columns << column
235 239 end
236 240 end
237 241
238 242 Redmine::Export::CSV.generate do |csv|
239 243 # csv header fields
240 244 csv << columns.map {|c| c.caption.to_s}
241 245 # csv lines
242 246 items.each do |item|
243 247 csv << columns.map {|c| csv_content(c, item)}
244 248 end
245 249 end
246 250 end
247 251
248 252 # Retrieve query from session or build a new query
249 253 def retrieve_query(klass=IssueQuery, use_session=true)
250 254 session_key = klass.name.underscore.to_sym
251 255
252 256 if params[:query_id].present?
253 257 cond = "project_id IS NULL"
254 258 cond << " OR project_id = #{@project.id}" if @project
255 259 @query = klass.where(cond).find(params[:query_id])
256 260 raise ::Unauthorized unless @query.visible?
257 261 @query.project = @project
258 262 session[session_key] = {:id => @query.id, :project_id => @query.project_id} if use_session
259 263 sort_clear
260 264 elsif api_request? || params[:set_filter] || !use_session || session[session_key].nil? || session[session_key][:project_id] != (@project ? @project.id : nil)
261 265 # Give it a name, required to be valid
262 266 @query = klass.new(:name => "_", :project => @project)
263 267 @query.build_from_params(params)
264 268 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
265 269 else
266 270 # retrieve from session
267 271 @query = nil
268 272 @query = klass.find_by_id(session[session_key][:id]) if session[session_key][:id]
269 273 @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])
270 274 @query.project = @project
271 275 end
272 276 end
273 277
274 278 def retrieve_query_from_session(klass=IssueQuery)
275 279 session_key = klass.name.underscore.to_sym
276 280 session_data = session[session_key]
277 281
278 282 if session_data
279 283 if session_data[:id]
280 284 @query = IssueQuery.find_by_id(session_data[:id])
281 285 return unless @query
282 286 else
283 287 @query = IssueQuery.new(:name => "_", :filters => session_data[:filters], :group_by => session_data[:group_by], :column_names => session_data[:column_names], :totalable_names => session_data[:totalable_names])
284 288 end
285 289 if session_data.has_key?(:project_id)
286 290 @query.project_id = session_data[:project_id]
287 291 else
288 292 @query.project = @project
289 293 end
290 294 @query
291 295 end
292 296 end
293 297
294 298 # Returns the query definition as hidden field tags
295 299 def query_as_hidden_field_tags(query)
296 300 tags = hidden_field_tag("set_filter", "1", :id => nil)
297 301
298 302 if query.filters.present?
299 303 query.filters.each do |field, filter|
300 304 tags << hidden_field_tag("f[]", field, :id => nil)
301 305 tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
302 306 filter[:values].each do |value|
303 307 tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
304 308 end
305 309 end
306 310 else
307 311 tags << hidden_field_tag("f[]", "", :id => nil)
308 312 end
309 313 if query.column_names.present?
310 314 query.column_names.each do |name|
311 315 tags << hidden_field_tag("c[]", name, :id => nil)
312 316 end
313 317 end
314 318 if query.totalable_names.present?
315 319 query.totalable_names.each do |name|
316 320 tags << hidden_field_tag("t[]", name, :id => nil)
317 321 end
318 322 end
319 323 if query.group_by.present?
320 324 tags << hidden_field_tag("group_by", query.group_by, :id => nil)
321 325 end
322 326
323 327 tags
324 328 end
325 329
326 330 # Returns the queries that are rendered in the sidebar
327 331 def sidebar_queries(klass, project)
328 332 klass.visible.global_or_on_project(@project).sorted.to_a
329 333 end
330 334
331 335 # Renders a group of queries
332 336 def query_links(title, queries)
333 337 return '' if queries.empty?
334 338 # links to #index on issues/show
335 339 url_params = controller_name == 'issues' ? {:controller => 'issues', :action => 'index', :project_id => @project} : {}
336 340
337 341 content_tag('h3', title) + "\n" +
338 342 content_tag('ul',
339 343 queries.collect {|query|
340 344 css = 'query'
341 345 css << ' selected' if query == @query
342 346 content_tag('li', link_to(query.name, url_params.merge(:query_id => query), :class => css))
343 347 }.join("\n").html_safe,
344 348 :class => 'queries'
345 349 ) + "\n"
346 350 end
347 351
348 352 # Renders the list of queries for the sidebar
349 353 def render_sidebar_queries(klass, project)
350 354 queries = sidebar_queries(klass, project)
351 355
352 356 out = ''.html_safe
353 357 out << query_links(l(:label_my_queries), queries.select(&:is_private?))
354 358 out << query_links(l(:label_query_plural), queries.reject(&:is_private?))
355 359 out
356 360 end
357 361 end
General Comments 0
You need to be logged in to leave comments. Login now