mirror of
https://github.com/opf/openproject.git
synced 2026-06-13 19:20:00 +00:00
Allow filtering by group hierarchy
This commit is contained in:
@@ -113,6 +113,12 @@ module Members
|
||||
end
|
||||
end
|
||||
|
||||
def filter_group(query, group_id)
|
||||
if group_id.present?
|
||||
query.where(:group_hierarchy, "=", group_id)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def filter_shares(query, role_id)
|
||||
|
||||
@@ -36,6 +36,7 @@ module Queries::Members
|
||||
filter Filters::StatusFilter
|
||||
filter Filters::BlockedFilter
|
||||
filter Filters::GroupFilter
|
||||
filter Filters::GroupHierarchyFilter
|
||||
filter Filters::RoleFilter
|
||||
filter Filters::PrincipalFilter
|
||||
filter Filters::PrincipalTypeFilter
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# 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 COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
# Like GroupFilter but hierarchy-aware: matches members who are users of the
|
||||
# given group or any of its descendant groups, as well as the descendant
|
||||
# groups themselves (which carry inherited Member records).
|
||||
class Queries::Members::Filters::GroupHierarchyFilter < Queries::Members::Filters::MemberFilter
|
||||
def self.key
|
||||
:group_hierarchy
|
||||
end
|
||||
|
||||
def allowed_values
|
||||
@allowed_values ||= ::Group.pluck(:id).map { |g| [g, g.to_s] }
|
||||
end
|
||||
|
||||
def available?
|
||||
::Group.exists?
|
||||
end
|
||||
|
||||
def type
|
||||
:list_optional
|
||||
end
|
||||
|
||||
def human_name
|
||||
I18n.t("query_fields.member_of_group")
|
||||
end
|
||||
|
||||
def joins
|
||||
:principal
|
||||
end
|
||||
|
||||
def where
|
||||
case operator
|
||||
when "="
|
||||
"users.id IN (#{hierarchy_subselect})"
|
||||
when "!"
|
||||
"users.id NOT IN (#{hierarchy_subselect})"
|
||||
when "*"
|
||||
"users.id IN (#{User.within_group([]).select(:id).to_sql})"
|
||||
when "!*"
|
||||
"users.id NOT IN (#{User.within_group([]).select(:id).to_sql})"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hierarchy_subselect
|
||||
groups = Group.where(id: values.map(&:to_i))
|
||||
all_group_ids = groups.flat_map { |g| g.self_and_descendants.pluck(:id) }.uniq
|
||||
user_ids = User.in_group(all_group_ids).pluck(:id)
|
||||
(user_ids + all_group_ids).uniq.join(",").presence || "NULL"
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,107 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
#-- copyright
|
||||
# OpenProject is an open source project management software.
|
||||
# Copyright (C) the OpenProject GmbH
|
||||
#
|
||||
# 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 COPYRIGHT and LICENSE files for more details.
|
||||
#++
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
RSpec.describe Queries::Members::Filters::GroupHierarchyFilter do
|
||||
include_context "filter tests"
|
||||
|
||||
let(:project) { create(:project) }
|
||||
let(:admin) { create(:admin) }
|
||||
let(:role) { create(:project_role) }
|
||||
|
||||
let(:root_user) { create(:user) }
|
||||
let(:mid_user) { create(:user) }
|
||||
let(:leaf_user) { create(:user) }
|
||||
let(:unrelated_user) { create(:user) }
|
||||
|
||||
let!(:root_group) { create(:group, members: [root_user]) }
|
||||
let!(:mid_group) { create(:group, members: [mid_user], parent: root_group) }
|
||||
let!(:leaf_group) { create(:group, members: [leaf_user], parent: mid_group) }
|
||||
let!(:other_group) { create(:group, members: [unrelated_user]) }
|
||||
|
||||
before do
|
||||
allow(Notifications::GroupMemberAlteredJob).to receive(:perform_later)
|
||||
|
||||
Members::CreateService
|
||||
.new(user: admin)
|
||||
.call(principal: root_group, project_id: project.id, role_ids: [role.id])
|
||||
end
|
||||
|
||||
it "has key :group_hierarchy" do
|
||||
expect(described_class.key).to eq(:group_hierarchy)
|
||||
end
|
||||
|
||||
describe "#allowed_values" do
|
||||
it "lists all group IDs" do
|
||||
expect(instance.allowed_values.map(&:first))
|
||||
.to include(root_group.id, mid_group.id, leaf_group.id, other_group.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#where with operator "="' do
|
||||
let(:operator) { "=" }
|
||||
let(:values) { [root_group.id.to_s] }
|
||||
|
||||
it "returns members for users in the group and its descendants, plus the descendant groups" do
|
||||
members = Member.joins(:principal).where(project:).where(instance.where)
|
||||
|
||||
principal_ids = members.pluck(:user_id)
|
||||
|
||||
# Users from root, mid, and leaf groups
|
||||
expect(principal_ids).to include(root_user.id, mid_user.id, leaf_user.id)
|
||||
# Descendant groups themselves
|
||||
expect(principal_ids).to include(mid_group.id, leaf_group.id)
|
||||
# The root group itself (it's in the tree)
|
||||
expect(principal_ids).to include(root_group.id)
|
||||
# Unrelated user is excluded
|
||||
expect(principal_ids).not_to include(unrelated_user.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#where with operator "!"' do
|
||||
let(:operator) { "!" }
|
||||
let(:values) { [root_group.id.to_s] }
|
||||
|
||||
it "excludes members for users in the group hierarchy and the descendant groups" do
|
||||
# Add unrelated_user as a member so there's something to match
|
||||
Members::CreateService
|
||||
.new(user: admin)
|
||||
.call(principal: other_group, project_id: project.id, role_ids: [role.id])
|
||||
|
||||
members = Member.joins(:principal).where(project:).where(instance.where)
|
||||
principal_ids = members.pluck(:user_id)
|
||||
|
||||
expect(principal_ids).to include(unrelated_user.id, other_group.id)
|
||||
expect(principal_ids).not_to include(root_user.id, mid_user.id, leaf_user.id)
|
||||
expect(principal_ids).not_to include(mid_group.id, leaf_group.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user