diff --git a/app/controllers/api/experimental/queries_controller.rb b/app/controllers/api/experimental/queries_controller.rb
index 356b7cca67..dfc592be96 100644
--- a/app/controllers/api/experimental/queries_controller.rb
+++ b/app/controllers/api/experimental/queries_controller.rb
@@ -160,14 +160,14 @@ module Api::Experimental
end
def fetch_custom_field_filters(project)
- filters = Queries::WorkPackages::Filter::CustomFieldFilter.create(project)
+ filters = Queries::WorkPackages::Filter::CustomFieldFilter.all_for(project)
- filters.each_with_object({}) do |(key, filter), hash|
- new_key = API::Utilities::PropertyNameConverter.from_ar_name(key)
+ filters.each_with_object({}) do |filter, hash|
+ new_key = API::Utilities::PropertyNameConverter.from_ar_name(filter.name)
hash[new_key] = { type: filter.type,
- values: filter.values,
+ values: filter.allowed_values,
order: filter.order,
- name: filter.name }
+ name: filter.human_name }
end
end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index dda1cb9af1..d7490ddf7f 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -56,21 +56,6 @@ module IssuesHelper
#{@cached_label_priority}: #{h(issue.priority.name)}".html_safe)
end
- # Find the name of an associated record stored in the field attribute
- def find_name_by_reflection(field, id)
- association = WorkPackage.reflect_on_association(field.to_sym)
- if association
- record = association.class_name.constantize.find_by(id: id)
- return record.name if record
- end
- end
-
- def entries_for_filter_select_sorted(query)
- [['', '']] + query.available_work_package_filters.map { |field| [field[1][:name] || WorkPackage.human_attribute_name(field[0]), field[0]] unless query.has_filter?(field[0]) }.compact.sort_by { |el|
- ActiveSupport::Inflector.transliterate(el[0]).downcase
- }
- end
-
def last_issue_note(issue)
note_journals = issue.journals.select(&:notes?)
return t(:text_no_notes) if note_journals.empty?
diff --git a/app/helpers/queries_helper.rb b/app/helpers/queries_helper.rb
index 0c993d4555..00a4c0541a 100644
--- a/app/helpers/queries_helper.rb
+++ b/app/helpers/queries_helper.rb
@@ -29,11 +29,19 @@
module QueriesHelper
def operators_for_select(filter_type)
- Queries::Filter.operators_by_filter_type[filter_type].map { |o| [l(Queries::Filter.operators[o]), o] }
+ Queries::BaseFilter.operators_by_filter_type[filter_type].map { |o| [l(Queries::BaseFilter.operators[o]), o] }
+ end
+
+ def entries_for_filter_select_sorted(query)
+ [['', '']] +
+ query.available_filters
+ .reject { |filter| query.has_filter?(filter.name) }
+ .map { |filter| [filter.human_name, filter.name] }
+ .sort_by { |el| ActiveSupport::Inflector.transliterate(el[0]).downcase }
end
def column_locale(column)
- (column.is_a? QueryCustomFieldColumn) ? column.custom_field.name_locale : nil
+ column.is_a?(QueryCustomFieldColumn) ? column.custom_field.name_locale : nil
end
def add_filter_from_params
@@ -62,7 +70,7 @@ module QueriesHelper
if params[:fields] || params[:f]
add_filter_from_params
else
- @query.available_work_package_filters.keys.each do |field|
+ @query.available_filters.map(&:name).each do |field|
@query.add_short_filter(field, params[field]) if params[field]
end
end
@@ -163,7 +171,7 @@ module QueriesHelper
return [] if field_names.nil?
context = WorkPackage.new
- available_keys = query.available_work_package_filters.keys
+ available_keys = query.available_filters.map(&:name)
field_names
.map { |name| API::Utilities::PropertyNameConverter.to_ar_name name, context: context }
diff --git a/app/models/custom_value/bool_strategy.rb b/app/models/custom_value/bool_strategy.rb
index 12d5702b04..37d1900ef2 100644
--- a/app/models/custom_value/bool_strategy.rb
+++ b/app/models/custom_value/bool_strategy.rb
@@ -28,6 +28,9 @@
#++
class CustomValue::BoolStrategy < CustomValue::FormatStrategy
+ DB_VALUE_FALSE = 'f'.freeze
+ DB_VALUE_TRUE = 't'.freeze
+
def value_present?
# can't use :blank? safely, because false.blank? == true
# can't use :present? safely, because false.present? == false
@@ -44,9 +47,9 @@ class CustomValue::BoolStrategy < CustomValue::FormatStrategy
if !value_present?
nil
elsif ActiveRecord::Type::Boolean::FALSE_VALUES.include?(value)
- 'f'
+ DB_VALUE_FALSE
else
- 't'
+ DB_VALUE_TRUE
end
end
diff --git a/app/models/queries/work_packages/available_filter_options.rb b/app/models/queries/available_filters.rb
similarity index 55%
rename from app/models/queries/work_packages/available_filter_options.rb
rename to app/models/queries/available_filters.rb
index 80d489ac56..9774c033cd 100644
--- a/app/models/queries/work_packages/available_filter_options.rb
+++ b/app/models/queries/available_filters.rb
@@ -27,59 +27,74 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-module Queries::WorkPackages::AvailableFilterOptions
- def available_work_package_filters
- uninitialized = registered_work_package_filters - already_initialized_work_package_filters
+module Queries::AvailableFilters
+ def available_filters
+ uninitialized = registered_filters - already_initialized_filters
uninitialized.each do |filter|
- initialize_work_package_filter(filter)
+ initialize_filter(filter)
end
- initialized_available_work_package_filters
+ initialized_filters.select(&:available?)
end
- def work_package_filter_available?(key)
+ def filter_for(key, no_memoization = false)
+ filter_instance = get_initialized_filter(key, no_memoization) || Queries::NotExistingFilter.new
+ filter_instance.name = key
+
+ filter_instance
+ end
+
+ private
+
+ def get_initialized_filter(key, no_memoization)
filter = find_registered_filter(key)
return unless filter
- initialize_work_package_filter(filter)
+ if no_memoization
+ filter.new
+ else
+ initialize_filter(filter)
- initialized_available_work_package_filters[key]
+ find_initialized_filter(key)
+ end
end
- private
-
- def initialize_work_package_filter(filter)
- return if already_initialized_work_package_filters.include?(filter)
- already_initialized_work_package_filters << filter
-
- new_filters = filter.create(project)
+ def initialize_filter(filter)
+ return if already_initialized_filters.include?(filter)
+ already_initialized_filters << filter
- available_filters = new_filters.reject { |_, f| !f.available? }
+ new_filters = filter.all_for(context)
- initialized_available_work_package_filters.merge! available_filters
+ initialized_filters.push(*Array(new_filters))
end
def find_registered_filter(key)
- registered_work_package_filters.detect do |f|
+ registered_filters.detect do |f|
f.key === key.to_sym
end
end
- def already_initialized_work_package_filters
- @already_initialized_work_package_filters ||= []
+ def find_initialized_filter(key)
+ initialized_filters.detect do |f|
+ f.name == key.to_sym
+ end
+ end
+
+ def already_initialized_filters
+ @already_initialized_filters ||= []
end
- def registered_work_package_filters
- @registered_work_package_filters ||= filter_register.filters
+ def registered_filters
+ @registered_filters ||= filter_register
end
- def initialized_available_work_package_filters
- @initialized_available_work_package_filters ||= {}.with_indifferent_access
+ def initialized_filters
+ @initialized_filters ||= []
end
def filter_register
- Queries::WorkPackages::FilterRegister
+ Queries::Register.filters[self.class]
end
end
diff --git a/app/models/queries/available_orders.rb b/app/models/queries/available_orders.rb
new file mode 100644
index 0000000000..d7667b240c
--- /dev/null
+++ b/app/models/queries/available_orders.rb
@@ -0,0 +1,46 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module Queries::AvailableOrders
+ def order_for(key)
+ (find_registered_order(key) || Queries::NotExistingOrder).new(key)
+ end
+
+ private
+
+ def find_registered_order(key)
+ orders_register.detect do |s|
+ s.key === key.to_sym
+ end
+ end
+
+ def orders_register
+ Queries::Register.orders[self.class]
+ end
+end
diff --git a/app/models/queries/filter.rb b/app/models/queries/base_filter.rb
similarity index 56%
rename from app/models/queries/filter.rb
rename to app/models/queries/base_filter.rb
index f10c2e0b21..1fc505e1de 100644
--- a/app/models/queries/filter.rb
+++ b/app/models/queries/base_filter.rb
@@ -27,18 +27,86 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::Filter
+class Queries::BaseFilter
include ActiveModel::Validations
- include ActiveModel::Serialization
+ include Queries::SqlForField
- class_attribute :filter_types_by_field, instance_writer: false
+ attr_accessor :context
- self.filter_types_by_field = {
- created_at: :date_past,
- updated_at: :date_past
- }
+ class_attribute :model
+
+ @@filter_params = [:operator, :values]
+
+ attr_accessor *@@filter_params
+
+ def initialize(options = {})
+ self.context = options[:context]
+
+ @@filter_params.each do |param_field|
+ send("#{param_field}=", options[param_field])
+ end
+ end
+
+ def [](name)
+ send(name)
+ end
+
+ def name
+ @name || self.class.key
+ end
+ alias :field :name
+
+ def name=(name)
+ @name = name.to_sym
+ end
+
+ def human_name
+ raise NotImplementedError
+ end
- @@filter_params = [:operator, :values] # will be serialized and persisted with the query
+ def type
+ raise NotImplementedError
+ end
+
+ def allowed_values
+ nil
+ end
+
+ def available?
+ true
+ end
+
+ def scope
+ scope = model.where(where)
+ scope = scope.joins(joins) if joins
+ scope
+ end
+
+ def self.key
+ name.to_sym
+ end
+
+ def self.name
+ to_s.demodulize.underscore.gsub(/_filter$/, '')
+ end
+
+ def self.connection
+ model.connection
+ end
+
+ def self.all_for(context = nil)
+ filter = new
+ filter.context = context
+ filter
+ end
+
+ def where
+ sql_for_field(self.class.key, operator, values, self.class.model.table_name, self.class.key)
+ end
+
+ def joins
+ nil
+ end
@@operators = {
label_equals: '=',
@@ -77,87 +145,64 @@ class Queries::Filter
cattr_reader :operators, :operators_by_filter_type
- attr_accessor :field, *@@filter_params
+ attr_accessor *@@filter_params
- validates_presence_of :field, :operator
- validate :validate_presence_of_values, unless: Proc.new { |filter| @@operators_not_requiring_values.include?(filter.operator) }
+ validate :validate_inclusion_of_operator
+ validate :validate_presence_of_values,
+ unless: Proc.new { |filter| @@operators_not_requiring_values.include?(filter.operator) }
validate :validate_filter_values
- def initialize(field = nil, options = {})
- self.field = field
- values = []
-
- @@filter_params.each do |param_field|
- send("#{param_field}=", options[param_field])
- end
-
- stringify_values
- end
-
- # (de-)serialization
- def self.from_hash(filter_hash)
- filter_hash.keys.map { |field| new(field, filter_hash[field]) }
- end
-
- def to_hash
- { field => attributes_hash }
- end
-
- alias_method :name, :field
-
- def attributes
- { name: name, operator: operator, values: values }
- end
-
- def field=(field)
- @field = field.try :to_sym
+ def values
+ @values || []
end
- def possible_types_by_operator
- @@operators_by_filter_type.select { |_key, operators| operators.include?(operator) }.keys.sort
- end
-
- def type
- filter_types_by_field[field]
- end
-
- def ==(filter)
- filter.attributes_hash == attributes_hash
+ def values=(values)
+ @values = Array(values).reject(&:blank?).map(&:to_s)
end
protected
- def attributes_hash
- @@filter_params.inject({}) do |params, param_field|
- params.merge(param_field => send(param_field))
- end
- end
-
- private
-
- def stringify_values
- unless values.nil?
- values.map!(&:to_s)
+ def validate_inclusion_of_operator
+ unless @@operators_by_filter_type[type].include? operator
+ errors.add(:operator, :inclusion)
end
end
def validate_presence_of_values
- errors.add(:values, I18n.t('activerecord.errors.messages.blank')) if values.nil? || values.reject(&:blank?).empty?
+ if values.nil? || values.reject(&:blank?).empty?
+ errors.add(:values, I18n.t('activerecord.errors.messages.blank'))
+ end
end
def validate_filter_values
return true if @@operators_not_requiring_values.include?(operator)
case type
- when :integer
- errors.add(:values, I18n.t('activerecord.errors.messages.not_an_integer')) unless values.all? { |value| is_integer?(value) }
- when :date, :date_past
- errors.add(:values, I18n.t('activerecord.errors.messages.not_an_integer')) unless values.all? { |value| is_integer?(value) }
- # ...
+ when :integer, :date, :date_past
+ validate_values_all_integer
+ when :list, :list_optional
+ validate_values_in_allowed_values_list
+ end
+ end
+
+ def validate_values_all_integer
+ unless values.all? { |value| integer?(value) }
+ errors.add(:values, I18n.t('activerecord.errors.messages.not_an_integer'))
+ end
+ end
+
+ def validate_values_in_allowed_values_list
+ # TODO: the -1 is a special value that exists for historical reasons
+ # so one can send the operator '=' and the values ['-1']
+ # which results in a IS NULL check in the DB
+ if (values & (allowed_values.map(&:last).map(&:to_s) + ['-1'])) != values
+ errors.add(:values, :inclusion)
end
end
- def is_integer?(str)
- true if Integer(str) rescue false
+ def integer?(str)
+ true if Integer(str)
+ rescue
+ false
end
end
diff --git a/app/models/queries/base_order.rb b/app/models/queries/base_order.rb
new file mode 100644
index 0000000000..40f6713533
--- /dev/null
+++ b/app/models/queries/base_order.rb
@@ -0,0 +1,66 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::BaseOrder
+ include ActiveModel::Validations
+
+ validates :direction, inclusion: { in: %i(asc desc) }
+
+ class_attribute :model
+ attr_accessor :direction,
+ :attribute
+
+ def initialize(attribute)
+ self.attribute = attribute
+ end
+
+ def self.key
+ raise NotImplementedError
+ end
+
+ def scope
+ scope = order
+ scope = scope.joins(joins) if joins
+ scope
+ end
+
+ def name
+ attribute
+ end
+
+ private
+
+ def order
+ model.order(name => direction)
+ end
+
+ def joins
+ nil
+ end
+end
diff --git a/app/models/queries/base_query.rb b/app/models/queries/base_query.rb
new file mode 100644
index 0000000000..ead68a5929
--- /dev/null
+++ b/app/models/queries/base_query.rb
@@ -0,0 +1,124 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::BaseQuery
+ class << self
+ attr_accessor :model, :default_scope
+ end
+
+ include Queries::AvailableFilters
+ include Queries::AvailableOrders
+ include ActiveModel::Validations
+
+ validate :filters_valid,
+ :sortation_valid
+
+ def initialize
+ @scope = self.class.default_scope
+ @filters = []
+ @orders = []
+ end
+
+ def results
+ if valid?
+ filters.each do |filter|
+ self.scope = scope.merge(filter.scope)
+ end
+
+ orders.each do |order|
+ self.scope = scope.merge(order.scope)
+ end
+ else
+ empty_scope
+ end
+
+ scope
+ end
+
+ def where(attribute, operator, values)
+ filter = filter_for(attribute)
+ filter.operator = operator
+ filter.values = values
+ filter.context = context
+
+ filters << filter
+
+ self
+ end
+
+ def order(hash)
+ hash.each do |attribute, direction|
+ order = order_for(attribute)
+ order.direction = direction
+ orders << order
+ end
+
+ self
+ end
+
+ protected
+
+ attr_accessor :scope,
+ :filters,
+ :orders
+
+ def filters_valid
+ filters.each do |filter|
+ next if filter.valid?
+
+ add_error(:filters, filter.human_name, filter)
+ end
+ end
+
+ def sortation_valid
+ orders.each do |order|
+ next if order.valid?
+
+ add_error(:orders, order.class.key, order)
+ end
+ end
+
+ def add_error(local_attribute, attribute_name, object)
+ messages = object
+ .errors
+ .messages
+ .values
+ .flatten
+ .join(" #{I18n.t('support.array.sentence_connector')} ")
+
+ errors.add local_attribute, errors.full_message(attribute_name, messages)
+ end
+
+ def empty_scope
+ self.scope = self.class.model.where(Arel::Nodes::Equality.new(1, 0))
+ end
+
+ def context
+ nil
+ end
+end
diff --git a/app/models/queries/filter_serializer.rb b/app/models/queries/filter_serializer.rb
index cf35267469..3b0f217e24 100644
--- a/app/models/queries/filter_serializer.rb
+++ b/app/models/queries/filter_serializer.rb
@@ -28,12 +28,25 @@
#++
module Queries::FilterSerializer
+ extend Queries::AvailableFilters
+
def self.load(serialized_filter_hash)
return [] if serialized_filter_hash.nil?
- Queries::WorkPackages::Filter.from_hash(YAML.load(serialized_filter_hash) || {})
+
+ (YAML.load(serialized_filter_hash) || {}).each_with_object([]) do |(field, options), array|
+ options = options.with_indifferent_access
+ filter = filter_for(field, true)
+ filter.operator = options['operator']
+ filter.values = options['values']
+ array << filter
+ end
end
def self.dump(filters)
YAML.dump ((filters || []).map(&:to_hash).reduce(:merge) || {}).stringify_keys
end
+
+ def self.filter_register
+ Queries::Register.filters[Query]
+ end
end
diff --git a/app/models/queries/not_existing_filter.rb b/app/models/queries/not_existing_filter.rb
new file mode 100644
index 0000000000..2f808f8e97
--- /dev/null
+++ b/app/models/queries/not_existing_filter.rb
@@ -0,0 +1,55 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::NotExistingFilter < Queries::BaseFilter
+ def available?
+ false
+ end
+
+ def type
+ :inexistent
+ end
+
+ def self.key
+ :not_existent
+ end
+
+ def human_name
+ name
+ end
+
+ validate :always_false
+
+ def always_false
+ errors.add :base, I18n.t(:'activerecord.errors.messages.does_not_exist')
+ end
+
+ # deactivating superclass validation
+ def validate_inclusion_of_operator; end
+end
diff --git a/app/models/queries/not_existing_order.rb b/app/models/queries/not_existing_order.rb
new file mode 100644
index 0000000000..7137fb6173
--- /dev/null
+++ b/app/models/queries/not_existing_order.rb
@@ -0,0 +1,42 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::NotExistingOrder < Queries::BaseOrder
+ validate :always_false
+
+ def self.key
+ :inexistent
+ end
+
+ private
+
+ def always_false
+ errors.add :base, I18n.t(:'activerecord.errors.messages.does_not_exist')
+ end
+end
diff --git a/app/models/queries/register.rb b/app/models/queries/register.rb
new file mode 100644
index 0000000000..5f299538ea
--- /dev/null
+++ b/app/models/queries/register.rb
@@ -0,0 +1,40 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+require Rails.root.join('config/constants/query_register')
+
+module Queries::Register
+ class << self
+ delegate :filter,
+ :filters,
+ :order,
+ :orders,
+ to: ::Constants::QueryRegister
+ end
+end
diff --git a/app/models/queries/sql_for_field.rb b/app/models/queries/sql_for_field.rb
new file mode 100644
index 0000000000..71ee2785d7
--- /dev/null
+++ b/app/models/queries/sql_for_field.rb
@@ -0,0 +1,125 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module Queries::SqlForField
+ private
+
+ # Helper method to generate the WHERE sql for a +field+, +operator+ and a +values+ array
+ def sql_for_field(field, operator, values, db_table, db_field, is_custom_filter = false)
+ # code expects strings (e.g. for quoting), but ints would work as well: unify them here
+ values = values.map(&:to_s)
+
+ sql = ''
+ case operator
+ when '='
+ if values.present?
+ if values.include?('-1')
+ sql = "#{db_table}.#{db_field} IS NULL OR "
+ end
+
+ sql += "#{db_table}.#{db_field} IN (" + values.map { |val| "'#{connection.quote_string(val)}'" }.join(',') + ')'
+ else
+ # empty set of allowed values produces no result
+ sql = '0=1'
+ end
+ when '!'
+ if values.present?
+ sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + values.map { |val| "'#{connection.quote_string(val)}'" }.join(',') + '))'
+ else
+ # empty set of forbidden values allows all results
+ sql = '1=1'
+ end
+ 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 '>='
+ if is_custom_filter
+ sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{values.first.to_f}"
+ else
+ sql = "#{db_table}.#{db_field} >= #{values.first.to_f}"
+ end
+ when '<='
+ if is_custom_filter
+ sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{values.first.to_f}"
+ else
+ sql = "#{db_table}.#{db_field} <= #{values.first.to_f}"
+ end
+ when 'o'
+ sql = "#{Status.table_name}.is_closed=#{connection.quoted_false}" if field == 'status_id'
+ when 'c'
+ sql = "#{Status.table_name}.is_closed=#{connection.quoted_true}" if field == 'status_id'
+ when '>t-'
+ sql = date_range_clause(db_table, db_field, - values.first.to_i, 0)
+ when 't+'
+ sql = date_range_clause(db_table, db_field, values.first.to_i, nil)
+ when ' '%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
+
+ def connection
+ self.class.connection
+ end
+end
diff --git a/app/models/queries/users.rb b/app/models/queries/users.rb
new file mode 100644
index 0000000000..e57f8c5c04
--- /dev/null
+++ b/app/models/queries/users.rb
@@ -0,0 +1,38 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module Queries::Users
+ Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::NameFilter
+ Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::GroupFilter
+ Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::StatusFilter
+
+ Queries::Register.order Queries::Users::UserQuery, Queries::Users::Orders::DefaultOrder
+ Queries::Register.order Queries::Users::UserQuery, Queries::Users::Orders::NameOrder
+ Queries::Register.order Queries::Users::UserQuery, Queries::Users::Orders::GroupOrder
+end
diff --git a/app/models/queries/work_packages/filter/base_filter.rb b/app/models/queries/users/filters/group_filter.rb
similarity index 73%
rename from app/models/queries/work_packages/filter/base_filter.rb
rename to app/models/queries/users/filters/group_filter.rb
index 88ab587064..0a275d54ad 100644
--- a/app/models/queries/work_packages/filter/base_filter.rb
+++ b/app/models/queries/users/filters/group_filter.rb
@@ -27,43 +27,34 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :project
-
- def initialize(project)
- self.project = project
- end
-
- def [](name)
- send(name)
+class Queries::Users::Filters::GroupFilter < Queries::Users::Filters::UserFilter
+ def allowed_values
+ @allowed_values ||= begin
+ ::Group.pluck(:name, :id).map { |g| [g[0], g[1].to_s] }
+ end
end
- def name
- WorkPackage.human_attribute_name(self.class.name)
- end
-
- def values
- nil
+ def available?
+ ::Group.exists?
end
- def available?
- true
+ def type
+ :list_optional
end
- def key
- self.class.key
+ def human_name
+ I18n.t('query_fields.member_of_group')
end
def self.key
- name.to_sym
+ :group
end
- def self.create(project)
- { key => new(project) }
+ def joins
+ :groups
end
- private_class_method :new
- def self.name
- to_s.demodulize.underscore.gsub(/_filter$/, '')
+ def where
+ sql_for_field(self.class.key, operator, values, 'groups', 'id')
end
end
diff --git a/app/models/queries/users/filters/name_filter.rb b/app/models/queries/users/filters/name_filter.rb
new file mode 100644
index 0000000000..2878bd5a76
--- /dev/null
+++ b/app/models/queries/users/filters/name_filter.rb
@@ -0,0 +1,75 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::Users::Filters::NameFilter < Queries::Users::Filters::UserFilter
+ def type
+ :string
+ end
+
+ def self.key
+ :name
+ end
+
+ def where
+ case operator
+ when '='
+ ["#{sql_concat_name} IN (?)", sql_value]
+ when '!'
+ ["#{sql_concat_name} NOT IN (?)", sql_value]
+ when '~'
+ ["#{sql_concat_name} LIKE ?", "%#{sql_value}%"]
+ when '!~'
+ ["#{sql_concat_name} NOT LIKE ?", "%#{sql_value}%"]
+ end
+ end
+
+ private
+
+ def sql_value
+ case operator
+ when '=', '!'
+ values.map { |val| connection.quote_string(val.downcase) }.join(',')
+ when '~', '!~'
+ values.first.downcase
+ end
+ end
+
+ def sql_concat_name
+ case Setting.user_format
+ when :firstname_lastname, :lastname_coma_firstname
+ "LOWER(CONCAT(firstname, CONCAT(' ', lastname)))"
+ when :firstname
+ 'LOWER(firstname)'
+ when :lastname_firstname
+ "LOWER(CONCAT(lastname, CONCAT(' ', firstname)))"
+ when :username
+ "LOWER(login)"
+ end
+ end
+end
diff --git a/app/models/queries/users/filters/status_filter.rb b/app/models/queries/users/filters/status_filter.rb
new file mode 100644
index 0000000000..f6e3a9e975
--- /dev/null
+++ b/app/models/queries/users/filters/status_filter.rb
@@ -0,0 +1,44 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::Users::Filters::StatusFilter < Queries::Users::Filters::UserFilter
+ def allowed_values
+ Principal::STATUSES.keys.map do |key|
+ [I18n.t(:"status_#{key}"), key]
+ end
+ end
+
+ def type
+ :list
+ end
+
+ def self.key
+ :status
+ end
+end
diff --git a/app/models/queries/work_packages/filter_register.rb b/app/models/queries/users/filters/user_filter.rb
similarity index 86%
rename from app/models/queries/work_packages/filter_register.rb
rename to app/models/queries/users/filters/user_filter.rb
index 5e5c0396a7..085f24cc8c 100644
--- a/app/models/queries/work_packages/filter_register.rb
+++ b/app/models/queries/users/filters/user_filter.rb
@@ -27,10 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-require Rails.root.join('config/constants/work_package_filter')
+class Queries::Users::Filters::UserFilter < Queries::BaseFilter
+ self.model = User
-module Queries::WorkPackages::FilterRegister
- class << self
- delegate :register, :filters, to: ::Constants::WorkPackageFilter
+ def human_name
+ User.human_attribute_name(name)
end
end
diff --git a/config/constants/work_package_filter.rb b/app/models/queries/users/orders/default_order.rb
similarity index 86%
rename from config/constants/work_package_filter.rb
rename to app/models/queries/users/orders/default_order.rb
index 4c2bcd0eca..904e51fc27 100644
--- a/config/constants/work_package_filter.rb
+++ b/app/models/queries/users/orders/default_order.rb
@@ -27,16 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-module Constants
- module WorkPackageFilter
- class << self
- def register(filter)
- self.filters ||= []
+class Queries::Users::Orders::DefaultOrder < Queries::BaseOrder
+ self.model = User
- self.filters << filter
- end
-
- attr_accessor :filters
- end
+ def self.key
+ /id|lastname|firstname|mail|login/
end
end
diff --git a/app/models/queries/users/orders/group_order.rb b/app/models/queries/users/orders/group_order.rb
new file mode 100644
index 0000000000..f3a5341de4
--- /dev/null
+++ b/app/models/queries/users/orders/group_order.rb
@@ -0,0 +1,50 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::Users::Orders::GroupOrder < Queries::BaseOrder
+ self.model = User
+
+ def self.key
+ :group
+ end
+
+ private
+
+ def order
+ order_string = "groups_users.lastname"
+
+ order_string += " DESC" if direction == :desc
+
+ model.order(order_string)
+ end
+
+ def joins
+ :groups
+ end
+end
diff --git a/app/models/queries/users/orders/name_order.rb b/app/models/queries/users/orders/name_order.rb
new file mode 100644
index 0000000000..0ba8f88bbe
--- /dev/null
+++ b/app/models/queries/users/orders/name_order.rb
@@ -0,0 +1,48 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::Users::Orders::NameOrder < Queries::BaseOrder
+ self.model = User
+
+ def self.key
+ :name
+ end
+
+ private
+
+ def order
+ ordered = User.order_by_name
+
+ if direction == :desc
+ ordered = ordered.reverse_order
+ end
+
+ ordered
+ end
+end
diff --git a/spec/factories/queries/filter_factory.rb b/app/models/queries/users/user_query.rb
similarity index 85%
rename from spec/factories/queries/filter_factory.rb
rename to app/models/queries/users/user_query.rb
index 3c76bd50d7..b977851737 100644
--- a/spec/factories/queries/filter_factory.rb
+++ b/app/models/queries/users/user_query.rb
@@ -26,13 +26,12 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-FactoryGirl.define do
- factory :filter, class: Queries::Filter do
- field :subject
- operator '='
- values ['Feature']
+class Queries::Users::UserQuery < Queries::BaseQuery
+ def self.model
+ User
+ end
- factory :work_packages_filter, class: Queries::WorkPackages::Filter do
- end
+ def self.default_scope
+ User.not_builtin
end
end
diff --git a/app/models/queries/work_packages.rb b/app/models/queries/work_packages.rb
new file mode 100644
index 0000000000..811bc53cbd
--- /dev/null
+++ b/app/models/queries/work_packages.rb
@@ -0,0 +1,52 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module Queries::WorkPackages
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::AssignedToFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::AuthorFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::CategoryFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::CreatedAtFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::CustomFieldFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::DoneRatioFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::DueDateFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::EstimatedHoursFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::GroupFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::PriorityFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::ProjectFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::ResponsibleFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::RoleFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::StartDateFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::StatusFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::SubjectFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::SubprojectFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::TypeFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::UpdatedAtFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::VersionFilter
+ Queries::Register.filter Query, Queries::WorkPackages::Filter::WatcherFilter
+end
diff --git a/app/models/queries/work_packages/filter.rb b/app/models/queries/work_packages/filter.rb
index 5509a32cf8..ae56b35f54 100644
--- a/app/models/queries/work_packages/filter.rb
+++ b/app/models/queries/work_packages/filter.rb
@@ -27,31 +27,5 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter < Queries::Filter
- self.filter_types_by_field = filter_types_by_field.merge(
- status_id: :list_status,
- type_id: :list,
- priority_id: :list,
- subject: :text,
- start_date: :date,
- due_date: :date,
- estimated_hours: :integer,
- done_ratio: :integer,
- project_id: :list,
- category_id: :list_optional,
- fixed_version_id: :list_optional,
- subproject_id: :list_subprojects,
- assigned_to_id: :list_optional,
- author_id: :list,
- member_of_group: :list_optional,
- assigned_to_role: :list_optional,
- responsible_id: :list_optional,
- watcher_id: :list
- )
-
- validates :field, inclusion: { in: Proc.new { filter_types_by_field.keys }, message: '%(value) is not a valid filter' }, unless: Proc.new { |filter| filter.field.to_s.starts_with?('cf_') }
-
- def self.add_filter_type_by_field(field, filter_type)
- filter_types_by_field[field.to_sym] = filter_type.to_sym
- end
+module Queries::WorkPackages::Filter
end
diff --git a/app/models/queries/work_packages/filter/assigned_to_filter.rb b/app/models/queries/work_packages/filter/assigned_to_filter.rb
index 1c9bfa43f4..101cdcebf3 100644
--- a/app/models/queries/work_packages/filter/assigned_to_filter.rb
+++ b/app/models/queries/work_packages/filter/assigned_to_filter.rb
@@ -29,8 +29,8 @@
class Queries::WorkPackages::Filter::AssignedToFilter <
Queries::WorkPackages::Filter::PrincipalBaseFilter
- def values
- @values ||= begin
+ def allowed_values
+ @allowed_values ||= begin
values = principal_loader.user_values
if Setting.work_package_group_assignment?
@@ -49,7 +49,7 @@ class Queries::WorkPackages::Filter::AssignedToFilter <
4
end
- def name
+ def human_name
WorkPackage.human_attribute_name('assigned_to_id')
end
diff --git a/app/models/queries/work_packages/filter/author_filter.rb b/app/models/queries/work_packages/filter/author_filter.rb
index fd17689a05..7716852cb8 100644
--- a/app/models/queries/work_packages/filter/author_filter.rb
+++ b/app/models/queries/work_packages/filter/author_filter.rb
@@ -29,7 +29,7 @@
class Queries::WorkPackages::Filter::AuthorFilter <
Queries::WorkPackages::Filter::PrincipalBaseFilter
- def values
+ def allowed_values
@author_values ||= begin
me_value + principal_loader.user_values
end
diff --git a/app/models/queries/work_packages/filter/category_filter.rb b/app/models/queries/work_packages/filter/category_filter.rb
index eaa46d2b60..4c50094a51 100644
--- a/app/models/queries/work_packages/filter/category_filter.rb
+++ b/app/models/queries/work_packages/filter/category_filter.rb
@@ -27,15 +27,11 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::CategoryFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :project
+class Queries::WorkPackages::Filter::CategoryFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
- def initialize(project)
- self.project = project
- end
-
- def values
- @values ||= begin
+ def allowed_values
+ @allowed_values ||= begin
project.categories.map { |s| [s.name, s.id.to_s] }
end
end
diff --git a/app/models/queries/work_packages/filter/created_at_filter.rb b/app/models/queries/work_packages/filter/created_at_filter.rb
index ec0600252c..81fc83e799 100644
--- a/app/models/queries/work_packages/filter/created_at_filter.rb
+++ b/app/models/queries/work_packages/filter/created_at_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::CreatedAtFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::CreatedAtFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:date_past
end
diff --git a/app/models/queries/work_packages/filter/custom_field_filter.rb b/app/models/queries/work_packages/filter/custom_field_filter.rb
index d2fde2bb55..531622f528 100644
--- a/app/models/queries/work_packages/filter/custom_field_filter.rb
+++ b/app/models/queries/work_packages/filter/custom_field_filter.rb
@@ -27,28 +27,30 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::CustomFieldFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :field
+require 'custom_value'
- def initialize(field, project)
- self.field = field
- self.project = project
- end
+class Queries::WorkPackages::Filter::CustomFieldFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
+ attr_accessor :custom_field
+
+ validate :custom_field_valid
- def values
- case field.field_format
+ def allowed_values
+ case custom_field.field_format
when 'list'
- field.possible_values
+ custom_field.possible_values.map { |value| [value, value] }
when 'bool'
- [[I18n.t(:general_text_yes), ActiveRecord::Base.connection.unquoted_true],
- [I18n.t(:general_text_no), ActiveRecord::Base.connection.unquoted_false]]
+ [[I18n.t(:general_text_yes), CustomValue::BoolStrategy::DB_VALUE_TRUE],
+ [I18n.t(:general_text_no), CustomValue::BoolStrategy::DB_VALUE_FALSE]]
when 'user', 'version'
- field.possible_values_options(project)
+ custom_field.possible_values_options(context)
end
end
def type
- case field.field_format
+ return nil unless custom_field
+
+ case custom_field.field_format
when 'int', 'float'
:integer
when 'text'
@@ -68,33 +70,73 @@ class Queries::WorkPackages::Filter::CustomFieldFilter < Queries::WorkPackages::
20
end
- def key
- "cf_#{field.id}".to_sym
+ def name
+ :"cf_#{custom_field.id}"
end
- def name
- field.name
+ def human_name
+ custom_field ? custom_field.name : ''
end
- def self.create(project)
- custom_fields = if project
- project
- .all_work_package_custom_fields(include: :translations)
- else
- WorkPackageCustomField.filter
- .for_all
- .where.not(field_format: ['user', 'version'])
- .includes(:translations)
- end
-
- custom_fields.each_with_object({}.with_indifferent_access) do |cf, hash|
- filter = new(cf, project)
-
- hash[filter.key] = filter
- end
+ def name=(field_name)
+ cf_id = self.class.key.match(field_name)[1]
+
+ self.custom_field = WorkPackageCustomField.find_by_id(cf_id.to_i)
+
+ super
end
def self.key
- /cf_\d+/
+ /cf_(\d+)/
+ end
+
+ def self.all_for(context = nil)
+ custom_fields(context).map do |cf|
+ filter = new
+ filter.custom_field = cf
+ filter.context = context
+ filter
+ end
+ end
+
+ def self.custom_fields(context)
+ if context
+ context
+ .all_work_package_custom_fields
+ else
+ WorkPackageCustomField
+ .filter
+ .for_all
+ .where.not(field_format: ['user', 'version'])
+ end
+ end
+
+ private
+
+ def custom_field_valid
+ if custom_field.nil?
+ errors.add(:base, I18n.t('activerecord.errors.models.query.filters.custom_fields.inexistent'))
+ elsif invalid_custom_field_for_context?
+ errors.add(:base, I18n.t('activerecord.errors.models.query.filters.custom_fields.invalid'))
+ end
+ end
+
+ def validate_inclusion_of_operator
+ super if custom_field
+ end
+
+ def invalid_custom_field_for_context?
+ context && invalid_custom_field_for_project? ||
+ !context && invalid_custom_field_globally?
+ end
+
+ def invalid_custom_field_globally?
+ !self.class.custom_fields(context)
+ .exists?(custom_field.id)
+ end
+
+ def invalid_custom_field_for_project?
+ !self.class.custom_fields(context)
+ .map(&:id).include? custom_field.id
end
end
diff --git a/app/models/queries/work_packages/filter/done_ratio_filter.rb b/app/models/queries/work_packages/filter/done_ratio_filter.rb
index e4b52303e2..3ceaad6c33 100644
--- a/app/models/queries/work_packages/filter/done_ratio_filter.rb
+++ b/app/models/queries/work_packages/filter/done_ratio_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::DoneRatioFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::DoneRatioFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:integer
end
diff --git a/app/models/queries/work_packages/filter/due_date_filter.rb b/app/models/queries/work_packages/filter/due_date_filter.rb
index 5f468ff4f1..4957d0c513 100644
--- a/app/models/queries/work_packages/filter/due_date_filter.rb
+++ b/app/models/queries/work_packages/filter/due_date_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::DueDateFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::DueDateFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:date
end
diff --git a/app/models/queries/work_packages/filter/estimated_hours_filter.rb b/app/models/queries/work_packages/filter/estimated_hours_filter.rb
index 9c45d96ed3..0a46aa6dde 100644
--- a/app/models/queries/work_packages/filter/estimated_hours_filter.rb
+++ b/app/models/queries/work_packages/filter/estimated_hours_filter.rb
@@ -28,7 +28,7 @@
#++
class Queries::WorkPackages::Filter::EstimatedHoursFilter <
- Queries::WorkPackages::Filter::BaseFilter
+ Queries::WorkPackages::Filter::WorkPackageFilter
def type
:integer
end
diff --git a/app/models/queries/work_packages/filter/group_filter.rb b/app/models/queries/work_packages/filter/group_filter.rb
index 6fed5ff54c..d349d4cace 100644
--- a/app/models/queries/work_packages/filter/group_filter.rb
+++ b/app/models/queries/work_packages/filter/group_filter.rb
@@ -27,9 +27,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::GroupFilter < Queries::WorkPackages::Filter::BaseFilter
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::GroupFilter < Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
::Group.all.map { |g| [g.name, g.id.to_s] }
end
end
@@ -46,7 +46,7 @@ class Queries::WorkPackages::Filter::GroupFilter < Queries::WorkPackages::Filter
6
end
- def name
+ def human_name
I18n.t('query_fields.member_of_group')
end
diff --git a/app/models/queries/work_packages/filter/principal_base_filter.rb b/app/models/queries/work_packages/filter/principal_base_filter.rb
index 8474a184c5..49254bb737 100644
--- a/app/models/queries/work_packages/filter/principal_base_filter.rb
+++ b/app/models/queries/work_packages/filter/principal_base_filter.rb
@@ -27,20 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::PrincipalBaseFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :principal_loader
-
- def initialize(principal_loader)
- self.principal_loader = principal_loader
- end
-
+class Queries::WorkPackages::Filter::PrincipalBaseFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
def available?
- User.current.logged? || values.any?
- end
-
- def self.create(project)
- principal_loader = ::Queries::WorkPackages::Filter::PrincipalLoader.new(project)
- { key => new(principal_loader) }
+ User.current.logged? || allowed_values.any?
end
private
@@ -50,4 +40,8 @@ class Queries::WorkPackages::Filter::PrincipalBaseFilter < Queries::WorkPackages
values << [I18n.t(:label_me), 'me'] if User.current.logged?
values
end
+
+ def principal_loader
+ @principal_loader ||= ::Queries::WorkPackages::Filter::PrincipalLoader.new(project)
+ end
end
diff --git a/app/models/queries/work_packages/filter/priority_filter.rb b/app/models/queries/work_packages/filter/priority_filter.rb
index 4e43042451..e3ac294e3e 100644
--- a/app/models/queries/work_packages/filter/priority_filter.rb
+++ b/app/models/queries/work_packages/filter/priority_filter.rb
@@ -27,9 +27,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::PriorityFilter < Queries::WorkPackages::Filter::BaseFilter
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::PriorityFilter < Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
priorities.map { |s| [s.name, s.id.to_s] }
end
end
diff --git a/app/models/queries/work_packages/filter/project_filter.rb b/app/models/queries/work_packages/filter/project_filter.rb
index 04a5fed8fa..81c62f01ca 100644
--- a/app/models/queries/work_packages/filter/project_filter.rb
+++ b/app/models/queries/work_packages/filter/project_filter.rb
@@ -27,15 +27,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::ProjectFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :project
-
- def initialize(project)
- self.project = project
- end
-
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::ProjectFilter < Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
project_values = []
Project.project_tree(visible_projects) do |p, level|
prefix = (level > 0 ? ('--' * level + ' ') : '')
diff --git a/app/models/queries/work_packages/filter/responsible_filter.rb b/app/models/queries/work_packages/filter/responsible_filter.rb
index c047b27ddd..8925ff630a 100644
--- a/app/models/queries/work_packages/filter/responsible_filter.rb
+++ b/app/models/queries/work_packages/filter/responsible_filter.rb
@@ -29,8 +29,8 @@
class Queries::WorkPackages::Filter::ResponsibleFilter <
Queries::WorkPackages::Filter::PrincipalBaseFilter
- def values
- @values ||= begin
+ def allowed_values
+ @allowed_values ||= begin
values = principal_loader.user_values
me_value + values
end
diff --git a/app/models/queries/work_packages/filter/role_filter.rb b/app/models/queries/work_packages/filter/role_filter.rb
index 556a094f8a..f73863a5e1 100644
--- a/app/models/queries/work_packages/filter/role_filter.rb
+++ b/app/models/queries/work_packages/filter/role_filter.rb
@@ -27,9 +27,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::RoleFilter < Queries::WorkPackages::Filter::BaseFilter
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::RoleFilter < Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
roles.map { |r| [r.name, r.id.to_s] }
end
end
@@ -46,7 +46,7 @@ class Queries::WorkPackages::Filter::RoleFilter < Queries::WorkPackages::Filter:
7
end
- def name
+ def human_name
I18n.t('query_fields.assigned_to_role')
end
diff --git a/app/models/queries/work_packages/filter/start_date_filter.rb b/app/models/queries/work_packages/filter/start_date_filter.rb
index a52a536938..a82810b8bd 100644
--- a/app/models/queries/work_packages/filter/start_date_filter.rb
+++ b/app/models/queries/work_packages/filter/start_date_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::StartDateFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::StartDateFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:date
end
diff --git a/app/models/queries/work_packages/filter/status_filter.rb b/app/models/queries/work_packages/filter/status_filter.rb
index cf44c287ab..4010a325ca 100644
--- a/app/models/queries/work_packages/filter/status_filter.rb
+++ b/app/models/queries/work_packages/filter/status_filter.rb
@@ -27,9 +27,9 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::StatusFilter < Queries::WorkPackages::Filter::BaseFilter
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::StatusFilter < Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
Status.all.map { |s| [s.name, s.id.to_s] }
end
end
diff --git a/app/models/queries/work_packages/filter/subject_filter.rb b/app/models/queries/work_packages/filter/subject_filter.rb
index 0f75132147..0d8a10dd32 100644
--- a/app/models/queries/work_packages/filter/subject_filter.rb
+++ b/app/models/queries/work_packages/filter/subject_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::SubjectFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::SubjectFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:text
end
diff --git a/app/models/queries/work_packages/filter/subproject_filter.rb b/app/models/queries/work_packages/filter/subproject_filter.rb
index b764ff4369..2f7759d4c4 100644
--- a/app/models/queries/work_packages/filter/subproject_filter.rb
+++ b/app/models/queries/work_packages/filter/subproject_filter.rb
@@ -27,15 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::SubprojectFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :project
-
- def initialize(project)
- self.project = project
- end
-
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::SubprojectFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
project.descendants.visible.map { |s| [s.name, s.id.to_s] }
end
end
@@ -54,7 +49,7 @@ class Queries::WorkPackages::Filter::SubprojectFilter < Queries::WorkPackages::F
13
end
- def name
+ def human_name
I18n.t('query_fields.subproject_id')
end
diff --git a/app/models/queries/work_packages/filter/type_filter.rb b/app/models/queries/work_packages/filter/type_filter.rb
index 3fb0a8d30d..3153d85cfa 100644
--- a/app/models/queries/work_packages/filter/type_filter.rb
+++ b/app/models/queries/work_packages/filter/type_filter.rb
@@ -27,15 +27,10 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::TypeFilter < Queries::WorkPackages::Filter::BaseFilter
- attr_accessor :project
-
- def initialize(project)
- self.project = project
- end
-
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::TypeFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
+ def allowed_values
+ @allowed_values ||= begin
types.map { |s| [s.name, s.id.to_s] }
end
end
diff --git a/app/models/queries/work_packages/filter/updated_at_filter.rb b/app/models/queries/work_packages/filter/updated_at_filter.rb
index 7fac3d5c87..a2b665e5b6 100644
--- a/app/models/queries/work_packages/filter/updated_at_filter.rb
+++ b/app/models/queries/work_packages/filter/updated_at_filter.rb
@@ -27,7 +27,7 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::UpdatedAtFilter < Queries::WorkPackages::Filter::BaseFilter
+class Queries::WorkPackages::Filter::UpdatedAtFilter < Queries::WorkPackages::Filter::WorkPackageFilter
def type
:date_past
end
diff --git a/app/models/queries/work_packages/filter/version_filter.rb b/app/models/queries/work_packages/filter/version_filter.rb
index fa2c78670d..cfb994431b 100644
--- a/app/models/queries/work_packages/filter/version_filter.rb
+++ b/app/models/queries/work_packages/filter/version_filter.rb
@@ -27,17 +27,15 @@
# See doc/COPYRIGHT.rdoc for more details.
#++
-class Queries::WorkPackages::Filter::VersionFilter < Queries::WorkPackages::Filter::BaseFilter
- def values
- @values ||= begin
+class Queries::WorkPackages::Filter::VersionFilter <
+ Queries::WorkPackages::Filter::WorkPackageFilter
+
+ def allowed_values
+ @allowed_values ||= begin
versions.sort.map { |s| ["#{s.project.name} - #{s.name}", s.id.to_s] }
end
end
- def available?
- versions.exists?
- end
-
def type
:list_optional
end
@@ -46,7 +44,7 @@ class Queries::WorkPackages::Filter::VersionFilter < Queries::WorkPackages::Filt
7
end
- def name
+ def human_name
WorkPackage.human_attribute_name('fixed_version_id')
end
diff --git a/app/models/queries/work_packages/filter/watcher_filter.rb b/app/models/queries/work_packages/filter/watcher_filter.rb
index 3f40849d9b..3ffe235787 100644
--- a/app/models/queries/work_packages/filter/watcher_filter.rb
+++ b/app/models/queries/work_packages/filter/watcher_filter.rb
@@ -29,8 +29,8 @@
class Queries::WorkPackages::Filter::WatcherFilter <
Queries::WorkPackages::Filter::PrincipalBaseFilter
- def values
- @values ||= begin
+ def allowed_values
+ @allowed_values ||= begin
# populate the watcher list with the same user list as other user filters
# if the user has the :view_work_package_watchers permission
# in at least one project
@@ -38,7 +38,7 @@ class Queries::WorkPackages::Filter::WatcherFilter <
# more, e.g. all users could watch issues in public projects,
# but won't necessarily be shown here
values = me_value
- if User.current.allowed_to?(:view_work_packages_watchers, project, global: project.nil?)
+ if User.current.allowed_to?(:view_work_package_watchers, project, global: project.nil?)
values += principal_loader.user_values
end
values
diff --git a/app/models/queries/work_packages/filter/work_package_filter.rb b/app/models/queries/work_packages/filter/work_package_filter.rb
new file mode 100644
index 0000000000..a5b4e15a54
--- /dev/null
+++ b/app/models/queries/work_packages/filter/work_package_filter.rb
@@ -0,0 +1,75 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+class Queries::WorkPackages::Filter::WorkPackageFilter < ::Queries::BaseFilter
+ include ActiveModel::Serialization
+ # (de-)serialization
+ def self.from_hash(filter_hash)
+ filter_hash.keys.map { |field| new(field, filter_hash[field]) }
+ end
+
+ def to_hash
+ { name => attributes_hash }
+ end
+
+ def human_name
+ WorkPackage.human_attribute_name(name)
+ end
+
+ alias :project :context
+ alias :project= :context=
+
+ def attributes
+ { name: name, operator: operator, values: values }
+ end
+
+ def possible_types_by_operator
+ @@operators_by_filter_type.select { |_key, operators| operators.include?(operator) }.keys.sort
+ end
+
+ def ==(filter)
+ filter.attributes_hash == attributes_hash
+ end
+
+ protected
+
+ def attributes_hash
+ @@filter_params.inject({}) do |params, param_field|
+ params.merge(param_field => send(param_field))
+ end
+ end
+
+ private
+
+ def stringify_values
+ unless values.nil?
+ values.map!(&:to_s)
+ end
+ end
+end
diff --git a/app/models/query.rb b/app/models/query.rb
index b651f2f0f2..ffbbdf5fb7 100644
--- a/app/models/query.rb
+++ b/app/models/query.rb
@@ -28,10 +28,8 @@
#++
class Query < ActiveRecord::Base
- include Queries::WorkPackages::AvailableFilterOptions
-
- # referenced in plugin patches - currently there are only work package queries and filters
- alias_method :available_filters, :available_work_package_filters
+ include Queries::AvailableFilters
+ include Queries::SqlForField
@@user_filters = %w{assigned_to_id author_id watcher_id responsible_id}.freeze
@@ -131,7 +129,7 @@ class Query < ActiveRecord::Base
groupable: true),
QueryColumn.new(:created_at,
sortable: "#{WorkPackage.table_name}.created_at",
- default_order: 'desc'),
+ default_order: 'desc')
]
cattr_reader :available_columns
@@ -140,21 +138,47 @@ class Query < ActiveRecord::Base
add_default_filter if options[:initialize_with_default_filter]
end
+ after_initialize :set_context
+
+ def set_context
+ # We need to set the project for each filter if a project
+ # is present because the information is not available when
+ # deserializing the filters from the db.
+
+ # Allow to use AR's select(...) without
+ # the filters attribute
+ return unless respond_to?(:filters)
+
+ filters.each do |filter|
+ filter.context = project
+ end
+ end
+
+ alias :context :project
+
def add_default_filter
- self.filters = [Queries::WorkPackages::Filter.new('status_id', operator: 'o', values: [''])] if filters.blank?
+ return unless filters.blank?
+
+ add_filter('status_id', 'o', [''])
end
def validate_work_package_filters
filters.each do |filter|
unless filter.valid?
- messages = filter.errors.messages.values.flatten.join(" #{I18n.t('support.array.sentence_connector')} ")
- cf_id = custom_field_id filter
-
- if cf_id && CustomField.find(cf_id)
- attribute_name = CustomField.find(cf_id).name
- errors.add :base, attribute_name + I18n.t(default: ' %{message}', message: messages)
+ messages = filter
+ .errors
+ .messages
+ .values
+ .flatten
+ .join(" #{I18n.t('support.array.sentence_connector')} ")
+
+ attribute_name = filter.human_name
+
+ # TODO: check if this can be handled without the case statment
+ case filter
+ when Queries::WorkPackages::Filter::CustomFieldFilter
+ errors.add :base, attribute_name + I18n.t(default: ' %{message}', message: messages)
else
- attribute_name = WorkPackage.human_attribute_name(filter.field)
errors.add :base, errors.full_message(attribute_name, messages)
end
end
@@ -170,14 +194,12 @@ class Query < ActiveRecord::Base
end
def add_filter(field, operator, values)
- return unless work_package_filter_available?(field)
+ filter = filter_for(field)
- if filter = filter_for(field)
- filter.operator = operator
- filter.values = values
- else
- filters << Queries::WorkPackages::Filter.new(field, operator: operator, values: values)
- end
+ filter.operator = operator
+ filter.values = values
+
+ filters << filter
end
def add_short_filter(field, expression)
@@ -198,28 +220,15 @@ class Query < ActiveRecord::Base
end
def has_filter?(field)
- filters.present? && filters.any? { |filter| filter.field.to_s == field.to_s }
+ filters.present? && filters.any? { |f| f.field.to_s == field.to_s }
end
def filter_for(field)
- (filters || []).detect { |filter| filter.field.to_s == field.to_s }
- end
+ filter = (filters || []).detect { |f| f.field.to_s == field.to_s } || super(field)
- # Deprecated
- def operator_for(field)
- warn '#operator_for is deprecated. Query the filter object directly, instead.'
- filter_for(field).try :operator
- end
+ filter.context = project
- # Deprecated
- def values_for(field)
- warn '#values_for is deprecated. Query the filter object directly, instead.'
- filter_for(field).try :values
- end
-
- def label_for(field)
- label = available_work_package_filters[field][:name] if work_package_filter_available?(field)
- label ||= field.gsub(/\_id\z/, '')
+ filter
end
def normalized_name
@@ -241,11 +250,11 @@ class Query < ActiveRecord::Base
end
def self.available_columns=(v)
- self.available_columns = (v)
+ self.available_columns = v
end
def self.add_available_column(column)
- available_columns << (column) if column.is_a?(QueryColumn)
+ available_columns << column if column.is_a?(QueryColumn)
end
# Returns an array of columns that can be used to group the results
@@ -255,10 +264,13 @@ class Query < ActiveRecord::Base
# Returns a Hash of columns and the key for sorting
def sortable_columns
- { 'id' => "#{WorkPackage.table_name}.id" }.merge(available_columns.inject({}) {|h, column|
- h[column.name.to_s] = column.sortable
- h
- })
+ column_sortability = available_columns.inject({}) do |h, column|
+ h[column.name.to_s] = column.sortable
+ h
+ end
+
+ { 'id' => "#{WorkPackage.table_name}.id" }
+ .merge(column_sortability)
end
def columns
@@ -307,7 +319,6 @@ class Query < ActiveRecord::Base
end
def sort_criteria=(arg)
- c = []
if arg.is_a?(Hash)
arg = arg.keys.sort.map { |k| arg[k] }
end
@@ -523,112 +534,14 @@ class Query < ActiveRecord::Base
raise ::Query::StatementInvalid.new(e.message)
end
- # Note: Convenience method to allow the angular front end to deal with query menu items in a non implementation-specific way
+ # Note: Convenience method to allow the angular front end to deal with query
+ # menu items in a non implementation-specific way
def starred
!!query_menu_item
end
private
- def custom_field_id(filter)
- matchdata = /cf\_(?\d+)/.match(filter.field.to_s)
-
- matchdata.nil? ? nil : matchdata[:id]
- end
-
- # Helper method to generate the WHERE sql for a +field+, +operator+ and a +values+ array
- def sql_for_field(field, operator, values, db_table, db_field, is_custom_filter = false)
- # code expects strings (e.g. for quoting), but ints would work as well: unify them here
- values = values.map(&:to_s)
-
- sql = ''
- case operator
- when '='
- if values.present?
- if values.include?('-1')
- sql = "#{db_table}.#{db_field} IS NULL OR "
- end
-
- sql += "#{db_table}.#{db_field} IN (" + values.map { |val| "'#{connection.quote_string(val)}'" }.join(',') + ')'
- else
- # empty set of allowed values produces no result
- sql = '0=1'
- end
- when '!'
- if values.present?
- sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + values.map { |val| "'#{connection.quote_string(val)}'" }.join(',') + '))'
- else
- # empty set of forbidden values allows all results
- sql = '1=1'
- end
- 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 '>='
- if is_custom_filter
- sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) >= #{values.first.to_f}"
- else
- sql = "#{db_table}.#{db_field} >= #{values.first.to_f}"
- end
- when '<='
- if is_custom_filter
- sql = "#{db_table}.#{db_field} != '' AND CAST(#{db_table}.#{db_field} AS decimal(60,4)) <= #{values.first.to_f}"
- else
- sql = "#{db_table}.#{db_field} <= #{values.first.to_f}"
- end
- when 'o'
- sql = "#{Status.table_name}.is_closed=#{connection.quoted_false}" if field == 'status_id'
- when 'c'
- sql = "#{Status.table_name}.is_closed=#{connection.quoted_true}" if field == 'status_id'
- when '>t-'
- sql = date_range_clause(db_table, db_field, - values.first.to_i, 0)
- when 't+'
- sql = date_range_clause(db_table, db_field, values.first.to_i, nil)
- when ' '%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
-
- def connection
- self.class.connection
- end
-
def for_all?
@for_all ||= project.nil?
end
diff --git a/app/seeders/demo_data/query_seeder.rb b/app/seeders/demo_data/query_seeder.rb
index 7dfb255537..76ee72e7af 100644
--- a/app/seeders/demo_data/query_seeder.rb
+++ b/app/seeders/demo_data/query_seeder.rb
@@ -53,12 +53,44 @@ module DemoData
end
def data
+ admin = User.admin.first
+ bug_type = Type.find_by(name: I18n.t('default_type_bug'))
+ milestone_type = Type.find_by(name: I18n.t('default_type_milestone'))
+ phase_type = Type.find_by(name: I18n.t('default_type_phase'))
+ task_type = Type.find_by(name: I18n.t('default_type_task'))
+ story_type = Type.find_by(name: I18n.t('default_type_user_story'))
+
[
- { name: "Bugs", filters: [Queries::WorkPackages::Filter.new(:status_id, operator: "o"), Queries::WorkPackages::Filter.new(:type_id, operator: "=", values: ['7'])], user_id: User.admin.first.id, is_public: true, column_names: [:id, :type, :status, :priority, :subject, :assigned_to, :create_at] },
- { name: "Milestones", filters: [Queries::WorkPackages::Filter.new(:status_id, operator: "o"), Queries::WorkPackages::Filter.new(:type_id, operator: "=", values: ['2'])], user_id: User.admin.first.id, is_public: true, column_names: [:id, :type, :status, :subject, :start_date, :due_date] },
- { name: "Phases", filters: [Queries::WorkPackages::Filter.new(:status_id, operator: "o"), Queries::WorkPackages::Filter.new(:type_id, operator: "=", values: ['3'])], user_id: User.admin.first.id, is_public: true, column_names: [:id, :type, :status, :subject, :start_date, :due_date] },
- { name: "Tasks", filters: [Queries::WorkPackages::Filter.new(:status_id, operator: "o"), Queries::WorkPackages::Filter.new(:type_id, operator: "=", values: ['1'])], user_id: User.admin.first.id, is_public: true, column_names: [:id, :type, :status, :priority, :subject, :assigned_to] },
- { name: "User Stories", filters: [Queries::WorkPackages::Filter.new(:status_id, operator: "o"), Queries::WorkPackages::Filter.new(:type_id, operator: "=", values: ['6'])], user_id: User.admin.first.id, is_public: true, column_names: [:id, :type, :status, :priority, :subject, :assigned_to] }
+ { name: "Bugs",
+ filters: [status_id: { operator: "o" },
+ type_id: { operator: "=", values: [bug_type.id.to_s] }],
+ user_id: admin.id,
+ is_public: true,
+ column_names: [:id, :type, :status, :priority, :subject, :assigned_to, :create_at] },
+ { name: "Milestones",
+ filters: [status_id: { operator: "o" },
+ type_id: { operator: "=", values: [milestone_type.id.to_s] }],
+ user_id: admin.id,
+ is_public: true,
+ column_names: [:id, :type, :status, :subject, :start_date, :due_date] },
+ { name: "Phases",
+ filters: [status_id: { operator: "o" },
+ type_id: { operator: "=", values: [phase_type.id.to_s] }],
+ user_id: admin.id,
+ is_public: true,
+ column_names: [:id, :type, :status, :subject, :start_date, :due_date] },
+ { name: "Tasks",
+ filters: [status_id: { operator: "o" },
+ type_id: { operator: "=", values: [task_type.id.to_s] }],
+ user_id: admin.id,
+ is_public: true,
+ column_names: [:id, :type, :status, :priority, :subject, :assigned_to] },
+ { name: "User Stories",
+ filters: [status_id: { operator: "o" },
+ type_id: { operator: "=", values: [story_type.id.to_s] }],
+ user_id: admin.id,
+ is_public: true,
+ column_names: [:id, :type, :status, :priority, :subject, :assigned_to] }
]
end
end
diff --git a/app/services/api/v3/params_to_query_service.rb b/app/services/api/v3/params_to_query_service.rb
new file mode 100644
index 0000000000..ca072502ce
--- /dev/null
+++ b/app/services/api/v3/params_to_query_service.rb
@@ -0,0 +1,128 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module API
+ module V3
+ class ParamsToQueryService
+ attr_accessor :model
+
+ def initialize(model)
+ self.model = model
+ end
+
+ def call(params)
+ query = new_query
+
+ query = apply_filters(query, params)
+ query = apply_order(query, params)
+
+ query
+ end
+
+ private
+
+ def new_query
+ model_name = model.name
+
+ query_class = Kernel.const_get "::Queries::#{model_name.pluralize}::#{model_name}Query"
+
+ query_class.new
+ end
+
+ def apply_filters(query, params)
+ return query unless params[:filters]
+
+ filters = parse_filters_from_json(params[:filters])
+
+ filters[:attributes].each do |filter_name|
+ query = query.where(filter_name,
+ filters[:operators][filter_name],
+ filters[:values][filter_name])
+ end
+
+ query
+ end
+
+ def apply_order(query, params)
+ return query unless params[:sortBy]
+
+ sort = parse_sorting_from_json(params[:sortBy])
+
+ hash_sort = sort.each_with_object({}) do |(attribute, direction), hash|
+ hash[attribute.to_sym] = direction.to_sym
+ end
+
+ query.order(hash_sort)
+ end
+
+ # Expected format looks like:
+ # [
+ # {
+ # "filtered_field_name": {
+ # "operator": "a name for a filter operation",
+ # "values": ["values", "for the", "operation"]
+ # }
+ # },
+ # { /* more filters if needed */}
+ # ]
+ def parse_filters_from_json(json)
+ filters = JSON.parse(json)
+ operators = {}
+ values = {}
+ filters.each do |filter|
+ attribute = filter.keys.first # there should only be one attribute per filter
+ ar_attribute = convert_attribute attribute, append_id: true
+ operators[ar_attribute] = filter[attribute]['operator']
+ values[ar_attribute] = filter[attribute]['values']
+ end
+
+ {
+ attributes: values.keys,
+ operators: operators,
+ values: values
+ }
+ end
+
+ def parse_sorting_from_json(json)
+ JSON.parse(json).map do |(attribute, order)|
+ [convert_attribute(attribute), order]
+ end
+ end
+
+ def convert_attribute(attribute, append_id: false)
+ ::API::Utilities::PropertyNameConverter.to_ar_name(attribute,
+ context: conversion_model,
+ refer_to_ids: append_id)
+ end
+
+ def conversion_model
+ @conversion_model ||= model.new
+ end
+ end
+ end
+end
diff --git a/app/views/queries/_filters.html.erb b/app/views/queries/_filters.html.erb
index c1f8e7c020..25647c4c22 100644
--- a/app/views/queries/_filters.html.erb
+++ b/app/views/queries/_filters.html.erb
@@ -103,13 +103,13 @@ See doc/COPYRIGHT.rdoc for more details.
//]]>
id="tr_<%= field %>" class="filter advanced-filters--filter">
<%= label_tag "op_#{field}", l(:description_filter), class: "hidden-for-sighted" %>
@@ -124,7 +124,7 @@ See doc/COPYRIGHT.rdoc for more details.
<% field_values = query.filter_for(field).try(:values) || []
- case options[:type]
+ case filter.type
when :list, :list_optional, :list_status, :list_subprojects %>
<%= link_to_function icon_wrapper('icon-context icon-plus', l(:label_enable_multi_select)),
diff --git a/app/views/timelines/_custom_fields.html.erb b/app/views/timelines/_custom_fields.html.erb
index 7e3bea2c61..eb32bdd5b1 100644
--- a/app/views/timelines/_custom_fields.html.erb
+++ b/app/views/timelines/_custom_fields.html.erb
@@ -44,8 +44,8 @@ custom_fields.each do |custom_field|
[
['', ''],
[l('timelines.filter.noneElement'), '-1'],
- [l('general_text_Yes'), 't' ],
- [l('general_text_No'), 'f']
+ [l('general_text_Yes'), CustomValue::BoolStrategy::DB_VALUE_TRUE],
+ [l('general_text_No'), CustomValue::BoolStrategy::DB_VALUE_FALSE]
],
timeline.custom_fields_filter[custom_field.id.to_s]),
class: "cf_boolean_select",
diff --git a/config/constants/query_register.rb b/config/constants/query_register.rb
new file mode 100644
index 0000000000..9fbcd24a64
--- /dev/null
+++ b/config/constants/query_register.rb
@@ -0,0 +1,52 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+module Constants
+ module QueryRegister
+ class << self
+ def filter(query, filter)
+ self.filters ||= Hash.new do |hash, filter_key|
+ hash[filter_key] = []
+ end
+
+ self.filters[query] << filter
+ end
+
+ def order(query, order)
+ self.orders ||= Hash.new do |hash, order_key|
+ hash[order_key] = []
+ end
+
+ self.orders[query] << order
+ end
+
+ attr_accessor :filters, :orders
+ end
+ end
+end
diff --git a/config/initializers/work_package_filters.rb b/config/initializers/work_package_filters.rb
deleted file mode 100644
index e77de39676..0000000000
--- a/config/initializers/work_package_filters.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-#-- encoding: UTF-8
-#-- copyright
-# OpenProject is a project management system.
-# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
-#
-# This program is free software; you can redistribute it and/or
-# modify it under the terms of the GNU General Public License version 3.
-#
-# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
-# Copyright (C) 2006-2013 Jean-Philippe Lang
-# Copyright (C) 2010-2013 the ChiliProject Team
-#
-# 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.
-#
-# See doc/COPYRIGHT.rdoc for more details.
-#++
-
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::AssignedToFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::AuthorFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::CategoryFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::CreatedAtFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::CustomFieldFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::DoneRatioFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::DueDateFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::EstimatedHoursFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::GroupFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::PriorityFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::ProjectFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::ResponsibleFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::RoleFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::StartDateFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::StatusFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::SubjectFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::SubprojectFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::TypeFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::UpdatedAtFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::VersionFilter
-Queries::WorkPackages::FilterRegister.register Queries::WorkPackages::Filter::WatcherFilter
diff --git a/config/locales/en.yml b/config/locales/en.yml
index dcba9d044f..de8de6bbf1 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -305,13 +305,13 @@ en:
user:
admin: "Administrator"
auth_source: "Authentication mode"
+ current_password: "Current password"
+ force_password_change: "Enforce password change on next login"
language: "Language"
last_login_on: "Last login"
mail_notification: "Email notifications"
- current_password: "Current password"
new_password: "New password"
password_confirmation: "Confirmation"
- force_password_change: "Enforce password change on next login"
user_preference:
comments_sorting: "Display comments"
hide_mail: "Hide my email address"
@@ -329,18 +329,15 @@ en:
parent_title: "Parent page"
redirect_existing_links: "Redirect existing links"
planning_element_type_color:
- name: Name
hexcode: Hex code
project_association:
description: Cause
project_b: 'Dependent project'
project_type:
allows_association: Allows association
- name: Name
reported_project_statuses: Reported project statuses
position: Position
timeline:
- name: Name
options: ""
reporting:
reported_project_status_comment: Status comment
@@ -373,6 +370,7 @@ en:
cant_link_a_work_package_with_a_descendant: "A work package cannot be linked to one of its subtasks."
circular_dependency: "This relation would create a circular dependency."
confirmation: "doesn't match %{attribute}."
+ does_not_exist: "does not exist."
empty: "can't be empty."
even: "must be even."
exclusion: "is reserved."
@@ -410,6 +408,11 @@ en:
project_association:
identical_projects: "Dependency cannot be created between one project and itself."
project_association_not_allowed: "does not allow associations."
+ query:
+ filters:
+ custom_fields:
+ inexistent: "There is no custom field for the filter."
+ invalid: "The custom field is not valid in the given context."
repository:
not_available: "SCM vendor is not available"
not_whitelisted: "is not allowed by the configuration."
@@ -423,7 +426,6 @@ en:
cannot_be_milestone: "cannot be a milestone."
cannot_be_in_another_project: "cannot be in another project."
not_a_valid_parent: "is invalid."
- does_not_exist: "does not exist."
start_date:
violates_relationships: "can only be set to %{soonest_start} or later so as not to violate the work package's relationships."
status_id:
diff --git a/doc/apiv3/endpoints/users.apib b/doc/apiv3/endpoints/users.apib
index 51d0f8ccb3..7decd6aba1 100644
--- a/doc/apiv3/endpoints/users.apib
+++ b/doc/apiv3/endpoints/users.apib
@@ -404,7 +404,7 @@ Lists users. Only administrators have permission to do this.
+ pageSize (optional, integer, `25`) ... Number of elements to display per page.
- + filters (optional, string, `[{ "status": { "operator": "=", "values": "invited" } }, { "group": { "operator": "=", "values": "Managers" } }, { "name": { "operator": "=", "values": "h.wurst@openproject.com" } }]`) ... JSON specifying filter conditions.
+ + filters (optional, string, `[{ "status": { "operator": "=", "values": ["invited"] } }, { "group": { "operator": "=", "values": ["1"] } }, { "name": { "operator": "=", "values": ["h.wurst@openproject.com"] } }]`) ... JSON specifying filter conditions.
Accepts the same format as returned by the [queries](#queries) endpoint. Currently supported filters are:
+ status: Status the user has
+ group: Name of the group in which to-be-listed users are members.
diff --git a/features/step_definitions/work_package_steps.rb b/features/step_definitions/work_package_steps.rb
index 57cb0db35e..74bd8e65df 100644
--- a/features/step_definitions/work_package_steps.rb
+++ b/features/step_definitions/work_package_steps.rb
@@ -75,7 +75,10 @@ Given(/^the user "([^\"]+)" has the following queries by type in the project "(.
table.hashes.each_with_index do |t, _i|
types = ::Type.where(name: t['type_value']).map { |type| type.id.to_s }
- p.queries.create(user_id: u.id, name: t['name'], filters: [Queries::WorkPackages::Filter.new(:type_id, operator: '=', values: types)])
+ query = p.queries.create(user_id: u.id, name: t['name'])
+ query.filters.clear
+ query.add_filter(:type_id, '=', types)
+ query.save!
end
end
diff --git a/lib/api/v3/users/paginated_user_collection_representer.rb b/lib/api/v3/users/paginated_user_collection_representer.rb
new file mode 100644
index 0000000000..24b17cb7ee
--- /dev/null
+++ b/lib/api/v3/users/paginated_user_collection_representer.rb
@@ -0,0 +1,44 @@
+#-- encoding: UTF-8
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+# TODO: this is to be removed or rather the UserCollectionRepresenter is
+# to be turned into an OffsetPaginatedCollection representer.
+# It is not possible to do that right now as we do not have a
+# solution for an accessible autocompleter drop down widget. We therefore
+# have to fetch all users when we want to present them inside of a drop down.
+
+module API
+ module V3
+ module Users
+ class PaginatedUserCollectionRepresenter < ::API::Decorators::OffsetPaginatedCollection
+ element_decorator ::API::V3::Users::UserRepresenter
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/users/users_api.rb b/lib/api/v3/users/users_api.rb
index ee4fa7b3d8..264fe124e3 100644
--- a/lib/api/v3/users/users_api.rb
+++ b/lib/api/v3/users/users_api.rb
@@ -27,6 +27,7 @@
#++
require 'api/v3/users/user_representer'
+require 'api/v3/users/paginated_user_collection_representer'
module API
module V3
@@ -50,6 +51,10 @@ module API
fail ::API::Errors::Unauthorized
end
end
+
+ def to_i_or_nil(string)
+ string ? string.to_i : nil
+ end
end
resources :users do
@@ -60,6 +65,23 @@ module API
create_user(request_body, current_user)
end
+ get do
+ allow_only_admin
+
+ query = ::API::V3::ParamsToQueryService.new(User).call(params)
+
+ if query.valid?
+ users = query.results.includes(:preference)
+ PaginatedUserCollectionRepresenter.new(users,
+ api_v3_paths.users,
+ page: to_i_or_nil(params[:offset]),
+ per_page: to_i_or_nil(params[:pageSize]),
+ current_user: current_user)
+ else
+ raise ::API::Errors::InvalidQuery.new(query.errors.full_messages)
+ end
+ end
+
params do
requires :id, desc: 'User\'s id'
end
@@ -67,7 +89,7 @@ module API
helpers ::API::V3::Users::UpdateUser
before do
- @user = User.find(params[:id])
+ @user = User.find(params[:id])
end
get do
@@ -90,9 +112,7 @@ module API
namespace :lock do
# Authenticate lock transitions
before do
- unless current_user.admin?
- fail ::API::Errors::Unauthorized
- end
+ allow_only_admin
end
desc 'Set lock on user account'
diff --git a/spec/features/timelines/filter_custom_fields_spec.rb b/spec/features/timelines/filter_custom_fields_spec.rb
index 07db96265f..776029f908 100644
--- a/spec/features/timelines/filter_custom_fields_spec.rb
+++ b/spec/features/timelines/filter_custom_fields_spec.rb
@@ -100,8 +100,11 @@ describe Timeline, 'filtering custom fields', type: :feature, js: true do
shared_examples_for 'filtering by bool custom field' do
it 'filters accordingly' do
- FactoryGirl.create(:custom_value, customized: wp1, custom_field: cf, value: 'f')
- FactoryGirl.create(:custom_value, customized: wp2, custom_field: cf, value: 't')
+ wp1.custom_field_values = { cf.id => false }
+ wp1.save!
+
+ wp2.custom_field_values = { cf.id => true }
+ wp2.save!
wp3
# wp3 has no such custom value
diff --git a/spec/features/work_packages/select_query_spec.rb b/spec/features/work_packages/select_query_spec.rb
index 046eb2d5e9..957911bb80 100644
--- a/spec/features/work_packages/select_query_spec.rb
+++ b/spec/features/work_packages/select_query_spec.rb
@@ -42,22 +42,21 @@ describe 'Query selection', type: :feature do
let(:i18n_filter_1_name) { WorkPackage.human_attribute_name(:assigned_to_id) }
let(:i18n_filter_2_name) { WorkPackage.human_attribute_name(:done_ratio) }
+ before do
+ allow(User).to receive(:current).and_return current_user
+ end
+
let!(:query) do
FactoryGirl.build(:query, project: project, is_public: true).tap do |query|
- query.filters = [
- Queries::WorkPackages::Filter.new('assigned_to_id', operator: '=', values: ['me']),
- Queries::WorkPackages::Filter.new('done_ratio', operator: '>=', values: [10])
- ]
+ query.filters.clear
+ query.add_filter('assigned_to_id', '=', ['me'])
+ query.add_filter('done_ratio', '>=', [10])
query.save!
end
end
let(:work_packages_page) { WorkPackagesPage.new(project) }
- before do
- allow(User).to receive(:current).and_return current_user
- end
-
context 'default view, without a query selected' do
before do
work_packages_page.visit_index
diff --git a/spec/lib/api/v3/queries/query_representer_spec.rb b/spec/lib/api/v3/queries/query_representer_spec.rb
index b8696f395b..0e3be5c8f7 100644
--- a/spec/lib/api/v3/queries/query_representer_spec.rb
+++ b/spec/lib/api/v3/queries/query_representer_spec.rb
@@ -100,14 +100,11 @@ describe ::API::V3::Queries::QueryRepresenter do
end
describe 'with filters' do
- let(:query) {
- FactoryGirl.build_stubbed(:query,
- filters: [
- Queries::WorkPackages::Filter.new('status_id',
- operator: '=',
- values: ['1'])
- ])
- }
+ let(:query) do
+ query = FactoryGirl.build_stubbed(:query)
+ query.add_filter('status_id', '=', ['1'])
+ query
+ end
it 'should render the filters' do
expected = [
diff --git a/spec/lib/api/v3/users/paginated_user_collection_representer_spec.rb b/spec/lib/api/v3/users/paginated_user_collection_representer_spec.rb
new file mode 100644
index 0000000000..2b0cbd4036
--- /dev/null
+++ b/spec/lib/api/v3/users/paginated_user_collection_representer_spec.rb
@@ -0,0 +1,69 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+
+describe ::API::V3::Users::PaginatedUserCollectionRepresenter do
+ let(:self_base_link) { '/api/v3/users' }
+ let(:collection_inner_type) { 'User' }
+ let(:total) { 3 }
+ let(:page) { 1 }
+ let(:page_size) { 2 }
+ let(:actual_count) { 3 }
+
+ let(:users) {
+ users = FactoryGirl.build_stubbed_list(:user,
+ actual_count,
+ created_on: Time.now,
+ updated_on: Time.now)
+ allow(users)
+ .to receive(:per_page)
+ .with(page_size)
+ .and_return(users)
+
+ allow(users)
+ .to receive(:page)
+ .with(page)
+ .and_return(users)
+
+ users
+ }
+ let(:representer) {
+ described_class.new(users,
+ '/api/v3/users',
+ per_page: page_size,
+ page: page,
+ current_user: users.first)
+ }
+
+ context 'generation' do
+ subject(:collection) { representer.to_json }
+
+ it_behaves_like 'offset-paginated APIv3 collection'
+ end
+end
diff --git a/spec/lib/api/v3/utilities/path_helper_spec.rb b/spec/lib/api/v3/utilities/path_helper_spec.rb
index 194eefa944..d2fa02a82d 100644
--- a/spec/lib/api/v3/utilities/path_helper_spec.rb
+++ b/spec/lib/api/v3/utilities/path_helper_spec.rb
@@ -331,10 +331,18 @@ describe ::API::V3::Utilities::PathHelper do
end
end
- describe '#user' do
- subject { helper.user 1 }
+ describe 'users paths' do
+ describe '#users' do
+ subject { helper.users }
- it_behaves_like 'api v3 path', '/users/1'
+ it_behaves_like 'api v3 path', '/users'
+ end
+
+ describe '#user' do
+ subject { helper.user 1 }
+
+ it_behaves_like 'api v3 path', '/users/1'
+ end
end
describe '#version' do
diff --git a/spec/models/queries/work_packages/available_filter_options_spec.rb b/spec/models/queries/available_filters_spec.rb
similarity index 52%
rename from spec/models/queries/work_packages/available_filter_options_spec.rb
rename to spec/models/queries/available_filters_spec.rb
index 6fcb0b4c53..16431594b8 100644
--- a/spec/models/queries/work_packages/available_filter_options_spec.rb
+++ b/spec/models/queries/available_filters_spec.rb
@@ -28,46 +28,57 @@
require 'spec_helper'
-describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
- let(:project) { FactoryGirl.build_stubbed(:project) }
- let(:register) { Queries::WorkPackages::FilterRegister }
+describe Queries::AvailableFilters, type: :model do
+ let(:context) { FactoryGirl.build_stubbed(:project) }
+ let(:register) { Queries::FilterRegister }
class HelperClass
- attr_accessor :project
+ attr_accessor :context
- def initialize(project)
- self.project = project
+ def initialize(context)
+ self.context = context
end
- include Queries::WorkPackages::AvailableFilterOptions
-
- def filter_register
- register_class
- end
+ include Queries::AvailableFilters
end
- let(:includer) {
- includer = HelperClass.new(project)
+ let(:includer) do
+ includer = HelperClass.new(context)
allow(includer)
.to receive(:filter_register)
- .and_return(register)
+ .and_return(registered_filters)
includer
- }
+ end
- describe '#work_package_filter_available?' do
+ describe '#filter_for' do
let(:filter_1_available) { true }
let(:filter_2_available) { true }
+ let(:filter_1_key) { :filter_1 }
+ let(:filter_2_key) { /f_\d+/ }
+ let(:filter_1_name) { :filter_1 }
+ let(:filter_2_name) { :f_1 }
let(:registered_filters) { [filter_1, filter_2] }
- let(:filter_1) do
- instance = double('filter_1_instance')
+ let(:filter_1_instance) do
+ instance = double("filter_1_instance")
allow(instance)
.to receive(:available?)
- .and_return(filter_1_available)
+ .and_return(:filter_1_available)
+
+ allow(instance)
+ .to receive(:name)
+ .and_return(:filter_1)
+ allow(instance)
+ .to receive(:name=)
+
+ instance
+ end
+
+ let(:filter_1) do
filter = double('filter_1')
allow(filter)
@@ -75,19 +86,35 @@ describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
.and_return(:filter_1)
allow(filter)
- .to receive(:create)
- .and_return(filter.key => instance)
+ .to receive(:new)
+ .and_return(filter_1_instance)
+
+ allow(filter)
+ .to receive(:all_for)
+ .with(context)
+ .and_return(filter_1_instance)
filter
end
- let(:filter_2) do
- instance = double('filter_2_instance')
+ let(:filter_2_instance) do
+ instance = double("filter_2_instance")
allow(instance)
.to receive(:available?)
- .and_return(filter_2_available)
+ .and_return(:filter_2_available)
+
+ allow(instance)
+ .to receive(:name)
+ .and_return(:f_1)
+
+ allow(instance)
+ .to receive(:name=)
+
+ instance
+ end
+ let(:filter_2) do
filter = double('filter_2')
allow(filter)
@@ -95,21 +122,13 @@ describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
.and_return(/f_\d+/)
allow(filter)
- .to receive(:create)
- .and_return('f_1' => instance)
+ .to receive(:all_for)
+ .with(context)
+ .and_return(filter_2_instance)
filter
end
- let(:register) do
- register = double('register')
- allow(register)
- .to receive(:filters)
- .and_return(registered_filters)
-
- register
- end
-
context 'for a filter identified by a symbol' do
let(:filter_3_available) { true }
let(:registered_filters) { [filter_3, filter_1, filter_2] }
@@ -131,8 +150,9 @@ describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
.and_return(:filter)
allow(filter)
- .to receive(:create)
- .and_return(filter.key => instance)
+ .to receive(:all_for)
+ .with(context)
+ .and_return(instance)
filter
end
@@ -140,12 +160,12 @@ describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
context 'if available' do
let(:filter_3_available) { false }
- it 'is true' do
- expect(includer.work_package_filter_available?(:filter_1)).to be_truthy
+ it 'returns an instance of the matching filter' do
+ expect(includer.filter_for(:filter_1)).to eql filter_1_instance
end
- it 'is false if the key is not matched' do
- expect(includer.work_package_filter_available?(:not_a_filter_name)).to be_falsey
+ it 'returns the NotExistingFilter if the name is not matched' do
+ expect(includer.filter_for(:not_a_filter_name)).to be_a Queries::NotExistingFilter
end
end
@@ -153,32 +173,36 @@ describe Queries::WorkPackages::AvailableFilterOptions, type: :model do
let(:filter_1_available) { false }
let(:filter_3_available) { true }
- it 'is false' do
- expect(includer.work_package_filter_available?(:filter_1)).to be_falsey
+ it 'returns the NotExistingFilter if the name is not matched' do
+ expect(includer.filter_for(:not_a_filter_name)).to be_a Queries::NotExistingFilter
+ end
+
+ it 'returns an instance of the matching filter if not caring for availablility' do
+ expect(includer.filter_for(:filter_1, true)).to eql filter_1_instance
end
end
end
context 'for a filter identified by a regexp' do
- context 'is true if if available' do
- it 'is true' do
- expect(includer.work_package_filter_available?(:f_1)).to be_truthy
+ context 'if available' do
+ it 'returns an instance of the matching filter' do
+ expect(includer.filter_for(:f_1)).to eql filter_2_instance
end
- it 'is false if the key is not matched' do
- expect(includer.work_package_filter_available?(:f_i1)).to be_falsey
+ it 'returns the NotExistingFilter if the key is not matched' do
+ expect(includer.filter_for(:f_i1)).to be_a Queries::NotExistingFilter
end
- it 'is false if the regexp matches but the created instance key does not' do
- expect(includer.work_package_filter_available?(:f_2)).to be_falsey
+ it 'returns the NotExistingFilter if the key is matched but the name is not' do
+ expect(includer.filter_for(:f_2)).to be_a Queries::NotExistingFilter
end
end
- context 'is false if if unavailable' do
+ context 'is false if unavailable' do
let(:filter_2_available) { false }
- it 'is false' do
- expect(includer.work_package_filter_available?(:f_1)).to be_falsey
+ it 'returns the NotExistingFilter' do
+ expect(includer.filter_for(:f_i)).to be_a Queries::NotExistingFilter
end
end
end
diff --git a/spec/models/queries/base_filter_spec.rb b/spec/models/queries/base_filter_spec.rb
new file mode 100644
index 0000000000..09b63f4b6b
--- /dev/null
+++ b/spec/models/queries/base_filter_spec.rb
@@ -0,0 +1,198 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+require 'spec_helper'
+
+describe Queries::BaseFilter, type: :model do
+ let(:integer_filter) do
+ filter_class = Class.new(described_class) do
+ def type
+ :integer
+ end
+ end
+
+ filter_class.new
+ end
+
+ let(:date_filter) do
+ filter_class = Class.new(described_class) do
+ def type
+ :date
+ end
+ end
+
+ filter_class.new
+ end
+
+ let(:date_past_filter) do
+ filter_class = Class.new(described_class) do
+ def type
+ :date_past
+ end
+ end
+
+ filter_class.new
+ end
+
+ shared_examples_for 'validity checked' do
+ describe '#valid?' do
+ context 'when the operator does not require values' do
+ before do
+ filter.operator = operator_without_value
+ end
+
+ it 'is valid if no values are given' do
+ expect(filter).to be_valid
+ end
+ end
+
+ context 'when the operator requires values' do
+ before do
+ filter.operator = valid_operator
+ end
+
+ context 'and no value is given' do
+ it 'is invalid' do
+ expect(filter).to be_invalid
+ end
+ end
+
+ context 'and only an empty string is given as value' do
+ before do
+ filter.values = ['']
+ end
+
+ it 'is invalid' do
+ expect(filter).to be_invalid
+ end
+ end
+
+ context 'and values are given' do
+ before do
+ filter.values = valid_values
+ end
+
+ it 'is valid' do
+ expect(filter).to be_valid
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples_for 'date validity checked' do
+ describe '#valid?' do
+ context "and the operator is 't' (today)" do
+ before do
+ filter.operator = 't'
+ end
+
+ it 'is valid' do
+ expect(filter).to be_valid
+ end
+ end
+
+ context "and the operator is 'w' (this week)" do
+ before do
+ filter.operator = 'w'
+ end
+
+ it 'is valid' do
+ expect(filter).to be_valid
+ end
+ end
+
+ context 'and the operator compares the current day' do
+ before do
+ filter.operator = '>t-'
+ end
+
+ context 'and the value is an integer' do
+ before do
+ filter.values = ['4']
+ end
+
+ it 'is valid' do
+ expect(filter).to be_valid
+ end
+ end
+
+ context 'and the value is not an integer' do
+ before do
+ filter.values = ['four']
+ end
+
+ it 'is invalid' do
+ expect(filter).to be_invalid
+ end
+ end
+ end
+ end
+ end
+
+ context 'for an integer filter' do
+ let(:filter) { integer_filter }
+ let(:valid_values) { [5] }
+ let(:valid_operator) { '=' }
+ let(:operator_without_value) { '*' }
+
+ it_behaves_like 'validity checked'
+
+ describe '#valid?' do
+ context 'when the filter values is not an integer' do
+ before do
+ filter.values == [1, 'asdf']
+ end
+
+ it 'is invalid' do
+ expect(filter).to be_invalid
+ end
+ end
+ end
+ end
+
+ context 'for a date filter' do
+ let(:filter) { date_filter }
+ let(:valid_values) { [5] }
+ let(:valid_operator) { '=', values: [] }
-
- context 'and no value is given' do
- it { is_expected.not_to be_valid }
- end
-
- context 'and only an empty string is given as value' do
- let(:filter) { FactoryGirl.build :work_packages_filter, field: :due_date, operator: 't-', values: [''] }
-
- it { is_expected.not_to be_valid }
- end
-
- context 'and values are given' do
- before do filter.values = [5] end
-
- it { is_expected.to be_valid }
- end
- end
-
- context 'when it is of type integer' do
- let(:filter) { FactoryGirl.build :work_packages_filter, field: :done_ratio, operator: '>=', values: [] }
-
- before do filter.field = 'done_ratio' end
-
- context 'and the filter values is an integer' do
- before do filter.values = [1, '12', 123] end
-
- it { is_expected.to be_valid }
- end
-
- context 'and the filter values is not an integer' do
- before do filter.values == [1, 'asdf'] end
-
- it { is_expected.not_to be_valid }
-
- context 'and the operator is *' do
- before do filter.operator = '*' end
-
- it { is_expected.to be_valid }
- end
- end
- end
-
- context 'when it if of type date or date_past' do
- let(:filter) { FactoryGirl.build :work_packages_filter, field: :created_at }
-
- context "and the operator is 't' (today)" do
- before do filter.operator = 't' end
-
- it { is_expected.to be_valid }
- end
-
- context "and the operator is 'w' (this week)" do
- before do filter.operator = 'w' end
-
- it { is_expected.to be_valid }
- end
-
- context 'and the operator compares the current day' do
- before do filter.operator = '>t-' end
-
- context 'and the value is an integer' do
- before do filter.values = ['4'] end
-
- it { is_expected.to be_valid }
- end
-
- context 'and the value is not an integer' do
- before do filter.values = ['four'] end
-
- it { is_expected.not_to be_valid }
- end
- end
- end
-
- context 'when it is a work package filter' do
- let(:filter) { FactoryGirl.build :work_packages_filter }
-
- context 'and the field is whitelisted' do
- before do filter.field = :project_id end
-
- it { is_expected.to be_valid }
- end
-
- # this context tests the case when a new item is injected in
- # the filter_types_by_field hash afterwards
- # from within some plugins that patch Queries::WorkPackages::Filter
- context 'and the field is whitelisted afterwards' do
- before do
- filter.field = :some_new_key
- filter.class.add_filter_type_by_field('some_new_key', 'list')
- end
-
- it { is_expected.to be_valid }
- end
-
- context 'and the field is not whitelisted and no custom field key' do
- before do filter.field = :any_key end
-
- it { is_expected.not_to be_valid }
- end
-
- context 'and the field is a custom field starting with "cf"' do
- before do filter.field = :cf_any_key end
-
- it { is_expected.to be_valid }
- end
- end
- end
- end
-end
diff --git a/spec/models/query_spec.rb b/spec/models/query_spec.rb
index b64519163a..241df0b5be 100644
--- a/spec/models/query_spec.rb
+++ b/spec/models/query_spec.rb
@@ -77,17 +77,68 @@ describe Query, type: :model do
end
context 'with a missing value for a custom field' do
- let(:custom_field) { FactoryGirl.create :text_issue_custom_field }
+ let(:custom_field) do
+ FactoryGirl.create :text_issue_custom_field, is_filter: true, is_for_all: true
+ end
let(:query) { FactoryGirl.build(:query) }
before do
- query.filters = [Queries::WorkPackages::Filter.new('cf_' + custom_field.id.to_s, operator: '=', values: [''])]
+ query.add_filter('cf_' + custom_field.id.to_s, '=', [''])
end
it 'should have the name of the custom field in the error message' do
- expect(query.valid?).to be_falsey
+ expect(query).to_not be_valid
expect(query.errors.messages[:base].to_s).to include(custom_field.name)
end
end
+
+ context 'with a filter for a non existing custom field' do
+ before do
+ query.add_filter('cf_0', '=', ['1'])
+ end
+
+ it 'is not valid' do
+ expect(query.valid?).to be_falsey
+ end
+ end
+ end
+
+ describe '#filter_for' do
+ context 'for a status_id filter' do
+ before do
+ allow(Status)
+ .to receive(:exists?)
+ .and_return(true)
+ end
+
+ subject { query.filter_for('status_id') }
+
+ it 'exists' do
+ is_expected.to_not be_nil
+ end
+
+ it 'has the context set' do
+ expect(subject.context).to eql query.project
+
+ query.project = nil
+
+ expect(query.filter_for('status_id').context).to be_nil
+ end
+
+ it 'reuses an existing filter' do
+ expect(subject.object_id).to eql query.filter_for('status_id').object_id
+ end
+ end
+ end
+
+ describe 'filters after deserialization' do
+ it 'sets the context (project) on deserialization' do
+ query.save!
+
+ query.reload
+ query.filters.each do |filter|
+ expect(filter.context).to eql(query.project)
+ end
+ end
end
end
diff --git a/spec/models/work_package/planning_comparison_spec.rb b/spec/models/work_package/planning_comparison_spec.rb
index 1e4fe3a968..65edf39785 100644
--- a/spec/models/work_package/planning_comparison_spec.rb
+++ b/spec/models/work_package/planning_comparison_spec.rb
@@ -92,7 +92,11 @@ describe 'Planning Comparison', type: :model do
end
describe 'filtering work_packages also applies to the history' do
- let(:assigned_to_user) { FactoryGirl.create(:user) }
+ let(:assigned_to_user) do
+ FactoryGirl.create(:user,
+ member_in_project: project,
+ member_through_role: FactoryGirl.build(:role))
+ end
let (:filter) do
{ f: ['assigned_to_id'],
op: { 'assigned_to_id' => '=' },
diff --git a/spec/requests/api/v3/user/user_resource_spec.rb b/spec/requests/api/v3/user/user_resource_spec.rb
index bdf115eabd..6590617bc1 100644
--- a/spec/requests/api/v3/user/user_resource_spec.rb
+++ b/spec/requests/api/v3/user/user_resource_spec.rb
@@ -38,9 +38,125 @@ describe 'API v3 User resource', type: :request do
let(:model) { ::API::V3::Users::UserModel.new(user) }
let(:representer) { ::API::V3::Users::UserRepresenter.new(model) }
- describe '#get' do
- subject(:response) { last_response }
+ subject(:response) { last_response }
+
+ describe '#index' do
+ let(:get_path) { api_v3_paths.users }
+
+ before do
+ user
+ allow(User).to receive(:current).and_return current_user
+ get get_path
+ end
+
+ context 'admin user' do
+ let(:current_user) { FactoryGirl.create(:admin) }
+ it 'should respond with 200' do
+ expect(subject.status).to eq(200)
+ end
+
+ # note that the order of the users is depending on the id
+ # meaning the order in which they where saved
+ it 'contains the user in the response' do
+ expect(subject.body)
+ .to be_json_eql(user.name.to_json)
+ .at_path('_embedded/elements/0/name')
+ end
+
+ it 'contains the current user in the response' do
+ expect(subject.body)
+ .to be_json_eql(current_user.name.to_json)
+ .at_path('_embedded/elements/1/name')
+ end
+
+ it 'has the users index path for link self href' do
+ expect(subject.body)
+ .to be_json_eql((api_v3_paths.users + '?offset=1&pageSize=30').to_json)
+ .at_path('_links/self/href')
+ end
+
+ context 'if pageSize = 1 and offset = 2' do
+ let(:get_path) { api_v3_paths.users + '?pageSize=1&offset=2' }
+
+ it 'contains the current user in the response' do
+ expect(subject.body)
+ .to be_json_eql(current_user.name.to_json)
+ .at_path('_embedded/elements/0/name')
+ end
+ end
+
+ context 'on filtering for name' do
+ let(:get_path) do
+ filter = [{ 'name' => {
+ 'operator' => '~',
+ 'values' => [user.name]
+ } }]
+
+ "#{api_v3_paths.users}?#{{ filters: filter.to_json }.to_query}"
+ end
+
+ it 'contains the filtered user in the response' do
+ expect(subject.body)
+ .to be_json_eql(user.name.to_json)
+ .at_path('_embedded/elements/0/name')
+ end
+
+ it 'contains no more users' do
+ expect(subject.body)
+ .to be_json_eql(1.to_json)
+ .at_path('total')
+ end
+ end
+
+ context 'on sorting' do
+ let(:users_by_name_order) do
+ User.not_builtin.order_by_name.reverse_order
+ end
+
+ let(:get_path) do
+ sort = [['name', 'desc']]
+
+ "#{api_v3_paths.users}?#{{ sortBy: sort.to_json }.to_query}"
+ end
+
+ it 'contains the first user as the first element' do
+ expect(subject.body)
+ .to be_json_eql(users_by_name_order[0].name.to_json)
+ .at_path('_embedded/elements/0/name')
+ end
+
+ it 'contains the first user as the second element' do
+ expect(subject.body)
+ .to be_json_eql(users_by_name_order[1].name.to_json)
+ .at_path('_embedded/elements/1/name')
+ end
+ end
+
+ context 'on an invalid filter' do
+ let(:get_path) do
+ filter = [{ 'name' => {
+ 'operator' => 'a',
+ 'values' => [user.name]
+ } }]
+
+ "#{api_v3_paths.users}?#{{ filters: filter.to_json }.to_query}"
+ end
+
+ it 'returns an error' do
+ expect(subject.status).to eql(400)
+ end
+ end
+ end
+
+ context 'other user' do
+ it 'should respond with 403' do
+ expect(subject.status).to eq(403)
+ end
+ end
+ end
+
+ describe '#get' do
context 'logged in user' do
let(:get_path) { api_v3_paths.user user.id }
before do
@@ -52,7 +168,7 @@ describe 'API v3 User resource', type: :request do
expect(subject.status).to eq(200)
end
- it 'should respond with correct attachment' do
+ it 'should respond with correct body' do
expect(subject.body).to be_json_eql(user.name.to_json).at_path('name')
end
@@ -85,8 +201,6 @@ describe 'API v3 User resource', type: :request do
delete path
end
- subject(:response) { last_response }
-
shared_examples 'deletion through allowed user' do
it 'should respond with 202' do
expect(subject.status).to eq 202
diff --git a/spec/support/queries/filters/shared_filter_examples.rb b/spec/support/queries/filters/shared_filter_examples.rb
new file mode 100644
index 0000000000..7d7b6a0ba9
--- /dev/null
+++ b/spec/support/queries/filters/shared_filter_examples.rb
@@ -0,0 +1,237 @@
+#-- copyright
+# OpenProject is a project management system.
+# Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License version 3.
+#
+# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
+# Copyright (C) 2006-2013 Jean-Philippe Lang
+# Copyright (C) 2010-2013 the ChiliProject Team
+#
+# 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.
+#
+# See doc/COPYRIGHT.rdoc for more details.
+#++
+
+shared_context 'filter tests' do
+ let(:context) { nil }
+ let(:values) { ['bogus'] }
+ let(:operator) { '=' }
+ let(:instance) do
+ filter = described_class.new
+ filter.context = context
+ filter.operator = operator
+ filter.values = values
+ filter
+ end
+ let(:name) { model.human_attribute_name(instance_key || class_key) }
+ let(:model) { WorkPackage }
+end
+
+shared_examples_for 'basic query filter' do
+ include_context 'filter tests'
+
+ let(:context) { project }
+ let(:project) { FactoryGirl.build_stubbed(:project) }
+ let(:instance_key) { nil }
+ let(:class_key) { raise 'needs to be defined' }
+ let(:type) { raise 'needs to be defined' }
+ let(:order) { nil }
+
+ describe '.key' do
+ it 'is the defined key' do
+ expect(described_class.key).to eql(class_key)
+ end
+ end
+
+ describe '#name' do
+ it 'is the defined key' do
+ expect(instance.name).to eql(instance_key || class_key)
+ end
+ end
+
+ describe '#order' do
+ it 'has the defined order' do
+ if order
+ expect(instance.order).to eql(order)
+ end
+ end
+ end
+
+ describe '#type' do
+ it 'is the defined filter type' do
+ expect(instance.type).to eql(type)
+ end
+ end
+
+ describe '#human_name' do
+ it 'is the l10 name for the filter' do
+ expect(instance.human_name).to eql(name)
+ end
+ end
+end
+
+shared_examples_for 'list query filter' do
+ include_context 'filter tests'
+ let(:attribute) { raise "needs to be defined" }
+ let(:type) { :list }
+
+ describe '#scope' do
+ context 'for "="' do
+ let(:operator) { '=' }
+ let(:values) { valid_values }
+
+ it 'is the same as handwriting the query' do
+ expected = model.where(["#{model.table_name}.#{attribute} IN (?)", values])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+
+ context 'for "!"' do
+ let(:operator) { '!' }
+ let(:values) { valid_values }
+
+ it 'is the same as handwriting the query' do
+ sql = "(#{model.table_name}.#{attribute} IS NULL
+ OR #{model.table_name}.#{attribute} NOT IN (?))".squish
+ expected = model.where([sql, values])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+ end
+
+ describe '#valid?' do
+ let(:operator) { '=' }
+ let(:values) { valid_values }
+
+ it 'is valid' do
+ expect(instance).to be_valid
+ end
+
+ context 'for an invalid operator' do
+ let(:operator) { '*' }
+
+ it 'is invalid' do
+ expect(instance).to be_invalid
+ end
+ end
+
+ context 'for an invalid value' do
+ let(:values) { ['inexistent'] }
+
+ it 'is invalid' do
+ expect(instance).to be_invalid
+ end
+ end
+ end
+end
+
+shared_examples_for 'list_optional query filter' do
+ include_context 'filter tests'
+ let(:attribute) { raise "needs to be defined" }
+ let(:type) { :list_optional }
+ let(:joins) { nil }
+ let(:expected_base_scope) do
+ if joins
+ model.joins(joins)
+ else
+ model
+ end
+ end
+ let(:expected_table_name) do
+ if joins
+ joins
+ else
+ model.table_name
+ end
+ end
+
+ describe '#scope' do
+ let(:values) { valid_values }
+
+ context 'for "="' do
+ let(:operator) { '=' }
+
+ it 'is the same as handwriting the query' do
+ expected = expected_base_scope
+ .where(["#{expected_table_name}.#{attribute} IN (?)", values])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+
+ context 'for "!"' do
+ let(:operator) { '!' }
+
+ it 'is the same as handwriting the query' do
+ sql = "(#{expected_table_name}.#{attribute} IS NULL
+ OR #{expected_table_name}.#{attribute} NOT IN (?))".squish
+ expected = expected_base_scope.where([sql, values])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+
+ context 'for "*"' do
+ let(:operator) { '*' }
+
+ it 'is the same as handwriting the query' do
+ sql = "#{expected_table_name}.#{attribute} IS NOT NULL"
+ expected = expected_base_scope.where([sql])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+
+ context 'for "!*"' do
+ let(:operator) { '!*' }
+
+ it 'is the same as handwriting the query' do
+ sql = "#{expected_table_name}.#{attribute} IS NULL"
+ expected = expected_base_scope.where([sql])
+
+ expect(instance.scope.to_sql).to eql expected.to_sql
+ end
+ end
+ end
+
+ describe '#valid?' do
+ let(:operator) { '=' }
+ let(:values) { valid_values }
+
+ it 'is valid' do
+ expect(instance).to be_valid
+ end
+
+ context 'for an invalid operator' do
+ let(:operator) { '~' }
+
+ it 'is invalid' do
+ expect(instance).to be_invalid
+ end
+ end
+
+ context 'for an invalid value' do
+ let(:values) { ['inexistent'] }
+
+ it 'is invalid' do
+ expect(instance).to be_invalid
+ end
+ end
+ end
+end
diff --git a/spec_legacy/unit/query_spec.rb b/spec_legacy/unit/query_spec.rb
index 72a358be92..3671a34a74 100644
--- a/spec_legacy/unit/query_spec.rb
+++ b/spec_legacy/unit/query_spec.rb
@@ -26,38 +26,31 @@
#
# See doc/COPYRIGHT.rdoc for more details.
#++
-require 'legacy_spec_helper'
+require_relative '../legacy_spec_helper'
describe Query, type: :model do
fixtures :all
- it 'should custom fields for all projects should be available in global queries' do
- query = Query.new(project: nil, name: '_')
- assert query.work_package_filter_available?('cf_1')
- assert !query.work_package_filter_available?('cf_3')
- end
-
it 'should system shared versions should be available in global queries' do
Version.find(2).update_attribute :sharing, 'system'
query = Query.new(project: nil, name: '_')
- assert query.work_package_filter_available?('fixed_version_id')
- assert query.available_work_package_filters['fixed_version_id'][:values].detect { |v| v.last == '2' }
+ assert query.filter_for('fixed_version_id')[:allowed_values].detect { |v| v.last == '2' }
end
it 'should project filter in global queries' do
# User.current should be anonymous here
query = Query.new(project: nil, name: '_')
- project_filter = query.available_work_package_filters['project_id']
+ project_filter = query.filter_for('project_id')
refute_nil project_filter
- project_ids = project_filter[:values].map { |p| p[1] }
+ project_ids = project_filter[:allowed_values].map { |p| p[1] }
assert project_ids.include?('1') # public project
assert !project_ids.include?('2') # private project anonymous user cannot see
end
def find_issues_with_query(query)
WorkPackage.includes(:assigned_to, :status, :type, :project, :priority)
- .where(query.statement)
- .references(:projects)
+ .where(query.statement)
+ .references(:projects)
end
def assert_find_issues_with_query_is_successful(query)
@@ -67,7 +60,8 @@ describe Query, type: :model do
end
def assert_query_statement_includes(query, condition)
- assert query.statement.include?(condition), "Query statement condition not found in: #{query.statement}"
+ assert query.statement.include?(condition),
+ "Query statement condition not found in: #{query.statement}"
end
it 'should query should allow shared versions for a project query' do
@@ -220,7 +214,7 @@ describe Query, type: :model do
i3 = FactoryGirl.create(:work_package, project: project, type: project.types.first, assigned_to: Group.find(11))
group.users << user
- query = Query.new(name: '_', filters: [Queries::WorkPackages::Filter.new(:assigned_to_id, operator: '=', values: ['me'])])
+ query = Query.new(name: '_', filters: [{ assigned_to_id: { operator: '=', values: ['me'] } }])
result = query.results.work_packages
assert_equal WorkPackage.visible.where(assigned_to_id: ([2] + user.reload.group_ids)).sort_by(&:id), result.sort_by(&:id)
@@ -233,7 +227,7 @@ describe Query, type: :model do
it 'should filter watched issues' do
User.current = User.find(1)
- query = Query.new(name: '_', filters: [Queries::WorkPackages::Filter.new(:watcher_id, operator: '=', values: ['me'])])
+ query = Query.new(name: '_', filters: [{ watcher_id: { operator: '=', values: ['me'] } }])
result = find_issues_with_query(query)
refute_nil result
assert !result.empty?
@@ -243,11 +237,11 @@ describe Query, type: :model do
it 'should filter unwatched issues' do
User.current = User.find(1)
- query = Query.new(name: '_', filters: [Queries::WorkPackages::Filter.new(:watcher_id, operator: '!', values: ['me'])])
+ query = Query.new(name: '_', filters: [{ watcher_id: { operator: '!', values: ['me'] } }])
result = find_issues_with_query(query)
refute_nil result
assert !result.empty?
- assert_equal((WorkPackage.visible - WorkPackage.watched_by(User.current)).sort_by(&:id).size, result.sort_by(&:id).size)
+ expect((WorkPackage.visible - WorkPackage.watched_by(User.current)).size).to eql result.size
User.current = nil
end
@@ -385,11 +379,6 @@ describe Query, type: :model do
assert_equal %w(Fixnum), count_by_group.values.map { |k| k.class.name }.uniq
end
- it 'should label for' do
- q = Query.new name: '_'
- assert_equal WorkPackage.human_attribute_name('assigned_to_id'), q.label_for('assigned_to_id')
- end
-
it 'should editable by' do
admin = User.find(1)
manager = User.find(2)
@@ -420,30 +409,30 @@ describe Query, type: :model do
assert !q.editable_by?(developer)
end
- context '#available_work_package_filters' do
+ context '#filter_for' do
before do
@query = Query.new(name: '_')
end
it 'should include users of visible projects in cross-project view' do
- users = @query.available_work_package_filters['assigned_to_id']
+ users = @query.filter_for('assigned_to_id')
refute_nil users
- assert users[:values].map { |u| u[1] }.include?('3')
+ assert users[:allowed_values].map { |u| u[1] }.include?('3')
end
it 'should include visible projects in cross-project view' do
- projects = @query.available_work_package_filters['project_id']
+ projects = @query.filter_for('project_id')
refute_nil projects
- assert projects[:values].map { |u| u[1] }.include?('1')
+ assert projects[:allowed_values].map { |u| u[1] }.include?('1')
end
context "'member_of_group' filter" do
it 'should be present' do
- assert @query.available_work_package_filters.keys.include?('member_of_group')
+ assert @query.filter_for('member_of_group')
end
it 'should be an optional list' do
- assert_equal :list_optional, @query.available_work_package_filters['member_of_group'][:type]
+ assert_equal :list_optional, @query.filter_for('member_of_group')[:type]
end
it 'should have a list of the groups as values' do
@@ -455,39 +444,33 @@ describe Query, type: :model do
[group1.name, group1.id.to_s],
[group2.name, group2.id.to_s]
]
- assert_equal expected_group_list.sort, @query.available_work_package_filters['member_of_group'][:values].sort
+ assert_equal expected_group_list.sort, @query.filter_for('member_of_group')[:allowed_values].sort
end
end
context "'assigned_to_role' filter" do
it 'should be present' do
- assert @query.available_work_package_filters.keys.include?('assigned_to_role')
+ assert @query.filter_for('assigned_to_role')
end
it 'should be an optional list' do
- assert_equal :list_optional, @query.available_work_package_filters['assigned_to_role'][:type]
+ assert_equal :list_optional, @query.filter_for('assigned_to_role')[:type]
end
it 'should have a list of the Roles as values' do
- assert @query.available_work_package_filters['assigned_to_role'][:values].include?(['Manager', '1'])
- assert @query.available_work_package_filters['assigned_to_role'][:values].include?(['Developer', '2'])
- assert @query.available_work_package_filters['assigned_to_role'][:values].include?(['Reporter', '3'])
+ assert @query.filter_for('assigned_to_role')[:allowed_values].include?(['Manager', '1'])
+ assert @query.filter_for('assigned_to_role')[:allowed_values].include?(['Developer', '2'])
+ assert @query.filter_for('assigned_to_role')[:allowed_values].include?(['Reporter', '3'])
end
- it 'should not include the built in Roles as values' do
- assert ! @query.available_work_package_filters['assigned_to_role'][:values].include?(['Non member', '4'])
- assert ! @query.available_work_package_filters['assigned_to_role'][:values].include?(['Anonymous', '5'])
+ it 'should not include the built in Roles as allowed_values' do
+ assert !@query.filter_for('assigned_to_role')[:allowed_values].include?(['Non member', '4'])
+ assert !@query.filter_for('assigned_to_role')[:allowed_values].include?(['Anonymous', '5'])
end
end
context "'watcher_id' filter" do
context 'globally' do
- context 'for an anonymous user' do
- it 'should not be present' do
- assert ! @query.available_work_package_filters.keys.include?('watcher_id')
- end
- end
-
context 'for a logged in user' do
before do
User.current = User.find 1
@@ -498,31 +481,31 @@ describe Query, type: :model do
end
it 'should be present' do
- assert @query.available_work_package_filters.keys.include?('watcher_id')
+ assert @query.filter_for('watcher_id')
end
it 'should be a list' do
- assert_equal :list, @query.available_work_package_filters['watcher_id'][:type]
+ assert_equal :list, @query.filter_for('watcher_id')[:type]
end
- it 'should have a list of active users as values' do
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['me', 'me'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['John Smith', '2'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['Dave Lopper', '3'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['redMine Admin', '1'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['User Misc', '8'])
+ it 'should have a list of active users as allowed_values' do
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['me', 'me'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['John Smith', '2'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['Dave Lopper', '3'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['redMine Admin', '1'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['User Misc', '8'])
end
it 'should not include active users not member of any project' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['Robert Hill', '4'])
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['Robert Hill', '4'])
end
it 'should not include locked users as values' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['Dave2 Lopper2', '5'])
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['Dave2 Lopper2', '5'])
end
it 'should not include the anonymous user as values' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['Anonymous', '6'])
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['Anonymous', '6'])
end
end
end
@@ -532,45 +515,35 @@ describe Query, type: :model do
@query.project = Project.find(1)
end
- context 'for an anonymous user' do
- it 'should not be present' do
- assert ! @query.available_work_package_filters.keys.include?('watcher_id')
- end
- end
-
context 'for a logged in user' do
before do
- User.current = User.find 1
- end
-
- after do
- User.current = nil
+ allow(User).to receive(:current).and_return(User.find(1))
end
it 'should be present' do
- assert @query.available_work_package_filters.keys.include?('watcher_id')
+ assert @query.filter_for('watcher_id')
end
it 'should be a list' do
- assert_equal :list, @query.available_work_package_filters['watcher_id'][:type]
+ assert_equal :list, @query.filter_for('watcher_id')[:type]
end
- it 'should have a list of the project members as values' do
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['me', 'me'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['John Smith', '2'])
- assert @query.available_work_package_filters['watcher_id'][:values].include?(['Dave Lopper', '3'])
+ it 'should have a list of the project members as allowed_values' do
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['me', 'me'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['John Smith', '2'])
+ assert @query.filter_for('watcher_id')[:allowed_values].include?(['Dave Lopper', '3'])
end
- it 'should not include non-project members as values' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['redMine Admin', '1'])
+ it 'should not include non-project members as allowed_values' do
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['redMine Admin', '1'])
end
- it 'should not include locked project members as values' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['Dave2 Lopper2', '5'])
+ it 'should not include locked project members as allowed_values' do
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['Dave2 Lopper2', '5'])
end
- it 'should not include the anonymous user as values' do
- assert ! @query.available_work_package_filters['watcher_id'][:values].include?(['Anonymous', '6'])
+ it 'should not include the anonymous user as allowed_values' do
+ assert !@query.filter_for('watcher_id')[:allowed_values].include?(['Anonymous', '6'])
end
end
end