Commit 16d8efd1 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch '213211-resolve-count-issue-in-sharing-group' into 'master'

Resolve "Defects with the share with group feature"

Closes #213211

See merge request gitlab-org/gitlab!28645
parents 1e105739 e924f27a
...@@ -13,6 +13,8 @@ class GroupGroupLink < ApplicationRecord ...@@ -13,6 +13,8 @@ class GroupGroupLink < ApplicationRecord
validates :group_access, inclusion: { in: Gitlab::Access.all_values }, validates :group_access, inclusion: { in: Gitlab::Access.all_values },
presence: true presence: true
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
def self.access_options def self.access_options
Gitlab::Access.options_with_owner Gitlab::Access.options_with_owner
end end
......
...@@ -3,11 +3,6 @@ ...@@ -3,11 +3,6 @@
class ProjectGroupLink < ApplicationRecord class ProjectGroupLink < ApplicationRecord
include Expirable include Expirable
GUEST = 10
REPORTER = 20
DEVELOPER = 30
MAINTAINER = 40
belongs_to :project belongs_to :project
belongs_to :group belongs_to :group
...@@ -18,6 +13,8 @@ class ProjectGroupLink < ApplicationRecord ...@@ -18,6 +13,8 @@ class ProjectGroupLink < ApplicationRecord
validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true
validate :different_group validate :different_group
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
after_commit :refresh_group_members_authorized_projects after_commit :refresh_group_members_authorized_projects
alias_method :shared_with_group, :group alias_method :shared_with_group, :group
...@@ -27,7 +24,7 @@ class ProjectGroupLink < ApplicationRecord ...@@ -27,7 +24,7 @@ class ProjectGroupLink < ApplicationRecord
end end
def self.default_access def self.default_access
DEVELOPER Gitlab::Access::DEVELOPER
end end
def self.search(query) def self.search(query)
......
...@@ -261,15 +261,15 @@ module EE ...@@ -261,15 +261,15 @@ module EE
strong_memoize(:gold_billed_user_ids) do strong_memoize(:gold_billed_user_ids) do
(billed_group_members.non_guests.distinct.pluck(:user_id) + (billed_group_members.non_guests.distinct.pluck(:user_id) +
billed_project_members.non_guests.distinct.pluck(:user_id) + billed_project_members.non_guests.distinct.pluck(:user_id) +
billed_shared_group_members.non_guests.distinct.pluck(:user_id) + billed_shared_non_guests_group_members.non_guests.distinct.pluck(:user_id) +
billed_invited_group_members.non_guests.distinct.pluck(:user_id)).to_set billed_invited_non_guests_group_to_project_members.non_guests.distinct.pluck(:user_id)).to_set
end end
else else
strong_memoize(:non_gold_billed_user_ids) do strong_memoize(:non_gold_billed_user_ids) do
(billed_group_members.distinct.pluck(:user_id) + (billed_group_members.distinct.pluck(:user_id) +
billed_project_members.distinct.pluck(:user_id) + billed_project_members.distinct.pluck(:user_id) +
billed_shared_group_members.distinct.pluck(:user_id) + billed_shared_group_members.distinct.pluck(:user_id) +
billed_invited_group_members.distinct.pluck(:user_id)).to_set billed_invited_group_to_project_members.distinct.pluck(:user_id)).to_set
end end
end end
end end
...@@ -320,35 +320,62 @@ module EE ...@@ -320,35 +320,62 @@ module EE
errors.add(:custom_project_templates_group_id, 'has to be a subgroup of the group') errors.add(:custom_project_templates_group_id, 'has to be a subgroup of the group')
end end
# Members belonging directly to Group or its subgroups
def billed_group_members def billed_group_members
::GroupMember.active_without_invites_and_requests.where( ::GroupMember.active_without_invites_and_requests.where(
source_id: self_and_descendants source_id: self_and_descendants
) )
end end
# Members belonging directly to Projects within Group or Projects within subgroups
def billed_project_members def billed_project_members
::ProjectMember.active_without_invites_and_requests.where( ::ProjectMember.active_without_invites_and_requests.where(
source_id: ::Project.joins(:group).where(namespace: self_and_descendants) source_id: ::Project.joins(:group).where(namespace: self_and_descendants)
) )
end end
def billed_invited_group_members # Members belonging to Groups invited to collaborate with Projects
def billed_invited_group_to_project_members
invited_or_shared_group_members(invited_groups_in_projects) invited_or_shared_group_members(invited_groups_in_projects)
end end
def billed_shared_group_members def billed_invited_non_guests_group_to_project_members
return ::GroupMember.none unless ::Feature.enabled?(:share_group_with_group) invited_or_shared_group_members(invited_group_as_non_guests_in_projects)
invited_or_shared_group_members(shared_groups)
end end
def invited_or_shared_group_members(groups) def invited_group_as_non_guests_in_projects
::GroupMember.active_without_invites_and_requests.where(source_id: ::Gitlab::ObjectHierarchy.new(groups).base_and_ancestors) invited_groups_in_projects.merge(::ProjectGroupLink.non_guests)
end end
def invited_groups_in_projects def invited_groups_in_projects
::Group.joins(:project_group_links) ::Group.joins(:project_group_links)
.where(project_group_links: { project_id: all_projects }) .where(project_group_links: { project_id: all_projects })
end end
# Members belonging to Groups invited to collaborate with Groups and Subgroups
def billed_shared_group_members
return ::GroupMember.none unless ::Feature.enabled?(:share_group_with_group)
invited_or_shared_group_members(invited_group_in_groups)
end
def billed_shared_non_guests_group_members
return ::GroupMember.none unless ::Feature.enabled?(:share_group_with_group)
invited_or_shared_group_members(invited_non_guest_group_in_groups)
end
def invited_non_guest_group_in_groups
invited_group_in_groups.merge(::GroupGroupLink.non_guests)
end
def invited_group_in_groups
::Group.joins(:shared_group_links)
.where(group_group_links: { shared_group_id: ::Group.groups_including_descendants_by([self]) })
end
def invited_or_shared_group_members(groups)
::GroupMember.active_without_invites_and_requests.where(source_id: ::Gitlab::ObjectHierarchy.new(groups).base_and_ancestors)
end
end end
end end
---
title: Fix billed users id and count from shared group
merge_request: 28645
author:
type: fixed
...@@ -1030,11 +1030,26 @@ describe Namespace do ...@@ -1030,11 +1030,26 @@ describe Namespace do
invited_group.add_guest(create(:user)) invited_group.add_guest(create(:user))
invited_group.add_developer(create(:user, :blocked)) invited_group.add_developer(create(:user, :blocked))
invited_group.add_developer(developer) invited_group.add_developer(developer)
create(:project_group_link, project: project, group: invited_group)
end end
it 'includes the only active users except guests of the invited groups' do context 'when group is invited as non guest' do
expect(group.billed_user_ids).to match_array([invited_group_developer.id, project_developer.id, developer.id]) before do
create(:project_group_link, project: project, group: invited_group)
end
it 'includes the only active users except guests of the invited groups' do
expect(group.billed_user_ids).to match_array([invited_group_developer.id, project_developer.id, developer.id])
end
end
context 'when group is invited as a guest to the project' do
before do
create(:project_group_link, :guest, project: project, group: invited_group)
end
it 'does not include any members from the invited group' do
expect(group.billed_user_ids).to match_array([project_developer.id, developer.id])
end
end end
end end
end end
...@@ -1048,8 +1063,8 @@ describe Namespace do ...@@ -1048,8 +1063,8 @@ describe Namespace do
shared_group.add_guest(create(:user)) shared_group.add_guest(create(:user))
shared_group.add_developer(create(:user, :blocked)) shared_group.add_developer(create(:user, :blocked))
create(:group_group_link, { shared_with_group: group, create(:group_group_link, { shared_with_group: shared_group,
shared_group: shared_group }) shared_group: group })
end end
context 'when feature is not enabled' do context 'when feature is not enabled' do
...@@ -1057,8 +1072,9 @@ describe Namespace do ...@@ -1057,8 +1072,9 @@ describe Namespace do
stub_feature_flags(share_group_with_group: false) stub_feature_flags(share_group_with_group: false)
end end
it 'does not include users coming from the shared groups' do it 'does not include users coming from the shared groups', :aggregate_failures do
expect(group.billed_user_ids).to match_array([developer.id]) expect(group.billed_user_ids).to match_array([developer.id])
expect(shared_group.billed_user_ids).not_to include([developer.id])
end end
end end
...@@ -1067,8 +1083,46 @@ describe Namespace do ...@@ -1067,8 +1083,46 @@ describe Namespace do
stub_feature_flags(share_group_with_group: true) stub_feature_flags(share_group_with_group: true)
end end
it 'includes active users from the shared group to the billed members count' do it 'includes active users from the shared group to the billed members', :aggregate_failures do
expect(group.billed_user_ids).to match_array([shared_group_developer.id, developer.id]) expect(group.billed_user_ids).to match_array([shared_group_developer.id, developer.id])
expect(shared_group.billed_user_ids).not_to include([developer.id])
end
context 'when subgroup invited another group to collaborate' do
let(:another_shared_group) { create(:group) }
let(:another_shared_group_developer) { create(:user) }
before do
another_shared_group.add_developer(another_shared_group_developer)
another_shared_group.add_guest(create(:user))
another_shared_group.add_developer(create(:user, :blocked))
end
context 'when subgroup invites another group as non guest' do
before do
subgroup = create(:group, parent: group)
create(:group_group_link, { shared_with_group: another_shared_group,
shared_group: subgroup })
end
it 'includes all the active and non guest users from the shared group', :aggregate_failures do
expect(group.billed_user_ids).to match_array([shared_group_developer.id, developer.id, another_shared_group_developer.id])
expect(shared_group.billed_user_ids).not_to include([developer.id])
expect(another_shared_group.billed_user_ids).not_to include([developer.id, shared_group_developer.id])
end
end
context 'when subgroup invites another group as guest' do
before do
subgroup = create(:group, parent: group)
create(:group_group_link, :guest, { shared_with_group: another_shared_group,
shared_group: subgroup })
end
it 'does not includes any user from the shared group from the subgroup' do
expect(group.billed_user_ids).to match_array([shared_group_developer.id, developer.id])
end
end
end end
end end
end end
...@@ -1133,8 +1187,8 @@ describe Namespace do ...@@ -1133,8 +1187,8 @@ describe Namespace do
shared_group.add_guest(shared_group_guest) shared_group.add_guest(shared_group_guest)
shared_group.add_developer(create(:user, :blocked)) shared_group.add_developer(create(:user, :blocked))
create(:group_group_link, { shared_with_group: group, create(:group_group_link, { shared_with_group: shared_group,
shared_group: shared_group }) shared_group: group })
end end
context 'when feature is not enabled' do context 'when feature is not enabled' do
...@@ -1152,8 +1206,9 @@ describe Namespace do ...@@ -1152,8 +1206,9 @@ describe Namespace do
stub_feature_flags(share_group_with_group: true) stub_feature_flags(share_group_with_group: true)
end end
it 'includes active users from the shared group including guests' do it 'includes active users from the shared group including guests', :aggregate_failures do
expect(group.billed_user_ids).to match_array([developer.id, guest.id, shared_group_developer.id, shared_group_guest.id]) expect(group.billed_user_ids).to match_array([developer.id, guest.id, shared_group_developer.id, shared_group_guest.id])
expect(shared_group.billed_user_ids).to match_array([shared_group_developer.id, shared_group_guest.id])
end end
end end
end end
...@@ -1229,8 +1284,8 @@ describe Namespace do ...@@ -1229,8 +1284,8 @@ describe Namespace do
shared_group.add_guest(create(:user)) shared_group.add_guest(create(:user))
shared_group.add_developer(create(:user, :blocked)) shared_group.add_developer(create(:user, :blocked))
create(:group_group_link, { shared_with_group: group, create(:group_group_link, { shared_with_group: shared_group,
shared_group: shared_group }) shared_group: group })
end end
context 'when feature is not enabled' do context 'when feature is not enabled' do
...@@ -1303,8 +1358,8 @@ describe Namespace do ...@@ -1303,8 +1358,8 @@ describe Namespace do
shared_group.add_guest(create(:user)) shared_group.add_guest(create(:user))
shared_group.add_developer(create(:user, :blocked)) shared_group.add_developer(create(:user, :blocked))
create(:group_group_link, { shared_with_group: group, create(:group_group_link, { shared_with_group: shared_group,
shared_group: shared_group }) shared_group: group })
end end
context 'when feature is not enabled' do context 'when feature is not enabled' do
......
...@@ -13,7 +13,7 @@ describe Groups::SharedProjectsController do ...@@ -13,7 +13,7 @@ describe Groups::SharedProjectsController do
Projects::GroupLinks::CreateService.new( Projects::GroupLinks::CreateService.new(
project, project,
user, user,
link_group_access: ProjectGroupLink::DEVELOPER link_group_access: Gitlab::Access::DEVELOPER
).execute(group) ).execute(group)
end end
......
...@@ -4,6 +4,12 @@ FactoryBot.define do ...@@ -4,6 +4,12 @@ FactoryBot.define do
factory :group_group_link do factory :group_group_link do
shared_group { create(:group) } shared_group { create(:group) }
shared_with_group { create(:group) } shared_with_group { create(:group) }
group_access { GroupMember::DEVELOPER } group_access { Gitlab::Access::DEVELOPER }
trait(:guest) { group_access { Gitlab::Access::GUEST } }
trait(:reporter) { group_access { Gitlab::Access::REPORTER } }
trait(:developer) { group_access { Gitlab::Access::DEVELOPER } }
trait(:owner) { group_access { Gitlab::Access::OWNER } }
trait(:maintainer) { group_access { Gitlab::Access::MAINTAINER } }
end end
end end
...@@ -5,5 +5,10 @@ FactoryBot.define do ...@@ -5,5 +5,10 @@ FactoryBot.define do
project project
group group
expires_at { nil } expires_at { nil }
trait(:guest) { group_access { Gitlab::Access::GUEST } }
trait(:reporter) { group_access { Gitlab::Access::REPORTER } }
trait(:developer) { group_access { Gitlab::Access::DEVELOPER } }
trait(:maintainer) { group_access { Gitlab::Access::MAINTAINER } }
end end
end end
...@@ -123,7 +123,7 @@ describe GroupDescendantsFinder do ...@@ -123,7 +123,7 @@ describe GroupDescendantsFinder do
project = create(:project, namespace: group) project = create(:project, namespace: group)
other_project = create(:project) other_project = create(:project)
other_project.project_group_links.create(group: group, other_project.project_group_links.create(group: group,
group_access: ProjectGroupLink::MAINTAINER) group_access: Gitlab::Access::MAINTAINER)
expect(finder.execute).to contain_exactly(project) expect(finder.execute).to contain_exactly(project)
end end
......
...@@ -15,6 +15,22 @@ describe GroupGroupLink do ...@@ -15,6 +15,22 @@ describe GroupGroupLink do
it { is_expected.to belong_to(:shared_with_group) } it { is_expected.to belong_to(:shared_with_group) }
end end
describe 'scopes' do
describe '.non_guests' do
let!(:group_group_link_reporter) { create :group_group_link, :reporter }
let!(:group_group_link_maintainer) { create :group_group_link, :maintainer }
let!(:group_group_link_owner) { create :group_group_link, :owner }
let!(:group_group_link_guest) { create :group_group_link, :guest }
it 'returns all records which are greater than Guests access' do
expect(described_class.non_guests).to match_array([
group_group_link_reporter, group_group_link,
group_group_link_maintainer, group_group_link_owner
])
end
end
end
describe 'validation' do describe 'validation' do
it { is_expected.to validate_presence_of(:shared_group) } it { is_expected.to validate_presence_of(:shared_group) }
......
...@@ -32,6 +32,23 @@ describe ProjectGroupLink do ...@@ -32,6 +32,23 @@ describe ProjectGroupLink do
end end
end end
describe 'scopes' do
describe '.non_guests' do
let!(:project_group_link_reporter) { create :project_group_link, :reporter }
let!(:project_group_link_maintainer) { create :project_group_link, :maintainer }
let!(:project_group_link_developer) { create :project_group_link }
let!(:project_group_link_guest) { create :project_group_link, :guest }
it 'returns all records which are greater than Guests access' do
expect(described_class.non_guests).to match_array([
project_group_link_reporter,
project_group_link_developer,
project_group_link_maintainer
])
end
end
end
describe "destroying a record", :delete do describe "destroying a record", :delete do
it "refreshes group users' authorized projects" do it "refreshes group users' authorized projects" do
project = create(:project, :private) project = create(:project, :private)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment