##// END OF EJS Templates
Custom fields for issues can now be used as filters on issue list....
Jean-Philippe Lang -
r444:d570bc5cc5ad
parent child
Show More
@@ -0,0 +1,9
1 class AddCustomFieldIsFilter < ActiveRecord::Migration
2 def self.up
3 add_column :custom_fields, :is_filter, :boolean, :null => false, :default => false
4 end
5
6 def self.down
7 remove_column :custom_fields, :is_filter
8 end
9 end
@@ -259,10 +259,10 class ProjectsController < ApplicationController
259 end
259 end
260
260
261 if @query.valid?
261 if @query.valid?
262 @issue_count = Issue.count(:include => [:status, :project], :conditions => @query.statement)
262 @issue_count = Issue.count(:include => [:status, :project, :custom_values], :conditions => @query.statement)
263 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
263 @issue_pages = Paginator.new self, @issue_count, @results_per_page, params['page']
264 @issues = Issue.find :all, :order => sort_clause,
264 @issues = Issue.find :all, :order => sort_clause,
265 :include => [ :assigned_to, :status, :tracker, :project, :priority ],
265 :include => [ :assigned_to, :status, :tracker, :project, :priority, :custom_values ],
266 :conditions => @query.statement,
266 :conditions => @query.statement,
267 :limit => @issue_pages.items_per_page,
267 :limit => @issue_pages.items_per_page,
268 :offset => @issue_pages.current.offset
268 :offset => @issue_pages.current.offset
@@ -1,5 +1,5
1 # redMine - project management software
1 # redMine - project management software
2 # Copyright (C) 2006 Jean-Philippe Lang
2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 #
3 #
4 # This program is free software; you can redistribute it and/or
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
@@ -48,6 +48,7 class Query < ActiveRecord::Base
48 :list_one_or_more => [ "*", "=" ],
48 :list_one_or_more => [ "*", "=" ],
49 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
49 :date => [ "<t+", ">t+", "t+", "t", ">t-", "<t-", "t-" ],
50 :date_past => [ ">t-", "<t-", "t-", "t" ],
50 :date_past => [ ">t-", "<t-", "t-", "t" ],
51 :string => [ "=", "~", "!", "!~" ],
51 :text => [ "~", "!~" ] }
52 :text => [ "~", "!~" ] }
52
53
53 cattr_reader :operators_by_filter_type
54 cattr_reader :operators_by_filter_type
@@ -60,7 +61,7 class Query < ActiveRecord::Base
60
61
61 def validate
62 def validate
62 filters.each_key do |field|
63 filters.each_key do |field|
63 errors.add field.gsub(/\_id$/, ""), :activerecord_error_blank unless
64 errors.add label_for(field), :activerecord_error_blank unless
64 # filter requires one or more values
65 # filter requires one or more values
65 (values_for(field) and !values_for(field).first.empty?) or
66 (values_for(field) and !values_for(field).first.empty?) or
66 # filter doesn't require any value
67 # filter doesn't require any value
@@ -87,6 +88,21 class Query < ActiveRecord::Base
87 unless @project.children.empty?
88 unless @project.children.empty?
88 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.children.collect{|s| [s.name, s.id.to_s] } }
89 @available_filters["subproject_id"] = { :type => :list_one_or_more, :order => 13, :values => @project.children.collect{|s| [s.name, s.id.to_s] } }
89 end
90 end
91 @project.all_custom_fields.select(&:is_filter?).each do |field|
92 case field.field_format
93 when "string", "int"
94 options = { :type => :string, :order => 20 }
95 when "text"
96 options = { :type => :text, :order => 20 }
97 when "list"
98 options = { :type => :list_optional, :values => field.possible_values, :order => 20}
99 when "date"
100 options = { :type => :date, :order => 20 }
101 when "bool"
102 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 }
103 end
104 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
105 end
90 # remove category filter if no category defined
106 # remove category filter if no category defined
91 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
107 @available_filters.delete "category_id" if @available_filters["category_id"][:values].empty?
92 end
108 end
@@ -126,6 +142,11 class Query < ActiveRecord::Base
126 has_filter?(field) ? filters[field][:values] : nil
142 has_filter?(field) ? filters[field][:values] : nil
127 end
143 end
128
144
145 def label_for(field)
146 label = @available_filters[field][:name] if @available_filters.has_key?(field)
147 label ||= field.gsub(/\_id$/, "")
148 end
149
129 def statement
150 def statement
130 sql = "1=1"
151 sql = "1=1"
131 if has_filter?("subproject_id")
152 if has_filter?("subproject_id")
@@ -142,40 +163,56 class Query < ActiveRecord::Base
142 filters.each_key do |field|
163 filters.each_key do |field|
143 next if field == "subproject_id"
164 next if field == "subproject_id"
144 v = values_for field
165 v = values_for field
145 next unless v and !v.empty?
166 next unless v and !v.empty?
167
146 sql = sql + " AND " unless sql.empty?
168 sql = sql + " AND " unless sql.empty?
169 sql << "("
170
171 if field =~ /^cf_(\d+)$/
172 # custom field
173 db_table = CustomValue.table_name
174 db_field = "value"
175 sql << "#{db_table}.custom_field_id = #{$1} AND "
176 else
177 # regular field
178 db_table = Issue.table_name
179 db_field = field
180 end
181
147 case operator_for field
182 case operator_for field
148 when "="
183 when "="
149 sql = sql + "#{Issue.table_name}.#{field} IN (" + v.each(&:to_i).join(",") + ")"
184 sql = sql + "#{db_table}.#{db_field} IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
150 when "!"
185 when "!"
151 sql = sql + "#{Issue.table_name}.#{field} NOT IN (" + v.each(&:to_i).join(",") + ")"
186 sql = sql + "#{db_table}.#{db_field} NOT IN (" + v.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
152 when "!*"
187 when "!*"
153 sql = sql + "#{Issue.table_name}.#{field} IS NULL"
188 sql = sql + "#{db_table}.#{db_field} IS NULL"
154 when "*"
189 when "*"
155 sql = sql + "#{Issue.table_name}.#{field} IS NOT NULL"
190 sql = sql + "#{db_table}.#{db_field} IS NOT NULL"
156 when "o"
191 when "o"
157 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
192 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
158 when "c"
193 when "c"
159 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
194 sql = sql + "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
160 when ">t-"
195 when ">t-"
161 sql = sql + "#{Issue.table_name}.#{field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i)
196 sql = sql + "#{db_table}.#{db_field} >= '%s'" % connection.quoted_date(Date.today - v.first.to_i)
162 when "<t-"
197 when "<t-"
163 sql = sql + "#{Issue.table_name}.#{field} <= '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
198 sql = sql + "#{db_table}.#{db_field} BETWEEN '#{connection.quoted_date(Date.new(0))}' AND '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
164 when "t-"
199 when "t-"
165 sql = sql + "#{Issue.table_name}.#{field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
200 sql = sql + "#{db_table}.#{db_field} = '" + (Date.today - v.first.to_i).strftime("%Y-%m-%d") + "'"
166 when ">t+"
201 when ">t+"
167 sql = sql + "#{Issue.table_name}.#{field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
202 sql = sql + "#{db_table}.#{db_field} >= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
168 when "<t+"
203 when "<t+"
169 sql = sql + "#{Issue.table_name}.#{field} <= '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
204 sql = sql + "#{db_table}.#{db_field} BETWEEN '#{connection.quoted_date(Date.new(0))}' AND '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
170 when "t+"
205 when "t+"
171 sql = sql + "#{Issue.table_name}.#{field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
206 sql = sql + "#{db_table}.#{db_field} = '" + (Date.today + v.first.to_i).strftime("%Y-%m-%d") + "'"
172 when "t"
207 when "t"
173 sql = sql + "#{Issue.table_name}.#{field} = '%s'" % connection.quoted_date(Date.today)
208 sql = sql + "#{db_table}.#{db_field} = '%s'" % connection.quoted_date(Date.today)
174 when "~"
209 when "~"
175 sql = sql + "#{Issue.table_name}.#{field} LIKE '%#{connection.quote_string(v.first)}%'"
210 sql = sql + "#{db_table}.#{db_field} LIKE '%#{connection.quote_string(v.first)}%'"
176 when "!~"
211 when "!~"
177 sql = sql + "#{Issue.table_name}.#{field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
212 sql = sql + "#{db_table}.#{db_field} NOT LIKE '%#{connection.quote_string(v.first)}%'"
178 end
213 end
214 sql << ")"
215
179 end if filters and valid?
216 end if filters and valid?
180 sql
217 sql
181 end
218 end
@@ -84,6 +84,7 when "IssueCustomField" %>
84 &nbsp;
84 &nbsp;
85 <p><%= f.check_box :is_required %></p>
85 <p><%= f.check_box :is_required %></p>
86 <p><%= f.check_box :is_for_all %></p>
86 <p><%= f.check_box :is_for_all %></p>
87 <p><%= f.check_box :is_filter %></p>
87
88
88 <% when "UserCustomField" %>
89 <% when "UserCustomField" %>
89 <p><%= f.check_box :is_required %></p>
90 <p><%= f.check_box :is_required %></p>
@@ -66,7 +66,7 function toggle_multi_select(field) {
66 <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>">
66 <tr <%= 'style="display:none;"' unless query.has_filter?(field) %> id="tr_<%= field %>">
67 <td valign="top" style="width:200px;">
67 <td valign="top" style="width:200px;">
68 <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
68 <%= check_box_tag 'fields[]', field, query.has_filter?(field), :onclick => "toggle_filter('#{field}');", :id => "cb_#{field}" %>
69 <label for="cb_<%= field %>"><%= l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
69 <label for="cb_<%= field %>"><%= filter[1][:name] || l(("field_"+field.to_s.gsub(/\_id$/, "")).to_sym) %></label>
70 </td>
70 </td>
71 <td valign="top" style="width:150px;">
71 <td valign="top" style="width:150px;">
72 <%= 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;" %>
72 <%= 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) {
81 <%= link_to_function image_tag('expand.png'), "toggle_multi_select('#{field}');" %>
81 <%= link_to_function image_tag('expand.png'), "toggle_multi_select('#{field}');" %>
82 <% when :date, :date_past %>
82 <% when :date, :date_past %>
83 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
83 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 3, :class => "select-small" %> <%= l(:label_day_plural) %>
84 <% when :text %>
84 <% when :string, :text %>
85 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
85 <%= text_field_tag "values[#{field}][]", query.values_for(field), :id => "values_#{field}", :size => 30, :class => "select-small" %>
86 <% end %>
86 <% end %>
87 </div>
87 </div>
@@ -93,7 +93,7 function toggle_multi_select(field) {
93 </td>
93 </td>
94 <td align="right" valign="top">
94 <td align="right" valign="top">
95 <%= l(:label_filter_add) %>:
95 <%= l(:label_filter_add) %>:
96 <%= 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" %>
96 <%= 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" %>
97 </td>
97 </td>
98 </tr>
98 </tr>
99 </table>
99 </table>
@@ -147,6 +147,7 field_hours: Stunden
147 field_activity: Aktivität
147 field_activity: Aktivität
148 field_spent_on: Datum
148 field_spent_on: Datum
149 field_identifier: Identifier
149 field_identifier: Identifier
150 field_is_filter: Used as a filter
150
151
151 setting_app_title: Applikation Titel
152 setting_app_title: Applikation Titel
152 setting_app_subtitle: Applikation Untertitel
153 setting_app_subtitle: Applikation Untertitel
@@ -147,6 +147,7 field_hours: Hours
147 field_activity: Activity
147 field_activity: Activity
148 field_spent_on: Date
148 field_spent_on: Date
149 field_identifier: Identifier
149 field_identifier: Identifier
150 field_is_filter: Used as a filter
150
151
151 setting_app_title: Application title
152 setting_app_title: Application title
152 setting_app_subtitle: Application subtitle
153 setting_app_subtitle: Application subtitle
@@ -147,6 +147,7 field_hours: Hours
147 field_activity: Activity
147 field_activity: Activity
148 field_spent_on: Fecha
148 field_spent_on: Fecha
149 field_identifier: Identifier
149 field_identifier: Identifier
150 field_is_filter: Used as a filter
150
151
151 setting_app_title: Título del aplicación
152 setting_app_title: Título del aplicación
152 setting_app_subtitle: Subtítulo del aplicación
153 setting_app_subtitle: Subtítulo del aplicación
@@ -147,6 +147,7 field_hours: Heures
147 field_activity: Activité
147 field_activity: Activité
148 field_spent_on: Date
148 field_spent_on: Date
149 field_identifier: Identifiant
149 field_identifier: Identifiant
150 field_is_filter: Utilisé comme filtre
150
151
151 setting_app_title: Titre de l'application
152 setting_app_title: Titre de l'application
152 setting_app_subtitle: Sous-titre de l'application
153 setting_app_subtitle: Sous-titre de l'application
@@ -147,6 +147,7 field_hours: Hours
147 field_activity: Activity
147 field_activity: Activity
148 field_spent_on: Data
148 field_spent_on: Data
149 field_identifier: Identifier
149 field_identifier: Identifier
150 field_is_filter: Used as a filter
150
151
151 setting_app_title: Titolo applicazione
152 setting_app_title: Titolo applicazione
152 setting_app_subtitle: Sottotitolo applicazione
153 setting_app_subtitle: Sottotitolo applicazione
@@ -148,6 +148,7 field_hours: 時間
148 field_activity: 活動
148 field_activity: 活動
149 field_spent_on: 日付
149 field_spent_on: 日付
150 field_identifier: 識別子
150 field_identifier: 識別子
151 field_is_filter: Used as a filter
151
152
152 setting_app_title: アプリケーションのタイトル
153 setting_app_title: アプリケーションのタイトル
153 setting_app_subtitle: アプリケーションのサブタイトル
154 setting_app_subtitle: アプリケーションのサブタイトル
@@ -150,6 +150,7 field_hours: Hours
150 field_activity: 活动
150 field_activity: 活动
151 field_spent_on: 日期
151 field_spent_on: 日期
152 field_identifier: Identifier
152 field_identifier: Identifier
153 field_is_filter: Used as a filter
153
154
154 setting_app_title: 应用程序标题
155 setting_app_title: 应用程序标题
155 setting_app_subtitle: 应用程序子标题
156 setting_app_subtitle: 应用程序子标题
General Comments 0
You need to be logged in to leave comments. Login now