diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb index ebca5e903133b6e285d62afd1ab3e8892097bb3e..a7e57b609366327f83379dd6ed3b9398bbf2e2b9 100644 --- a/app/models/bulk_imports/entity.rb +++ b/app/models/bulk_imports/entity.rb @@ -57,6 +57,10 @@ class BulkImports::Entity < ApplicationRecord alias_attribute :destination_slug, :destination_name + delegate :default_project_visibility, + :default_group_visibility, + to: :'Gitlab::CurrentSettings.current_application_settings' + state_machine :status, initial: :created do state :created, value: 0 state :started, value: 1 @@ -156,6 +160,12 @@ def full_path project? ? project&.full_path : group&.full_path end + def default_visibility_level + return default_group_visibility if group? + + default_project_visibility + end + private def validate_parent_is_a_group diff --git a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb index 83b442458dcf40f94427937bbd4c12ade4e567fd..19993629ff5a9702414f2f3c6371df6a07e8e569 100644 --- a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb +++ b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb @@ -4,6 +4,8 @@ module BulkImports module Groups module Transformers class GroupAttributesTransformer + include BulkImports::VisibilityLevel + # rubocop: disable Style/IfUnlessModifier def transform(context, data) import_entity = context.entity @@ -49,10 +51,10 @@ def transform(context, data) end if data.has_key?('visibility') - params['visibility_level'] = Gitlab::VisibilityLevel.string_options[data['visibility']] + params['visibility_level'] = visibility_level(import_entity, namespace, data['visibility']) end - params + params.with_indifferent_access end # rubocop: enable Style/IfUnlessModifier diff --git a/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb b/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb index 205c3185f722f97dcfb12d208b0d47fdd7b45e88..c5ed9d42e4443bd0de42efa6c574565eaf94ae17 100644 --- a/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb +++ b/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb @@ -4,21 +4,23 @@ module BulkImports module Projects module Transformers class ProjectAttributesTransformer + include BulkImports::VisibilityLevel + PROJECT_IMPORT_TYPE = 'gitlab_project_migration' def transform(context, data) project = {} entity = context.entity - visibility = data.delete('visibility') + namespace = Namespace.find_by_full_path(entity.destination_namespace) project[:name] = entity.destination_slug project[:path] = entity.destination_slug.parameterize project[:created_at] = data['created_at'] project[:import_type] = PROJECT_IMPORT_TYPE - project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present? - project[:namespace_id] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present? + project[:visibility_level] = visibility_level(entity, namespace, data['visibility']) + project[:namespace_id] = namespace.id if namespace - project + project.with_indifferent_access end end end diff --git a/lib/bulk_imports/visibility_level.rb b/lib/bulk_imports/visibility_level.rb new file mode 100644 index 0000000000000000000000000000000000000000..6b0af15dd7be595aac3ae038fa3f72e9675e0b71 --- /dev/null +++ b/lib/bulk_imports/visibility_level.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module BulkImports + module VisibilityLevel + private + + def visibility_level(entity, namespace, visibility_string) + requested = requested_visibility_level(entity, visibility_string) + max_allowed = max_allowed_visibility_level(namespace) + + return requested if max_allowed >= requested + + max_allowed + end + + def requested_visibility_level(entity, visibility_string) + Gitlab::VisibilityLevel.string_options[visibility_string] || entity.default_visibility_level + end + + def max_allowed_visibility_level(namespace) + return Gitlab::VisibilityLevel.allowed_levels.max if namespace.blank? + + Gitlab::VisibilityLevel.closest_allowed_level(namespace.visibility_level) + end + end +end diff --git a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb index 32d8dc8e207d4d2743700b543d18bea97e5cb297..138a92a7e6b99cb7806ac8e82d61368b3c0f7446 100644 --- a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' -RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do +RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer, feature_category: :importers do describe '#transform' do - let_it_be(:parent) { create(:group) } - let(:bulk_import) { build_stubbed(:bulk_import) } + let(:destination_group) { create(:group) } + let(:destination_namespace) { destination_group.full_path } let(:entity) do build_stubbed( @@ -14,7 +14,7 @@ bulk_import: bulk_import, source_full_path: 'source/full/path', destination_slug: 'destination-slug-path', - destination_namespace: parent.full_path + destination_namespace: destination_namespace ) end @@ -40,15 +40,13 @@ } end - subject { described_class.new } + subject(:transformed_data) { described_class.new.transform(context, data) } it 'returns original data with some keys transformed' do - transformed_data = subject.transform(context, data) - expect(transformed_data).to eq({ 'name' => 'Source Group Name', 'description' => 'Source Group Description', - 'parent_id' => parent.id, + 'parent_id' => destination_group.id, 'path' => entity.destination_slug, 'visibility_level' => Gitlab::VisibilityLevel.string_options[data['visibility']], 'project_creation_level' => Gitlab::Access.project_creation_string_options[data['project_creation_level']], @@ -64,21 +62,21 @@ end context 'when some fields are not present' do - it 'does not include those fields' do - data = { + let(:data) do + { 'name' => 'Source Group Name', 'description' => 'Source Group Description', 'path' => 'source-group-path', 'full_path' => 'source/full/path' } + end - transformed_data = subject.transform(context, data) - + it 'does not include those fields' do expect(transformed_data).to eq({ 'name' => 'Source Group Name', 'path' => 'destination-slug-path', 'description' => 'Source Group Description', - 'parent_id' => parent.id, + 'parent_id' => destination_group.id, 'share_with_group_lock' => nil, 'emails_disabled' => nil, 'lfs_enabled' => nil, @@ -89,9 +87,7 @@ describe 'parent group transformation' do it 'sets parent id' do - transformed_data = subject.transform(context, data) - - expect(transformed_data['parent_id']).to eq(parent.id) + expect(transformed_data['parent_id']).to eq(destination_group.id) end context 'when destination namespace is empty' do @@ -100,8 +96,6 @@ end it 'does not set parent id' do - transformed_data = subject.transform(context, data) - expect(transformed_data).not_to have_key('parent_id') end end @@ -114,8 +108,6 @@ end it 'does not transform name' do - transformed_data = subject.transform(context, data) - expect(transformed_data['name']).to eq('Source Group Name') end end @@ -123,35 +115,39 @@ context 'when destination namespace is present' do context 'when destination namespace does not have a group with same name' do it 'does not transform name' do - transformed_data = subject.transform(context, data) - expect(transformed_data['name']).to eq('Source Group Name') end end context 'when destination namespace already have a group with the same name' do before do - create(:group, parent: parent, name: 'Source Group Name', path: 'group_1') - create(:group, parent: parent, name: 'Source Group Name(1)', path: 'group_2') - create(:group, parent: parent, name: 'Source Group Name(2)', path: 'group_3') - create(:group, parent: parent, name: 'Source Group Name(1)(1)', path: 'group_4') + create(:group, parent: destination_group, name: 'Source Group Name', path: 'group_1') + create(:group, parent: destination_group, name: 'Source Group Name(1)', path: 'group_2') + create(:group, parent: destination_group, name: 'Source Group Name(2)', path: 'group_3') + create(:group, parent: destination_group, name: 'Source Group Name(1)(1)', path: 'group_4') end it 'makes the name unique by appeding a counter', :aggregate_failures do - transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name')) + transformed_data = described_class.new.transform(context, data.merge('name' => 'Source Group Name')) expect(transformed_data['name']).to eq('Source Group Name(3)') - transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(2)')) + transformed_data = described_class.new.transform(context, data.merge('name' => 'Source Group Name(2)')) expect(transformed_data['name']).to eq('Source Group Name(2)(1)') - transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)')) + transformed_data = described_class.new.transform(context, data.merge('name' => 'Source Group Name(1)')) expect(transformed_data['name']).to eq('Source Group Name(1)(2)') - transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)(1)')) + transformed_data = described_class.new.transform(context, data.merge('name' => 'Source Group Name(1)(1)')) expect(transformed_data['name']).to eq('Source Group Name(1)(1)(1)') end end end end + + describe 'visibility level' do + subject(:transformed_data) { described_class.new.transform(context, data) } + + include_examples 'visibility level settings' + end end end diff --git a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb index c1c4d0bf0db1dfb5c21a9897e7ac5836b4f54eac..a62d82da35ddefa3ddd4fca6b25444f74845f42e 100644 --- a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb @@ -2,27 +2,27 @@ require 'spec_helper' -RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer do +RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer, feature_category: :importers do describe '#transform' do let_it_be(:user) { create(:user) } - let_it_be(:destination_group) { create(:group) } let_it_be(:project) { create(:project, name: 'My Source Project') } let_it_be(:bulk_import) { create(:bulk_import, user: user) } - let_it_be(:entity) do + let(:entity) do create( :bulk_import_entity, source_type: :project_entity, bulk_import: bulk_import, source_full_path: 'source/full/path', destination_slug: 'Destination Project Name', - destination_namespace: destination_group.full_path + destination_namespace: destination_namespace ) end - let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) } - let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) } - + let(:destination_group) { create(:group) } + let(:destination_namespace) { destination_group.full_path } + let(:tracker) { create(:bulk_import_tracker, entity: entity) } + let(:context) { BulkImports::Pipeline::Context.new(tracker) } let(:data) do { 'visibility' => 'private', @@ -40,13 +40,6 @@ expect(transformed_data[:path]).to eq(entity.destination_slug.parameterize) end - it 'transforms visibility level' do - visibility = data['visibility'] - - expect(transformed_data).not_to have_key(:visibility) - expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel.string_options[visibility]) - end - it 'adds import type' do expect(transformed_data[:import_type]).to eq(described_class::PROJECT_IMPORT_TYPE) end @@ -89,8 +82,12 @@ transformed_data = described_class.new.transform(context, data) expect(transformed_data.keys) - .to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level) + .to contain_exactly('created_at', 'import_type', 'name', 'namespace_id', 'path', 'visibility_level') end end + + describe 'visibility level' do + include_examples 'visibility level settings' + end end end diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb index b1c65c6b9ee375a9ecb4ead41303def1b08a4c80..433b38a42c170be73bbd39a6e7b64d674d79325d 100644 --- a/spec/models/bulk_imports/entity_spec.rb +++ b/spec/models/bulk_imports/entity_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe BulkImports::Entity, type: :model do +RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers do describe 'associations' do it { is_expected.to belong_to(:bulk_import).required } it { is_expected.to belong_to(:parent) } @@ -345,4 +345,24 @@ expect(entity.full_path).to eq(nil) end end + + describe '#default_visibility_level' do + context 'when entity is a group' do + it 'returns default group visibility' do + stub_application_setting(default_group_visibility: Gitlab::VisibilityLevel::PUBLIC) + entity = build(:bulk_import_entity, :group_entity, group: build(:group)) + + expect(entity.default_visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + + context 'when entity is a project' do + it 'returns default project visibility' do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + entity = build(:bulk_import_entity, :project_entity, group: build(:group)) + + expect(entity.default_visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + end end diff --git a/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb new file mode 100644 index 0000000000000000000000000000000000000000..40e9726f89ca599b8726ba841adf27a0faa0371a --- /dev/null +++ b/spec/support/shared_examples/bulk_imports/visibility_level_examples.rb @@ -0,0 +1,124 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'visibility level settings' do + context 'when public' do + let(:data) { { 'visibility' => 'public' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to public' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to public' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + end + + context 'when internal' do + let(:data) { { 'visibility' => 'internal' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to internal' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::INTERNAL) + end + + context 'when visibility level is restricted' do + it 'sets visibility level to private' do + stub_application_setting( + restricted_visibility_levels: [ + Gitlab::VisibilityLevel::INTERNAL, + Gitlab::VisibilityLevel::PUBLIC + ] + ) + + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + end + end + + context 'when private' do + let(:data) { { 'visibility' => 'private' } } + + context 'when destination is a public group' do + let(:destination_group) { create(:group, :public) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is a internal group' do + let(:destination_group) { create(:group, :internal) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is a private group' do + let(:destination_group) { create(:group, :private) } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + + context 'when destination is blank' do + let(:destination_namespace) { '' } + + it 'sets visibility level to private' do + expect(transformed_data[:visibility_level]).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + end + end +end