"Fossies" - the Fresh Open Source Software Archive

Member "redmine-4.1.1/app/helpers/queries_helper.rb" (6 Apr 2020, 15824 Bytes) of package /linux/www/redmine-4.1.1.tar.gz:


As a special service "Fossies" has tried to format the requested text file into HTML format (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "queries_helper.rb": 4.1.0_vs_4.1.1.

    1 # frozen_string_literal: true
    2 
    3 # Redmine - project management software
    4 # Copyright (C) 2006-2019  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 
   20 require 'redmine/export/csv'
   21 
   22 module QueriesHelper
   23   include ApplicationHelper
   24 
   25   def filters_options_for_select(query)
   26     ungrouped = []
   27     grouped = {}
   28     query.available_filters.map do |field, field_options|
   29       if field_options[:type] == :relation
   30         group = :label_relations
   31       elsif field_options[:type] == :tree
   32         group = query.is_a?(IssueQuery) ? :label_relations : nil
   33       elsif /^cf_\d+\./.match?(field)
   34         group = (field_options[:through] || field_options[:field]).try(:name)
   35       elsif field =~ /^(.+)\./
   36         # association filters
   37         group = "field_#{$1}".to_sym
   38       elsif %w(member_of_group assigned_to_role).include?(field)
   39         group = :field_assigned_to
   40       elsif field_options[:type] == :date_past || field_options[:type] == :date
   41         group = :label_date
   42       elsif %w(estimated_hours spent_time).include?(field)
   43         group = :label_time_tracking
   44       end
   45       if group
   46         (grouped[group] ||= []) << [field_options[:name], field]
   47       else
   48         ungrouped << [field_options[:name], field]
   49       end
   50     end
   51     # Don't group dates if there's only one (eg. time entries filters)
   52     if grouped[:label_date].try(:size) == 1
   53       ungrouped << grouped.delete(:label_date).first
   54     end
   55     s = options_for_select([[]] + ungrouped)
   56     if grouped.present?
   57       localized_grouped = grouped.map {|k,v| [k.is_a?(Symbol) ? l(k) : k.to_s, v]}
   58       s << grouped_options_for_select(localized_grouped)
   59     end
   60     s
   61   end
   62 
   63   def query_filters_hidden_tags(query)
   64     tags = ''.html_safe
   65     query.filters.each do |field, options|
   66       tags << hidden_field_tag("f[]", field, :id => nil)
   67       tags << hidden_field_tag("op[#{field}]", options[:operator], :id => nil)
   68       options[:values].each do |value|
   69         tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
   70       end
   71     end
   72     tags
   73   end
   74 
   75   def query_columns_hidden_tags(query)
   76     tags = ''.html_safe
   77     query.columns.each do |column|
   78       tags << hidden_field_tag("c[]", column.name, :id => nil)
   79     end
   80     tags
   81   end
   82 
   83   def query_hidden_tags(query)
   84     query_filters_hidden_tags(query) + query_columns_hidden_tags(query)
   85   end
   86 
   87   def group_by_column_select_tag(query)
   88     options = [[]] + query.groupable_columns.collect {|c| [c.caption, c.name.to_s]}
   89     select_tag('group_by', options_for_select(options, @query.group_by))
   90   end
   91 
   92   def available_block_columns_tags(query)
   93     tags = ''.html_safe
   94     query.available_block_columns.each do |column|
   95       tags << content_tag('label', check_box_tag('c[]', column.name.to_s, query.has_column?(column), :id => nil) + " #{column.caption}", :class => 'inline')
   96     end
   97     tags
   98   end
   99 
  100   def available_totalable_columns_tags(query, options={})
  101     tag_name = (options[:name] || 't') + '[]'
  102     tags = ''.html_safe
  103     query.available_totalable_columns.each do |column|
  104       tags << content_tag('label', check_box_tag(tag_name, column.name.to_s, query.totalable_columns.include?(column), :id => nil) + " #{column.caption}", :class => 'inline')
  105     end
  106     tags << hidden_field_tag(tag_name, '')
  107     tags
  108   end
  109 
  110   def query_available_inline_columns_options(query)
  111     (query.available_inline_columns - query.columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
  112   end
  113 
  114   def query_selected_inline_columns_options(query)
  115     (query.inline_columns & query.available_inline_columns).reject(&:frozen?).collect {|column| [column.caption, column.name]}
  116   end
  117 
  118   def render_query_columns_selection(query, options={})
  119     tag_name = (options[:name] || 'c') + '[]'
  120     render :partial => 'queries/columns', :locals => {:query => query, :tag_name => tag_name}
  121   end
  122 
  123   def available_display_types_tags(query)
  124     tags = ''.html_safe
  125     query.available_display_types.each do |t|
  126       tags << radio_button_tag('display_type', t, @query.display_type == t, :id => "display_type_#{t}") +
  127         content_tag('label', l(:"label_display_type_#{t}"), :for => "display_type_#{t}", :class => "inline")
  128     end
  129     tags
  130   end
  131 
  132   def grouped_query_results(items, query, &block)
  133     result_count_by_group = query.result_count_by_group
  134     previous_group, first = false, true
  135     totals_by_group = query.totalable_columns.inject({}) do |h, column|
  136       h[column] = query.total_by_group_for(column)
  137       h
  138     end
  139     items.each do |item|
  140       group_name = group_count = nil
  141       if query.grouped?
  142         group = query.group_by_column.group_value(item)
  143         if first || group != previous_group
  144           if group.blank? && group != false
  145             group_name = "(#{l(:label_blank_value)})"
  146           else
  147             group_name = format_object(group)
  148           end
  149           group_name ||= ""
  150           group_count = result_count_by_group ? result_count_by_group[group] : nil
  151           group_totals = totals_by_group.map {|column, t| total_tag(column, t[group] || 0)}.join(" ").html_safe
  152         end
  153       end
  154       yield item, group_name, group_count, group_totals
  155       previous_group, first = group, false
  156     end
  157   end
  158 
  159   def render_query_totals(query)
  160     return unless query.totalable_columns.present?
  161     totals = query.totalable_columns.map do |column|
  162       total_tag(column, query.total_for(column))
  163     end
  164     content_tag('p', totals.join(" ").html_safe, :class => "query-totals")
  165   end
  166 
  167   def total_tag(column, value)
  168     label = content_tag('span', "#{column.caption}:")
  169     value =
  170       if [:hours, :spent_hours, :total_spent_hours, :estimated_hours].include? column.name
  171         format_hours(value)
  172       else
  173         format_object(value)
  174       end
  175     value = content_tag('span', value, :class => 'value')
  176     content_tag('span', label + " " + value, :class => "total-for-#{column.name.to_s.dasherize}")
  177   end
  178 
  179   def column_header(query, column, options={})
  180     if column.sortable?
  181       css, order = nil, column.default_order
  182       if column.name.to_s == query.sort_criteria.first_key
  183         if query.sort_criteria.first_asc?
  184           css = 'sort asc icon icon-sorted-desc'
  185           order = 'desc'
  186         else
  187           css = 'sort desc icon icon-sorted-asc'
  188           order = 'asc'
  189         end
  190       end
  191       param_key = options[:sort_param] || :sort
  192       sort_param = {param_key => query.sort_criteria.add(column.name, order).to_param}
  193       sort_param = {$1 => {$2 => sort_param.values.first}} while sort_param.keys.first.to_s =~ /^(.+)\[(.+)\]$/
  194       link_options = {
  195           :title => l(:label_sort_by, "\"#{column.caption}\""),
  196           :class => css
  197         }
  198       if options[:sort_link_options]
  199         link_options.merge! options[:sort_link_options]
  200       end
  201       content = link_to(
  202           column.caption,
  203           {:params => request.query_parameters.deep_merge(sort_param)},
  204           link_options
  205         )
  206     else
  207       content = column.caption
  208     end
  209     content_tag('th', content, :class => column.css_classes)
  210   end
  211 
  212   def column_content(column, item)
  213     value = column.value_object(item)
  214     if value.is_a?(Array)
  215       values = value.collect {|v| column_value(column, item, v)}.compact
  216       safe_join(values, ', ')
  217     else
  218       column_value(column, item, value)
  219     end
  220   end
  221 
  222   def column_value(column, item, value)
  223     case column.name
  224     when :id
  225       link_to value, issue_path(item)
  226     when :subject
  227       link_to value, issue_path(item)
  228     when :parent
  229       value ? (value.visible? ? link_to_issue(value, :subject => false) : "##{value.id}") : ''
  230     when :description
  231       item.description? ? content_tag('div', textilizable(item, :description), :class => "wiki") : ''
  232     when :last_notes
  233       item.last_notes.present? ? content_tag('div', textilizable(item, :last_notes), :class => "wiki") : ''
  234     when :done_ratio
  235       progress_bar(value)
  236     when :relations
  237       content_tag(
  238         'span',
  239         value.to_s(item) {|other| link_to_issue(other, :subject => false, :tracker => false)}.html_safe,
  240         :class => value.css_classes_for(item))
  241     when :hours, :estimated_hours
  242       format_hours(value)
  243     when :spent_hours
  244       link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "#{item.id}"))
  245     when :total_spent_hours
  246       link_to_if(value > 0, format_hours(value), project_time_entries_path(item.project, :issue_id => "~#{item.id}"))
  247     when :attachments
  248       value.to_a.map {|a| format_object(a)}.join(" ").html_safe
  249     else
  250       format_object(value)
  251     end
  252   end
  253 
  254   def csv_content(column, item)
  255     value = column.value_object(item)
  256     if value.is_a?(Array)
  257       value.collect {|v| csv_value(column, item, v)}.compact.join(', ')
  258     else
  259       csv_value(column, item, value)
  260     end
  261   end
  262 
  263   def csv_value(column, object, value)
  264     case column.name
  265     when :attachments
  266       value.to_a.map {|a| a.filename}.join("\n")
  267     else
  268       format_object(value, false) do |value|
  269         case value.class.name
  270         when 'Float'
  271           sprintf("%.2f", value).gsub('.', l(:general_csv_decimal_separator))
  272         when 'IssueRelation'
  273           value.to_s(object)
  274         when 'Issue'
  275           if object.is_a?(TimeEntry)
  276             value.visible? ? "#{value.tracker} ##{value.id}: #{value.subject}" : "##{value.id}"
  277           else
  278             value.id
  279           end
  280         else
  281           value
  282         end
  283       end
  284     end
  285   end
  286 
  287   def query_to_csv(items, query, options={})
  288     columns = query.columns
  289 
  290     Redmine::Export::CSV.generate(:encoding => params[:encoding]) do |csv|
  291       # csv header fields
  292       csv << columns.map {|c| c.caption.to_s}
  293       # csv lines
  294       items.each do |item|
  295         csv << columns.map {|c| csv_content(c, item)}
  296       end
  297     end
  298   end
  299 
  300   # Retrieve query from session or build a new query
  301   def retrieve_query(klass=IssueQuery, use_session=true, options={})
  302     session_key = klass.name.underscore.to_sym
  303 
  304     if params[:query_id].present?
  305       scope = klass.where(:project_id => nil)
  306       scope = scope.or(klass.where(:project_id => @project)) if @project
  307       @query = scope.find(params[:query_id])
  308       raise ::Unauthorized unless @query.visible?
  309       @query.project = @project
  310       session[session_key] = {:id => @query.id, :project_id => @query.project_id} if use_session
  311     elsif api_request? || params[:set_filter] || !use_session || session[session_key].nil? || session[session_key][:project_id] != (@project ? @project.id : nil)
  312       # Give it a name, required to be valid
  313       @query = klass.new(:name => "_", :project => @project)
  314       @query.build_from_params(params, options[:defaults])
  315       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, :sort => @query.sort_criteria.to_a} if use_session
  316     else
  317       # retrieve from session
  318       @query = nil
  319       @query = klass.find_by_id(session[session_key][:id]) if session[session_key][:id]
  320       @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], :sort_criteria => session[session_key][:sort])
  321       @query.project = @project
  322     end
  323     if params[:sort].present?
  324       @query.sort_criteria = params[:sort]
  325       if use_session
  326         session[session_key] ||= {}
  327         session[session_key][:sort] = @query.sort_criteria.to_a
  328       end
  329     end
  330     @query
  331   end
  332 
  333   def retrieve_query_from_session(klass=IssueQuery)
  334     session_key = klass.name.underscore.to_sym
  335     session_data = session[session_key]
  336 
  337     if session_data
  338       if session_data[:id]
  339         @query = IssueQuery.find_by_id(session_data[:id])
  340         return unless @query
  341       else
  342         @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], :sort_criteria => session[session_key][:sort])
  343       end
  344       if session_data.has_key?(:project_id)
  345         @query.project_id = session_data[:project_id]
  346       else
  347         @query.project = @project
  348       end
  349       @query
  350     end
  351   end
  352 
  353   # Returns the query definition as hidden field tags
  354   def query_as_hidden_field_tags(query)
  355     tags = hidden_field_tag("set_filter", "1", :id => nil)
  356 
  357     if query.filters.present?
  358       query.filters.each do |field, filter|
  359         tags << hidden_field_tag("f[]", field, :id => nil)
  360         tags << hidden_field_tag("op[#{field}]", filter[:operator], :id => nil)
  361         filter[:values].each do |value|
  362           tags << hidden_field_tag("v[#{field}][]", value, :id => nil)
  363         end
  364       end
  365     else
  366       tags << hidden_field_tag("f[]", "", :id => nil)
  367     end
  368     query.columns.each do |column|
  369       tags << hidden_field_tag("c[]", column.name, :id => nil)
  370     end
  371     if query.totalable_names.present?
  372       query.totalable_names.each do |name|
  373         tags << hidden_field_tag("t[]", name, :id => nil)
  374       end
  375     end
  376     if query.group_by.present?
  377       tags << hidden_field_tag("group_by", query.group_by, :id => nil)
  378     end
  379     if query.sort_criteria.present?
  380       tags << hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
  381     end
  382 
  383     tags
  384   end
  385 
  386   def query_hidden_sort_tag(query)
  387     hidden_field_tag("sort", query.sort_criteria.to_param, :id => nil)
  388   end
  389 
  390   # Returns the queries that are rendered in the sidebar
  391   def sidebar_queries(klass, project)
  392     klass.visible.global_or_on_project(@project).sorted.to_a
  393   end
  394 
  395   # Renders a group of queries
  396   def query_links(title, queries)
  397     return '' if queries.empty?
  398     # links to #index on issues/show
  399     url_params =
  400       if controller_name == 'issues'
  401         {:controller => 'issues', :action => 'index', :project_id => @project}
  402       else
  403         {}
  404       end
  405     content_tag('h3', title) + "\n" +
  406       content_tag(
  407         'ul',
  408         queries.collect {|query|
  409           css = +'query'
  410           clear_link = +''
  411           if query == @query
  412             css << ' selected'
  413             clear_link += link_to_clear_query
  414           end
  415           content_tag('li',
  416                       link_to(query.name,
  417                               url_params.merge(:query_id => query),
  418                               :class => css) +
  419                         clear_link.html_safe)
  420         }.join("\n").html_safe,
  421         :class => 'queries'
  422       ) + "\n"
  423   end
  424 
  425   def link_to_clear_query
  426     link_to(
  427       l(:button_clear),
  428       {:set_filter => 1, :sort => '', :project_id => @project},
  429       :class => 'icon-only icon-clear-query',
  430       :title => l(:button_clear)
  431     )
  432   end
  433 
  434   # Renders the list of queries for the sidebar
  435   def render_sidebar_queries(klass, project)
  436     queries = sidebar_queries(klass, project)
  437 
  438     out = ''.html_safe
  439     out << query_links(l(:label_my_queries), queries.select(&:is_private?))
  440     out << query_links(l(:label_query_plural), queries.reject(&:is_private?))
  441     out
  442   end
  443 end