Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

queries_helper.rb 13KB

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