From d570bc5cc5ad2e04dfae94c1b2bb09cc35f61891 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Lang Date: Tue, 17 Apr 2007 10:53:20 +0000 Subject: [PATCH] Custom fields for issues can now be used as filters on issue list. To use a custom field as a filter, check "Used as a filter" on the custom field edit screen. git-svn-id: http://redmine.rubyforge.org/svn/trunk@447 e93f8b46-1217-0410-a6f0-8f06a7374b81 --- app/controllers/projects_controller.rb | 4 +- app/models/query.rb | 69 +++++++++++++++----- app/views/custom_fields/_form.rhtml | 1 + app/views/queries/_filters.rhtml | 6 +- db/migrate/038_add_custom_field_is_filter.rb | 9 +++ lang/de.yml | 1 + lang/en.yml | 1 + lang/es.yml | 1 + lang/fr.yml | 1 + lang/it.yml | 1 + lang/ja.yml | 1 + lang/zh.yml | 1 + 12 files changed, 75 insertions(+), 21 deletions(-) create mode 100644 db/migrate/038_add_custom_field_is_filter.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index c3170bf5..a3e805a9 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -259,10 +259,10 @@ class ProjectsController < ApplicationController end if @query.valid? - @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement) + @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement) @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page'] @issues = Issue.find :all, :order => sort_clause, - :include => [ :assigned_to, :status, :tracker, :project, :priority ], + :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ], :conditions => @query.statement, :limit => @issue_pages.items_per_page, :offset => @issue_pages.current.offset diff --git a/app/models/query.rb b/app/models/query.rb index fcfad683..e26e6a68 100644 --- a/app/models/query.rb +++ b/app/models/query.rb @@ -1,5 +1,5 @@ # redMine - project management software -# Copyright (C) 2006 Jean-Philippe Lang +# Copyright (C) 2006-2007 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -48,6 +48,7 @@ class Query < ActiveRecord::Base :list_one_or_more => [ "*", "=" ], :date => [ "t+", "t+", "t", ">t-", " [ ">t-", " [ "=", "~", "!", "!~" ], :text => [ "~", "!~" ] } cattr_reader :operators_by_filter_type @@ -60,7 +61,7 @@ class Query < ActiveRecord::Base def validate filters.each_key do |field| - errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless + errors.add label_for(field), :activerecord_error_blank unless # filter requires one or more values (values_for(field) and !values_for(field).first.empty?) or # filter doesn't require any value @@ -87,6 +88,21 @@ class Query < ActiveRecord::Base unless @project.children.empty? @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.children.collect{|s| [s.name, s.id.to_s] } } end + @project.all_custom_fields.select(&:is_filter?).each do |field| + case field.field_format + when "string", "int" + options = { :type => :string, :order => 20 } + when "text" + options = { :type => :text, :order => 20 } + when "list" + options = { :type => :list_optional, :values => field.possible_values, :order => 20} + when "date" + options = { :type => :date, :order => 20 } + when "bool" + options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } + end + @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) + end # remove category filter if no category defined @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty? end @@ -126,6 +142,11 @@ class Query < ActiveRecord::Base has_filter?(field) ? filters[field][:values] : nil end + def label_for(field) + label = @available_filters[field][:name] if @available_filters.has_key?(field) + label ||= field.gsub(/\_id$/, "") + end + def statement sql = "1=1" if has_filter?("subproject_id") @@ -142,40 +163,56 @@ class Query < ActiveRecord::Base filters.each_key do |field| next if field == "subproject_id" v = values_for field - next unless v and !v.empty? + next unless v and !v.empty? + sql = sql + " AND " unless sql.empty? + sql << "(" + + if field =~ /^cf_(\d+)$/ + # custom field + db_table = CustomValue.table_name + db_field = "value" + sql << "#{db_table}.custom_field_id = #{$1} AND " + else + # regular field + db_table = Issue.table_name + db_field = field + end + case operator_for field when "=" - sql = sql + "#{Issue.table_name}.#{field} IN (" + v.each(&:to_i).join(",") + ")" + sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" when "!" - sql = sql + "#{Issue.table_name}.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")" + sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")" when "!*" - sql = sql + "#{Issue.table_name}.#{field} IS NULL" + sql = sql + "#{db_table}.#{db_field} IS NULL" when "*" - sql = sql + "#{Issue.table_name}.#{field} IS NOT NULL" + sql = sql + "#{db_table}.#{db_field} IS NOT NULL" when "o" sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id" when "c" sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id" when ">t-" - sql = sql + "#{Issue.table_name}.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i) + sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i) when "t+" - sql = sql + "#{Issue.table_name}.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" + sql = sql + "#{db_table}.#{db_field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'" when "  

<%= f.check_box :is_required %>

<%= f.check_box :is_for_all %>

+

<%= f.check_box :is_filter %>

<% when "UserCustomField" %>

<%= f.check_box :is_required %>

