##// END OF EJS Templates
Remove the limitation on characters that can be used in custom_field, issue_status, role, tracker, user names (#5152)....
Remove the limitation on characters that can be used in custom_field, issue_status, role, tracker, user names (#5152). git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/trunk@4599 e93f8b46-1217-0410-a6f0-8f06a7374b81

File last commit:

r4439:703b0ec422bb
r4479:44ffc5a3365f
Show More
query.rb
647 lines | 25.6 KiB | text/x-ruby | RubyLexer
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 # Redmine - project management software
# Copyright (C) 2006-2008 Jean-Philippe Lang
Jean-Philippe Lang
"queries" branch merged...
r92 #
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 class QueryColumn
Jean-Philippe Lang
Reverts r3072 (#4302: error raised when sorting on an association not included as column)....
r2983 attr_accessor :name, :sortable, :groupable, :default_order
Jean-Philippe Lang
Merged Rails 2.2 branch. Redmine now requires Rails 2.2.2....
r2430 include Redmine::I18n
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771
def initialize(name, options={})
self.name = name
self.sortable = options[:sortable]
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 self.groupable = options[:groupable] || false
Jean-Philippe Lang
Allow issue grouping by custom field (#2679)....
r2957 if groupable == true
self.groupable = name.to_s
end
Jean-Philippe Lang
More appropriate default sort order on sortable columns....
r1107 self.default_order = options[:default_order]
Jean-Philippe Lang
Issue list improvements for subtasking (#5196):...
r3504 @caption_key = options[:caption] || "field_#{name}"
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 end
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 def caption
Jean-Philippe Lang
Issue list improvements for subtasking (#5196):...
r3504 l(@caption_key)
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 end
Jean-Philippe Lang
Ability to save "sort order" in custom queries (#2899)....
r2504
# Returns true if the column is sortable, otherwise false
def sortable?
!sortable.nil?
end
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998
def value(issue)
issue.send name
end
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 end
class QueryCustomFieldColumn < QueryColumn
def initialize(custom_field)
self.name = "cf_#{custom_field.id}".to_sym
Jean-Philippe Lang
Ability to sort the issue list by text, list, date and boolean custom fields (#1139)....
r2255 self.sortable = custom_field.order_statement || false
Jean-Philippe Lang
Allow issue grouping by custom field (#2679)....
r2957 if %w(list date bool int).include?(custom_field.field_format)
self.groupable = custom_field.order_statement
end
self.groupable ||= false
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 @cf = custom_field
end
def caption
@cf.name
end
def custom_field
@cf
end
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998
def value(issue)
cv = issue.custom_values.detect {|v| v.custom_field_id == @cf.id}
cv && @cf.cast_value(cv.value)
end
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 end
Jean-Philippe Lang
"queries" branch merged...
r92 class Query < ActiveRecord::Base
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 class StatementInvalid < ::ActiveRecord::StatementInvalid
end
Jean-Philippe Lang
"queries" branch merged...
r92 belongs_to :project
belongs_to :user
serialize :filters
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 serialize :column_names
Jean-Philippe Lang
Ability to save "sort order" in custom queries (#2899)....
r2504 serialize :sort_criteria, Array
Jean-Philippe Lang
"queries" branch merged...
r92
Jean-Philippe Lang
Fix query management broken by r1027....
r1018 attr_protected :project_id, :user_id
Jean-Philippe Lang
"queries" branch merged...
r92
validates_presence_of :name, :on => :save
Jean-Philippe Lang
Added several validates_length_of...
r590 validates_length_of :name, :maximum => 255
Jean-Philippe Lang
"queries" branch merged...
r92
@@operators = { "=" => :label_equals,
"!" => :label_not_equals,
"o" => :label_open_issues,
"c" => :label_closed_issues,
"!*" => :label_none,
"*" => :label_all,
Azamat Hackimov
Fixing bug #3009, trivial updates of locales...
r2538 ">=" => :label_greater_or_equal,
"<=" => :label_less_or_equal,
Jean-Philippe Lang
"queries" branch merged...
r92 "<t+" => :label_in_less_than,
">t+" => :label_in_more_than,
"t+" => :label_in,
"t" => :label_today,
Jean-Philippe Lang
Added a new value for date filters: 'this week'...
r693 "w" => :label_this_week,
Jean-Philippe Lang
"queries" branch merged...
r92 ">t-" => :label_less_than_ago,
"<t-" => :label_more_than_ago,
"t-" => :label_ago,
"~" => :label_contains,
"!~" => :label_not_contains }
cattr_reader :operators
@@operators_by_filter_type = { :list => [ "=", "!" ],
:list_status => [ "o", "=", "!", "c", "*" ],
:list_optional => [ "=", "!", "!*", "*" ],
Jean-Philippe Lang
Include subprojects on the issue list, calendar and gantt by default....
r1164 :list_subprojects => [ "*", "!*", "=" ],
Jean-Philippe Lang
Added a new value for date filters: 'this week'...
r693 :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
:date_past => [ ">t-", "<t-", "t-", "t", "w" ],
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 :string => [ "=", "~", "!", "!~" ],
Jean-Philippe Lang
Added "% done" in the filter list....
r710 :text => [ "~", "!~" ],
Jean-Philippe Lang
Adds estimated hours to issue filters (#1678)....
r1687 :integer => [ "=", ">=", "<=", "!*", "*" ] }
Jean-Philippe Lang
"queries" branch merged...
r92
cattr_reader :operators_by_filter_type
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 @@available_columns = [
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 QueryColumn.new(:project, :sortable => "#{Project.table_name}.name", :groupable => true),
Jean-Philippe Lang
Reverts r3072 (#4302: error raised when sorting on an association not included as column)....
r2983 QueryColumn.new(:tracker, :sortable => "#{Tracker.table_name}.position", :groupable => true),
Jean-Philippe Lang
Issue list improvements for subtasking (#5196):...
r3504 QueryColumn.new(:parent, :sortable => ["#{Issue.table_name}.root_id", "#{Issue.table_name}.lft ASC"], :default_order => 'desc', :caption => :field_parent_issue),
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 QueryColumn.new(:status, :sortable => "#{IssueStatus.table_name}.position", :groupable => true),
Eric Davis
Changed Enumerations to use a Single Table Inheritance...
r2677 QueryColumn.new(:priority, :sortable => "#{IssuePriority.table_name}.position", :default_order => 'desc', :groupable => true),
Jean-Philippe Lang
More appropriate default sort order on sortable columns....
r1107 QueryColumn.new(:subject, :sortable => "#{Issue.table_name}.subject"),
Jean-Philippe Lang
Add 'Author' to the available columns for the issue list....
r1096 QueryColumn.new(:author),
Jean-Philippe Lang
Reverts r3072 (#4302: error raised when sorting on an association not included as column)....
r2983 QueryColumn.new(:assigned_to, :sortable => ["#{User.table_name}.lastname", "#{User.table_name}.firstname", "#{User.table_name}.id"], :groupable => true),
Jean-Philippe Lang
More appropriate default sort order on sortable columns....
r1107 QueryColumn.new(:updated_on, :sortable => "#{Issue.table_name}.updated_on", :default_order => 'desc'),
Jean-Philippe Lang
Reverts r3072 (#4302: error raised when sorting on an association not included as column)....
r2983 QueryColumn.new(:category, :sortable => "#{IssueCategory.table_name}.name", :groupable => true),
QueryColumn.new(:fixed_version, :sortable => ["#{Version.table_name}.effective_date", "#{Version.table_name}.name"], :default_order => 'desc', :groupable => true),
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 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"),
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 QueryColumn.new(:done_ratio, :sortable => "#{Issue.table_name}.done_ratio", :groupable => true),
Jean-Philippe Lang
More appropriate default sort order on sortable columns....
r1107 QueryColumn.new(:created_on, :sortable => "#{Issue.table_name}.created_on", :default_order => 'desc'),
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 ]
cattr_reader :available_columns
Jean-Philippe Lang
"queries" branch merged...
r92 def initialize(attributes = nil)
super attributes
self.filters ||= { 'status_id' => {:operator => "o", :values => [""]} }
Jean-Philippe Lang
"me" value is now available in queries for "assigned to" and "author" filters....
r517 end
Jean-Philippe Lang
Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list (#897, closes #671)....
r1296 def after_initialize
# Store the fact that project is nil (used in #editable_by?)
@is_for_all = project.nil?
end
Jean-Philippe Lang
"queries" branch merged...
r92 def validate
filters.each_key do |field|
Jean-Philippe Lang
Merged Rails 2.2 branch. Redmine now requires Rails 2.2.2....
r2430 errors.add label_for(field), :blank unless
Jean-Philippe Lang
"queries" branch merged...
r92 # filter requires one or more values
Jean-Philippe Lang
Fixed: Search for target version of "none" fails with postgres 8.3 (#1134)....
r1364 (values_for(field) and !values_for(field).first.blank?) or
Jean-Philippe Lang
"queries" branch merged...
r92 # filter doesn't require any value
Jean-Philippe Lang
Added a new value for date filters: 'this week'...
r693 ["o", "c", "!*", "*", "t", "w"].include? operator_for(field)
Jean-Philippe Lang
"queries" branch merged...
r92 end if filters
end
Jean-Philippe Lang
Added per user custom queries....
r563 def editable_by?(user)
return false unless user
Jean-Philippe Lang
Queries can be marked as 'For all projects'. Such queries will be available on all projects and on the global issue list (#897, closes #671)....
r1296 # 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)
Jean-Philippe Lang
Added per user custom queries....
r563 end
Jean-Philippe Lang
"queries" branch merged...
r92 def available_filters
return @available_filters if @available_filters
Jean-Philippe Lang
On the calendar, the gantt and in the Tracker filter on the issue list, only active trackers of the project (and its sub projects) can be selected....
r1057
trackers = project.nil? ? Tracker.find(:all, :order => 'position') : project.rolled_up_trackers
Jean-Philippe Lang
added the ability to set the sort order for issue statuses...
r202 @available_filters = { "status_id" => { :type => :list_status, :order => 1, :values => IssueStatus.find(:all, :order => 'position').collect{|s| [s.name, s.id.to_s] } },
Jean-Philippe Lang
On the calendar, the gantt and in the Tracker filter on the issue list, only active trackers of the project (and its sub projects) can be selected....
r1057 "tracker_id" => { :type => :list, :order => 2, :values => trackers.collect{|s| [s.name, s.id.to_s] } },
Eric Davis
Changed Enumerations to use a Single Table Inheritance...
r2677 "priority_id" => { :type => :list, :order => 3, :values => IssuePriority.all.collect{|s| [s.name, s.id.to_s] } },
Jean-Philippe Lang
added back "fixed version" in issue/show and filters...
r162 "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 },
Jean-Philippe Lang
Added "% done" in the filter list....
r710 "due_date" => { :type => :date, :order => 12 },
Jean-Philippe Lang
Adds estimated hours to issue filters (#1678)....
r1687 "estimated_hours" => { :type => :integer, :order => 13 },
"done_ratio" => { :type => :integer, :order => 14 }}
Jean-Philippe Lang
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
r673
user_values = []
Jean-Philippe Lang
Fixed: 'assigned to me' filter broken....
r966 user_values << ["<< #{l(:label_me)} >>", "me"] if User.current.logged?
Jean-Philippe Lang
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
r673 if project
Jean-Philippe Lang
'Assigned to' drop down list is now sorted by user's lastname....
r926 user_values += project.users.sort.collect{|s| [s.name, s.id.to_s] }
Jean-Philippe Lang
Fixed: 'assigned to me' filter broken....
r966 else
Jean-Philippe Lang
Prevents n SQL queries (n = project count) on cross-project issues list....
r4436 all_projects = Project.visible.all
if all_projects.any?
# members of visible projects
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] }
# project filter
project_values = []
Project.project_tree(all_projects) do |p, level|
prefix = (level > 0 ? ('--' * level + ' ') : '')
project_values << ["#{prefix}#{p.name}", p.id.to_s]
end
@available_filters["project_id"] = { :type => :list, :order => 1, :values => project_values} unless project_values.empty?
Jean-Philippe Lang
Optimize retrieval of user's projects members....
r3493 end
Jean-Philippe Lang
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
r673 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?
Eric Davis
Added a "Member of Group" to the issues filter. #5869...
r3963
Eric Davis
Remember the selected "Member of Role" and "Member of Group" options. #6467...
r4146 group_values = Group.all.collect {|g| [g.name, g.id.to_s] }
Eric Davis
Added a "Member of Group" to the issues filter. #5869...
r3963 @available_filters["member_of_group"] = { :type => :list_optional, :order => 6, :values => group_values } unless group_values.empty?
Eric Davis
Added a "Member of Role" to the issues filters. #5869...
r3964
Eric Davis
Remember the selected "Member of Role" and "Member of Group" options. #6467...
r4146 role_values = Role.givable.collect {|r| [r.name, r.id.to_s] }
Eric Davis
Added a "Member of Role" to the issues filters. #5869...
r3964 @available_filters["assigned_to_role"] = { :type => :list_optional, :order => 7, :values => role_values } unless role_values.empty?
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395
if User.current.logged?
@available_filters["watcher_id"] = { :type => :list, :order => 15, :values => [["<< #{l(:label_me)} >>", "me"]] }
end
Jean-Philippe Lang
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
r673
if project
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562 # 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
Jean-Philippe Lang
Version sharing (#465) + optional inclusion of subprojects in the roadmap view (#2666)....
r3009 unless @project.shared_versions.empty?
@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] } }
Jean-Philippe Lang
Hide 'Target version' filter if no version is defined....
r1561 end
Jean-Philippe Lang
Merged nested projects branch. Removes limit on subproject nesting (#594)....
r2302 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] } }
Jean-Philippe Lang
added back "subproject" filter on issue list...
r347 end
Jean-Philippe Lang
Custom fields refactoring: most of code moved from controllers to models (using new module ActsAsCustomizable)....
r1578 add_custom_fields_filters(@project.all_issue_custom_fields)
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562 else
# global filters for cross project issue list
Jean-Philippe Lang
Adds filter for system shared versions on the cross project issue list (#4792)....
r3295 system_shared_versions = Version.visible.find_all_by_sharing('system')
unless system_shared_versions.empty?
@available_filters["fixed_version_id"] = { :type => :list_optional, :order => 7, :values => system_shared_versions.sort.collect{|s| ["#{s.project.name} - #{s.name}", s.id.to_s] } }
end
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562 add_custom_fields_filters(IssueCustomField.find(:all, :conditions => {:is_filter => true, :is_for_all => true}))
Jean-Philippe Lang
"queries" branch merged...
r92 end
@available_filters
end
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
def add_short_filter(field, expression)
return unless expression
Jean-Philippe Lang
Fixed: issue summary counts should link to the issue list without subprojects (#4525)....
r3169 parms = expression.scan(/^(o|c|!\*|!|\*)?(.*)$/).first
Jean-Philippe Lang
"queries" branch merged...
r92 add_filter field, (parms[0] || "="), [parms[1] || ""]
end
Eric Davis
Refactor: Move method to Query model...
r3570
# Add multiple filters using +add_filter+
def add_filters(fields, operators, values)
Jean-Philippe Lang
Fixed: unchecking status filter on the issue list has no effect (#6844)....
r4273 if fields.is_a?(Array) && operators.is_a?(Hash) && values.is_a?(Hash)
fields.each do |field|
add_filter(field, operators[field], values[field])
end
Eric Davis
Refactor: Move method to Query model...
r3570 end
end
Jean-Philippe Lang
"queries" branch merged...
r92
def has_filter?(field)
filters and filters[field]
end
def operator_for(field)
has_filter?(field) ? filters[field][:operator] : nil
end
def values_for(field)
has_filter?(field) ? filters[field][:values] : nil
end
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 def label_for(field)
Jean-Philippe Lang
Prevents NoMethodError on @available_filters.has_key? in query.rb (#1178)....
r1440 label = available_filters[field][:name] if available_filters.has_key?(field)
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 label ||= field.gsub(/\_id$/, "")
end
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771
def available_columns
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 return @available_columns if @available_columns
@available_columns = Query.available_columns
@available_columns += (project ?
Jean-Philippe Lang
Custom fields refactoring: most of code moved from controllers to models (using new module ActsAsCustomizable)....
r1578 project.all_issue_custom_fields :
Jean-Philippe Lang
Ability to use any custom field as a cross-project custom query column (#3321)....
r2644 IssueCustomField.find(:all)
Jean-Philippe Lang
Custom fields can now be displayed as columns on the issue list....
r876 ).collect {|cf| QueryCustomFieldColumn.new(cf) }
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 end
Eric Davis
Adding missing setter for Query#available_columns...
r3571
def self.available_columns=(v)
self.available_columns = (v)
end
def self.add_available_column(column)
self.available_columns << (column) if column.is_a?(QueryColumn)
end
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 # Returns an array of columns that can be used to group the results
def groupable_columns
available_columns.select {|c| c.groupable}
end
Eric Davis
Refactor: Extract Query#sortable_columns from the controller....
r3490
# Returns a Hash of columns and the key for sorting
def sortable_columns
{'id' => "#{Issue.table_name}.id"}.merge(available_columns.inject({}) {|h, column|
h[column.name.to_s] = column.sortable
h
})
end
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 def columns
Jean-Philippe Lang
Added a checkbox on custom query form to explicitly say if the query uses default columns or not....
r772 if has_default_columns?
Jean-Philippe Lang
Adds a sortable "Project" column to the issue list....
r2498 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
Jean-Philippe Lang
Added a checkbox on custom query form to explicitly say if the query uses default columns or not....
r772 else
Jean-Philippe Lang
Custom query columns: checkboxes replaced by two selects that let the user specify columns order....
r773 # preserve the column_names order
column_names.collect {|name| available_columns.find {|col| col.name == name}}.compact
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 end
end
def column_names=(names)
Jean-Philippe Lang
Adds dynamic columns selection on the issue list (#4272)....
r2991 if names
names = names.select {|n| n.is_a?(Symbol) || !n.blank? }
names = names.collect {|n| n.is_a?(Symbol) ? n : n.to_sym }
# Set column_names to nil if default columns
if names.map(&:to_s) == Setting.issue_list_default_columns
names = nil
end
end
Jean-Philippe Lang
Added the ability to customize columns of a saved query....
r771 write_attribute(:column_names, names)
end
def has_column?(column)
column_names && column_names.include?(column.name)
end
Jean-Philippe Lang
Added a checkbox on custom query form to explicitly say if the query uses default columns or not....
r772
def has_default_columns?
column_names.nil? || column_names.empty?
end
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796
Jean-Philippe Lang
Ability to save "sort order" in custom queries (#2899)....
r2504 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
def sort_criteria
read_attribute(:sort_criteria) || []
end
def sort_criteria_key(arg)
sort_criteria && sort_criteria[arg] && sort_criteria[arg].first
end
def sort_criteria_order(arg)
sort_criteria && sort_criteria[arg] && sort_criteria[arg].last
end
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 # Returns the SQL sort order that should be prepended for grouping
def group_by_sort_order
if grouped? && (column = group_by_column)
column.sortable.is_a?(Array) ?
column.sortable.collect {|s| "#{s} #{column.default_order}"}.join(',') :
"#{column.sortable} #{column.default_order}"
end
end
# Returns true if the query is a grouped query
def grouped?
Jean-Philippe Lang
Fixed: 500 error on issue query grouped by a custom field that was deleted (#7144)....
r4439 !group_by_column.nil?
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 end
def group_by_column
Jean-Philippe Lang
Fixed: 500 error on issue query grouped by a custom field that was deleted (#7144)....
r4439 groupable_columns.detect {|c| c.groupable && c.name.to_s == group_by}
Jean-Philippe Lang
Ticket grouping (#2679)....
r2604 end
Jean-Philippe Lang
Allow issue grouping by custom field (#2679)....
r2957 def group_by_statement
Jean-Philippe Lang
Fixed: 500 error on issue query grouped by a custom field that was deleted (#7144)....
r4439 group_by_column.try(:groupable)
Jean-Philippe Lang
Allow issue grouping by custom field (#2679)....
r2957 end
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 def project_statement
Jean-Philippe Lang
Fixed: private subprojects are listed on the issues view (#1217)....
r1417 project_clauses = []
Jean-Philippe Lang
Merged nested projects branch. Removes limit on subproject nesting (#594)....
r2302 if project && !@project.descendants.active.empty?
Jean-Philippe Lang
Include subprojects on the issue list, calendar and gantt by default....
r1164 ids = [project.id]
Jean-Philippe Lang
Adds an application setting to choose whether or not subprojects issues should be displayed by default on the issue list, calendar and gantt (r1178). Default is true....
r1184 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
Jean-Philippe Lang
Merged nested projects branch. Removes limit on subproject nesting (#594)....
r2302 ids += project.descendants.collect(&:id)
Jean-Philippe Lang
Adds an application setting to choose whether or not subprojects issues should be displayed by default on the issue list, calendar and gantt (r1178). Default is true....
r1184 end
elsif Setting.display_subprojects_issues?
Jean-Philippe Lang
Merged nested projects branch. Removes limit on subproject nesting (#594)....
r2302 ids += project.descendants.collect(&:id)
Jean-Philippe Lang
added back "subproject" filter on issue list...
r347 end
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 project_clauses << "#{Project.table_name}.id IN (%s)" % ids.join(',')
Jean-Philippe Lang
Added a cross-project issue list. It displays the issues of all the projects visible by the user....
r673 elsif project
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 project_clauses << "#{Project.table_name}.id = %d" % project.id
Jean-Philippe Lang
added back "subproject" filter on issue list...
r347 end
Jean-Philippe Lang
Fixed: cross-project issue list should not show issues of projects for which the issue tracking module was disabled....
r1905 project_clauses << Project.allowed_to_condition(User.current, :view_issues)
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 project_clauses.join(' AND ')
end
def statement
Jean-Philippe Lang
Fixed: queries with multiple custom fields return no result....
r662 # filters clauses
filters_clauses = []
Jean-Philippe Lang
"queries" branch merged...
r92 filters.each_key do |field|
Jean-Philippe Lang
added back "subproject" filter on issue list...
r347 next if field == "subproject_id"
Jean-Philippe Lang
"me" value is now available in queries for "assigned to" and "author" filters....
r517 v = values_for(field).clone
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 next unless v and !v.empty?
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395 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
Jean-Philippe Lang
Fixed: Search for target version of "none" fails with postgres 8.3 (#1134)....
r1364 sql = ''
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 if field =~ /^cf_(\d+)$/
# custom field
db_table = CustomValue.table_name
Jean-Philippe Lang
Fixed: queries with multiple custom fields return no result....
r662 db_field = 'value'
Jean-Philippe Lang
Fixed: Search for target version of "none" fails with postgres 8.3 (#1134)....
r1364 is_custom_filter = true
Jean-Philippe Lang
Fixes custom field filters behaviour (#1078)....
r1347 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 "
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395 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) + ')'
Eric Davis
Added a "Member of Group" to the issues filter. #5869...
r3963 elsif field == "member_of_group" # named field
if operator == '*' # Any group
groups = Group.all
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*"
groups = Group.all
operator = '!' # Override the operator since we want to find by assigned_to
else
groups = Group.find_all_by_id(v)
end
Eric Davis
Refactor: replace chained finders with an inject. Should handle edge cases better....
r3965 groups ||= []
members_of_groups = groups.inject([]) {|user_ids, group|
if group && group.user_ids.present?
user_ids << group.user_ids
end
user_ids.flatten.uniq.compact
}.sort.collect(&:to_s)
Eric Davis
Added a "Member of Group" to the issues filter. #5869...
r3963
sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'
Eric Davis
Added a "Member of Role" to the issues filters. #5869...
r3964 elsif field == "assigned_to_role" # named field
if operator == "*" # Any Role
roles = Role.givable
operator = '=' # Override the operator since we want to find by assigned_to
elsif operator == "!*" # No role
roles = Role.givable
operator = '!' # Override the operator since we want to find by assigned_to
else
roles = Role.givable.find_all_by_id(v)
end
roles ||= []
members_of_roles = roles.inject([]) {|user_ids, role|
if role && role.members
user_ids << role.members.collect(&:user_id)
end
user_ids.flatten.uniq.compact
}.sort.collect(&:to_s)
sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 else
# regular field
db_table = Issue.table_name
db_field = field
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395 sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
Jean-Philippe Lang
Custom fields for issues can now be used as filters on issue list....
r444 end
Jean-Philippe Lang
Fixed: queries with multiple custom fields return no result....
r662 filters_clauses << sql
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395
Jean-Philippe Lang
"queries" branch merged...
r92 end if filters and valid?
Jean-Philippe Lang
Fixed: queries with multiple custom fields return no result....
r662
Jean-Philippe Lang
Adds support for free ticket filtering and custom queries on Calendar....
r1796 (filters_clauses << project_statement).join(' AND ')
Jean-Philippe Lang
"queries" branch merged...
r92 end
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 # Returns the issue count
def issue_count
Issue.count(:include => [:status, :project], :conditions => statement)
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
# Returns the issue count by group or nil if query is not grouped
def issue_count_by_group
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998 r = nil
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 if grouped?
begin
# Rails will raise an (unexpected) RecordNotFound if there's only a nil group value
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998 r = Issue.count(:group => group_by_statement, :include => [:status, :project], :conditions => statement)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 rescue ActiveRecord::RecordNotFound
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998 r = {nil => issue_count}
end
c = group_by_column
if c.is_a?(QueryCustomFieldColumn)
r = r.keys.inject({}) {|h, k| h[c.custom_field.cast_value(k)] = r[k]; h}
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
end
Jean-Philippe Lang
Fixed: "None" category issue count is empty while grouping by category (#4308)....
r2998 r
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
# Returns the issues
# Valid options are :order, :offset, :limit, :include, :conditions
def issues(options={})
order_option = [group_by_sort_order, options[:order]].reject {|s| s.blank?}.join(',')
order_option = nil if order_option.blank?
Issue.find :all, :include => ([:status, :project] + (options[:include] || [])).uniq,
:conditions => Query.merge_conditions(statement, options[:conditions]),
:order => order_option,
:limit => options[:limit],
:offset => options[:offset]
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
# Returns the journals
# Valid options are :order, :offset, :limit
def journals(options={})
Journal.find :all, :include => [:details, :user, {:issue => [:project, :author, :tracker, :status]}],
:conditions => statement,
:order => options[:order],
:limit => options[:limit],
:offset => options[:offset]
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
# Returns the versions
# Valid options are :conditions
def versions(options={})
Version.find :all, :include => :project,
:conditions => Query.merge_conditions(project_statement, options[:conditions])
Jean-Philippe Lang
Rescue invalid query statement error with an error message....
r2990 rescue ::ActiveRecord::StatementInvalid => e
raise StatementInvalid.new(e.message)
Jean-Philippe Lang
Move issues, journals, versions queries from IssuesController to Query model....
r2989 end
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562 private
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395 # Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 sql = ''
Jean-Philippe Lang
Adds ability to filter watched issues (#846)....
r2395 case operator
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "="
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "!"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "!*"
sql = "#{db_table}.#{db_field} IS NULL"
sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
when "*"
sql = "#{db_table}.#{db_field} IS NOT NULL"
sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
when ">="
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "<="
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "o"
Eric Davis
Bit more refactoring on Query#sql_for_field to remove multiple returns...
r2089 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "c"
Eric Davis
Bit more refactoring on Query#sql_for_field to remove multiple returns...
r2089 sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when ">t-"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "<t-"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "t-"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when ">t+"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "<t+"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "t+"
Eric Davis
Final refactoring on Query#sql_for_field to rename v to value...
r2090 sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "t"
Eric Davis
Bit more refactoring on Query#sql_for_field to remove multiple returns...
r2089 sql = date_range_clause(db_table, db_field, 0, 0)
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "w"
from = l(:general_first_day_of_week) == '7' ?
# week starts on sunday
((Date.today.cwday == 7) ? Time.now.at_beginning_of_day : Time.now.at_beginning_of_week - 1.day) :
# week starts on monday (Rails default)
Time.now.at_beginning_of_week
Eric Davis
Bit more refactoring on Query#sql_for_field to remove multiple returns...
r2089 sql = "#{db_table}.#{db_field} BETWEEN '%s' AND '%s'" % [connection.quoted_date(from), connection.quoted_date(from + 7.days)]
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "~"
Jean-Philippe Lang
Fixed: case sensitivity in issue subject filtering (#3536)....
r2696 sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 when "!~"
Jean-Philippe Lang
Fixed: case sensitivity in issue subject filtering (#3536)....
r2696 sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 end
Eric Davis
Bit more refactoring on Query#sql_for_field to remove multiple returns...
r2089
return sql
Eric Davis
Refactor: Extracted new method Query#sql_for_field from Query#statement in...
r2088 end
Jean-Philippe Lang
Add filters on cross-project issue list for custom fields marked as 'For all projects'....
r1562 def add_custom_fields_filters(custom_fields)
@available_filters ||= {}
custom_fields.select(&:is_filter?).each do |field|
case field.field_format
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 }
else
options = { :type => :string, :order => 20 }
end
@available_filters["cf_#{field.id}"] = options.merge({ :name => field.name })
end
end
Jean-Philippe Lang
Fixed date filters accuracy with SQLite (#2221)....
r2052
# Returns a SQL clause for a date or datetime field.
def date_range_clause(table, field, from, to)
s = []
if from
s << ("#{table}.#{field} > '%s'" % [connection.quoted_date((Date.yesterday + from).to_time.end_of_day)])
end
if to
s << ("#{table}.#{field} <= '%s'" % [connection.quoted_date((Date.today + to).to_time.end_of_day)])
end
s.join(' AND ')
end
Jean-Philippe Lang
"queries" branch merged...
r92 end