fixes filtering members by group (#5800)

additionally fixes:
* with "all" selected, all members are shown, not only those being
active, invited or registered
* with "locked" selected, locked users are now displayed.
* the Project#members_principals associations lacked the `references`
method which lead to the users table being undefined in the resulting
query.

In order to achive the bug fixes, the member filtering is now based on
the query engine also used for users, queries, work_packages, ... As a
lot of the members filters are very similar to user filters (in fact, a lot of the
members filter work on the users model), those filters are shared via
modules.

[ci skip]
pull/5809/head
ulferts 7 years ago committed by Oliver Günther
parent 4551860fdc
commit daa1a4d4b7
  1. 81
      app/cells/members/user_filter_cell.rb
  2. 90
      app/cells/user_filter_cell.rb
  3. 28
      app/controllers/members_controller.rb
  4. 2
      app/controllers/users_controller.rb
  5. 7
      app/helpers/users_helper.rb
  6. 48
      app/models/project.rb
  7. 70
      app/models/queries/filters/shared/group_filter.rb
  8. 56
      app/models/queries/filters/shared/project_filter.rb
  9. 56
      app/models/queries/filters/shared/user_blocked_filter.rb
  10. 89
      app/models/queries/filters/shared/user_name_filter.rb
  11. 67
      app/models/queries/filters/shared/user_status_filter.rb
  12. 41
      app/models/queries/members.rb
  13. 37
      app/models/queries/members/filters/blocked_filter.rb
  14. 37
      app/models/queries/members/filters/group_filter.rb
  15. 37
      app/models/queries/members/filters/member_filter.rb
  16. 37
      app/models/queries/members/filters/name_filter.rb
  17. 33
      app/models/queries/members/filters/project_filter.rb
  18. 53
      app/models/queries/members/filters/role_filter.rb
  19. 37
      app/models/queries/members/filters/status_filter.rb
  20. 37
      app/models/queries/members/member_query.rb
  21. 17
      app/models/queries/queries/filters/project_filter.rb
  22. 2
      app/models/queries/users.rb
  23. 33
      app/models/queries/users/filters/blocked_filter.rb
  24. 31
      app/models/queries/users/filters/group_filter.rb
  25. 46
      app/models/queries/users/filters/name_filter.rb
  26. 28
      app/models/queries/users/filters/status_filter.rb
  27. 13
      app/models/user.rb
  28. 37
      app/models/user/status_options.rb
  29. 1
      config/locales/en.yml
  30. 2
      features/support/paths.rb
  31. 12
      spec/controllers/members_controller_spec.rb
  32. 7
      spec/features/members/invitation_spec.rb
  33. 46
      spec/models/queries/members/filters/blocked_filter_spec.rb
  34. 64
      spec/models/queries/members/filters/group_filter_spec.rb
  35. 97
      spec/models/queries/members/filters/name_filter_spec.rb
  36. 44
      spec/models/queries/members/filters/project_filter_spec.rb
  37. 64
      spec/models/queries/members/filters/role_filter_spec.rb
  38. 48
      spec/models/queries/members/filters/status_filter_spec.rb
  39. 1
      spec/models/queries/queries/filters/project_filter_spec.rb
  40. 7
      spec/models/queries/users/filters/group_filter_spec.rb
  41. 9
      spec/models/queries/users/filters/name_filter_spec.rb
  42. 1
      spec/models/queries/users/filters/status_filter_spec.rb
  43. 6
      spec/models/queries/users/user_query_spec.rb

@ -1,41 +1,5 @@
module Members
class UserFilterCell < ::UserFilterCell
class << self
def status_param(params)
params[:status].presence || "all"
end
def filter_name_condition
super.gsub /lastname|firstname|mail/, "users.\\0"
end
def filter_name_columns
[:lastname, :firstname, :mail]
end
def filter_status_condition
super.sub /status/, "users.\\0"
end
def filter_group_condition
# we want to list both the filtered group itself if a member (left of OR)
# and users of that group (right of OR)
super.sub /group_id/, "users.id = :group_id OR group_users.\\0"
end
def join_group_users(query)
query # it will be joined by the table already
end
def filter_role_condition
super.sub /role_id/, "member_roles.\\0"
end
def join_role(query)
query # it will be joined by the table already
end
end
def initially_visible?
false
end
@ -44,45 +8,24 @@ module Members
# Adapts the user filter counts to count members as opposed to users.
def extra_user_status_options
{
all: all_members_query.count,
blocked: blocked_members_query.count,
active: active_members_query.count,
invited: invited_members_query.count,
registered: registered_members_query.count,
locked: locked_members_query.count
all: status_members_query('all').count,
blocked: status_members_query('blocked').count,
active: status_members_query('active').count,
invited: status_members_query('invited').count,
registered: status_members_query('registered').count,
locked: status_members_query('locked').count
}
end
def all_members_query
project
.member_principals
.includes(:principal)
.references(:users)
end
def active_members_query
blocked_members_query false
end
def blocked_members_query(blocked = true)
project_members_query User.create_blocked_scope(all_members_query, blocked)
end
def invited_members_query
project_members_query all_members_query, status: :invited
end
def registered_members_query
project_members_query all_members_query, status: :registered
end
def status_members_query(status)
params = { project_id: project.id,
status: status }
def locked_members_query
project_members_query all_members_query, status: :locked
self.class.filter(params)
end
def project_members_query(query, status: :active)
query
.where(users: { status: User::STATUSES[status] })
def self.base_query
Queries::Members::MemberQuery
end
end
end

@ -5,17 +5,20 @@ class UserFilterCell < RailsCell
options :groups, :status, :roles, :clear_url, :project
class << self
def filter(query, params)
[query]
.map { |q| filter_name q, params[:name] }
.map { |q| filter_status q, status_param(params) }
.map { |q| filter_group q, params[:group_id] }
.map { |q| filter_role q, params[:role_id] }
.first
def filter(params)
q = base_query.new
filter_project q, params[:project_id]
filter_name q, params[:name]
filter_status q, status_param(params)
filter_group q, params[:group_id]
filter_role q, params[:role_id]
q.results
end
def is_filtered(params)
[:name, :status, :group_id, :role_id].any? { |name| params[name].present? }
def filtered?(params)
%i(name status group_id role_id).any? { |name| params[name].present? }
end
##
@ -23,78 +26,49 @@ class UserFilterCell < RailsCell
# or the default status to be filtered by (active)
# if no status is given.
def status_param(params)
params[:status].presence || User::STATUSES[:active]
params[:status].presence || :active
end
def filter_name(query, name)
if name.present?
query.where(filter_name_condition, name: "%#{name.downcase}%")
else
query
query.where(:name, '~', name)
end
end
def filter_name_condition
filter_name_columns
.map { |col| "LOWER(#{col}) LIKE :name" }
.join(" OR ")
end
def filter_name_columns
[:lastname, :firstname, :mail, :login]
end
def filter_status(query, status)
q = specific_filter_status(query, status) || query
q = User.create_blocked_scope q, false if status.to_i == User::STATUSES[:active]
q.where("status <> :builtin", builtin: User::STATUSES[:builtin])
end
def specific_filter_status(query, status)
if status.present?
if status == "blocked"
User.create_blocked_scope query, true
elsif status != "all"
query.where(filter_status_condition, status: status.to_i)
end
return unless status && status != 'all'
case status
when 'blocked'
query.where(:blocked, '=', :blocked)
when 'active'
query.where(:status, '=', status.to_sym)
query.where(:blocked, '!', :blocked)
else
query.where(:status, '=', status.to_sym)
end
end
def filter_status_condition
"status = :status"
end
def filter_group(query, group_id)
if group_id.present?
join_group_users(query).where(filter_group_condition, group_id: group_id.to_i)
else
query
query.where(:group, '=', group_id)
end
end
def join_group_users(query)
query.joins("LEFT JOIN group_users ON group_users.user_id = users.id")
end
def filter_group_condition
"group_id = :group_id"
end
def filter_role(query, role_id)
if role_id.present?
join_role(query).where(filter_role_condition, role_id: role_id.to_i)
else
query
query.where(:role_id, '=', role_id)
end
end
def filter_role_condition
"role_id = :role_id"
def filter_project(query, project_id)
if project_id.present?
query.where(:project_id, '=', project_id)
end
end
def join_role(query)
query.joins(members: { member_roles: :role })
def base_query
Queries::Users::UserQuery
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -59,7 +60,7 @@ class MembersController < ApplicationController
if members.present? && members.all?(&:valid?)
flash[:notice] = members_added_notice members
redirect_to project_members_path(project_id: @project)
redirect_to project_members_path(project_id: @project, status: 'all')
else
if members.present? && params[:member]
@member = members.first
@ -152,7 +153,7 @@ class MembersController < ApplicationController
def members_filter_options(roles)
groups = Group.all.sort
status = params[:status] ? params[:status] : "all"
status = Members::UserFilterCell.status_param(params)
{
groups: groups,
@ -174,16 +175,6 @@ class MembersController < ApplicationController
/\A\S+@\S+\.\S+\z/
end
##
# Queries all members for this project.
# Pagination and order is taken care of by the TableCell.
def index_members(project)
project
.member_principals
.includes(:roles, :principal, :member_roles)
.references(:users)
end
def self.tab_scripts
@@scripts.join('(); ') + '();'
end
@ -191,8 +182,8 @@ class MembersController < ApplicationController
def set_index_data!
set_roles_and_principles!
@is_filtered = Members::UserFilterCell.is_filtered params
@members = Members::UserFilterCell.filter index_members(@project), params
@is_filtered = Members::UserFilterCell.filtered? params
@members = index_members
@members_table_options = members_table_options @roles
@members_filter_options = members_filter_options @roles
end
@ -203,6 +194,15 @@ class MembersController < ApplicationController
@principals_available = @project.possible_members('', 1)
end
def index_members
filters = params.slice(:name, :group_id, :role_id, :status)
filters[:project_id] = @project.id.to_s
@members = Member
.where(id: Members::UserFilterCell.filter(filters))
.includes(:roles, :principal, :member_roles)
end
def new_members_from_params
roles = Role.where(id: possibly_seperated_ids_for_entity(params[:member], :role))

@ -54,7 +54,7 @@ class UsersController < ApplicationController
def index
@groups = Group.all.sort
@status = Users::UserFilterCell.status_param params
@users = Users::UserFilterCell.filter User.all, params
@users = Users::UserFilterCell.filter params
respond_to do |format|
format.html do

@ -36,13 +36,12 @@ module UsersHelper
# For example: { random: 42 }
def users_status_options_for_select(selected, extra: {})
statuses = User::StatusOptions.user_statuses_with_count extra: extra
options = statuses.map do |name, values|
count, value = values
["#{translate_user_status(name)} (#{count})", value]
options = statuses.map do |sym, count|
["#{translate_user_status(sym)} (#{count})", sym]
end
options_for_select options, selected
options_for_select options.sort, selected
end
def translate_user_status(status_name)

@ -84,29 +84,33 @@ class Project < ActiveRecord::Base
.references(:principals, :roles)
}, class_name: 'Member'
# Read only
has_many :possible_responsibles, -> (object){
# Have to reference members and roles again although
# possible_responsible_members does already specify it to be able to use
# the Project.possible_principles_condition there
#
# The .where(members_users: { project_id: object.id })
# part is an optimization preventing to have all the members joined
includes(members: :roles)
.where(members_users: { project_id: object.id })
.references(:roles)
.merge(Principal.order_by_name)
},
through: :possible_responsible_members,
source: :principal
has_many :possible_responsibles,
->(object) {
# Have to reference members and roles again although
# possible_responsible_members does already specify it to be able to use
# the Project.possible_principles_condition there
#
# The .where(members_users: { project_id: object.id })
# part is an optimization preventing to have all the members joined
includes(members: :roles)
.where(members_users: { project_id: object.id })
.references(:roles)
.merge(Principal.order_by_name)
},
through: :possible_responsible_members,
source: :principal
has_many :memberships, class_name: 'Member'
has_many :member_principals, -> {
includes(:principal)
.where("#{Principal.table_name}.type='Group' OR " +
"(#{Principal.table_name}.type='User' AND " +
"(#{Principal.table_name}.status=#{Principal::STATUSES[:active]} OR " +
"#{Principal.table_name}.status=#{Principal::STATUSES[:registered]} OR " +
"#{Principal.table_name}.status=#{Principal::STATUSES[:invited]}))")
}, class_name: 'Member'
has_many :member_principals,
-> {
includes(:principal)
.references(:principals)
.where("#{Principal.table_name}.type='Group' OR " +
"(#{Principal.table_name}.type='User' AND " +
"(#{Principal.table_name}.status=#{Principal::STATUSES[:active]} OR " +
"#{Principal.table_name}.status=#{Principal::STATUSES[:registered]} OR " +
"#{Principal.table_name}.status=#{Principal::STATUSES[:invited]}))")
},
class_name: 'Member'
has_many :users, through: :members
has_many :principals, through: :member_principals, source: :principal

@ -0,0 +1,70 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Filters::Shared::GroupFilter
def self.included(base)
base.include(InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
def allowed_values
@allowed_values ||= begin
::Group.pluck(:id).map { |g| [g, g.to_s] }
end
end
def available?
::Group.exists?
end
def type
:list_optional
end
def human_name
I18n.t('query_fields.member_of_group')
end
def joins
:groups
end
def where
operator_strategy.sql_for_field(values, 'groups_users', 'id')
end
end
module ClassMethods
def key
:group
end
end
end

@ -0,0 +1,56 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Filters::Shared::ProjectFilter
def self.included(base)
base.include(InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
def type
:list_optional
end
def type_strategy
# Instead of getting the IDs of all the projects a user is allowed
# to see we only check that the value is an integer. Non valid ids
# will then simply create an empty result but will not cause any
# harm.
@type_strategy ||= ::Queries::Filters::Strategies::IntegerListOptional.new(self)
end
end
module ClassMethods
def key
:project_id
end
end
end

@ -0,0 +1,56 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Filters::Shared::UserBlockedFilter
def self.included(base)
base.include(InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
def allowed_values
[[I18n.t(:status_blocked), :blocked]]
end
def type
:list
end
def where
User.blocked_condition(operator == '=')
end
end
module ClassMethods
def self.key
:blocked
end
end
end

@ -0,0 +1,89 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Filters::Shared::UserNameFilter
def self.included(base)
base.include(InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
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| self.class.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(users.firstname, CONCAT(' ', users.lastname)))"
when :firstname
'LOWER(users.firstname)'
when :lastname_firstname
"LOWER(CONCAT(users.lastname, CONCAT(' ', users.firstname)))"
when :username
"LOWER(users.login)"
end
end
end
module ClassMethods
def key
:name
end
end
end

@ -0,0 +1,67 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Filters::Shared::UserStatusFilter
def self.included(base)
base.include(InstanceMethods)
base.extend(ClassMethods)
end
module InstanceMethods
def allowed_values
Principal::STATUSES.keys.map do |key|
[I18n.t(:"status_#{key}"), key]
end
end
def type
:list
end
def status_values
values.map { |value| Principal::STATUSES[value.to_sym] }
end
def where
case operator
when "="
["users.status IN (?)", status_values.join(", ")]
when "!"
["users.status NOT IN (?)", status_values.join(", ")]
end
end
end
module ClassMethods
def self.key
:status
end
end
end

@ -0,0 +1,41 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members
query = Queries::Members::MemberQuery
filter_ns = Queries::Members::Filters
Queries::Register.filter query, filter_ns::NameFilter
Queries::Register.filter query, filter_ns::ProjectFilter
Queries::Register.filter query, filter_ns::StatusFilter
Queries::Register.filter query, filter_ns::BlockedFilter
Queries::Register.filter query, filter_ns::GroupFilter
Queries::Register.filter query, filter_ns::RoleFilter
end

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::BlockedFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::UserBlockedFilter
def joins
:principal
end
end

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::GroupFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::GroupFilter
def joins
{ user: :groups }
end
end

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::MemberFilter < Queries::Filters::Base
self.model = Member
def human_name
Member.human_attribute_name(name)
end
end

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::NameFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::UserNameFilter
def joins
:principal
end
end

@ -0,0 +1,33 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::ProjectFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::ProjectFilter
end

@ -0,0 +1,53 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::RoleFilter < Queries::Members::Filters::MemberFilter
def allowed_values
@allowed_values ||= begin
Role.pluck(:name, :id).map { |name, id| [name, id] }
end
end
def type
:list_optional
end
def self.key
:role_id
end
def joins
:member_roles
end
def where
operator_strategy.sql_for_field(values, 'member_roles', 'role_id')
end
end

@ -0,0 +1,37 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::StatusFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::UserStatusFilter
def joins
:principal
end
end

@ -0,0 +1,37 @@
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::MemberQuery < Queries::BaseQuery
def self.model
Member
end
def default_scope
Member.all
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -28,19 +29,5 @@
#++
class Queries::Queries::Filters::ProjectFilter < Queries::Queries::Filters::QueryFilter
def type
:list_optional
end
def self.key
:project_id
end
def type_strategy
# Instead of getting the IDs of all the projects a user is allowed
# to see we only check that the value is an integer. Non valid ids
# will then simply create an empty result but will not cause any
# harm.
@type_strategy ||= ::Queries::Filters::Strategies::IntegerListOptional.new(self)
end
include Queries::Filters::Shared::ProjectFilter
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -32,6 +33,7 @@ module Queries::Users
Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::GroupFilter
Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::StatusFilter
Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::LoginFilter
Queries::Register.filter Queries::Users::UserQuery, Queries::Users::Filters::BlockedFilter
Queries::Register.order Queries::Users::UserQuery, Queries::Users::Orders::DefaultOrder
Queries::Register.order Queries::Users::UserQuery, Queries::Users::Orders::NameOrder

@ -0,0 +1,33 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::BlockedFilter < Queries::Members::Filters::MemberFilter
include Queries::Filters::Shared::UserBlockedFilter
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -28,33 +29,5 @@
#++
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 available?
::Group.exists?
end
def type
:list_optional
end
def human_name
I18n.t('query_fields.member_of_group')
end
def self.key
:group
end
def joins
:groups
end
def where
operator_strategy.sql_for_field(values, 'groups', 'id')
end
include Queries::Filters::Shared::GroupFilter
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -28,48 +29,5 @@
#++
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| self.class.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
include Queries::Filters::Shared::UserNameFilter
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -28,30 +29,5 @@
#++
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
def status_values
values.map { |value| Principal::STATUSES[value.to_sym] }
end
def where
case operator
when "="
["users.status IN (?)", status_values.join(", ")]
when "!"
["users.status NOT IN (?)", status_values.join(", ")]
end
end
include Queries::Filters::Shared::UserStatusFilter
end

@ -97,14 +97,17 @@ class User < Principal
scope :not_blocked, -> { create_blocked_scope(self, false) }
def self.create_blocked_scope(scope, blocked)
scope.where(blocked_condition(blocked))
end
def self.blocked_condition(blocked)
block_duration = Setting.brute_force_block_minutes.to_i.minutes
blocked_if_login_since = Time.now - block_duration
negation = blocked ? '' : 'NOT'
scope.where(
"#{negation} (failed_login_count >= ? AND last_failed_login_on > ?)",
Setting.brute_force_block_after_failed_logins.to_i,
blocked_if_login_since
)
["#{negation} (users.failed_login_count >= ? AND users.last_failed_login_on > ?)",
Setting.brute_force_block_after_failed_logins.to_i,
blocked_if_login_since]
end
acts_as_customizable

@ -5,42 +5,29 @@ class User
##
# @param extra [Hash] A hash containing extra entries with a count for each.
# For example: { random: 42 }
# @return [Hash[Symbol, Array]] A hash mapping each status symbol (such as :active, :blocked,
# etc.) to its count and value (e.g. :active => 1,
# :blocked => :blocked) in a tuple.
# @return [Hash[Symbol, Integer]] A hash mapping each status symbol (such as :active, :blocked,
# etc.) to its count (e.g. { active: 1, blocked: 5, random: 42).
def user_statuses_with_count(extra: {})
counts = user_count_by_status extra: extra
symbols = status_symbols extra: extra
symbols
.map { |name, id| status_count_value_tuple name, id, counts }
user_count_by_status(extra: extra)
.compact
.to_h
end
def status_count_value_tuple(name, id, counts)
value = (counts.include?(id) && id) || User::STATUSES[id] # :active => 1
count = counts[value]
[name.to_sym, [count.to_i, value]] if count
end
# use non-numerical values as index to prevent clash with normal user
# statuses
def status_symbols(extra: {})
{ all: :all, blocked: :blocked }
.merge(User::STATUSES.except(:builtin))
.merge(extra.map { |key, _| [key, key] }.to_h)
end
def user_count_by_status(extra: {})
counts = User.group(:status).count.to_hash
counts = User.not_builtin.group(:status).count.to_hash
counts
.merge(symbolic_user_counts)
.merge(extra)
.reject { |_, v| v.nil? } # remove nil counts to support dropping counts via extra
.map { |k, v| [User::STATUSES[k] || k, v] } # map to status id if :active, :invited etc.
.map do |k, v|
known_status = Principal::STATUSES.detect { |_, i| i == k }
if known_status
[known_status.first, v]
else
[k, v]
end
end
.to_h
end

@ -1978,6 +1978,7 @@ en:
status_active: "active"
status_invited: invited
status_locked: locked
status_registered: registered
# Used in array.to_sentence.

@ -77,7 +77,7 @@ module NavigationHelpers
'/admin/groups'
when /^the admin page of pending users$/
'/users?sort=created_on:desc&status=2'
'/users?sort=created_on:desc&status=registered'
when /^the edit menu item page of the [wW]iki [pP]age "([^\"]+)" (?:for|of) the project called "([^\"]+)"$/
project_identifier = $2.gsub("\"", '')

@ -79,13 +79,13 @@ describe MembersController, type: :controller do
let(:project_2) { FactoryGirl.create(:project) }
let(:role_1) { FactoryGirl.create(:role) }
let(:role_2) { FactoryGirl.create(:role) }
let(:member_2) {
let(:member_2) do
FactoryGirl.create(
:member,
project: project_2,
user: admin,
roles: [role_1])
}
end
before do
allow(User).to receive(:current).and_return(admin)
@ -152,7 +152,7 @@ describe MembersController, type: :controller do
it 'should add a member' do
expect { action }.to change { Member.count }.by(1)
expect(response).to redirect_to '/projects/pet_project/members'
expect(response).to redirect_to '/projects/pet_project/members?status=all'
expect(user2).to be_member_of(project)
end
end
@ -168,7 +168,7 @@ describe MembersController, type: :controller do
it 'should add all members' do
expect { action }.to change { Member.count }.by(3)
expect(response).to redirect_to '/projects/pet_project/members'
expect(response).to redirect_to '/projects/pet_project/members?status=all'
expect(user2).to be_member_of(project)
expect(user3).to be_member_of(project)
expect(user4).to be_member_of(project)
@ -177,11 +177,11 @@ describe MembersController, type: :controller do
end
context 'with a failed save' do
let(:invalid_params) {
let(:invalid_params) do
{ project_id: project.id,
member: { role_ids: [],
user_ids: [user2.id, user3.id, user4.id] } }
}
end
before do
post :create, params: invalid_params

@ -50,10 +50,9 @@ feature 'invite user via email', type: :feature, js: true do
expect(members_page).to have_selected_new_principal('Invite finkelstein@openproject.com')
click_on 'Add'
expect(members_page).to have_added_user('finkelstein @openproject.com', visible: false)
click_on 'filter-member-button' # toggle filters
select 'all', from: 'status'
click_on 'Apply'
expect(members_page).to have_added_user('finkelstein @openproject.com')
expect(members_page).to have_user 'finkelstein @openproject.com'
end
end

@ -0,0 +1,46 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::BlockedFilter, type: :model do
it_behaves_like 'basic query filter' do
let(:class_key) { :blocked }
let(:type) { :list }
describe '#allowed_values' do
it 'is a list of the possible values' do
expected = [[I18n.t(:status_blocked), :blocked]]
expect(instance.allowed_values).to match_array(expected)
end
end
end
end

@ -0,0 +1,64 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::GroupFilter, type: :model do
let(:group1) { FactoryGirl.build_stubbed(:group) }
let(:group2) { FactoryGirl.build_stubbed(:group) }
before do
allow(Group)
.to receive(:pluck)
.with(:id)
.and_return([group1.id, group2.id])
end
it_behaves_like 'basic query filter' do
let(:class_key) { :group }
let(:type) { :list_optional }
let(:name) { I18n.t('query_fields.member_of_group') }
describe '#allowed_values' do
it 'is a list of the possible values' do
expected = [[group1.id, group1.id.to_s], [group2.id, group2.id.to_s]]
expect(instance.allowed_values).to match_array(expected)
end
end
end
it_behaves_like 'list_optional query filter' do
let(:attribute) { :id }
let(:model) { Member.joins(user: :groups) }
let(:valid_values) { [group1.id.to_s] }
let(:expected_table_name) { 'groups_users' }
end
end

@ -0,0 +1,97 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::NameFilter, type: :model do
include_context 'filter tests'
let(:values) { ['A name'] }
let(:model) { Member.joins(:principal) }
it_behaves_like 'basic query filter' do
let(:class_key) { :name }
let(:type) { :string }
let(:model) { Member.joins(:principal) }
describe '#allowed_values' do
it 'is nil' do
expect(instance.allowed_values).to be_nil
end
end
end
describe '#scope' do
before do
allow(Setting)
.to receive(:user_format)
.and_return(:firstname)
end
context 'for "="' do
let(:operator) { '=' }
it 'is the same as handwriting the query' do
expected = model.where("LOWER(users.firstname) IN ('#{values.first.downcase}')")
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
expected = model.where("LOWER(users.firstname) NOT IN ('#{values.first.downcase}')")
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
expected = model.where("LOWER(users.firstname) LIKE '%#{values.first.downcase}%'")
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
expected = model.where("LOWER(users.firstname) NOT LIKE '%#{values.first.downcase}%'")
expect(instance.scope.to_sql).to eql expected.to_sql
end
end
end
end

@ -0,0 +1,44 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::ProjectFilter, type: :model do
it_behaves_like 'basic query filter' do
let(:class_key) { :project_id }
let(:type) { :list_optional }
end
it_behaves_like 'list_optional query filter' do
let(:attribute) { :project_id }
let(:model) { Member }
let(:valid_values) { ['1'] }
end
end

@ -0,0 +1,64 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::RoleFilter, type: :model do
let(:role1) { FactoryGirl.build_stubbed(:role) }
let(:role2) { FactoryGirl.build_stubbed(:role) }
before do
allow(Role)
.to receive(:pluck)
.with(:name, :id)
.and_return([[role1.name, role1.id], [role2.name, role2.id]])
end
it_behaves_like 'basic query filter' do
let(:class_key) { :role_id }
let(:type) { :list_optional }
let(:name) { Member.human_attribute_name(:role) }
describe '#allowed_values' do
it 'is a list of the possible values' do
expected = [[role1.name, role1.id], [role2.name, role2.id]]
expect(instance.allowed_values).to match_array(expected)
end
end
end
it_behaves_like 'list_optional query filter' do
let(:attribute) { :role_id }
let(:model) { Member }
let(:joins) { :member_roles }
let(:valid_values) { [role1.id.to_s] }
end
end

@ -0,0 +1,48 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 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-2017 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::Members::Filters::StatusFilter, type: :model do
it_behaves_like 'basic query filter' do
let(:class_key) { :status }
let(:type) { :list }
describe '#allowed_values' do
it 'is a list of the possible values' do
expected = Principal::STATUSES.keys.map do |key|
[I18n.t(:"status_#{key}"), key]
end
expect(instance.allowed_values).to match_array(expected)
end
end
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -36,7 +37,8 @@ describe Queries::Users::Filters::GroupFilter, type: :model do
before do
allow(Group)
.to receive(:pluck)
.and_return([[group1.name, group1.id.to_s], [group2.name, group2.id.to_s]])
.with(:id)
.and_return([group1.id, group2.id])
end
it_behaves_like 'basic query filter' do
@ -46,7 +48,7 @@ describe Queries::Users::Filters::GroupFilter, type: :model do
describe '#allowed_values' do
it 'is a list of the possible values' do
expected = [[group1.name, group1.id.to_s], [group2.name, group2.id.to_s]]
expected = [[group1.id, group1.id.to_s], [group2.id, group2.id.to_s]]
expect(instance.allowed_values).to match_array(expected)
end
@ -58,5 +60,6 @@ describe Queries::Users::Filters::GroupFilter, type: :model do
let(:model) { User }
let(:joins) { :groups }
let(:valid_values) { [group1.id.to_s] }
let(:expected_table_name) { 'groups_users' }
end
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)
@ -57,7 +58,7 @@ describe Queries::Users::Filters::NameFilter, type: :model do
let(:operator) { '=' }
it 'is the same as handwriting the query' do
expected = model.where("LOWER(firstname) IN ('#{values.first.downcase}')")
expected = model.where("LOWER(users.firstname) IN ('#{values.first.downcase}')")
expect(instance.scope.to_sql).to eql expected.to_sql
end
@ -67,7 +68,7 @@ describe Queries::Users::Filters::NameFilter, type: :model do
let(:operator) { '!' }
it 'is the same as handwriting the query' do
expected = model.where("LOWER(firstname) NOT IN ('#{values.first.downcase}')")
expected = model.where("LOWER(users.firstname) NOT IN ('#{values.first.downcase}')")
expect(instance.scope.to_sql).to eql expected.to_sql
end
@ -77,7 +78,7 @@ describe Queries::Users::Filters::NameFilter, type: :model do
let(:operator) { '~' }
it 'is the same as handwriting the query' do
expected = model.where("LOWER(firstname) LIKE '%#{values.first.downcase}%'")
expected = model.where("LOWER(users.firstname) LIKE '%#{values.first.downcase}%'")
expect(instance.scope.to_sql).to eql expected.to_sql
end
@ -87,7 +88,7 @@ describe Queries::Users::Filters::NameFilter, type: :model do
let(:operator) { '!~' }
it 'is the same as handwriting the query' do
expected = model.where("LOWER(firstname) NOT LIKE '%#{values.first.downcase}%'")
expected = model.where("LOWER(users.firstname) NOT LIKE '%#{values.first.downcase}%'")
expect(instance.scope.to_sql).to eql expected.to_sql
end

@ -1,4 +1,5 @@
#-- encoding: UTF-8
#-- copyright
# OpenProject is a project management system.
# Copyright (C) 2012-2017 the OpenProject Foundation (OPF)

@ -52,8 +52,8 @@ describe Queries::Users::UserQuery, type: :model do
it 'is the same as handwriting the query' do
expected = base_scope
.merge(User
.where(["LOWER(CONCAT(firstname, CONCAT(' ', lastname))) LIKE ?",
"%a user%"]))
.where(["LOWER(CONCAT(users.firstname, CONCAT(' ', users.lastname))) LIKE ?",
"%a user%"]))
expect(instance.results.to_sql).to eql expected.to_sql
end
@ -116,7 +116,7 @@ describe Queries::Users::UserQuery, type: :model do
expected = base_scope
.merge(User
.joins(:groups)
.where("groups.id IN ('#{group_1.id}')"))
.where("groups_users.id IN ('#{group_1.id}')"))
expect(instance.results.to_sql).to eql expected.to_sql
end

Loading…
Cancel
Save