Commit 95c3aee2 authored by Sean McGivern's avatar Sean McGivern

Merge branch 'xanf-use-new-import-ui-for-all-providers-controllers' into 'master'

Implement JSON status for importers behind feature flag

See merge request gitlab-org/gitlab!33980
parents 56e3823d 4c12d33f
<script>
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import ImportProjectsTable from './import_projects_table.vue';
export default {
components: {
ImportProjectsTable,
GlAlert,
GlSprintf,
GlLink,
},
props: {
providerTitle: {
type: String,
required: true,
},
},
data() {
return {
isWarningDismissed: false,
};
},
computed: {
currentPage() {
return window.location.href;
},
},
};
</script>
<template>
<import-projects-table provider-title="providerTitle">
<template #actions>
<slot name="actions"></slot>
</template>
<template #incompatible-repos-warning>
<gl-alert
v-if="!isWarningDismissed"
variant="warning"
class="gl-my-2"
@dismiss="isWarningDismissed = true"
>
<gl-sprintf
:message="
__(
'One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.',
)
"
>
<template #provider>
{{ providerTitle }}
</template>
</gl-sprintf>
<gl-sprintf
:message="
__(
'Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again.',
)
"
>
<template #link="{ content }">
<gl-link
href="https://www.atlassian.com/git/tutorials/migrating-overview"
target="_blank"
>{{ content }}</gl-link
>
</template>
<template #linkToImportFlow>
<gl-link :href="currentPage">{{ __('import flow') }}</gl-link>
</template>
</gl-sprintf>
</gl-alert>
</template>
</import-projects-table>
</template>
...@@ -24,6 +24,11 @@ export default { ...@@ -24,6 +24,11 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
filterable: {
type: Boolean,
required: false,
default: true,
},
}, },
computed: { computed: {
...@@ -114,7 +119,7 @@ export default { ...@@ -114,7 +119,7 @@ export default {
{{ importAllButtonText }} {{ importAllButtonText }}
</gl-button> </gl-button>
<slot name="actions"></slot> <slot name="actions"></slot>
<form class="gl-ml-auto" novalidate @submit.prevent> <form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
<input <input
:value="filter" :value="filter"
data-qa-selector="githubish_import_filter_field" data-qa-selector="githubish_import_filter_field"
......
...@@ -30,6 +30,7 @@ export function initStoreFromElement(element) { ...@@ -30,6 +30,7 @@ export function initStoreFromElement(element) {
export function initPropsFromElement(element) { export function initPropsFromElement(element) {
return { return {
providerTitle: element.dataset.providerTitle, providerTitle: element.dataset.providerTitle,
filterable: parseBoolean(element.dataset.filterable),
}; };
} }
......
...@@ -2,6 +2,7 @@ import Visibility from 'visibilityjs'; ...@@ -2,6 +2,7 @@ import Visibility from 'visibilityjs';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll'; import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash'; import createFlash from '~/flash';
import { s__, sprintf } from '~/locale'; import { s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -9,6 +10,9 @@ import { jobsPathWithFilter, reposPathWithFilter } from './getters'; ...@@ -9,6 +10,9 @@ import { jobsPathWithFilter, reposPathWithFilter } from './getters';
let eTagPoll; let eTagPoll;
const hasRedirectInError = e => e?.response?.data?.error?.redirect;
const redirectToUrlInError = e => visitUrl(e.response.data.error.redirect);
export const clearJobsEtagPoll = () => { export const clearJobsEtagPoll = () => {
eTagPoll = null; eTagPoll = null;
}; };
...@@ -33,14 +37,18 @@ export const fetchRepos = ({ state, dispatch, commit }) => { ...@@ -33,14 +37,18 @@ export const fetchRepos = ({ state, dispatch, commit }) => {
commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })), commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
) )
.then(() => dispatch('fetchJobs')) .then(() => dispatch('fetchJobs'))
.catch(() => { .catch(e => {
createFlash( if (hasRedirectInError(e)) {
sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), { redirectToUrlInError(e);
provider, } else {
}), createFlash(
); sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), {
provider,
commit(types.RECEIVE_REPOS_ERROR); }),
);
commit(types.RECEIVE_REPOS_ERROR);
}
}); });
}; };
...@@ -87,8 +95,13 @@ export const fetchJobs = ({ state, commit, dispatch }) => { ...@@ -87,8 +95,13 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
method: 'fetchJobs', method: 'fetchJobs',
successCallback: ({ data }) => successCallback: ({ data }) =>
commit(types.RECEIVE_JOBS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })), commit(types.RECEIVE_JOBS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
errorCallback: () => errorCallback: e => {
createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed')), if (hasRedirectInError(e)) {
redirectToUrlInError(e);
} else {
createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed'));
}
},
data: { filter }, data: { filter },
}); });
......
import Vue from 'vue';
import { initStoreFromElement, initPropsFromElement } from '~/import_projects';
import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
document.addEventListener('DOMContentLoaded', () => {
const mountElement = document.getElementById('import-projects-mount-element');
if (!mountElement) return undefined;
const store = initStoreFromElement(mountElement);
const props = initPropsFromElement(mountElement);
return new Vue({
el: mountElement,
store,
render(createElement) {
return createElement(BitbucketStatusTable, { props });
},
});
});
<script>
import { GlButton } from '@gitlab/ui';
import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
export default {
components: {
BitbucketStatusTable,
GlButton,
},
props: {
providerTitle: {
type: String,
required: true,
},
reconfigurePath: {
type: String,
required: true,
},
},
};
</script>
<template>
<bitbucket-status-table :provider-title="providerTitle">
<template #actions>
<gl-button variant="info" class="gl-ml-3" data-method="post" :href="reconfigurePath">{{
__('Reconfigure')
}}</gl-button>
</template>
</bitbucket-status-table>
</template>
import Vue from 'vue';
import { initStoreFromElement, initPropsFromElement } from '~/import_projects';
import BitbucketServerStatusTable from './components/bitbucket_server_status_table.vue';
document.addEventListener('DOMContentLoaded', () => {
const mountElement = document.getElementById('import-projects-mount-element');
if (!mountElement) return undefined;
const store = initStoreFromElement(mountElement);
const props = initPropsFromElement(mountElement);
const { reconfigurePath } = mountElement.dataset;
return new Vue({
el: mountElement,
store,
render(createElement) {
return createElement(BitbucketServerStatusTable, { props: { ...props, reconfigurePath } });
},
});
});
import mountImportProjectsTable from '~/import_projects';
document.addEventListener('DOMContentLoaded', () => {
const mountElement = document.getElementById('import-projects-mount-element');
mountImportProjectsTable(mountElement);
});
import mountImportProjectsTable from '~/import_projects';
document.addEventListener('DOMContentLoaded', () => {
const mountElement = document.getElementById('import-projects-mount-element');
mountImportProjectsTable(mountElement);
});
# frozen_string_literal: true # frozen_string_literal: true
class Import::BaseController < ApplicationController class Import::BaseController < ApplicationController
include ActionView::Helpers::SanitizeHelper
before_action :import_rate_limit, only: [:create] before_action :import_rate_limit, only: [:create]
def status
respond_to do |format|
format.json do
render json: { imported_projects: serialized_imported_projects,
provider_repos: serialized_provider_repos,
incompatible_repos: serialized_incompatible_repos,
namespaces: serialized_namespaces }
end
format.html
end
end
def realtime_changes
Gitlab::PollingInterval.set_header(response, interval: 3_000)
render json: already_added_projects.to_json(only: [:id], methods: [:import_status])
end
protected
def importable_repos
raise NotImplementedError
end
def incompatible_repos
[]
end
def provider_name
raise NotImplementedError
end
def provider_url
raise NotImplementedError
end
private private
def filter_attribute
:name
end
def sanitized_filter_param
@filter ||= sanitize(params[:filter])
end
def filtered(collection)
return collection unless sanitized_filter_param
collection.select { |item| item[filter_attribute].include?(sanitized_filter_param) }
end
def serialized_provider_repos
Import::ProviderRepoSerializer.new(current_user: current_user).represent(importable_repos, provider: provider_name, provider_url: provider_url)
end
def serialized_incompatible_repos
Import::ProviderRepoSerializer.new(current_user: current_user).represent(incompatible_repos, provider: provider_name, provider_url: provider_url)
end
def serialized_imported_projects
ProjectSerializer.new.represent(already_added_projects, serializer: :import, provider_url: provider_url)
end
def already_added_projects
@already_added_projects ||= filtered(find_already_added_projects(provider_name))
end
def serialized_namespaces
NamespaceSerializer.new.represent(namespaces)
end
def namespaces
current_user.manageable_groups_with_routes
end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def find_already_added_projects(import_type) def find_already_added_projects(import_type)
current_user.created_projects.where(import_type: import_type).with_import_state current_user.created_projects.where(import_type: import_type).with_import_state
......
# frozen_string_literal: true # frozen_string_literal: true
class Import::BitbucketController < Import::BaseController class Import::BitbucketController < Import::BaseController
extend ::Gitlab::Utils::Override
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :verify_bitbucket_import_enabled before_action :verify_bitbucket_import_enabled
...@@ -10,7 +12,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -10,7 +12,7 @@ class Import::BitbucketController < Import::BaseController
rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized rescue_from Bitbucket::Error::Unauthorized, with: :bitbucket_unauthorized
def callback def callback
response = client.auth_code.get_token(params[:code], redirect_uri: users_import_bitbucket_callback_url) response = oauth_client.auth_code.get_token(params[:code], redirect_uri: users_import_bitbucket_callback_url)
session[:bitbucket_token] = response.token session[:bitbucket_token] = response.token
session[:bitbucket_expires_at] = response.expires_at session[:bitbucket_expires_at] = response.expires_at
...@@ -22,9 +24,10 @@ class Import::BitbucketController < Import::BaseController ...@@ -22,9 +24,10 @@ class Import::BitbucketController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def status def status
return super if Feature.enabled?(:new_import_ui)
bitbucket_client = Bitbucket::Client.new(credentials) bitbucket_client = Bitbucket::Client.new(credentials)
repos = bitbucket_client.repos(filter: sanitized_filter_param) repos = bitbucket_client.repos(filter: sanitized_filter_param)
@repos, @incompatible_repos = repos.partition { |repo| repo.valid? } @repos, @incompatible_repos = repos.partition { |repo| repo.valid? }
@already_added_projects = find_already_added_projects('bitbucket') @already_added_projects = find_already_added_projects('bitbucket')
...@@ -38,6 +41,10 @@ class Import::BitbucketController < Import::BaseController ...@@ -38,6 +41,10 @@ class Import::BitbucketController < Import::BaseController
render json: find_jobs('bitbucket') render json: find_jobs('bitbucket')
end end
def realtime_changes
super
end
def create def create
bitbucket_client = Bitbucket::Client.new(credentials) bitbucket_client = Bitbucket::Client.new(credentials)
...@@ -59,7 +66,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -59,7 +66,7 @@ class Import::BitbucketController < Import::BaseController
project = Gitlab::BitbucketImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, credentials).execute project = Gitlab::BitbucketImport::ProjectCreator.new(repo, project_name, target_namespace, current_user, credentials).execute
if project.persisted? if project.persisted?
render json: ProjectSerializer.new.represent(project) render json: ProjectSerializer.new.represent(project, serializer: :import)
else else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end end
...@@ -68,16 +75,50 @@ class Import::BitbucketController < Import::BaseController ...@@ -68,16 +75,50 @@ class Import::BitbucketController < Import::BaseController
end end
end end
protected
# rubocop: disable CodeReuse/ActiveRecord
override :importable_repos
def importable_repos
already_added_projects_names = already_added_projects.map(&:import_source)
bitbucket_repos.reject { |repo| already_added_projects_names.include?(repo.full_name) || !repo.valid? }
end
# rubocop: enable CodeReuse/ActiveRecord
override :incompatible_repos
def incompatible_repos
bitbucket_repos.reject { |repo| repo.valid? }
end
override :provider_name
def provider_name
:bitbucket
end
override :provider_url
def provider_url
provider.url
end
private private
def client def oauth_client
@client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options) @oauth_client ||= OAuth2::Client.new(provider.app_id, provider.app_secret, options)
end end
def provider def provider
Gitlab::Auth::OAuth::Provider.config_for('bitbucket') Gitlab::Auth::OAuth::Provider.config_for('bitbucket')
end end
def client
@client ||= Bitbucket::Client.new(credentials)
end
def bitbucket_repos
@bitbucket_repos ||= client.repos(filter: sanitized_filter_param).to_a
end
def options def options
OmniAuth::Strategies::Bitbucket.default_options[:client_options].deep_symbolize_keys OmniAuth::Strategies::Bitbucket.default_options[:client_options].deep_symbolize_keys
end end
...@@ -91,7 +132,7 @@ class Import::BitbucketController < Import::BaseController ...@@ -91,7 +132,7 @@ class Import::BitbucketController < Import::BaseController
end end
def go_to_bitbucket_for_permissions def go_to_bitbucket_for_permissions
redirect_to client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url) redirect_to oauth_client.auth_code.authorize_url(redirect_uri: users_import_bitbucket_callback_url)
end end
def bitbucket_unauthorized def bitbucket_unauthorized
......
# frozen_string_literal: true # frozen_string_literal: true
class Import::BitbucketServerController < Import::BaseController class Import::BitbucketServerController < Import::BaseController
extend ::Gitlab::Utils::Override
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :verify_bitbucket_server_import_enabled before_action :verify_bitbucket_server_import_enabled
before_action :bitbucket_auth, except: [:new, :configure] before_action :bitbucket_auth, except: [:new, :configure]
before_action :validate_import_params, only: [:create] before_action :validate_import_params, only: [:create]
rescue_from BitbucketServer::Connection::ConnectionError, with: :bitbucket_connection_error
# As a basic sanity check to prevent URL injection, restrict project # As a basic sanity check to prevent URL injection, restrict project
# repository input and repository slugs to allowed characters. For Bitbucket: # repository input and repository slugs to allowed characters. For Bitbucket:
# #
...@@ -24,7 +28,7 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -24,7 +28,7 @@ class Import::BitbucketServerController < Import::BaseController
end end
def create def create
repo = bitbucket_client.repo(@project_key, @repo_slug) repo = client.repo(@project_key, @repo_slug)
unless repo unless repo
return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity return render json: { errors: _("Project %{project_repo} could not be found") % { project_repo: "#{@project_key}/#{@repo_slug}" } }, status: :unprocessable_entity
...@@ -38,15 +42,13 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -38,15 +42,13 @@ class Import::BitbucketServerController < Import::BaseController
project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute project = Gitlab::BitbucketServerImport::ProjectCreator.new(@project_key, @repo_slug, repo, project_name, target_namespace, current_user, credentials).execute
if project.persisted? if project.persisted?
render json: ProjectSerializer.new.represent(project) render json: ProjectSerializer.new.represent(project, serializer: :import)
else else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end end
else else
render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity render json: { errors: _('This namespace has already been taken! Please choose another one.') }, status: :unprocessable_entity
end end
rescue BitbucketServer::Connection::ConnectionError => error
render json: { errors: _("Unable to connect to server: %{error}") % { error: error } }, status: :unprocessable_entity
end end
def configure def configure
...@@ -59,7 +61,9 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -59,7 +61,9 @@ class Import::BitbucketServerController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def status def status
@collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param) return super if Feature.enabled?(:new_import_ui)
@collection = client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param)
@repos, @incompatible_repos = @collection.partition { |repo| repo.valid? } @repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
# Use the import URL to filter beyond what BaseService#find_already_added_projects # Use the import URL to filter beyond what BaseService#find_already_added_projects
...@@ -67,10 +71,6 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -67,10 +71,6 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source) already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) } @repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
rescue BitbucketServer::Connection::ConnectionError => error
flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data
redirect_to new_import_bitbucket_server_path
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
...@@ -78,6 +78,38 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -78,6 +78,38 @@ class Import::BitbucketServerController < Import::BaseController
render json: find_jobs('bitbucket_server') render json: find_jobs('bitbucket_server')
end end
def realtime_changes
super
end
protected
# rubocop: disable CodeReuse/ActiveRecord
override :importable_repos
def importable_repos
# Use the import URL to filter beyond what BaseService#find_already_added_projects
already_added_projects = filter_added_projects('bitbucket_server', bitbucket_repos.map(&:browse_url))
already_added_projects_names = already_added_projects.map(&:import_source)
bitbucket_repos.reject { |repo| already_added_projects_names.include?(repo.browse_url) || !repo.valid? }
end
# rubocop: enable CodeReuse/ActiveRecord
override :incompatible_repos
def incompatible_repos
bitbucket_repos.reject { |repo| repo.valid? }
end
override :provider_name
def provider_name
:bitbucket_server
end
override :provider_url
def provider_url
session[bitbucket_server_url_key]
end
private private
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
...@@ -86,8 +118,12 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -86,8 +118,12 @@ class Import::BitbucketServerController < Import::BaseController
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def bitbucket_client def client
@bitbucket_client ||= BitbucketServer::Client.new(credentials) @client ||= BitbucketServer::Client.new(credentials)
end
def bitbucket_repos
@bitbucket_repos ||= client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param).to_a
end end
def validate_import_params def validate_import_params
...@@ -153,4 +189,23 @@ class Import::BitbucketServerController < Import::BaseController ...@@ -153,4 +189,23 @@ class Import::BitbucketServerController < Import::BaseController
def sanitized_filter_param def sanitized_filter_param
sanitize(params[:filter]) sanitize(params[:filter])
end end
def bitbucket_connection_error(error)
flash[:alert] = _("Unable to connect to server: %{error}") % { error: error }
clear_session_data
respond_to do |format|
format.json do
render json: {
error: {
message: _("Unable to connect to server: %{error}") % { error: error },
redirect: new_import_bitbucket_server_path
}
}, status: :unprocessable_entity
end
format.html do
redirect_to new_import_bitbucket_server_path
end
end
end
end end
# frozen_string_literal: true # frozen_string_literal: true
class Import::FogbugzController < Import::BaseController class Import::FogbugzController < Import::BaseController
extend ::Gitlab::Utils::Override
before_action :verify_fogbugz_import_enabled before_action :verify_fogbugz_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
before_action :verify_blocked_uri, only: :callback before_action :verify_blocked_uri, only: :callback
...@@ -48,6 +50,8 @@ class Import::FogbugzController < Import::BaseController ...@@ -48,6 +50,8 @@ class Import::FogbugzController < Import::BaseController
return redirect_to new_import_fogbugz_path return redirect_to new_import_fogbugz_path
end end
return super if Feature.enabled?(:new_import_ui)
@repos = client.repos @repos = client.repos
@already_added_projects = find_already_added_projects('fogbugz') @already_added_projects = find_already_added_projects('fogbugz')
...@@ -57,6 +61,10 @@ class Import::FogbugzController < Import::BaseController ...@@ -57,6 +61,10 @@ class Import::FogbugzController < Import::BaseController
end end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
def realtime_changes
super
end
def jobs def jobs
render json: find_jobs('fogbugz') render json: find_jobs('fogbugz')
end end
...@@ -69,12 +77,35 @@ class Import::FogbugzController < Import::BaseController ...@@ -69,12 +77,35 @@ class Import::FogbugzController < Import::BaseController
project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, current_user.namespace, current_user, umap).execute project = Gitlab::FogbugzImport::ProjectCreator.new(repo, fb_session, current_user.namespace, current_user, umap).execute
if project.persisted? if project.persisted?
render json: ProjectSerializer.new.represent(project) render json: ProjectSerializer.new.represent(project, serializer: :import)
else else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end end
end end
protected
# rubocop: disable CodeReuse/ActiveRecord
override :importable_repos
def importable_repos
repos = client.repos
already_added_projects_names = already_added_projects.map(&:import_source)
repos.reject { |repo| already_added_projects_names.include? repo.name }
end
# rubocop: enable CodeReuse/ActiveRecord
override :provider_name
def provider_name
:fogbugz
end
override :provider_url
def provider_url
session[:fogbugz_uri]
end
private private
def client def client
......
...@@ -76,7 +76,7 @@ class Import::GithubController < Import::BaseController ...@@ -76,7 +76,7 @@ class Import::GithubController < Import::BaseController
def serialized_provider_repos def serialized_provider_repos
repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name } repos = client_repos.reject { |repo| already_added_project_names.include? repo.full_name }
ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url) Import::ProviderRepoSerializer.new(current_user: current_user).represent(repos, provider: provider, provider_url: provider_url)
end end
def serialized_namespaces def serialized_namespaces
......
# frozen_string_literal: true # frozen_string_literal: true
class Import::GitlabController < Import::BaseController class Import::GitlabController < Import::BaseController
extend ::Gitlab::Utils::Override
MAX_PROJECT_PAGES = 15 MAX_PROJECT_PAGES = 15
PER_PAGE_PROJECTS = 100 PER_PAGE_PROJECTS = 100
...@@ -16,6 +18,8 @@ class Import::GitlabController < Import::BaseController ...@@ -16,6 +18,8 @@ class Import::GitlabController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def status def status
return super if Feature.enabled?(:new_import_ui)
@repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS) @repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
@already_added_projects = find_already_added_projects('gitlab') @already_added_projects = find_already_added_projects('gitlab')
...@@ -37,7 +41,7 @@ class Import::GitlabController < Import::BaseController ...@@ -37,7 +41,7 @@ class Import::GitlabController < Import::BaseController
project = Gitlab::GitlabImport::ProjectCreator.new(repo, target_namespace, current_user, access_params).execute project = Gitlab::GitlabImport::ProjectCreator.new(repo, target_namespace, current_user, access_params).execute
if project.persisted? if project.persisted?
render json: ProjectSerializer.new.represent(project) render json: ProjectSerializer.new.represent(project, serializer: :import)
else else
render json: { errors: project_save_error(project) }, status: :unprocessable_entity render json: { errors: project_save_error(project) }, status: :unprocessable_entity
end end
...@@ -46,6 +50,29 @@ class Import::GitlabController < Import::BaseController ...@@ -46,6 +50,29 @@ class Import::GitlabController < Import::BaseController
end end
end end
protected
# rubocop: disable CodeReuse/ActiveRecord
override :importable_repos
def importable_repos
repos = client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
already_added_projects_names = already_added_projects.map(&:import_source)
repos.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
# rubocop: enable CodeReuse/ActiveRecord
override :provider_name
def provider_name
:gitlab
end
override :provider_url
def provider_url
'https://gitlab.com'
end
private private
def client def client
......
# frozen_string_literal: true
class Import::BaseProviderRepoEntity < Grape::Entity
expose :id
expose :full_name
expose :sanitized_name
expose :provider_link
end
# frozen_string_literal: true
class Import::BitbucketProviderRepoEntity < Import::BaseProviderRepoEntity
expose :id, override: true do |repo|
repo.full_name
end
expose :sanitized_name, override: true do |repo|
repo.name.gsub(/[^\s\w.-]/, '')
end
expose :provider_link, override: true do |repo, options|
repo.clone_url
end
end
# frozen_string_literal: true
class Import::BitbucketServerProviderRepoEntity < Import::BitbucketProviderRepoEntity
expose :provider_link, override: true do |repo, options|
repo.browse_url
end
end
# frozen_string_literal: true
class Import::FogbugzProviderRepoEntity < Import::BaseProviderRepoEntity
include ImportHelper
expose :full_name, override: true do |repo|
repo.name
end
expose :sanitized_name, override: true do |repo|
repo.safe_name
end
expose :provider_link, override: true do |repo, options|
provider_project_link_url(options[:provider_url], repo.path)
end
end
# frozen_string_literal: true # frozen_string_literal: true
class ProviderRepoEntity < Grape::Entity class Import::GithubishProviderRepoEntity < Import::BaseProviderRepoEntity
include ImportHelper include ImportHelper
expose :id expose :sanitized_name, override: true do |provider_repo|
expose :full_name
expose :owner_name do |provider_repo, options|
owner_name(provider_repo, options[:provider])
end
expose :sanitized_name do |provider_repo|
sanitize_project_name(provider_repo[:name]) sanitize_project_name(provider_repo[:name])
end end
expose :provider_link do |provider_repo, options| expose :provider_link, override: true do |provider_repo, options|
provider_project_link_url(options[:provider_url], provider_repo[:full_name]) provider_project_link_url(options[:provider_url], provider_repo[:full_name])
end end
......
# frozen_string_literal: true
class Import::GitlabProviderRepoEntity < Import::BaseProviderRepoEntity
expose :id, override: true do |repo|
repo["id"]
end
expose :full_name, override: true do |repo|
repo["path_with_namespace"]
end
expose :sanitized_name, override: true do |repo|
repo["path"]
end
expose :provider_link, override: true do |repo|
repo["web_url"]
end
end
# frozen_string_literal: true
class Import::ProviderRepoSerializer < BaseSerializer
def represent(repo, opts = {})
entity =
case opts[:provider]
when :fogbugz
Import::FogbugzProviderRepoEntity
when :github, :gitea
Import::GithubishProviderRepoEntity
when :bitbucket
Import::BitbucketProviderRepoEntity
when :bitbucket_server
Import::BitbucketServerProviderRepoEntity
when :gitlab
Import::GitlabProviderRepoEntity
else
raise NotImplementedError
end
super(repo, opts, entity)
end
end
# frozen_string_literal: true
class ProviderRepoSerializer < BaseSerializer
entity ProviderRepoEntity
end
- provider = local_assigns.fetch(:provider) - provider = local_assigns.fetch(:provider)
- extra_data = local_assigns.fetch(:extra_data, {})
- filterable = local_assigns.fetch(:filterable, true)
- provider_title = Gitlab::ImportSources.title(provider) - provider_title = Gitlab::ImportSources.title(provider)
#import-projects-mount-element{ data: { provider: provider, provider_title: provider_title, #import-projects-mount-element{ data: { provider: provider, provider_title: provider_title,
...@@ -6,4 +8,5 @@ ...@@ -6,4 +8,5 @@
ci_cd_only: has_ci_cd_only_params?.to_s, ci_cd_only: has_ci_cd_only_params?.to_s,
repos_path: url_for([:status, :import, provider, format: :json]), repos_path: url_for([:status, :import, provider, format: :json]),
jobs_path: url_for([:realtime_changes, :import, provider, format: :json]), jobs_path: url_for([:realtime_changes, :import, provider, format: :json]),
import_path: url_for([:import, provider, format: :json]) } } import_path: url_for([:import, provider, format: :json]),
filterable: filterable.to_s }.merge(extra_data) }
...@@ -5,90 +5,93 @@ ...@@ -5,90 +5,93 @@
%i.fa.fa-bitbucket %i.fa.fa-bitbucket
= _('Import projects from Bitbucket') = _('Import projects from Bitbucket')
- if @repos.any? - if Feature.enabled?(:new_import_ui)
%p.light = render 'import/githubish_status', provider: 'bitbucket'
= _('Select projects you want to import.') - else
%p - if @repos.any?
- if @incompatible_repos.any? %p.light
= button_tag class: 'btn btn-import btn-success js-import-all' do = _('Select projects you want to import.')
= _('Import all compatible projects') %p
= icon('spinner spin', class: 'loading-icon') - if @incompatible_repos.any?
- else = button_tag class: 'btn btn-import btn-success js-import-all' do
= button_tag class: 'btn btn-import btn-success js-import-all' do = _('Import all compatible projects')
= _('Import all projects') = icon('spinner spin', class: 'loading-icon')
= icon('spinner spin', class: 'loading-icon') - else
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
.position-relative.ms-no-clear.d-flex.flex-fill.float-right.append-bottom-10 .position-relative.ms-no-clear.d-flex.flex-fill.float-right.append-bottom-10
= form_tag status_import_bitbucket_path, method: 'get' do = form_tag status_import_bitbucket_path, method: 'get' do
= text_field_tag :filter, @filter, class: 'form-control pr-5', placeholder: _('Filter projects'), size: 40, autofocus: true, 'aria-label': _('Search') = text_field_tag :filter, @filter, class: 'form-control pr-5', placeholder: _('Filter projects'), size: 40, autofocus: true, 'aria-label': _('Search')
.position-absolute.position-top-0.d-flex.align-items-center.text-muted.position-right-0.h-100 .position-absolute.position-top-0.d-flex.align-items-center.text-muted.position-right-0.h-100
.border-left .border-left
%button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' } %button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' }
%i{ class: 'fa fa-search', 'aria-hidden': true } %i{ class: 'fa fa-search', 'aria-hidden': true }
.table-responsive .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col %colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col %colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th= _('From Bitbucket') %th= _('From Bitbucket')
%th= _('To GitLab') %th= _('To GitLab')
%th= _('Status') %th= _('Status')
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td %td
= link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer' = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: '_blank', rel: 'noopener noreferrer'
%td %td
= link_to project.full_path, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- case project.import_status - case project.import_status
- when 'finished' - when 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
= _('done') = _('done')
- when 'started' - when 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
= _('started') = _('started')
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td %td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
%fieldset.row %fieldset.row
.input-group .input-group
.project-path.input-group-prepend .project-path.input-group-prepend
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
- selected = params[:namespace_id] || :current_user - selected = params[:namespace_id] || :current_user
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {} - opts = current_user.can_create_group? ? { extra_group: Group.new(name: repo.owner, path: repo.owner) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 } = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else - else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend %span.input-group-prepend
.input-group-text / .input-group-text /
= text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do = button_tag class: 'btn btn-import js-add-to-import' do
= _('Import') = _('Import')
= icon('spinner spin', class: 'loading-icon') = icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.owner}___#{repo.slug}" } %tr{ id: "repo_#{repo.owner}___#{repo.slug}" }
%td %td
= link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer' = link_to repo.full_name, "https://bitbucket.org/#{repo.full_name}", target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
%td.import-actions-job-status %td.import-actions-job-status
= label_tag _('Incompatible Project'), nil, class: 'label badge-danger' = label_tag _('Incompatible Project'), nil, class: 'label badge-danger'
- if @incompatible_repos.any? - if @incompatible_repos.any?
%p %p
= _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.") = _("One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git.")
- link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview') - link_to_git = link_to(_('Git'), 'https://www.atlassian.com/git/tutorials/migrating-overview')
- link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path) - link_to_import_flow = link_to(_('import flow'), status_import_bitbucket_path)
= _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow } = _("Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again.").html_safe % { link_to_git: link_to_git, link_to_import_flow: link_to_import_flow }
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_path}", import_path: "#{import_bitbucket_path}" } }
...@@ -5,91 +5,94 @@ ...@@ -5,91 +5,94 @@
%i.fa.fa-bitbucket-square %i.fa.fa-bitbucket-square
= _('Import projects from Bitbucket Server') = _('Import projects from Bitbucket Server')
- if @repos.any? - if Feature.enabled?(:new_import_ui)
%p.light = render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }
= _('Select projects you want to import.') - else
.btn-group - if @repos.any?
- if @incompatible_repos.any? %p.light
= button_tag class: 'btn btn-import btn-success js-import-all' do = _('Select projects you want to import.')
= _('Import all compatible projects') .btn-group
= icon('spinner spin', class: 'loading-icon') - if @incompatible_repos.any?
- else = button_tag class: 'btn btn-import btn-success js-import-all' do
= button_tag class: 'btn btn-import btn-success js-import-all' do = _('Import all compatible projects')
= _('Import all projects') = icon('spinner spin', class: 'loading-icon')
= icon('spinner spin', class: 'loading-icon') - else
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
.btn-group .btn-group
= link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post) = link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post)
.input-btn-group.float-right .input-btn-group.float-right
= form_tag status_import_bitbucket_server_path, :method => 'get' do = form_tag status_import_bitbucket_server_path, :method => 'get' do
= text_field_tag :filter, sanitize(params[:filter]), class: 'form-control append-bottom-10', placeholder: _('Filter your projects by name'), size: 40, autoFocus: true = text_field_tag :filter, sanitize(params[:filter]), class: 'form-control append-bottom-10', placeholder: _('Filter your projects by name'), size: 40, autoFocus: true
.table-responsive.prepend-top-10 .table-responsive.prepend-top-10
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col %colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col %colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th= _('From Bitbucket Server') %th= _('From Bitbucket Server')
%th= _('To GitLab') %th= _('To GitLab')
%th= _('Status') %th= _('Status')
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td %td
= link_to project.import_source, project.import_source, target: '_blank', rel: 'noopener noreferrer' = link_to project.import_source, project.import_source, target: '_blank', rel: 'noopener noreferrer'
%td %td
= link_to project.full_path, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- case project.import_status - case project.import_status
- when 'finished' - when 'finished'
= icon('check', text: 'Done') = icon('check', text: 'Done')
- when 'started' - when 'started'
= icon('spin', text: 'started') = icon('spin', text: 'started')
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo.project_key}___#{repo.slug}", data: { project: repo.project_key, repository: repo.slug } } %tr{ id: "repo_#{repo.project_key}___#{repo.slug}", data: { project: repo.project_key, repository: repo.slug } }
%td %td
= link_to repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer' = link_to repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
%fieldset.row %fieldset.row
.input-group .input-group
.project-path.input-group-prepend .project-path.input-group-prepend
- if current_user.can_select_namespace? - if current_user.can_select_namespace?
- selected = params[:namespace_id] || :extra_group - selected = params[:namespace_id] || :extra_group
- opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {} - opts = current_user.can_create_group? ? { extra_group: Group.new(name: sanitize_project_name(repo.project_key), path: sanitize_project_name(repo.project_key)) } : {}
= select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 } = select_tag :namespace_id, namespaces_options(selected, opts.merge({ display_path: true })), { class: 'select2 js-select-namespace', tabindex: 1 }
- else - else
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true = text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend %span.input-group-prepend
.input-group-text / .input-group-text /
= text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, required: true = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, required: true
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do = button_tag class: 'btn btn-import js-add-to-import' do
Import Import
= icon('spinner spin', class: 'loading-icon') = icon('spinner spin', class: 'loading-icon')
- @incompatible_repos.each do |repo| - @incompatible_repos.each do |repo|
%tr{ id: "repo_#{repo.project_key}___#{repo.slug}" } %tr{ id: "repo_#{repo.project_key}___#{repo.slug}" }
%td %td
= link_to repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer' = link_to repo.browse_url, repo.browse_url, target: '_blank', rel: 'noopener noreferrer'
%td.import-target %td.import-target
%td.import-actions-job-status %td.import-actions-job-status
= label_tag 'Incompatible Project', nil, class: 'label badge-danger' = label_tag 'Incompatible Project', nil, class: 'label badge-danger'
- if @incompatible_repos.any? - if @incompatible_repos.any?
%p %p
One or more of your Bitbucket Server projects cannot be imported into GitLab One or more of your Bitbucket Server projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control, directly because they use Subversion or Mercurial for version control,
rather than Git. Please convert rather than Git. Please convert
= link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview' = link_to 'them to Git,', 'https://www.atlassian.com/git/tutorials/migrating-overview'
and go through the and go through the
= link_to 'import flow', status_import_bitbucket_server_path = link_to 'import flow', status_import_bitbucket_server_path
again. again.
= paginate_without_count(@collection) = paginate_without_count(@collection)
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_bitbucket_server_path}", import_path: "#{import_bitbucket_server_path}" } }
...@@ -4,56 +4,63 @@ ...@@ -4,56 +4,63 @@
%i.fa.fa-bug %i.fa.fa-bug
= _('Import projects from FogBugz') = _('Import projects from FogBugz')
- if @repos.any? - if Feature.enabled?(:new_import_ui)
%p.light
= _('Select projects you want to import.')
%p.light %p.light
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path) - link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize } = _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr %hr
%p = render 'import/githubish_status', provider: 'fogbugz', filterable: false
= button_tag class: 'btn btn-import btn-success js-import-all' do - else
= _('Import all projects') - if @repos.any?
= icon("spinner spin", class: "loading-icon") %p.light
= _('Select projects you want to import.')
%p.light
- link_to_customize = link_to('customize', new_user_map_import_fogbugz_path)
= _('Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab.').html_safe % { link_to_customize: link_to_customize }
%hr
%p
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col %colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col %colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th= _("From FogBugz") %th= _("From FogBugz")
%th= _("To GitLab") %th= _("To GitLab")
%th= _("Status") %th= _("Status")
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td %td
= project.import_source = project.import_source
%td %td
= link_to project.full_path, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- case project.import_status - case project.import_status
- when 'finished' - when 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
= _("done") = _("done")
- when 'started' - when 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
= _("started") = _("started")
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo.id}" } %tr{ id: "repo_#{repo.id}" }
%td %td
= repo.name = repo.name
%td.import-target %td.import-target
#{current_user.username}/#{repo.name} #{current_user.username}/#{repo.name}
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
= _("Import") = _("Import")
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_fogbugz_path}", import_path: "#{import_fogbugz_path}" } }
...@@ -4,52 +4,55 @@ ...@@ -4,52 +4,55 @@
%i.fa.fa-heart %i.fa.fa-heart
= _('Import projects from GitLab.com') = _('Import projects from GitLab.com')
%p.light - if Feature.enabled?(:new_import_ui)
= _('Select projects you want to import.') = render 'import/githubish_status', provider: 'gitlab', filterable: false
%hr - else
%p %p.light
= button_tag class: "btn btn-import btn-success js-import-all" do = _('Select projects you want to import.')
= _('Import all projects') %hr
= icon("spinner spin", class: "loading-icon") %p
= button_tag class: "btn btn-import btn-success js-import-all" do
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive .table-responsive
%table.table.import-jobs %table.table.import-jobs
%colgroup.import-jobs-from-col %colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col %colgroup.import-jobs-to-col
%colgroup.import-jobs-status-col %colgroup.import-jobs-status-col
%thead %thead
%tr %tr
%th= _('From GitLab.com') %th= _('From GitLab.com')
%th= _('To this GitLab instance') %th= _('To this GitLab instance')
%th= _('Status') %th= _('Status')
%tbody %tbody
- @already_added_projects.each do |project| - @already_added_projects.each do |project|
%tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" } %tr{ id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}" }
%td %td
= link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank"
%td %td
= link_to project.full_path, [project.namespace.becomes(Namespace), project] = link_to project.full_path, [project.namespace.becomes(Namespace), project]
%td.job-status %td.job-status
- case project.import_status - case project.import_status
- when 'finished' - when 'finished'
%span %span
%i.fa.fa-check %i.fa.fa-check
= _('done') = _('done')
- when 'started' - when 'started'
%i.fa.fa-spinner.fa-spin %i.fa.fa-spinner.fa-spin
= _('started') = _('started')
- else - else
= project.human_import_status_name = project.human_import_status_name
- @repos.each do |repo| - @repos.each do |repo|
%tr{ id: "repo_#{repo["id"]}" } %tr{ id: "repo_#{repo["id"]}" }
%td %td
= link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer' = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank", rel: 'noopener noreferrer'
%td.import-target %td.import-target
= import_project_target(repo['namespace']['path'], repo['name']) = import_project_target(repo['namespace']['path'], repo['name'])
%td.import-actions.job-status %td.import-actions.job-status
= button_tag class: "btn btn-import js-add-to-import" do = button_tag class: "btn btn-import js-add-to-import" do
= _('Import') = _('Import')
= icon("spinner spin", class: "loading-icon") = icon("spinner spin", class: "loading-icon")
.js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } } .js-importer-status{ data: { jobs_import_path: "#{jobs_import_gitlab_path}", import_path: "#{import_gitlab_path}" } }
---
title: Introduce a feature flag for Vue-based UI for all import providers
merge_request: 33980
author:
type: added
...@@ -25,12 +25,14 @@ namespace :import do ...@@ -25,12 +25,14 @@ namespace :import do
get :status get :status
get :callback get :callback
get :jobs get :jobs
get :realtime_changes
end end
resource :bitbucket, only: [:create], controller: :bitbucket do resource :bitbucket, only: [:create], controller: :bitbucket do
get :status get :status
get :callback get :callback
get :jobs get :jobs
get :realtime_changes
end end
resource :bitbucket_server, only: [:create, :new], controller: :bitbucket_server do resource :bitbucket_server, only: [:create, :new], controller: :bitbucket_server do
...@@ -38,6 +40,7 @@ namespace :import do ...@@ -38,6 +40,7 @@ namespace :import do
get :status get :status
get :callback get :callback
get :jobs get :jobs
get :realtime_changes
end end
resource :google_code, only: [:create, :new], controller: :google_code do resource :google_code, only: [:create, :new], controller: :google_code do
...@@ -53,6 +56,7 @@ namespace :import do ...@@ -53,6 +56,7 @@ namespace :import do
get :status get :status
post :callback post :callback
get :jobs get :jobs
get :realtime_changes
get :new_user_map, path: :user_map get :new_user_map, path: :user_map
post :create_user_map, path: :user_map post :create_user_map, path: :user_map
......
...@@ -15313,6 +15313,9 @@ msgstr "" ...@@ -15313,6 +15313,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked" msgid "One or more of you personal access tokens were revoked"
msgstr "" msgstr ""
msgid "One or more of your %{provider} projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr ""
msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git." msgid "One or more of your Bitbucket projects cannot be imported into GitLab directly because they use Subversion or Mercurial for version control, rather than Git."
msgstr "" msgstr ""
...@@ -16321,6 +16324,9 @@ msgstr "" ...@@ -16321,6 +16324,9 @@ msgstr ""
msgid "Please complete your profile with email address" msgid "Please complete your profile with email address"
msgstr "" msgstr ""
msgid "Please convert %{linkStart}them to Git%{linkEnd}, and go through the %{linkToImportFlow} again."
msgstr ""
msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again." msgid "Please convert them to %{link_to_git}, and go through the %{link_to_import_flow} again."
msgstr "" msgstr ""
...@@ -18229,6 +18235,9 @@ msgstr "" ...@@ -18229,6 +18235,9 @@ msgstr ""
msgid "Recipe" msgid "Recipe"
msgstr "" msgstr ""
msgid "Reconfigure"
msgstr ""
msgid "Recover hidden stage" msgid "Recover hidden stage"
msgstr "" msgstr ""
......
...@@ -56,8 +56,42 @@ RSpec.describe Import::BitbucketController do ...@@ -56,8 +56,42 @@ RSpec.describe Import::BitbucketController do
describe "GET status" do describe "GET status" do
before do before do
@repo = double(slug: 'vim', owner: 'asd', full_name: 'asd/vim', "valid?" => true) @repo = double(name: 'vim', slug: 'vim', owner: 'asd', full_name: 'asd/vim', clone_url: 'http://test.host/demo/url.git', 'valid?' => true)
@invalid_repo = double(name: 'mercurialrepo', slug: 'mercurialrepo', owner: 'asd', full_name: 'asd/mercurialrepo', clone_url: 'http://test.host/demo/mercurialrepo.git', 'valid?' => false)
assign_session_tokens assign_session_tokens
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
before do
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
let(:repo) { @repo }
let(:repo_id) { @repo.full_name }
let(:import_source) { @repo.full_name }
let(:provider_name) { 'bitbucket' }
let(:client_repos_field) { :repos }
end
context 'with new_import_ui feature flag enabled' do
before do
stub_feature_flags(new_import_ui: true)
allow(controller).to receive(:provider_url).and_return('http://demobitbucket.org')
end
it 'returns invalid repos' do
allow_any_instance_of(Bitbucket::Client).to receive(:repos).and_return([@repo, @invalid_repo])
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
end end
it "assigns variables" do it "assigns variables" do
......
...@@ -33,7 +33,7 @@ RSpec.describe Import::BitbucketServerController do ...@@ -33,7 +33,7 @@ RSpec.describe Import::BitbucketServerController do
let(:project_name) { "my-project_123" } let(:project_name) { "my-project_123" }
before do before do
allow(controller).to receive(:bitbucket_client).and_return(client) allow(controller).to receive(:client).and_return(client)
repo = double(name: project_name) repo = double(name: project_name)
allow(client).to receive(:repo).with(project_key, repo_slug).and_return(repo) allow(client).to receive(:repo).with(project_key, repo_slug).and_return(repo)
assign_session_tokens assign_session_tokens
...@@ -139,12 +139,39 @@ RSpec.describe Import::BitbucketServerController do ...@@ -139,12 +139,39 @@ RSpec.describe Import::BitbucketServerController do
let(:repos) { instance_double(BitbucketServer::Collection) } let(:repos) { instance_double(BitbucketServer::Collection) }
before do before do
allow(controller).to receive(:bitbucket_client).and_return(client) allow(controller).to receive(:client).and_return(client)
@repo = double(slug: 'vim', project_key: 'asd', full_name: 'asd/vim', "valid?" => true, project_name: 'asd', browse_url: 'http://test', name: 'vim') @repo = double(slug: 'vim', project_key: 'asd', full_name: 'asd/vim', "valid?" => true, project_name: 'asd', browse_url: 'http://test', name: 'vim')
@invalid_repo = double(slug: 'invalid', project_key: 'foobar', full_name: 'asd/foobar', "valid?" => false, browse_url: 'http://bad-repo') @invalid_repo = double(slug: 'invalid', project_key: 'foobar', full_name: 'asd/foobar', "valid?" => false, browse_url: 'http://bad-repo', name: 'invalid')
@created_repo = double(slug: 'created', project_key: 'existing', full_name: 'group/created', "valid?" => true, browse_url: 'http://existing') @created_repo = double(slug: 'created', project_key: 'existing', full_name: 'group/created', "valid?" => true, browse_url: 'http://existing')
assign_session_tokens assign_session_tokens
stub_feature_flags(new_import_ui: false)
end
context 'with new_import_ui feature flag enabled' do
before do
stub_feature_flags(new_import_ui: true)
end
it 'returns invalid repos' do
allow(client).to receive(:repos).with(filter: nil, limit: 25, page_offset: 0).and_return([@repo, @invalid_repo])
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['incompatible_repos'].length).to eq(1)
expect(json_response.dig("incompatible_repos", 0, "id")).to eq(@invalid_repo.full_name)
expect(json_response['provider_repos'].length).to eq(1)
expect(json_response.dig("provider_repos", 0, "id")).to eq(@repo.full_name)
end
end
it_behaves_like 'import controller with new_import_ui feature flag' do
let(:repo) { @repo }
let(:repo_id) { @repo.full_name }
let(:import_source) { @repo.browse_url }
let(:provider_name) { 'bitbucket_server' }
let(:client_repos_field) { :repos }
end end
it 'assigns repository categories' do it 'assigns repository categories' do
......
...@@ -80,8 +80,17 @@ RSpec.describe Import::FogbugzController do ...@@ -80,8 +80,17 @@ RSpec.describe Import::FogbugzController do
describe 'GET status' do describe 'GET status' do
before do before do
@repo = OpenStruct.new(name: 'vim') @repo = OpenStruct.new(id: 'demo', name: 'vim')
stub_client(valid?: true) stub_client(valid?: true)
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.name }
let(:provider_name) { 'fogbugz' }
let(:client_repos_field) { :repos }
end end
it 'assigns variables' do it 'assigns variables' do
......
...@@ -34,8 +34,17 @@ RSpec.describe Import::GitlabController do ...@@ -34,8 +34,17 @@ RSpec.describe Import::GitlabController do
describe "GET status" do describe "GET status" do
before do before do
@repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') @repo = OpenStruct.new(id: 1, path: 'vim', path_with_namespace: 'asd/vim', web_url: 'https://gitlab.com/asd/vim')
assign_session_token assign_session_token
stub_feature_flags(new_import_ui: false)
end
it_behaves_like 'import controller with new_import_ui feature flag' do
let(:repo) { @repo }
let(:repo_id) { @repo.id }
let(:import_source) { @repo.path_with_namespace }
let(:provider_name) { 'gitlab' }
let(:client_repos_field) { :projects }
end end
it "assigns variables" do it "assigns variables" do
......
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import { GlAlert } from '@gitlab/ui';
import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
import ImportProjectsTable from '~/import_projects/components/import_projects_table.vue';
const ImportProjectsTableStub = {
name: 'ImportProjectsTable',
template:
'<div><slot name="incompatible-repos-warning"></slot><slot name="actions"></slot></div>',
};
describe('BitbucketStatusTable', () => {
let wrapper;
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
function createComponent(propsData, importProjectsTableStub = true, slots) {
wrapper = shallowMount(BitbucketStatusTable, {
propsData,
stubs: {
ImportProjectsTable: importProjectsTableStub,
},
slots,
});
}
it('renders import table component', () => {
createComponent({ providerTitle: 'Test' });
expect(wrapper.contains(ImportProjectsTable)).toBe(true);
});
it('passes alert in incompatible-repos-warning slot', () => {
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
expect(wrapper.find(GlAlert).exists()).toBe(true);
});
it('passes actions slot to import project table component', () => {
const actionsSlotContent = 'DEMO';
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub, {
actions: actionsSlotContent,
});
expect(wrapper.find(ImportProjectsTable).text()).toBe(actionsSlotContent);
});
it('dismisses alert when requested', async () => {
createComponent({ providerTitle: 'Test' }, ImportProjectsTableStub);
wrapper.find(GlAlert).vm.$emit('dismiss');
await nextTick();
expect(wrapper.find(GlAlert).exists()).toBe(false);
});
});
...@@ -16,6 +16,9 @@ jest.mock('~/import_projects/event_hub', () => ({ ...@@ -16,6 +16,9 @@ jest.mock('~/import_projects/event_hub', () => ({
describe('ImportProjectsTable', () => { describe('ImportProjectsTable', () => {
let wrapper; let wrapper;
const findFilterField = () =>
wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
const providerTitle = 'THE PROVIDER'; const providerTitle = 'THE PROVIDER';
const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' }; const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
const importedProject = { const importedProject = {
...@@ -32,7 +35,12 @@ describe('ImportProjectsTable', () => { ...@@ -32,7 +35,12 @@ describe('ImportProjectsTable', () => {
.filter(w => w.props().variant === 'success') .filter(w => w.props().variant === 'success')
.at(0); .at(0);
function createComponent({ state: initialState, getters: customGetters, slots } = {}) { function createComponent({
state: initialState,
getters: customGetters,
slots,
filterable,
} = {}) {
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
...@@ -57,6 +65,7 @@ describe('ImportProjectsTable', () => { ...@@ -57,6 +65,7 @@ describe('ImportProjectsTable', () => {
store, store,
propsData: { propsData: {
providerTitle, providerTitle,
filterable,
}, },
slots, slots,
}); });
...@@ -159,9 +168,14 @@ describe('ImportProjectsTable', () => { ...@@ -159,9 +168,14 @@ describe('ImportProjectsTable', () => {
expect(findImportAllButton().props().loading).toBe(true); expect(findImportAllButton().props().loading).toBe(true);
}); });
it('renders filtering input field', () => { it('renders filtering input field by default', () => {
createComponent(); createComponent();
expect(wrapper.contains('input[data-qa-selector="githubish_import_filter_field"]')).toBe(true); expect(findFilterField().exists()).toBe(true);
});
it('does not render filtering input field when filterable is false', () => {
createComponent({ filterable: false });
expect(findFilterField().exists()).toBe(false);
}); });
it.each` it.each`
......
import { shallowMount } from '@vue/test-utils';
import { GlButton } from '@gitlab/ui';
import BitbucketServerStatusTable from '~/pages/import/bitbucket_server/status/components/bitbucket_server_status_table.vue';
import BitbucketStatusTable from '~/import_projects/components/bitbucket_status_table.vue';
const BitbucketStatusTableStub = {
name: 'BitbucketStatusTable',
template: '<div><slot name="actions"></slot></div>',
};
describe('BitbucketServerStatusTable', () => {
let wrapper;
const findReconfigureButton = () =>
wrapper
.findAll(GlButton)
.filter(w => w.props().variant === 'info')
.at(0);
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
function createComponent(bitbucketStatusTableStub = true) {
wrapper = shallowMount(BitbucketServerStatusTable, {
propsData: { providerTitle: 'Test', reconfigurePath: '/reconfigure' },
stubs: {
BitbucketStatusTable: bitbucketStatusTableStub,
},
});
}
it('renders bitbucket status table component', () => {
createComponent();
expect(wrapper.contains(BitbucketStatusTable)).toBe(true);
});
it('renders Reconfigure button', async () => {
createComponent(BitbucketStatusTableStub);
expect(findReconfigureButton().attributes().href).toBe('/reconfigure');
expect(findReconfigureButton().text()).toBe('Reconfigure');
});
});
# frozen_string_literal: true
require 'spec_helper'
describe Import::BitbucketProviderRepoEntity do
let(:repo_data) do
{
'name' => 'repo_name',
'full_name' => 'owner/repo_name',
'links' => {
'clone' => [
{
'href' => 'https://bitbucket.org/owner/repo_name',
'name' => 'https'
}
]
}
}
end
let(:repo) { Bitbucket::Representation::Repo.new(repo_data) }
subject { described_class.new(repo).as_json }
it_behaves_like 'exposes required fields for import entity' do
let(:expected_values) do
{
id: 'owner/repo_name',
full_name: 'owner/repo_name',
sanitized_name: 'repo_name',
provider_link: 'https://bitbucket.org/owner/repo_name'
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Import::BitbucketServerProviderRepoEntity do
let(:repo_data) do
{
'name' => 'test',
'project' => {
'name' => 'demo'
},
'links' => {
'self' => [
{
'href' => 'http://local.bitbucket.server/demo/test.git',
'name' => 'http'
}
]
}
}
end
let(:repo) { BitbucketServer::Representation::Repo.new(repo_data) }
subject { described_class.new(repo).as_json }
it_behaves_like 'exposes required fields for import entity' do
let(:expected_values) do
{
id: 'demo/test',
full_name: 'demo/test',
sanitized_name: 'test',
provider_link: 'http://local.bitbucket.server/demo/test.git'
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Import::FogbugzProviderRepoEntity do
let(:provider_url) { 'https://demo.fogbugz.com/' }
let(:repo_data) do
{
'ixProject' => 'foo',
'sProject' => 'demo'
}
end
let(:repo) { Gitlab::FogbugzImport::Repository.new(repo_data) }
subject { described_class.represent(repo, { provider_url: provider_url }).as_json }
it_behaves_like 'exposes required fields for import entity' do
let(:expected_values) do
{
id: 'foo',
full_name: 'demo',
sanitized_name: 'demo',
provider_link: 'https://demo.fogbugz.com/demo'
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Import::GithubishProviderRepoEntity do
let(:provider_url) { 'https://github.com/' }
let(:repo) do
{
id: 1,
full_name: 'full/name',
name: 'name'
}
end
subject { described_class.represent(repo, { provider_url: provider_url }).as_json }
it_behaves_like 'exposes required fields for import entity' do
let(:expected_values) do
{
id: 1,
full_name: 'full/name',
sanitized_name: 'name',
provider_link: 'https://github.com/full/name'
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Import::GitlabProviderRepoEntity do
let(:repo_data) do
{
'id' => 1,
'path_with_namespace' => 'demo/test',
'path' => 'test',
'web_url' => 'https://gitlab.com/demo/test'
}
end
subject { described_class.new(repo_data).as_json }
it_behaves_like 'exposes required fields for import entity' do
let(:expected_values) do
{
id: 1,
full_name: 'demo/test',
sanitized_name: 'test',
provider_link: 'https://gitlab.com/demo/test'
}
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Import::ProviderRepoSerializer do
using RSpec::Parameterized::TableSyntax
describe '#represent' do
where(:provider, :class_name) do
:github | 'Import::GithubishProviderRepoEntity'
:gitea | 'Import::GithubishProviderRepoEntity'
:bitbucket | 'Import::BitbucketProviderRepoEntity'
:bitbucket_server | 'Import::BitbucketServerProviderRepoEntity'
:fogbugz | 'Import::FogbugzProviderRepoEntity'
end
with_them do
it 'uses correct entity class' do
opts = { provider: provider }
expect(class_name.constantize).to receive(:represent)
described_class.new.represent({}, opts)
end
end
it 'raises an error if invalid provider supplied' do
expect { described_class.new.represent({}, { provider: :invalid })}.to raise_error { NotImplementedError }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ProviderRepoEntity do
include ImportHelper
let(:provider_repo) { { id: 1, full_name: 'full/name', name: 'name', owner: { login: 'owner' } } }
let(:provider) { :github }
let(:provider_url) { 'https://github.com' }
let(:entity) { described_class.represent(provider_repo, provider: provider, provider_url: provider_url) }
describe '#as_json' do
subject { entity.as_json }
it 'includes required fields' do
expect(subject[:id]).to eq(provider_repo[:id])
expect(subject[:full_name]).to eq(provider_repo[:full_name])
expect(subject[:owner_name]).to eq(provider_repo[:owner][:login])
expect(subject[:sanitized_name]).to eq(sanitize_project_name(provider_repo[:name]))
expect(subject[:provider_link]).to eq(provider_project_link_url(provider_url, provider_repo[:full_name]))
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'import controller with new_import_ui feature flag' do
include ImportSpecHelper
context 'with new_import_ui feature flag enabled' do
let(:group) { create(:group) }
before do
stub_feature_flags(new_import_ui: true)
group.add_owner(user)
end
it "returns variables for json request" do
project = create(:project, import_type: provider_name, creator_id: user.id)
stub_client(client_repos_field => [repo])
get :status, format: :json
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
expect(json_response.dig("provider_repos", 0, "id")).to eq(repo_id)
expect(json_response.dig("namespaces", 0, "id")).to eq(group.id)
end
it "does not show already added project" do
project = create(:project, import_type: provider_name, namespace: user.namespace, import_status: :finished, import_source: import_source)
stub_client(client_repos_field => [repo])
get :status, format: :json
expect(json_response.dig("imported_projects", 0, "id")).to eq(project.id)
expect(json_response.dig("provider_repos")).to eq([])
end
end
end
# frozen_string_literal: true
RSpec.shared_examples 'exposes required fields for import entity' do
describe 'exposes required fields' do
it 'correctly exposes id' do
expect(subject[:id]).to eql(expected_values[:id])
end
it 'correctly exposes full name' do
expect(subject[:full_name]).to eql(expected_values[:full_name])
end
it 'correctly exposes sanitized name' do
expect(subject[:sanitized_name]).to eql(expected_values[:sanitized_name])
end
it 'correctly exposes provider link' do
expect(subject[:provider_link]).to eql(expected_values[:provider_link])
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