diff options
Diffstat (limited to 'app/services/members/update_service.rb')
-rw-r--r-- | app/services/members/update_service.rb | 89 |
1 files changed, 68 insertions, 21 deletions
diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 8ef3e307519..0e6b02f7a80 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -2,37 +2,84 @@ module Members class UpdateService < Members::BaseService - # returns the updated member - def execute(member, permission: :update) - raise Gitlab::Access::AccessDeniedError unless can?(current_user, action_member_permission(permission, member), member) - raise Gitlab::Access::AccessDeniedError if prevent_upgrade_to_owner?(member) || prevent_downgrade_from_owner?(member) + # @param members [Member, Array<Member>] + # returns the updated member(s) + def execute(members, permission: :update) + members = Array.wrap(members) - return success(member: member) if update_results_in_no_change?(member) - - old_access_level = member.human_access - old_expiry = member.expires_at - - if member.update(params) - after_execute(action: permission, old_access_level: old_access_level, old_expiry: old_expiry, member: member) - - # Deletes only confidential issues todos for guests - enqueue_delete_todos(member) if downgrading_to_guest? + old_access_level_expiry_map = members.to_h do |member| + [member.id, { human_access: member.human_access, expires_at: member.expires_at }] end - if member.errors.any? - error(member.errors.full_messages.to_sentence, pass_back: { member: member }) + if Feature.enabled?(:bulk_update_membership_roles, current_user) + multiple_members_update(members, permission, old_access_level_expiry_map) else - success(member: member) + single_member_update(members.first, permission, old_access_level_expiry_map) end + + prepare_response(members) end private - def update_results_in_no_change?(member) - return false if params[:expires_at]&.to_date != member.expires_at - return false if params[:access_level] != member.access_level + def single_member_update(member, permission, old_access_level_expiry_map) + raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission) + + member.attributes = params + return success(member: member) unless member.changed? + + post_update(member, permission, old_access_level_expiry_map) if member.save + end + + def multiple_members_update(members, permission, old_access_level_expiry_map) + begin + updated_members = + Member.transaction do + # Using `next` with `filter_map` avoids the `post_update` call for the member that resulted in no change + members.filter_map do |member| + raise Gitlab::Access::AccessDeniedError unless has_update_permissions?(member, permission) + + member.attributes = params + next unless member.changed? + + member.save! + member + end + end + rescue ActiveRecord::RecordInvalid + return + end + + updated_members.each { |member| post_update(member, permission, old_access_level_expiry_map) } + end + + def post_update(member, permission, old_access_level_expiry_map) + old_access_level = old_access_level_expiry_map[member.id][:human_access] + old_expiry = old_access_level_expiry_map[member.id][:expires_at] + + after_execute(action: permission, old_access_level: old_access_level, old_expiry: old_expiry, member: member) + enqueue_delete_todos(member) if downgrading_to_guest? # Deletes only confidential issues todos for guests + end + + def prepare_response(members) + errored_member = members.detect { |member| member.errors.any? } + if errored_member.present? + return error(errored_member.errors.full_messages.to_sentence, pass_back: { member: errored_member }) + end + + # TODO: Remove the :member key when removing the bulk_update_membership_roles FF and update where it's used. + # https://gitlab.com/gitlab-org/gitlab/-/issues/373257 + if members.one? + success(member: members.first) + else + success(members: members) + end + end - true + def has_update_permissions?(member, permission) + can?(current_user, action_member_permission(permission, member), member) && + !prevent_upgrade_to_owner?(member) && + !prevent_downgrade_from_owner?(member) end def downgrading_to_guest? |