Commit 2660273f authored by Steve Abrams's avatar Steve Abrams

Merge branch 'mmj-project-recalculate-per-user' into 'master'

Add a service class to recalculate project authorizations of a project for a specific user

See merge request gitlab-org/gitlab!66725
parents c71688db 58e723c6
...@@ -59,8 +59,8 @@ module Projects ...@@ -59,8 +59,8 @@ module Projects
# @return [Array<[user_id, access_level]>] # @return [Array<[user_id, access_level]>]
def user_ids_and_access_levels_from_all_memberships def user_ids_and_access_levels_from_all_memberships
strong_memoize(:user_ids_and_access_levels_from_all_memberships) do strong_memoize(:user_ids_and_access_levels_from_all_memberships) do
all_possible_avenues_of_membership.flat_map do |relation| all_possible_avenues_of_membership.flat_map do |members|
relation.pluck(*USER_ID_AND_ACCESS_LEVEL) # rubocop: disable CodeReuse/ActiveRecord apply_scopes(members).pluck(*USER_ID_AND_ACCESS_LEVEL) # rubocop: disable CodeReuse/ActiveRecord
end end
end end
end end
...@@ -86,7 +86,7 @@ module Projects ...@@ -86,7 +86,7 @@ module Projects
members << Member.from_union(members_per_batch) members << Member.from_union(members_per_batch)
end end
members.flatten Member.from_union(members)
end end
def project_owner_acting_as_maintainer def project_owner_acting_as_maintainer
...@@ -120,6 +120,10 @@ module Projects ...@@ -120,6 +120,10 @@ module Projects
Arel.sql(column_alias) Arel.sql(column_alias)
) )
end end
def apply_scopes(members)
members
end
end end
end end
end end
# frozen_string_literal: true
module Projects
module Members
class EffectiveAccessLevelPerUserFinder < EffectiveAccessLevelFinder
def initialize(project, user)
@project = project
@user = user
end
private
attr_reader :user
def apply_scopes(members)
super.where(user_id: user.id) # rubocop: disable CodeReuse/ActiveRecord
end
end
end
end
# frozen_string_literal: true
module AuthorizedProjectUpdate
class ProjectRecalculatePerUserService < ProjectRecalculateService
def initialize(project, user)
@project = project
@user = user
end
private
attr_reader :user
def apply_scopes(project_authorizations)
super.where(user_id: user.id) # rubocop: disable CodeReuse/ActiveRecord
end
def effective_access_levels
Projects::Members::EffectiveAccessLevelPerUserFinder.new(project, user).execute
end
end
end
...@@ -26,7 +26,7 @@ module AuthorizedProjectUpdate ...@@ -26,7 +26,7 @@ module AuthorizedProjectUpdate
def current_authorizations def current_authorizations
strong_memoize(:current_authorizations) do strong_memoize(:current_authorizations) do
project.project_authorizations apply_scopes(project.project_authorizations)
.pluck(:user_id, :access_level) # rubocop: disable CodeReuse/ActiveRecord .pluck(:user_id, :access_level) # rubocop: disable CodeReuse/ActiveRecord
end end
end end
...@@ -35,8 +35,7 @@ module AuthorizedProjectUpdate ...@@ -35,8 +35,7 @@ module AuthorizedProjectUpdate
strong_memoize(:fresh_authorizations) do strong_memoize(:fresh_authorizations) do
result = [] result = []
Projects::Members::EffectiveAccessLevelFinder.new(project) effective_access_levels
.execute
.each_batch(of: BATCH_SIZE, column: :user_id) do |member_batch| .each_batch(of: BATCH_SIZE, column: :user_id) do |member_batch|
result += member_batch.pluck(:user_id, 'MAX(access_level)') # rubocop: disable CodeReuse/ActiveRecord result += member_batch.pluck(:user_id, 'MAX(access_level)') # rubocop: disable CodeReuse/ActiveRecord
end end
...@@ -76,5 +75,13 @@ module AuthorizedProjectUpdate ...@@ -76,5 +75,13 @@ module AuthorizedProjectUpdate
end end
end end
end end
def apply_scopes(project_authorizations)
project_authorizations
end
def effective_access_levels
Projects::Members::EffectiveAccessLevelFinder.new(project).execute
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Members::EffectiveAccessLevelPerUserFinder, '#execute' do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:user) { create(:user) }
# The result set is being converted to json just for the ease of testing.
subject { described_class.new(project, user).execute.as_json }
context 'a combination of all possible avenues of membership' do
let_it_be(:another_user) { create(:user) }
let_it_be(:shared_with_group) { create(:group) }
before do
create(:project_group_link, :maintainer, project: project, group: shared_with_group)
create(:group_group_link, :reporter, shared_group: project.group, shared_with_group: shared_with_group)
shared_with_group.add_maintainer(user)
shared_with_group.add_maintainer(another_user)
group.add_guest(user)
group.add_guest(another_user)
project.add_developer(user)
project.add_developer(another_user)
end
it 'includes the highest access level from all avenues of memberships for the specific user alone' do
expect(subject).to eq(
[{
'user_id' => user.id,
'access_level' => Gitlab::Access::MAINTAINER, # From project_group_link
'id' => nil
}]
)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AuthorizedProjectUpdate::ProjectRecalculatePerUserService, '#execute' do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:another_user) { create(:user) }
subject(:execute) { described_class.new(project, user).execute }
it 'returns success' do
expect(execute.success?).to eq(true)
end
context 'when there are no changes to be made' do
it 'does not change authorizations' do
expect { execute }.not_to(change { ProjectAuthorization.count })
end
end
context 'when there are changes to be made' do
context 'when addition is required' do
before do
project.add_developer(user)
project.add_developer(another_user)
project.project_authorizations.where(user: [user, another_user]).delete_all
end
it 'adds a new authorization record for the specific user' do
expect { execute }.to(
change { project.project_authorizations.where(user: user).count }
.from(0).to(1)
)
end
it 'does not add a new authorization record for the other user' do
expect { execute }.not_to(
change { project.project_authorizations.where(user: another_user).count }
)
end
it 'adds a new authorization record with the correct access level for the specific user' do
execute
project_authorization = project.project_authorizations.where(
user: user,
access_level: Gitlab::Access::DEVELOPER
)
expect(project_authorization).to exist
end
end
context 'when removal is required' do
before do
create(:project_authorization, user: user, project: project)
create(:project_authorization, user: another_user, project: project)
end
it 'removes the authorization record for the specific user' do
expect { execute }.to(
change { project.project_authorizations.where(user: user).count }
.from(1).to(0)
)
end
it 'does not remove the authorization record for the other user' do
expect { execute }.not_to(
change { project.project_authorizations.where(user: another_user).count }
)
end
end
context 'when an update in access level is required' do
before do
project.add_developer(user)
project.add_developer(another_user)
project.project_authorizations.where(user: [user, another_user]).delete_all
create(:project_authorization, project: project, user: user, access_level: Gitlab::Access::GUEST)
create(:project_authorization, project: project, user: another_user, access_level: Gitlab::Access::GUEST)
end
it 'updates the authorization of the specific user to the correct access level' do
expect { execute }.to(
change { project.project_authorizations.find_by(user: user).access_level }
.from(Gitlab::Access::GUEST).to(Gitlab::Access::DEVELOPER)
)
end
it 'does not update the authorization of the other user to the correct access level' do
expect { execute }.not_to(
change { project.project_authorizations.find_by(user: another_user).access_level }
.from(Gitlab::Access::GUEST)
)
end
end
end
end
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