Class: Query

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/query.rb

Constant Summary

@@operators =
{ "="   => :label_equals, 
"!"   => :label_not_equals,
"o"   => :label_open_issues,
"c"   => :label_closed_issues,
"!*"  => :label_none,
"*"   => :label_all,
">="   => '>=',
"<="   => '<=',
"<t+" => :label_in_less_than,
">t+" => :label_in_more_than,
"t+"  => :label_in,
"t"   => :label_today,
"w"   => :label_this_week,
">t-" => :label_less_than_ago,
"<t-" => :label_more_than_ago,
"t-"  => :label_ago,
"~"   => :label_contains,
"!~"  => :label_not_contains }
@@operators_by_filter_type =
{ :list => [ "=", "!" ],
:list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ],
:list_subprojects => [ "*", "!*", "=" ],
:date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
:date_past => [ ">t-", "<t-", "t-", "t", "w" ],
:string => [ "=", "~", "!", "!~" ],
:text => [  "~", "!~" ],
:integer => [ "=", ">=", "<=", "!*", "*" ] }
@@available_columns =
[
  QueryColumn.new(:project, :sortable => "#{Project.table_name}.name"),
  QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position"),
  QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position"),
  QueryColumn.new(:priority, :sortable => "#{Enumeration.table_name}.position", :default_order => 'desc'),
  QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
  QueryColumn.new(:author),
  QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname"]),
  QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
  QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name"),
  QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc'),
  QueryColumn.new(:start_date, :sortable => "#{Issue.table_name}.start_date"),
  QueryColumn.new(:due_date, :sortable => "#{Issue.table_name}.due_date"),
  QueryColumn.new(:estimated_hours, :sortable => "#{Issue.table_name}.estimated_hours"),
  QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio"),
  QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
]

Instance Method Summary collapse

Constructor Details

#initialize(attributes = nil) ⇒ Query



119
120
121
122
# File 'app/models/query.rb', line 119

def initialize(attributes = nil)
  super attributes
  self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
end

Instance Method Details

#add_filter(field, operator, values) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'app/models/query.rb', line 197

def add_filter(field, operator, values)
  # values must be an array
  return unless values and values.is_a? Array # and !values.first.empty?
  # check if field is defined as an available filter
  if available_filters.has_key? field
    filter_options = available_filters[field]
    # check if operator is allowed for that filter
    #if @@operators_by_filter_type[filter_options[:type]].include? operator
    #  allowed_values = values & ([""] + (filter_options[:values] || []).collect {|val| val[1]})
    #  filters[field] = {:operator => operator, :values => allowed_values } if (allowed_values.first and !allowed_values.first.empty?) or ["o", "c", "!*", "*", "t"].include? operator
    #end
    filters[field] = {:operator => operator, :values => values }
  end
end

#add_short_filter(field, expression) ⇒ Object



212
213
214
215
216
# File 'app/models/query.rb', line 212

def add_short_filter(field, expression)
  return unless expression
  parms = expression.scan(/^(o|c|\!|\*)?(.*)$/).first
  add_filter field, (parms[0] || "="), [parms[1] || ""]
end

#after_initializeObject



124
125
126
127
# File 'app/models/query.rb', line 124

def after_initialize
  # Store the fact that project is nil (used in #editable_by?)
  @is_for_all = project.nil?
end

#available_columnsObject



235
236
237
238
239
240
241
242
# File 'app/models/query.rb', line 235

def available_columns
  return @available_columns if @available_columns
  @available_columns = Query.available_columns
  @available_columns += (project ? 
                          project.all_issue_custom_fields :
                          IssueCustomField.find(:all, :conditions => {:is_for_all => true})
                         ).collect {|cf| QueryCustomFieldColumn.new(cf) }      
end

#available_filtersObject



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'app/models/query.rb', line 147

def available_filters
  return @available_filters if @available_filters
  
  trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
  
  @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },       
                         "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },                                                                                                                
                         "priority_id" => { :type => :list, :order => 3, :values => Enumeration.find(:all, :conditions => ['opt=?','IPRI'], :order => 'position').collect{|s| [s.name, s.id.to_s] } },
                         "subject" => { :type => :text, :order => 8 },  
                         "created_on" => { :type => :date_past, :order => 9 },                        
                         "updated_on" => { :type => :date_past, :order => 10 },
                         "start_date" => { :type => :date, :order => 11 },
                         "due_date" => { :type => :date, :order => 12 },
                         "estimated_hours" => { :type => :integer, :order => 13 },
                         "done_ratio" =>  { :type => :integer, :order => 14 }}
  
  user_values = []
  user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
  if project
    user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
  else
    # members of the user's projects
    user_values += User.current.projects.collect(&:users).flatten.uniq.sort.collect{|s| [s.name, s.id.to_s] }
  end
  @available_filters["assigned_to_id"] = { :type => :list_optional, :order => 4, :values => user_values } unless user_values.empty?
  @available_filters["author_id"] = { :type => :list, :order => 5, :values => user_values } unless user_values.empty?
  
  if User.current.logged?
    @available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
  end

  if project
    # project specific filters
    unless @project.issue_categories.empty?
      @available_filters["category_id"] = { :type => :list_optional, :order => 6, :values => @project.issue_categories.collect{|s| [s.name, s.id.to_s] } }
    end
    unless @project.versions.empty?
      @available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => @project.versions.sort.collect{|s| [s.name, s.id.to_s] } }
    end
    unless @project.descendants.active.empty?
      @available_filters["subproject_id"] = { :type => :list_subprojects, :order => 13, :values => @project.descendants.visible.collect{|s| [s.name, s.id.to_s] } }
    end
    add_custom_fields_filters(@project.all_issue_custom_fields)
  else
    # global filters for cross project issue list
    add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
  end
  @available_filters
end

#column_names=(names) ⇒ Object



256
257
258
259
260
# File 'app/models/query.rb', line 256

def column_names=(names)
  names = names.select {|n| n.is_a?(Symbol) || !n.blank? } if names
  names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym } if names
  write_attribute(:column_names, names)
end

#columnsObject



244
245
246
247
248
249
250
251
252
253
254
# File 'app/models/query.rb', line 244

def columns
  if has_default_columns?
    available_columns.select do |c|
      # Adds the project column by default for cross-project lists
      Setting.issue_list_default_columns.include?(c.name.to_s) || (c.name == :project && project.nil?)
    end
  else
    # preserve the column_names order
    column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
  end
end

#editable_by?(user) ⇒ Boolean



139
140
141
142
143
144
145
# File 'app/models/query.rb', line 139

def editable_by?(user)
  return false unless user
  # Admin can edit them all and regular users can edit their private queries
  return true if user.admin? || (!is_public && self.user_id == user.id)
  # Members can not edit public queries that are for all project (only admin is allowed to)
  is_public && !@is_for_all && user.allowed_to?(:manage_public_queries, project)
end

#has_column?(column) ⇒ Boolean



262
263
264
# File 'app/models/query.rb', line 262

def has_column?(column)
  column_names && column_names.include?(column.name)
end

#has_default_columns?Boolean



266
267
268
# File 'app/models/query.rb', line 266

def has_default_columns?
  column_names.nil? || column_names.empty?
end

#has_filter?(field) ⇒ Boolean



218
219
220
# File 'app/models/query.rb', line 218

def has_filter?(field)
  filters and filters[field]
end

#label_for(field) ⇒ Object



230
231
232
233
# File 'app/models/query.rb', line 230

def label_for(field)
  label = available_filters[field][:name] if available_filters.has_key?(field)
  label ||= field.gsub(/\_id$/, "")
end

#operator_for(field) ⇒ Object



222
223
224
# File 'app/models/query.rb', line 222

def operator_for(field)
  has_filter?(field) ? filters[field][:operator] : nil
end

#project_statementObject



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'app/models/query.rb', line 291

def project_statement
  project_clauses = []
  if project && !@project.descendants.active.empty?
    ids = [project.id]
    if has_filter?("subproject_id")
      case operator_for("subproject_id")
      when '='
        # include the selected subprojects
        ids += values_for("subproject_id").each(&:to_i)
      when '!*'
        # main project only
      else
        # all subprojects
        ids += project.descendants.collect(&:id)
      end
    elsif Setting.display_subprojects_issues?
      ids += project.descendants.collect(&:id)
    end
    project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
  elsif project
    project_clauses << "#{Project.table_name}.id = %d" % project.id
  end
  project_clauses <<  Project.allowed_to_condition(User.current, :view_issues)
  project_clauses.join(' AND ')
end

#sort_criteriaObject



279
280
281
# File 'app/models/query.rb', line 279

def sort_criteria
  read_attribute(:sort_criteria) || []
end

#sort_criteria=(arg) ⇒ Object



270
271
272
273
274
275
276
277
# File 'app/models/query.rb', line 270

def sort_criteria=(arg)
  c = []
  if arg.is_a?(Hash)
    arg = arg.keys.sort.collect {|k| arg[k]}
  end
  c = arg.select {|k,o| !k.to_s.blank?}.slice(0,3).collect {|k,o| [k.to_s, o == 'desc' ? o : 'asc']}
  write_attribute(:sort_criteria, c)
end

#sort_criteria_key(arg) ⇒ Object



283
284
285
# File 'app/models/query.rb', line 283

def sort_criteria_key(arg)
  sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
end

#sort_criteria_order(arg) ⇒ Object



287
288
289
# File 'app/models/query.rb', line 287

def sort_criteria_order(arg)
  sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end

#statementObject



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'app/models/query.rb', line 317

def statement
  # filters clauses
  filters_clauses = []
  filters.each_key do |field|
    next if field == "subproject_id"
    v = values_for(field).clone
    next unless v and !v.empty?
    operator = operator_for(field)
    
    # "me" value subsitution
    if %w(assigned_to_id author_id watcher_id).include?(field)
      v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
    end
    
    sql = ''
    if field =~ /^cf_(\d+)$/
      # custom field
      db_table = CustomValue.table_name
      db_field = 'value'
      is_custom_filter = true
      sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
      sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
    elsif field == 'watcher_id'
      db_table = Watcher.table_name
      db_field = 'user_id'
      sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
      sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
    else
      # regular field
      db_table = Issue.table_name
      db_field = field
      sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
    end
    filters_clauses << sql
    
  end if filters and valid?
  
  (filters_clauses << project_statement).join(' AND ')
end

#validateObject



129
130
131
132
133
134
135
136
137
# File 'app/models/query.rb', line 129

def validate
  filters.each_key do |field|
    errors.add label_for(field), :blank unless 
        # filter requires one or more values
        (values_for(field) and !values_for(field).first.blank?) or 
        # filter doesn't require any value
        ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
  end if filters
end

#values_for(field) ⇒ Object



226
227
228
# File 'app/models/query.rb', line 226

def values_for(field)
  has_filter?(field) ? filters[field][:values] : nil
end