Commit cf2ba49f authored by Illya Klymov's avatar Illya Klymov

Merge branch 'group-migration' into 'master'

Bulk group migration e2e test

See merge request gitlab-org/gitlab!60482
parents 7328c3f5 3a5cd561
......@@ -242,7 +242,7 @@ export default {
:description="s__('Check your source instance permissions.')"
/>
<template v-else>
<table class="gl-w-full">
<table class="gl-w-full" data-qa-selector="import_table">
<thead class="gl-border-solid gl-border-gray-200 gl-border-0 gl-border-b-1">
<th class="gl-py-4 import-jobs-from-col">{{ s__('BulkImport|From source group') }}</th>
<th class="gl-py-4 import-jobs-to-col">{{ s__('BulkImport|To new group') }}</th>
......
......@@ -119,7 +119,11 @@ export default {
</script>
<template>
<tr class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid">
<tr
class="gl-border-gray-200 gl-border-0 gl-border-b-1 gl-border-solid"
data-qa-selector="import_item"
:data-qa-source-group="group.full_path"
>
<td class="gl-p-4">
<gl-link
:href="group.web_url"
......@@ -150,6 +154,7 @@ export default {
:disabled="isAlreadyImported"
toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
class="import-entities-namespace-dropdown gl-h-7 gl-flex-fill-1"
data-qa-selector="target_namespace_selector_dropdown"
>
<gl-dropdown-item @click="$emit('update-target-namespace', '')">{{
s__('BulkImport|No parent')
......@@ -162,6 +167,8 @@ export default {
<gl-dropdown-item
v-for="ns in availableNamespaces"
:key="ns.full_path"
data-qa-selector="target_group_dropdown_item"
:data-qa-group-name="ns.full_path"
@click="$emit('update-target-namespace', ns.full_path)"
>
{{ ns.full_path }}
......@@ -192,7 +199,7 @@ export default {
</div>
</div>
</td>
<td class="gl-p-4 gl-white-space-nowrap">
<td class="gl-p-4 gl-white-space-nowrap" data-qa-selector="import_status_indicator">
<import-status :status="group.progress.status" class="gl-mt-2" />
</td>
<td class="gl-p-4">
......@@ -201,6 +208,7 @@ export default {
:disabled="isInvalid"
variant="confirm"
category="secondary"
data-qa-selector="import_group_button"
@click="$emit('import-group')"
>{{ __('Import') }}</gl-button
>
......
......@@ -18,7 +18,8 @@
= f.text_field :bulk_import_gitlab_url, placeholder: 'https://gitlab.example.com', class: 'gl-form-input col-xs-12 col-sm-8',
required: true,
title: s_('GroupsNew|Please fill in GitLab source URL.'),
id: 'import_gitlab_url'
id: 'import_gitlab_url',
data: { qa_selector: 'import_gitlab_url' }
.form-group.gl-display-flex.gl-flex-direction-column
= f.label :bulk_import_gitlab_access_token, s_('GroupsNew|Personal access token'), for: 'import_gitlab_token'
.gl-font-weight-normal
......@@ -27,6 +28,7 @@
= f.text_field :bulk_import_gitlab_access_token, placeholder: s_('GroupsNew|e.g. h8d3f016698e...'), class: 'gl-form-input gl-mt-3 col-xs-12 col-sm-8',
required: true,
title: s_('GroupsNew|Please fill in your personal access token.'),
id: 'import_gitlab_token'
id: 'import_gitlab_token',
data: { qa_selector: 'import_gitlab_token' }
.gl-border-gray-100.gl-border-solid.gl-border-1.gl-bg-gray-10.gl-p-5
= f.submit s_('GroupsNew|Connect instance'), class: 'btn gl-button btn-confirm'
= f.submit s_('GroupsNew|Connect instance'), class: 'btn gl-button btn-confirm', data: { qa_selector: 'connect_instance_button' }
......@@ -13,10 +13,10 @@
= link_to explore_groups_path, data: { track_label: "groups_dropdown_explore_groups", track_event: "click_link" } do
= _('Explore groups')
= nav_link(path: 'groups/new#create-group-pane', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do
= link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link" } do
= link_to new_group_path(anchor: 'create-group-pane'), data: { track_label: "groups_dropdown_create_group", track_event: "click_link", qa_selector: 'create_group_link' } do
= _('Create group')
= nav_link(path: 'groups/new#import-group-pane') do
= link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link" } do
= link_to new_group_path(anchor: 'import-group-pane'), data: { track_label: "groups_dropdown_import_group", track_event: "click_link", qa_selector: 'import_group_link' } do
= _('Import group')
.frequent-items-dropdown-content
#js-groups-dropdown{ data: { user_name: current_user.username, group: group_meta } }
......@@ -225,6 +225,7 @@ module QA
autoload :Show, 'qa/page/group/show'
autoload :Menu, 'qa/page/group/menu'
autoload :Members, 'qa/page/group/members'
autoload :BulkImport, 'qa/page/group/bulk_import'
module Milestone
autoload :Index, 'qa/page/group/milestone/index'
......
# frozen_string_literal: true
module QA
module Page
module Group
class BulkImport < Page::Base
view "app/assets/javascripts/import_entities/import_groups/components/import_table.vue" do
element :import_table
end
view "app/assets/javascripts/import_entities/import_groups/components/import_table_row.vue" do
element :import_item
element :target_namespace_selector_dropdown
element :target_group_dropdown_item
element :import_status_indicator
element :import_group_button
end
# Import source group in to target group
#
# @param [String] source_group_name
# @param [String] target_group_name
# @return [void]
def import_group(source_group_name, target_group_name)
finished_loading?
within_element(:import_item, source_group: source_group_name) do
click_element(:target_namespace_selector_dropdown)
click_element(:target_group_dropdown_item, group_name: target_group_name)
click_element(:import_group_button)
end
end
# Check if import page has a successfully imported group
#
# @param [String] source_group_name
# @param [Integer] wait
# @return [Boolean]
def has_imported_group?(source_group_name, wait: QA::Support::WaitForRequests::DEFAULT_MAX_WAIT_TIME)
within_element(:import_item, source_group: source_group_name) do
has_element?(:import_status_indicator, text: "Complete", wait: wait)
end
end
end
end
end
end
......@@ -15,6 +15,12 @@ module QA
element :create_group_button, "submit _('Create group')" # rubocop:disable QA/ElementWithPattern
end
view 'app/views/groups/_import_group_from_another_instance_panel.html.haml' do
element :import_gitlab_url
element :import_gitlab_token
element :connect_instance_button
end
def set_path(path)
fill_element(:group_path_field, path)
fill_element(:group_name_field, path)
......@@ -23,6 +29,26 @@ module QA
def create
click_button 'Create group'
end
def set_gitlab_url(url)
fill_element(:import_gitlab_url, url)
end
def set_gitlab_token(token)
fill_element(:import_gitlab_token, token)
end
# Connect gitlab instance
#
# @param [String] gitlab_url
# @param [String] gitlab_token
# @return [void]
def connect_gitlab_instance(gitlab_url, gitlab_token)
set_gitlab_url(gitlab_url)
set_gitlab_token(gitlab_token)
click_element(:connect_instance_button)
end
end
end
end
......
......@@ -35,17 +35,24 @@ module QA
element :your_projects_link
end
view 'app/views/layouts/nav/groups_dropdown/_show.html.haml' do
element :create_group_link
element :import_group_link
end
view 'app/views/layouts/_search.html.haml' do
element :search_term_field
end
def go_to_groups
within_top_menu do
click_element :groups_dropdown
within_groups_menu do
click_element :your_groups_link
end
end
page.within('.qa-groups-dropdown-sidebar') do
click_element :your_groups_link
def go_to_import_group
within_groups_menu do
click_element :import_group_link
end
end
......@@ -173,6 +180,14 @@ module QA
end
end
def within_groups_menu(&block)
within_top_menu do
click_element :groups_dropdown
end
page.within('.qa-groups-dropdown-sidebar', &block)
end
def click_admin_area
within_top_menu { click_element :admin_area_link }
end
......
......@@ -9,7 +9,7 @@ module QA
attr_reader :unique_id
attr_writer :username, :password
attr_accessor :admin, :provider, :extern_uid, :expect_fabrication_success
attr_accessor :admin, :provider, :extern_uid, :expect_fabrication_success, :hard_delete_on_api_removal
attribute :id
attribute :name
......@@ -19,6 +19,7 @@ module QA
def initialize
@admin = false
@hard_delete_on_api_removal = false
@unique_id = SecureRandom.hex(8)
@expect_fabrication_success = true
end
......@@ -77,9 +78,7 @@ module QA
def fabricate!
# Don't try to log-out if we're not logged-in
if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
Page::Main::Menu.perform { |main| main.sign_out }
end
Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
if credentials_given?
Page::Main::Login.perform do |login|
......@@ -103,9 +102,9 @@ module QA
end
def api_delete_path
"/users/#{id}"
"/users/#{id}?hard_delete=#{hard_delete_on_api_removal}"
rescue NoValueError
"/users/#{fetch_id(username)}"
"/users/#{fetch_id(username)}?hard_delete=#{hard_delete_on_api_removal}"
end
def api_get_path
......@@ -135,12 +134,12 @@ module QA
def self.fabricate_or_use(username = nil, password = nil)
if Runtime::Env.signup_disabled?
self.fabricate_via_api! do |user|
fabricate_via_api! do |user|
user.username = username
user.password = password
end
else
self.fabricate! do |user|
fabricate! do |user|
user.username = username if username
user.password = password if password
end
......@@ -149,10 +148,9 @@ module QA
def block!
response = post(Runtime::API::Request.new(api_client, api_block_path).url, nil)
return if response.code == HTTP_STATUS_CREATED
unless response.code == HTTP_STATUS_CREATED
raise ResourceUpdateFailedError, "Failed to block user. Request returned (#{response.code}): `#{response}`."
end
raise ResourceUpdateFailedError, "Failed to block user. Request returned (#{response.code}): `#{response}`."
end
private
......
# frozen_string_literal: true
module QA
RSpec.describe "Manage", :requires_admin do
describe "Group bulk import" do
let!(:api_client) { Runtime::API::Client.as_admin }
let!(:user) do
Resource::User.fabricate_via_api! do |usr|
usr.api_client = api_client
usr.hard_delete_on_api_removal = true
end
end
let!(:personal_access_token) { Runtime::API::Client.new(user: user).personal_access_token }
let!(:sandbox) do
Resource::Sandbox.fabricate_via_api! do |group|
group.api_client = api_client
end
end
let!(:source_group) do
Resource::Sandbox.fabricate_via_api! do |group|
group.api_client = api_client
group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
end
end
let(:imported_group) do
Resource::Group.new.tap do |group|
group.api_client = api_client
group.path = source_group.path
end.reload!
rescue Resource::ApiFabricator::ResourceNotFoundError
nil
end
# Return subset of fields for comparing groups
#
# @param [Resource::Group, nil] group
# @return [Hash]
def comparable_group(group)
group&.api_resource&.except(
:id,
:web_url,
:visibility,
:full_name,
:full_path,
:created_at,
:parent_id,
:runners_token
)
end
before(:all) do
Runtime::Feature.enable(:bulk_import)
end
before do
sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
source_group.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
Flow::Login.sign_in(as: user)
Page::Main::Menu.new.go_to_import_group
Page::Group::New.new.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token)
end
it(
"performs bulk group import from another gitlab instance",
testcase: "https://gitlab.com/gitlab-org/quality/testcases/-/issues/1785",
# https://gitlab.com/gitlab-org/gitlab/-/issues/330344
exclude: { job: ["ce:relative_url", "ee:relative_url"] }
) do
Page::Group::BulkImport.perform do |import_page|
import_page.import_group(source_group.path, sandbox.path)
aggregate_failures do
expect(import_page).to have_imported_group(source_group.path, wait: 120)
expect(comparable_group(imported_group)).to eq(comparable_group(source_group))
end
end
end
after do
user.remove_via_api!
source_group.remove_via_api!
end
after(:all) do
Runtime::Feature.disable(:bulk_import)
end
end
end
end
......@@ -3,6 +3,7 @@
require_relative '../qa'
require 'rspec/retry'
require 'rspec-parameterized'
require 'active_support/core_ext/hash'
if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
require 'knapsack'
......
# frozen_string_literal: true
require 'active_support/core_ext/hash'
RSpec.describe QA::Specs::Runner do
shared_examples 'excludes orchestrated, transient, and geo' do
it 'excludes the orchestrated, transient, and geo tags, and includes default args' do
......
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