Mercurial > hg > soundsoftware-site
comparison app/models/.svn/text-base/query.rb.svn-base @ 441:cbce1fd3b1b7 redmine-1.2
Update to Redmine 1.2-stable branch (Redmine SVN rev 6000)
author | Chris Cannam |
---|---|
date | Mon, 06 Jun 2011 14:24:13 +0100 |
parents | 051f544170fe |
children | 0c939c159af4 |
comparison
equal
deleted
inserted
replaced
245:051f544170fe | 441:cbce1fd3b1b7 |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2008 Jean-Philippe Lang | 2 # Copyright (C) 2006-2011 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 |
6 # as published by the Free Software Foundation; either version 2 | 6 # as published by the Free Software Foundation; either version 2 |
7 # of the License, or (at your option) any later version. | 7 # of the License, or (at your option) any later version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, | 9 # This program is distributed in the hope that it will be useful, |
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of | 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 # GNU General Public License for more details. | 12 # GNU General Public License for more details. |
13 # | 13 # |
14 # You should have received a copy of the GNU General Public License | 14 # You should have received a copy of the GNU General Public License |
15 # along with this program; if not, write to the Free Software | 15 # along with this program; if not, write to the Free Software |
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 |
18 class QueryColumn | 18 class QueryColumn |
19 attr_accessor :name, :sortable, :groupable, :default_order | 19 attr_accessor :name, :sortable, :groupable, :default_order |
20 include Redmine::I18n | 20 include Redmine::I18n |
21 | 21 |
22 def initialize(name, options={}) | 22 def initialize(name, options={}) |
23 self.name = name | 23 self.name = name |
24 self.sortable = options[:sortable] | 24 self.sortable = options[:sortable] |
25 self.groupable = options[:groupable] || false | 25 self.groupable = options[:groupable] || false |
26 if groupable == true | 26 if groupable == true |
27 self.groupable = name.to_s | 27 self.groupable = name.to_s |
28 end | 28 end |
29 self.default_order = options[:default_order] | 29 self.default_order = options[:default_order] |
30 @caption_key = options[:caption] || "field_#{name}" | 30 @caption_key = options[:caption] || "field_#{name}" |
31 end | 31 end |
32 | 32 |
33 def caption | 33 def caption |
34 l(@caption_key) | 34 l(@caption_key) |
35 end | 35 end |
36 | 36 |
37 # Returns true if the column is sortable, otherwise false | 37 # Returns true if the column is sortable, otherwise false |
38 def sortable? | 38 def sortable? |
39 !sortable.nil? | 39 !sortable.nil? |
40 end | 40 end |
41 | 41 |
42 def value(issue) | 42 def value(issue) |
43 issue.send name | 43 issue.send name |
44 end | |
45 | |
46 def css_classes | |
47 name | |
44 end | 48 end |
45 end | 49 end |
46 | 50 |
47 class QueryCustomFieldColumn < QueryColumn | 51 class QueryCustomFieldColumn < QueryColumn |
48 | 52 |
53 self.groupable = custom_field.order_statement | 57 self.groupable = custom_field.order_statement |
54 end | 58 end |
55 self.groupable ||= false | 59 self.groupable ||= false |
56 @cf = custom_field | 60 @cf = custom_field |
57 end | 61 end |
58 | 62 |
59 def caption | 63 def caption |
60 @cf.name | 64 @cf.name |
61 end | 65 end |
62 | 66 |
63 def custom_field | 67 def custom_field |
64 @cf | 68 @cf |
65 end | 69 end |
66 | 70 |
67 def value(issue) | 71 def value(issue) |
68 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id} | 72 cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id} |
69 cv && @cf.cast_value(cv.value) | 73 cv && @cf.cast_value(cv.value) |
70 end | 74 end |
75 | |
76 def css_classes | |
77 @css_classes ||= "#{name} #{@cf.field_format}" | |
78 end | |
71 end | 79 end |
72 | 80 |
73 class Query < ActiveRecord::Base | 81 class Query < ActiveRecord::Base |
74 class StatementInvalid < ::ActiveRecord::StatementInvalid | 82 class StatementInvalid < ::ActiveRecord::StatementInvalid |
75 end | 83 end |
76 | 84 |
77 belongs_to :project | 85 belongs_to :project |
78 belongs_to :user | 86 belongs_to :user |
79 serialize :filters | 87 serialize :filters |
80 serialize :column_names | 88 serialize :column_names |
81 serialize :sort_criteria, Array | 89 serialize :sort_criteria, Array |
82 | 90 |
83 attr_protected :project_id, :user_id | 91 attr_protected :project_id, :user_id |
84 | 92 |
85 validates_presence_of :name, :on => :save | 93 validates_presence_of :name, :on => :save |
86 validates_length_of :name, :maximum => 255 | 94 validates_length_of :name, :maximum => 255 |
87 | 95 |
88 @@operators = { "=" => :label_equals, | 96 @@operators = { "=" => :label_equals, |
89 "!" => :label_not_equals, | 97 "!" => :label_not_equals, |
90 "o" => :label_open_issues, | 98 "o" => :label_open_issues, |
91 "c" => :label_closed_issues, | 99 "c" => :label_closed_issues, |
92 "!*" => :label_none, | 100 "!*" => :label_none, |
93 "*" => :label_all, | 101 "*" => :label_all, |
103 "t-" => :label_ago, | 111 "t-" => :label_ago, |
104 "~" => :label_contains, | 112 "~" => :label_contains, |
105 "!~" => :label_not_contains } | 113 "!~" => :label_not_contains } |
106 | 114 |
107 cattr_reader :operators | 115 cattr_reader :operators |
108 | 116 |
109 @@operators_by_filter_type = { :list => [ "=", "!" ], | 117 @@operators_by_filter_type = { :list => [ "=", "!" ], |
110 :list_status => [ "o", "=", "!", "c", "*" ], | 118 :list_status => [ "o", "=", "!", "c", "*" ], |
111 :list_optional => [ "=", "!", "!*", "*" ], | 119 :list_optional => [ "=", "!", "!*", "*" ], |
112 :list_subprojects => [ "*", "!*", "=" ], | 120 :list_subprojects => [ "*", "!*", "=" ], |
113 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ], | 121 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ], |
135 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), | 143 QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"), |
136 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), | 144 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true), |
137 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), | 145 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'), |
138 ] | 146 ] |
139 cattr_reader :available_columns | 147 cattr_reader :available_columns |
140 | 148 |
141 def initialize(attributes = nil) | 149 def initialize(attributes = nil) |
142 super attributes | 150 super attributes |
143 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } | 151 self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} } |
144 end | 152 end |
145 | 153 |
146 def after_initialize | 154 def after_initialize |
147 # Store the fact that project is nil (used in #editable_by?) | 155 # Store the fact that project is nil (used in #editable_by?) |
148 @is_for_all = project.nil? | 156 @is_for_all = project.nil? |
149 end | 157 end |
150 | 158 |
151 def validate | 159 def validate |
152 filters.each_key do |field| | 160 filters.each_key do |field| |
153 errors.add label_for(field), :blank unless | 161 errors.add label_for(field), :blank unless |
154 # filter requires one or more values | 162 # filter requires one or more values |
155 (values_for(field) and !values_for(field).first.blank?) or | 163 (values_for(field) and !values_for(field).first.blank?) or |
156 # filter doesn't require any value | 164 # filter doesn't require any value |
157 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) | 165 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field) |
158 end if filters | 166 end if filters |
159 end | 167 end |
160 | 168 |
161 def editable_by?(user) | 169 def editable_by?(user) |
162 return false unless user | 170 return false unless user |
163 # Admin can edit them all and regular users can edit their private queries | 171 # Admin can edit them all and regular users can edit their private queries |
164 return true if user.admin? || (!is_public && self.user_id == user.id) | 172 return true if user.admin? || (!is_public && self.user_id == user.id) |
165 # Members can not edit public queries that are for all project (only admin is allowed to) | 173 # Members can not edit public queries that are for all project (only admin is allowed to) |
166 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) | 174 is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project) |
167 end | 175 end |
168 | 176 |
169 def available_filters | 177 def available_filters |
170 return @available_filters if @available_filters | 178 return @available_filters if @available_filters |
171 | 179 |
172 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers | 180 trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers |
173 | 181 |
174 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, | 182 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } }, |
175 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, | 183 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } }, |
176 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } }, | 184 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } }, |
177 "subject" => { :type => :text, :order => 8 }, | 185 "subject" => { :type => :text, :order => 8 }, |
178 "created_on" => { :type => :date_past, :order => 9 }, | 186 "created_on" => { :type => :date_past, :order => 9 }, |
179 "updated_on" => { :type => :date_past, :order => 10 }, | 187 "updated_on" => { :type => :date_past, :order => 10 }, |
180 "start_date" => { :type => :date, :order => 11 }, | 188 "start_date" => { :type => :date, :order => 11 }, |
181 "due_date" => { :type => :date, :order => 12 }, | 189 "due_date" => { :type => :date, :order => 12 }, |
182 "estimated_hours" => { :type => :integer, :order => 13 }, | 190 "estimated_hours" => { :type => :integer, :order => 13 }, |
183 "done_ratio" => { :type => :integer, :order => 14 }} | 191 "done_ratio" => { :type => :integer, :order => 14 }} |
184 | 192 |
185 user_values = [] | 193 user_values = [] |
186 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? | 194 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged? |
187 if project | 195 if project |
188 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } | 196 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] } |
189 else | 197 else |
190 all_projects = Project.visible.all | 198 all_projects = Project.visible.all |
191 if all_projects.any? | 199 if all_projects.any? |
192 # members of visible projects | 200 # members of visible projects |
193 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] } | 201 user_values += User.active.find(:all, :conditions => ["#{User.table_name}.id IN (SELECT DISTINCT user_id FROM members WHERE project_id IN (?))", all_projects.collect(&:id)]).sort.collect{|s| [s.name, s.id.to_s] } |
194 | 202 |
195 # project filter | 203 # project filter |
196 project_values = [] | 204 project_values = [] |
197 Project.project_tree(all_projects) do |p, level| | 205 Project.project_tree(all_projects) do |p, level| |
198 prefix = (level > 0 ? ('--' * level + ' ') : '') | 206 prefix = (level > 0 ? ('--' * level + ' ') : '') |
199 project_values << ["#{prefix}#{p.name}", p.id.to_s] | 207 project_values << ["#{prefix}#{p.name}", p.id.to_s] |
207 group_values = Group.all.collect {|g| [g.name, g.id.to_s] } | 215 group_values = Group.all.collect {|g| [g.name, g.id.to_s] } |
208 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? | 216 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty? |
209 | 217 |
210 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } | 218 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] } |
211 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty? | 219 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty? |
212 | 220 |
213 if User.current.logged? | 221 if User.current.logged? |
214 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] } | 222 @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] } |
215 end | 223 end |
216 | 224 |
217 if project | 225 if project |
218 # project specific filters | 226 # project specific filters |
219 unless @project.issue_categories.empty? | 227 categories = @project.issue_categories.all |
220 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } } | 228 unless categories.empty? |
221 end | 229 @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => categories.collect{|s| [s.name, s.id.to_s] } } |
222 unless @project.shared_versions.empty? | 230 end |
223 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } | 231 versions = @project.shared_versions.all |
224 end | 232 unless versions.empty? |
225 unless @project.descendants.active.empty? | 233 @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } } |
226 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } } | 234 end |
235 unless @project.leaf? | |
236 subprojects = @project.descendants.visible.all | |
237 unless subprojects.empty? | |
238 @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => subprojects.collect{|s| [s.name, s.id.to_s] } } | |
239 end | |
227 end | 240 end |
228 add_custom_fields_filters(@project.all_issue_custom_fields) | 241 add_custom_fields_filters(@project.all_issue_custom_fields) |
229 else | 242 else |
230 # global filters for cross project issue list | 243 # global filters for cross project issue list |
231 system_shared_versions = Version.visible.find_all_by_sharing('system') | 244 system_shared_versions = Version.visible.find_all_by_sharing('system') |
234 end | 247 end |
235 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) | 248 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true})) |
236 end | 249 end |
237 @available_filters | 250 @available_filters |
238 end | 251 end |
239 | 252 |
240 def add_filter(field, operator, values) | 253 def add_filter(field, operator, values) |
241 # values must be an array | 254 # values must be an array |
242 return unless values and values.is_a? Array # and !values.first.empty? | 255 return unless values and values.is_a? Array # and !values.first.empty? |
243 # check if field is defined as an available filter | 256 # check if field is defined as an available filter |
244 if available_filters.has_key? field | 257 if available_filters.has_key? field |
249 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator | 262 # filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator |
250 #end | 263 #end |
251 filters[field] = {:operator => operator, :values => values } | 264 filters[field] = {:operator => operator, :values => values } |
252 end | 265 end |
253 end | 266 end |
254 | 267 |
255 def add_short_filter(field, expression) | 268 def add_short_filter(field, expression) |
256 return unless expression | 269 return unless expression |
257 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first | 270 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first |
258 add_filter field, (parms[0] || "="), [parms[1] || ""] | 271 add_filter field, (parms[0] || "="), [parms[1] || ""] |
259 end | 272 end |
264 fields.each do |field| | 277 fields.each do |field| |
265 add_filter(field, operators[field], values[field]) | 278 add_filter(field, operators[field], values[field]) |
266 end | 279 end |
267 end | 280 end |
268 end | 281 end |
269 | 282 |
270 def has_filter?(field) | 283 def has_filter?(field) |
271 filters and filters[field] | 284 filters and filters[field] |
272 end | 285 end |
273 | 286 |
274 def operator_for(field) | 287 def operator_for(field) |
275 has_filter?(field) ? filters[field][:operator] : nil | 288 has_filter?(field) ? filters[field][:operator] : nil |
276 end | 289 end |
277 | 290 |
278 def values_for(field) | 291 def values_for(field) |
279 has_filter?(field) ? filters[field][:values] : nil | 292 has_filter?(field) ? filters[field][:values] : nil |
280 end | 293 end |
281 | 294 |
282 def label_for(field) | 295 def label_for(field) |
283 label = available_filters[field][:name] if available_filters.has_key?(field) | 296 label = available_filters[field][:name] if available_filters.has_key?(field) |
284 label ||= field.gsub(/\_id$/, "") | 297 label ||= field.gsub(/\_id$/, "") |
285 end | 298 end |
286 | 299 |
288 return @available_columns if @available_columns | 301 return @available_columns if @available_columns |
289 @available_columns = Query.available_columns | 302 @available_columns = Query.available_columns |
290 @available_columns += (project ? | 303 @available_columns += (project ? |
291 project.all_issue_custom_fields : | 304 project.all_issue_custom_fields : |
292 IssueCustomField.find(:all) | 305 IssueCustomField.find(:all) |
293 ).collect {|cf| QueryCustomFieldColumn.new(cf) } | 306 ).collect {|cf| QueryCustomFieldColumn.new(cf) } |
294 end | 307 end |
295 | 308 |
296 def self.available_columns=(v) | 309 def self.available_columns=(v) |
297 self.available_columns = (v) | 310 self.available_columns = (v) |
298 end | 311 end |
299 | 312 |
300 def self.add_available_column(column) | 313 def self.add_available_column(column) |
301 self.available_columns << (column) if column.is_a?(QueryColumn) | 314 self.available_columns << (column) if column.is_a?(QueryColumn) |
302 end | 315 end |
303 | 316 |
304 # Returns an array of columns that can be used to group the results | 317 # Returns an array of columns that can be used to group the results |
305 def groupable_columns | 318 def groupable_columns |
306 available_columns.select {|c| c.groupable} | 319 available_columns.select {|c| c.groupable} |
307 end | 320 end |
308 | 321 |
311 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| | 324 {'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column| |
312 h[column.name.to_s] = column.sortable | 325 h[column.name.to_s] = column.sortable |
313 h | 326 h |
314 }) | 327 }) |
315 end | 328 end |
316 | 329 |
317 def columns | 330 def columns |
318 if has_default_columns? | 331 if has_default_columns? |
319 available_columns.select do |c| | 332 available_columns.select do |c| |
320 # Adds the project column by default for cross-project lists | 333 # Adds the project column by default for cross-project lists |
321 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?) | 334 Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?) |
323 else | 336 else |
324 # preserve the column_names order | 337 # preserve the column_names order |
325 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact | 338 column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact |
326 end | 339 end |
327 end | 340 end |
328 | 341 |
329 def column_names=(names) | 342 def column_names=(names) |
330 if names | 343 if names |
331 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } | 344 names = names.select {|n| n.is_a?(Symbol) || !n.blank? } |
332 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } | 345 names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } |
333 # Set column_names to nil if default columns | 346 # Set column_names to nil if default columns |
335 names = nil | 348 names = nil |
336 end | 349 end |
337 end | 350 end |
338 write_attribute(:column_names, names) | 351 write_attribute(:column_names, names) |
339 end | 352 end |
340 | 353 |
341 def has_column?(column) | 354 def has_column?(column) |
342 column_names && column_names.include?(column.name) | 355 column_names && column_names.include?(column.name) |
343 end | 356 end |
344 | 357 |
345 def has_default_columns? | 358 def has_default_columns? |
346 column_names.nil? || column_names.empty? | 359 column_names.nil? || column_names.empty? |
347 end | 360 end |
348 | 361 |
349 def sort_criteria=(arg) | 362 def sort_criteria=(arg) |
350 c = [] | 363 c = [] |
351 if arg.is_a?(Hash) | 364 if arg.is_a?(Hash) |
352 arg = arg.keys.sort.collect {|k| arg[k]} | 365 arg = arg.keys.sort.collect {|k| arg[k]} |
353 end | 366 end |
354 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} | 367 c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']} |
355 write_attribute(:sort_criteria, c) | 368 write_attribute(:sort_criteria, c) |
356 end | 369 end |
357 | 370 |
358 def sort_criteria | 371 def sort_criteria |
359 read_attribute(:sort_criteria) || [] | 372 read_attribute(:sort_criteria) || [] |
360 end | 373 end |
361 | 374 |
362 def sort_criteria_key(arg) | 375 def sort_criteria_key(arg) |
363 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first | 376 sort_criteria && sort_criteria[arg] && sort_criteria[arg].first |
364 end | 377 end |
365 | 378 |
366 def sort_criteria_order(arg) | 379 def sort_criteria_order(arg) |
367 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last | 380 sort_criteria && sort_criteria[arg] && sort_criteria[arg].last |
368 end | 381 end |
369 | 382 |
370 # Returns the SQL sort order that should be prepended for grouping | 383 # Returns the SQL sort order that should be prepended for grouping |
371 def group_by_sort_order | 384 def group_by_sort_order |
372 if grouped? && (column = group_by_column) | 385 if grouped? && (column = group_by_column) |
373 column.sortable.is_a?(Array) ? | 386 column.sortable.is_a?(Array) ? |
374 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : | 387 column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') : |
375 "#{column.sortable} #{column.default_order}" | 388 "#{column.sortable} #{column.default_order}" |
376 end | 389 end |
377 end | 390 end |
378 | 391 |
379 # Returns true if the query is a grouped query | 392 # Returns true if the query is a grouped query |
380 def grouped? | 393 def grouped? |
381 !group_by_column.nil? | 394 !group_by_column.nil? |
382 end | 395 end |
383 | 396 |
384 def group_by_column | 397 def group_by_column |
385 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} | 398 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by} |
386 end | 399 end |
387 | 400 |
388 def group_by_statement | 401 def group_by_statement |
389 group_by_column.try(:groupable) | 402 group_by_column.try(:groupable) |
390 end | 403 end |
391 | 404 |
392 def project_statement | 405 def project_statement |
393 project_clauses = [] | 406 project_clauses = [] |
394 if project && !@project.descendants.active.empty? | 407 if project && !@project.descendants.active.empty? |
395 ids = [project.id] | 408 ids = [project.id] |
396 if has_filter?("subproject_id") | 409 if has_filter?("subproject_id") |
409 end | 422 end |
410 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') | 423 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',') |
411 elsif project | 424 elsif project |
412 project_clauses << "#{Project.table_name}.id = %d" % project.id | 425 project_clauses << "#{Project.table_name}.id = %d" % project.id |
413 end | 426 end |
414 project_clauses << Project.allowed_to_condition(User.current, :view_issues) | 427 project_clauses.any? ? project_clauses.join(' AND ') : nil |
415 project_clauses.join(' AND ') | |
416 end | 428 end |
417 | 429 |
418 def statement | 430 def statement |
419 # filters clauses | 431 # filters clauses |
420 filters_clauses = [] | 432 filters_clauses = [] |
421 filters.each_key do |field| | 433 filters.each_key do |field| |
422 next if field == "subproject_id" | 434 next if field == "subproject_id" |
423 v = values_for(field).clone | 435 v = values_for(field).clone |
424 next unless v and !v.empty? | 436 next unless v and !v.empty? |
425 operator = operator_for(field) | 437 operator = operator_for(field) |
426 | 438 |
427 # "me" value subsitution | 439 # "me" value subsitution |
428 if %w(assigned_to_id author_id watcher_id).include?(field) | 440 if %w(assigned_to_id author_id watcher_id).include?(field) |
429 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") | 441 v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me") |
430 end | 442 end |
431 | 443 |
432 sql = '' | 444 sql = '' |
433 if field =~ /^cf_(\d+)$/ | 445 if field =~ /^cf_(\d+)$/ |
434 # custom field | 446 # custom field |
435 db_table = CustomValue.table_name | 447 db_table = CustomValue.table_name |
436 db_field = 'value' | 448 db_field = 'value' |
458 if group && group.user_ids.present? | 470 if group && group.user_ids.present? |
459 user_ids << group.user_ids | 471 user_ids << group.user_ids |
460 end | 472 end |
461 user_ids.flatten.uniq.compact | 473 user_ids.flatten.uniq.compact |
462 }.sort.collect(&:to_s) | 474 }.sort.collect(&:to_s) |
463 | 475 |
464 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' | 476 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')' |
465 | 477 |
466 elsif field == "assigned_to_role" # named field | 478 elsif field == "assigned_to_role" # named field |
467 if operator == "*" # Any Role | 479 if operator == "*" # Any Role |
468 roles = Role.givable | 480 roles = Role.givable |
472 operator = '!' # Override the operator since we want to find by assigned_to | 484 operator = '!' # Override the operator since we want to find by assigned_to |
473 else | 485 else |
474 roles = Role.givable.find_all_by_id(v) | 486 roles = Role.givable.find_all_by_id(v) |
475 end | 487 end |
476 roles ||= [] | 488 roles ||= [] |
477 | 489 |
478 members_of_roles = roles.inject([]) {|user_ids, role| | 490 members_of_roles = roles.inject([]) {|user_ids, role| |
479 if role && role.members | 491 if role && role.members |
480 user_ids << role.members.collect(&:user_id) | 492 user_ids << role.members.collect(&:user_id) |
481 end | 493 end |
482 user_ids.flatten.uniq.compact | 494 user_ids.flatten.uniq.compact |
483 }.sort.collect(&:to_s) | 495 }.sort.collect(&:to_s) |
484 | 496 |
485 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')' | 497 sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')' |
486 else | 498 else |
487 # regular field | 499 # regular field |
488 db_table = Issue.table_name | 500 db_table = Issue.table_name |
489 db_field = field | 501 db_field = field |
490 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')' | 502 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')' |
491 end | 503 end |
492 filters_clauses << sql | 504 filters_clauses << sql |
493 | 505 |
494 end if filters and valid? | 506 end if filters and valid? |
495 | 507 |
496 (filters_clauses << project_statement).join(' AND ') | 508 filters_clauses << project_statement |
497 end | 509 filters_clauses.reject!(&:blank?) |
498 | 510 |
511 filters_clauses.any? ? filters_clauses.join(' AND ') : nil | |
512 end | |
513 | |
499 # Returns the issue count | 514 # Returns the issue count |
500 def issue_count | 515 def issue_count |
501 Issue.count(:include => [:status, :project], :conditions => statement) | 516 Issue.count(:include => [:status, :project], :conditions => statement) |
502 rescue ::ActiveRecord::StatementInvalid => e | 517 rescue ::ActiveRecord::StatementInvalid => e |
503 raise StatementInvalid.new(e.message) | 518 raise StatementInvalid.new(e.message) |
504 end | 519 end |
505 | 520 |
506 # Returns the issue count by group or nil if query is not grouped | 521 # Returns the issue count by group or nil if query is not grouped |
507 def issue_count_by_group | 522 def issue_count_by_group |
508 r = nil | 523 r = nil |
509 if grouped? | 524 if grouped? |
510 begin | 525 begin |
511 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value | 526 # Rails will raise an (unexpected) RecordNotFound if there's only a nil group value |
512 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) | 527 r = Issue.visible.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement) |
513 rescue ActiveRecord::RecordNotFound | 528 rescue ActiveRecord::RecordNotFound |
514 r = {nil => issue_count} | 529 r = {nil => issue_count} |
515 end | 530 end |
516 c = group_by_column | 531 c = group_by_column |
517 if c.is_a?(QueryCustomFieldColumn) | 532 if c.is_a?(QueryCustomFieldColumn) |
520 end | 535 end |
521 r | 536 r |
522 rescue ::ActiveRecord::StatementInvalid => e | 537 rescue ::ActiveRecord::StatementInvalid => e |
523 raise StatementInvalid.new(e.message) | 538 raise StatementInvalid.new(e.message) |
524 end | 539 end |
525 | 540 |
526 # Returns the issues | 541 # Returns the issues |
527 # Valid options are :order, :offset, :limit, :include, :conditions | 542 # Valid options are :order, :offset, :limit, :include, :conditions |
528 def issues(options={}) | 543 def issues(options={}) |
529 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') | 544 order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',') |
530 order_option = nil if order_option.blank? | 545 order_option = nil if order_option.blank? |
531 | 546 |
532 Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, | 547 Issue.visible.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq, |
533 :conditions => Query.merge_conditions(statement, options[:conditions]), | 548 :conditions => Query.merge_conditions(statement, options[:conditions]), |
534 :order => order_option, | 549 :order => order_option, |
535 :limit => options[:limit], | 550 :limit => options[:limit], |
536 :offset => options[:offset] | 551 :offset => options[:offset] |
537 rescue ::ActiveRecord::StatementInvalid => e | 552 rescue ::ActiveRecord::StatementInvalid => e |
539 end | 554 end |
540 | 555 |
541 # Returns the journals | 556 # Returns the journals |
542 # Valid options are :order, :offset, :limit | 557 # Valid options are :order, :offset, :limit |
543 def journals(options={}) | 558 def journals(options={}) |
544 Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], | 559 Journal.visible.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}], |
545 :conditions => statement, | 560 :conditions => statement, |
546 :order => options[:order], | 561 :order => options[:order], |
547 :limit => options[:limit], | 562 :limit => options[:limit], |
548 :offset => options[:offset] | 563 :offset => options[:offset] |
549 rescue ::ActiveRecord::StatementInvalid => e | 564 rescue ::ActiveRecord::StatementInvalid => e |
550 raise StatementInvalid.new(e.message) | 565 raise StatementInvalid.new(e.message) |
551 end | 566 end |
552 | 567 |
553 # Returns the versions | 568 # Returns the versions |
554 # Valid options are :conditions | 569 # Valid options are :conditions |
555 def versions(options={}) | 570 def versions(options={}) |
556 Version.find :all, :include => :project, | 571 Version.visible.find :all, :include => :project, |
557 :conditions => Query.merge_conditions(project_statement, options[:conditions]) | 572 :conditions => Query.merge_conditions(project_statement, options[:conditions]) |
558 rescue ::ActiveRecord::StatementInvalid => e | 573 rescue ::ActiveRecord::StatementInvalid => e |
559 raise StatementInvalid.new(e.message) | 574 raise StatementInvalid.new(e.message) |
560 end | 575 end |
561 | 576 |
562 private | 577 private |
563 | 578 |
564 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ | 579 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+ |
565 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) | 580 def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false) |
566 sql = '' | 581 sql = '' |
567 case operator | 582 case operator |
568 when "=" | 583 when "=" |
606 when "t+" | 621 when "t+" |
607 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i) | 622 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i) |
608 when "t" | 623 when "t" |
609 sql = date_range_clause(db_table, db_field, 0, 0) | 624 sql = date_range_clause(db_table, db_field, 0, 0) |
610 when "w" | 625 when "w" |
611 from = l(:general_first_day_of_week) == '7' ? | 626 first_day_of_week = l(:general_first_day_of_week).to_i |
612 # week starts on sunday | 627 day_of_week = Date.today.cwday |
613 ((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) : | 628 days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week) |
614 # week starts on monday (Rails default) | 629 sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6) |
615 Time.now.at_beginning_of_week | |
616 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)] | |
617 when "~" | 630 when "~" |
618 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" | 631 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" |
619 when "!~" | 632 when "!~" |
620 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" | 633 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'" |
621 end | 634 end |
622 | 635 |
623 return sql | 636 return sql |
624 end | 637 end |
625 | 638 |
626 def add_custom_fields_filters(custom_fields) | 639 def add_custom_fields_filters(custom_fields) |
627 @available_filters ||= {} | 640 @available_filters ||= {} |
628 | 641 |
629 custom_fields.select(&:is_filter?).each do |field| | 642 custom_fields.select(&:is_filter?).each do |field| |
630 case field.field_format | 643 case field.field_format |
631 when "text" | 644 when "text" |
632 options = { :type => :text, :order => 20 } | 645 options = { :type => :text, :order => 20 } |
633 when "list" | 646 when "list" |
634 options = { :type => :list_optional, :values => field.possible_values, :order => 20} | 647 options = { :type => :list_optional, :values => field.possible_values, :order => 20} |
635 when "date" | 648 when "date" |
636 options = { :type => :date, :order => 20 } | 649 options = { :type => :date, :order => 20 } |
637 when "bool" | 650 when "bool" |
638 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } | 651 options = { :type => :list, :values => [[l(:general_text_yes), "1"], [l(:general_text_no), "0"]], :order => 20 } |
652 when "user", "version" | |
653 next unless project | |
654 options = { :type => :list_optional, :values => field.possible_values_options(project), :order => 20} | |
639 else | 655 else |
640 options = { :type => :string, :order => 20 } | 656 options = { :type => :string, :order => 20 } |
641 end | 657 end |
642 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) | 658 @available_filters["cf_#{field.id}"] = options.merge({ :name => field.name }) |
643 end | 659 end |
644 end | 660 end |
645 | 661 |
646 # Returns a SQL clause for a date or datetime field. | 662 # Returns a SQL clause for a date or datetime field. |
647 def date_range_clause(table, field, from, to) | 663 def date_range_clause(table, field, from, to) |
648 s = [] | 664 s = [] |
649 if from | 665 if from |
650 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)]) | 666 s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)]) |