Commit d0965893 authored by Illya Klymov's avatar Illya Klymov

Introduce new import UI for all providers

- update templates
- introduce filterable flag for underlying component
parent 57b914cc
<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 {
type: String,
required: true,
},
filterable: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
......@@ -114,7 +119,7 @@ export default {
{{ importAllButtonText }}
</gl-button>
<slot name="actions"></slot>
<form class="gl-ml-auto" novalidate @submit.prevent>
<form v-if="filterable" class="gl-ml-auto" novalidate @submit.prevent>
<input
:value="filter"
data-qa-selector="githubish_import_filter_field"
......
......@@ -30,6 +30,7 @@ export function initStoreFromElement(element) {
export function initPropsFromElement(element) {
return {
providerTitle: element.dataset.providerTitle,
filterable: parseBoolean(element.dataset.filterable),
};
}
......
......@@ -2,6 +2,7 @@ import Visibility from 'visibilityjs';
import * as types from './mutation_types';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import { s__, sprintf } from '~/locale';
import axios from '~/lib/utils/axios_utils';
......@@ -9,6 +10,9 @@ import { jobsPathWithFilter, reposPathWithFilter } from './getters';
let eTagPoll;
const hasRedirectInError = e => e?.response?.data?.error?.redirect;
const redirectToUrlInError = e => visitUrl(e.response.data.error.redirect);
export const clearJobsEtagPoll = () => {
eTagPoll = null;
};
......@@ -33,7 +37,10 @@ export const fetchRepos = ({ state, dispatch, commit }) => {
commit(types.RECEIVE_REPOS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
)
.then(() => dispatch('fetchJobs'))
.catch(() => {
.catch(e => {
if (hasRedirectInError(e)) {
redirectToUrlInError(e);
} else {
createFlash(
sprintf(s__('ImportProjects|Requesting your %{provider} repositories failed'), {
provider,
......@@ -41,6 +48,7 @@ export const fetchRepos = ({ state, dispatch, commit }) => {
);
commit(types.RECEIVE_REPOS_ERROR);
}
});
};
......@@ -87,8 +95,13 @@ export const fetchJobs = ({ state, commit, dispatch }) => {
method: 'fetchJobs',
successCallback: ({ data }) =>
commit(types.RECEIVE_JOBS_SUCCESS, convertObjectPropsToCamelCase(data, { deep: true })),
errorCallback: () =>
createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed')),
errorCallback: e => {
if (hasRedirectInError(e)) {
redirectToUrlInError(e);
} else {
createFlash(s__('ImportProjects|Update of imported projects with realtime changes failed'));
}
},
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);
});
- provider = local_assigns.fetch(:provider)
- extra_data = local_assigns.fetch(:extra_data, {})
- filterable = local_assigns.fetch(:filterable, true)
- provider_title = Gitlab::ImportSources.title(provider)
#import-projects-mount-element{ data: { provider: provider, provider_title: provider_title,
......@@ -6,4 +8,5 @@
ci_cd_only: has_ci_cd_only_params?.to_s,
repos_path: url_for([:status, :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,7 +5,10 @@
%i.fa.fa-bitbucket
= _('Import projects from Bitbucket')
- if @repos.any?
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'bitbucket'
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
%p
......@@ -18,7 +21,7 @@
= _('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
= 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
......@@ -26,7 +29,7 @@
%button{ class: 'btn btn-transparent btn-secondary', 'aria-label': _('Search Button'), type: 'submit' }
%i{ class: 'fa fa-search', 'aria-hidden': true }
.table-responsive
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
......@@ -84,11 +87,11 @@
%td.import-actions-job-status
= label_tag _('Incompatible Project'), nil, class: 'label badge-danger'
- if @incompatible_repos.any?
- if @incompatible_repos.any?
%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.")
- 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)
= _("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,7 +5,10 @@
%i.fa.fa-bitbucket-square
= _('Import projects from Bitbucket Server')
- if @repos.any?
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'bitbucket_server', extra_data: { reconfigure_path: configure_import_bitbucket_server_path }
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
.btn-group
......@@ -18,14 +21,14 @@
= _('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)
.input-btn-group.float-right
.input-btn-group.float-right
= 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
.table-responsive.prepend-top-10
.table-responsive.prepend-top-10
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
......@@ -80,7 +83,7 @@
%td.import-actions-job-status
= label_tag 'Incompatible Project', nil, class: 'label badge-danger'
- if @incompatible_repos.any?
- if @incompatible_repos.any?
%p
One or more of your Bitbucket Server projects cannot be imported into GitLab
directly because they use Subversion or Mercurial for version control,
......@@ -90,6 +93,6 @@
= link_to 'import flow', status_import_bitbucket_server_path
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,7 +4,14 @@
%i.fa.fa-bug
= _('Import projects from FogBugz')
- if @repos.any?
- if Feature.enabled?(:new_import_ui)
%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
= render 'import/githubish_status', provider: 'fogbugz', filterable: false
- else
- if @repos.any?
%p.light
= _('Select projects you want to import.')
%p.light
......@@ -16,7 +23,7 @@
= _('Import all projects')
= icon("spinner spin", class: "loading-icon")
.table-responsive
.table-responsive
%table.table.import-jobs
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
......@@ -56,4 +63,4 @@
= _("Import")
= 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,15 +4,18 @@
%i.fa.fa-heart
= _('Import projects from GitLab.com')
%p.light
- if Feature.enabled?(:new_import_ui)
= render 'import/githubish_status', provider: 'gitlab', filterable: false
- else
%p.light
= _('Select projects you want to import.')
%hr
%p
%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
%colgroup.import-jobs-from-col
%colgroup.import-jobs-to-col
......@@ -52,4 +55,4 @@
= _('Import')
= 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: 34220
author:
type: added
......@@ -15313,6 +15313,9 @@ msgstr ""
msgid "One or more of you personal access tokens were revoked"
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."
msgstr ""
......@@ -16321,6 +16324,9 @@ msgstr ""
msgid "Please complete your profile with email address"
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."
msgstr ""
......@@ -18229,6 +18235,9 @@ msgstr ""
msgid "Recipe"
msgstr ""
msgid "Reconfigure"
msgstr ""
msgid "Recover hidden stage"
msgstr ""
......
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', () => ({
describe('ImportProjectsTable', () => {
let wrapper;
const findFilterField = () =>
wrapper.find('input[data-qa-selector="githubish_import_filter_field"]');
const providerTitle = 'THE PROVIDER';
const providerRepo = { id: 10, sanitizedName: 'sanitizedName', fullName: 'fullName' };
const importedProject = {
......@@ -32,7 +35,12 @@ describe('ImportProjectsTable', () => {
.filter(w => w.props().variant === 'success')
.at(0);
function createComponent({ state: initialState, getters: customGetters, slots } = {}) {
function createComponent({
state: initialState,
getters: customGetters,
slots,
filterable,
} = {}) {
const localVue = createLocalVue();
localVue.use(Vuex);
......@@ -57,6 +65,7 @@ describe('ImportProjectsTable', () => {
store,
propsData: {
providerTitle,
filterable,
},
slots,
});
......@@ -159,9 +168,14 @@ describe('ImportProjectsTable', () => {
expect(findImportAllButton().props().loading).toBe(true);
});
it('renders filtering input field', () => {
it('renders filtering input field by default', () => {
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`
......
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');
});
});
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