diff --git a/app/views/queries/_filters.rhtml b/app/views/queries/_filters.rhtml index 0314e30b..de45f92a 100644 --- a/app/views/queries/_filters.rhtml +++ b/app/views/queries/_filters.rhtml @@ -66,7 +66,7 @@ function toggle_multi_select(field) { id="tr_<%= field %>"> <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %> - + <%= select_tag "operators[#{field}]", options_for_select(operators_for_select(options[:type]), query.operator_for(field)), :id => "operators_#{field}", :onchange => "toggle_operator('#{field}');", :class => "select-small", :style => "vertical-align: top;" %> @@ -81,7 +81,7 @@ function toggle_multi_select(field) { <%= link_to_function image_tag('expand.png'), "toggle_multi_select('#{field}');" %> <% when :date, :date_past %> <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %> - <% when :text %> + <% when :string, :text %> <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %> <% end %> @@ -93,7 +93,7 @@ function toggle_multi_select(field) { <%= l(:label_filter_add) %>: -<%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %> +<%= select_tag 'add_filter_select', options_for_select([["",""]] + query.available_filters.sort{|a,b| a[1][:order]<=>b[1][:order]}.collect{|field| [ field[1][:name] || l(("field_"+field[0].to_s.gsub(/\_id$/, "")).to_sym), field[0]] unless query.has_filter?(field[0])}.compact), :onchange => "add_filter();", :class => "select-small" %> diff --git a/db/migrate/038_add_custom_field_is_filter.rb b/db/migrate/038_add_custom_field_is_filter.rb new file mode 100644 index 00000000..519ee0bd --- /dev/null +++ b/db/migrate/038_add_custom_field_is_filter.rb @@ -0,0 +1,9 @@ +class AddCustomFieldIsFilter < ActiveRecord::Migration + def self.up + add_column :custom_fields, :is_filter, :boolean, :null => false, :default => false + end + + def self.down + remove_column :custom_fields, :is_filter + end +end diff --git a/lang/de.yml b/lang/de.yml index 254dec4d..95ae4a63 100644 --- a/lang/de.yml +++ b/lang/de.yml @@ -147,6 +147,7 @@ field_hours: Stunden field_activity: Aktivität field_spent_on: Datum field_identifier: Identifier +field_is_filter: Used as a filter setting_app_title: Applikation Titel setting_app_subtitle: Applikation Untertitel diff --git a/lang/en.yml b/lang/en.yml index 3193e9c3..0e66a1fa 100644 --- a/lang/en.yml +++ b/lang/en.yml @@ -147,6 +147,7 @@ field_hours: Hours field_activity: Activity field_spent_on: Date field_identifier: Identifier +field_is_filter: Used as a filter setting_app_title: Application title setting_app_subtitle: Application subtitle diff --git a/lang/es.yml b/lang/es.yml index 393d473c..a245d05f 100644 --- a/lang/es.yml +++ b/lang/es.yml @@ -147,6 +147,7 @@ field_hours: Hours field_activity: Activity field_spent_on: Fecha field_identifier: Identifier +field_is_filter: Used as a filter setting_app_title: Título del aplicación setting_app_subtitle: Subtítulo del aplicación diff --git a/lang/fr.yml b/lang/fr.yml index 584c35dd..5cc05ef6 100644 --- a/lang/fr.yml +++ b/lang/fr.yml @@ -147,6 +147,7 @@ field_hours: Heures field_activity: Activité field_spent_on: Date field_identifier: Identifiant +field_is_filter: Utilisé comme filtre setting_app_title: Titre de l'application setting_app_subtitle: Sous-titre de l'application diff --git a/lang/it.yml b/lang/it.yml index a7379115..6beb963e 100644 --- a/lang/it.yml +++ b/lang/it.yml @@ -147,6 +147,7 @@ field_hours: Hours field_activity: Activity field_spent_on: Data field_identifier: Identifier +field_is_filter: Used as a filter setting_app_title: Titolo applicazione setting_app_subtitle: Sottotitolo applicazione diff --git a/lang/ja.yml b/lang/ja.yml index 5a476224..8bcf77fe 100644 --- a/lang/ja.yml +++ b/lang/ja.yml @@ -148,6 +148,7 @@ field_hours: 時間 field_activity: 活動 field_spent_on: 日付 field_identifier: 識別子 +field_is_filter: Used as a filter setting_app_title: アプリケーションのタイトル setting_app_subtitle: アプリケーションのサブタイトル diff --git a/lang/zh.yml b/lang/zh.yml index eede3868..be1c91f8 100644 --- a/lang/zh.yml +++ b/lang/zh.yml @@ -150,6 +150,7 @@ field_hours: Hours field_activity: 活动 field_spent_on: 日期 field_identifier: Identifier +field_is_filter: Used as a filter setting_app_title: 应用程序标题 setting_app_subtitle: 应用程序子标题