Commit 44df1be3 authored by George Koltsov's avatar George Koltsov

Migrate group badges when using Bulk Import

- Add group badges when using Bulk Import to provide
  close feature parity with Group Import/Export
parent 8cea5e29
---
title: Migrate group badges when using Bulk Import
merge_request: 56357
author:
type: added
......@@ -66,6 +66,10 @@ The following resources are migrated to the target instance:
- due date
- created at
- updated at
- Badges ([Introduced in 13.11](https://gitlab.com/gitlab-org/gitlab/-/issues/292431))
- name
- link URL
- image URL
Any other items are **not** migrated.
......
......@@ -23,7 +23,7 @@ module BulkImports
resource_url(resource),
headers: request_headers,
follow_redirects: false,
query: query.merge(request_query)
query: query.reverse_merge(request_query)
)
end
end
......
# frozen_string_literal: true
module BulkImports
module Common
module Extractors
class RestExtractor
def initialize(options = {})
@query = options[:query]
end
def extract(context)
client = http_client(context.configuration)
params = query.to_h(context)
response = client.get(params[:resource], params[:query])
BulkImports::Pipeline::ExtractedData.new(
data: response.parsed_response,
page_info: page_info(response.headers)
)
end
private
attr_reader :query
def http_client(configuration)
@http_client ||= BulkImports::Clients::Http.new(
uri: configuration.url,
token: configuration.access_token,
per_page: 100
)
end
def page_info(headers)
next_page = headers['x-next-page']
{
'has_next_page' => next_page.present?,
'next_page' => next_page
}
end
end
end
end
end
# frozen_string_literal: true
module BulkImports
module Groups
module Pipelines
class BadgesPipeline
include Pipeline
extractor BulkImports::Common::Extractors::RestExtractor,
query: BulkImports::Groups::Rest::GetBadgesQuery
transformer Common::Transformers::ProhibitedAttributesTransformer
def transform(_, data)
return if data.blank?
{
name: data['name'],
link_url: data['link_url'],
image_url: data['image_url']
}
end
def load(context, data)
return if data.blank?
context.group.badges.create!(data)
end
end
end
end
end
# frozen_string_literal: true
module BulkImports
module Groups
module Rest
module GetBadgesQuery
extend self
def to_h(context)
encoded_full_path = ERB::Util.url_encode(context.entity.source_full_path)
{
resource: ['groups', encoded_full_path, 'badges'].join('/'),
query: {
page: context.tracker.next_page
}
}
end
end
end
end
end
......@@ -34,7 +34,8 @@ module BulkImports
BulkImports::Groups::Pipelines::SubgroupEntitiesPipeline,
BulkImports::Groups::Pipelines::MembersPipeline,
BulkImports::Groups::Pipelines::LabelsPipeline,
BulkImports::Groups::Pipelines::MilestonesPipeline
BulkImports::Groups::Pipelines::MilestonesPipeline,
BulkImports::Groups::Pipelines::BadgesPipeline
]
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Common::Extractors::RestExtractor do
let(:http_client) { instance_double(BulkImports::Clients::Http) }
let(:options) { { query: double(to_h: { resource: nil, query: nil }) } }
let(:response) { double(parsed_response: { 'data' => { 'foo' => 'bar' } }, headers: { 'x-next-page' => '2' }) }
subject { described_class.new(options) }
describe '#extract' do
before do
allow(subject).to receive(:http_client).and_return(http_client)
allow(http_client).to receive(:get).and_return(response)
end
it 'returns instance of ExtractedData' do
entity = create(:bulk_import_entity)
tracker = create(:bulk_import_tracker, entity: entity)
context = BulkImports::Pipeline::Context.new(tracker)
extracted_data = subject.extract(context)
expect(extracted_data).to be_instance_of(BulkImports::Pipeline::ExtractedData)
expect(extracted_data.data).to contain_exactly(response.parsed_response)
expect(extracted_data.next_page).to eq(response.headers['x-next-page'])
expect(extracted_data.has_next_page?).to eq(true)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Groups::Pipelines::BadgesPipeline do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:entity) do
create(
:bulk_import_entity,
source_full_path: 'source/full/path',
destination_name: 'My Destination Group',
destination_namespace: group.full_path,
group: group
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
subject { described_class.new(context) }
describe '#run' do
it 'imports a group badge' do
first_page = extracted_data(has_next_page: true)
last_page = extracted_data(name: 'badge2')
allow_next_instance_of(BulkImports::Common::Extractors::RestExtractor) do |extractor|
allow(extractor)
.to receive(:extract)
.and_return(first_page, last_page)
end
expect { subject.run }.to change(Badge, :count).by(2)
badge = group.badges.last
expect(badge.name).to eq('badge2')
expect(badge.link_url).to eq(badge_data['link_url'])
expect(badge.image_url).to eq(badge_data['image_url'])
end
describe '#load' do
it 'creates a badge' do
expect { subject.load(context, badge_data) }.to change(Badge, :count).by(1)
badge = group.badges.first
badge_data.each do |key, value|
expect(badge[key]).to eq(value)
end
end
it 'does nothing when the data is blank' do
expect { subject.load(context, nil) }.not_to change(Badge, :count)
end
end
describe '#transform' do
it 'return transformed badge hash' do
badge = subject.transform(context, badge_data)
expect(badge[:name]).to eq('badge')
expect(badge[:link_url]).to eq(badge_data['link_url'])
expect(badge[:image_url]).to eq(badge_data['image_url'])
expect(badge.keys).to contain_exactly(:name, :link_url, :image_url)
end
context 'when data is blank' do
it 'does nothing when the data is blank' do
expect(subject.transform(context, nil)).to be_nil
end
end
end
describe 'pipeline parts' do
it { expect(described_class).to include_module(BulkImports::Pipeline) }
it { expect(described_class).to include_module(BulkImports::Pipeline::Runner) }
it 'has extractors' do
expect(described_class.get_extractor)
.to eq(
klass: BulkImports::Common::Extractors::RestExtractor,
options: {
query: BulkImports::Groups::Rest::GetBadgesQuery
}
)
end
it 'has transformers' do
expect(described_class.transformers)
.to contain_exactly(
{ klass: BulkImports::Common::Transformers::ProhibitedAttributesTransformer, options: nil }
)
end
end
def badge_data(name = 'badge')
{
'name' => name,
'link_url' => 'https://gitlab.example.com',
'image_url' => 'https://gitlab.example.com/image.png'
}
end
def extracted_data(name: 'badge', has_next_page: false)
page_info = {
'has_next_page' => has_next_page,
'next_page' => has_next_page ? '2' : nil
}
BulkImports::Pipeline::ExtractedData.new(data: [badge_data(name)], page_info: page_info)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe BulkImports::Groups::Rest::GetBadgesQuery do
describe '.to_h' do
it 'returns query resource and page info' do
entity = create(:bulk_import_entity)
tracker = create(:bulk_import_tracker, entity: entity)
context = BulkImports::Pipeline::Context.new(tracker)
encoded_full_path = ERB::Util.url_encode(entity.source_full_path)
expected = {
resource: ['groups', encoded_full_path, 'badges'].join('/'),
query: {
page: context.tracker.next_page
}
}
expect(described_class.to_h(context)).to eq(expected)
end
end
end
......@@ -23,6 +23,7 @@ RSpec.describe BulkImports::Importers::GroupImporter do
expect_to_run_pipeline BulkImports::Groups::Pipelines::MembersPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::LabelsPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::MilestonesPipeline, context: context
expect_to_run_pipeline BulkImports::Groups::Pipelines::BadgesPipeline, context: context
if Gitlab.ee?
expect_to_run_pipeline('EE::BulkImports::Groups::Pipelines::EpicsPipeline'.constantize, context: context)
......
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