diff --git a/app/assets/javascripts/import/gitea/import_from_gitea_root.vue b/app/assets/javascripts/import/gitea/import_from_gitea_root.vue new file mode 100644 index 0000000000000000000000000000000000000000..c7d8a83fe798959c84b4a61ae4cbeec83b8237ba --- /dev/null +++ b/app/assets/javascripts/import/gitea/import_from_gitea_root.vue @@ -0,0 +1,166 @@ + + + diff --git a/app/assets/javascripts/import/gitea/index.js b/app/assets/javascripts/import/gitea/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d305564679993a5d6a54de622b1664faad9fae95 --- /dev/null +++ b/app/assets/javascripts/import/gitea/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import ImportFromGiteaRoot from './import_from_gitea_root.vue'; + +export function initGiteaImportProjectForm() { + const el = document.getElementById('js-vue-import-gitea-project-root'); + + if (!el) { + return null; + } + + const { backButtonPath, namespaceId, formPath } = el.dataset; + + const props = { backButtonPath, namespaceId, formPath }; + + return new Vue({ + el, + name: 'ImportFromGiteaRoot', + render(h) { + return h(ImportFromGiteaRoot, { props }); + }, + }); +} diff --git a/app/assets/javascripts/pages/import/gitea/new/index.js b/app/assets/javascripts/pages/import/gitea/new/index.js new file mode 100644 index 0000000000000000000000000000000000000000..d917983432bc08a243324dd5e47356f97422bef7 --- /dev/null +++ b/app/assets/javascripts/pages/import/gitea/new/index.js @@ -0,0 +1,3 @@ +import { initGiteaImportProjectForm } from '~/import/gitea'; + +initGiteaImportProjectForm(); diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml index caed978b396141ff5a0aed87172863cde967b35e..aeca521203abb1accc6c3f7a0a1a9630f6ebc7c1 100644 --- a/app/views/import/gitea/new.html.haml +++ b/app/views/import/gitea/new.html.haml @@ -2,26 +2,33 @@ - header_title _("New project"), new_project_path - add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project') -%h1.page-title.gl-text-size-h-display.gl-flex.gl-items-center - .gl-flex.gl-items-center.gl-justify-center - = sprite_icon('gitea', css_class: 'gl-mr-3', size: 48) - = _('Import projects from Gitea') -%hr +- if Feature.enabled?(:new_project_creation_form, @user) + #js-vue-import-gitea-project-root{ data: { + back_button_path: new_project_path(anchor: 'import_project'), + namespace_id: namespace_id_from(params) || @current_user_group&.id, + form_path: personal_access_token_import_gitea_path + } } +- else + %h1.page-title.gl-text-size-h-display.gl-flex.gl-items-center + .gl-flex.gl-items-center.gl-justify-center + = sprite_icon('gitea', css_class: 'gl-mr-3', size: 48) + = _('Import projects from Gitea') + %hr -%p - - link_to_personal_token = link_to(_('personal access token'), 'https://docs.gitea.io/en-us/api-usage/#authentication-via-the-api') - = _('To get started, please enter your Gitea host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token } + %p + - link_to_personal_token = link_to(_('personal access token'), 'https://docs.gitea.io/en-us/api-usage/#authentication-via-the-api') + = _('To get started, please enter your Gitea host URL and a %{link_to_personal_token}.').html_safe % { link_to_personal_token: link_to_personal_token } -= form_tag personal_access_token_import_gitea_path do - = hidden_field_tag(:namespace_id, params[:namespace_id]) - .form-group.row - = label_tag :gitea_host_url, _('Gitea host URL'), class: 'col-form-label col-sm-2' - .col-sm-4 - = text_field_tag :gitea_host_url, nil, placeholder: 'https://gitea.com', class: 'form-control gl-form-input' - .form-group.row - = label_tag :personal_access_token, _('Personal access token'), for: :personal_access_token, class: 'col-form-label col-sm-2' - .col-sm-4 - = password_field_tag :personal_access_token, nil, class: 'form-control gl-form-input' - .form-actions - = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm) do - = _('List your Gitea repositories') + = form_tag personal_access_token_import_gitea_path do + = hidden_field_tag(:namespace_id, params[:namespace_id]) + .form-group.row + = label_tag :gitea_host_url, _('Gitea host URL'), class: 'col-form-label col-sm-2' + .col-sm-4 + = text_field_tag :gitea_host_url, nil, placeholder: 'https://gitea.com', class: 'form-control gl-form-input' + .form-group.row + = label_tag :personal_access_token, _('Personal access token'), for: :personal_access_token, class: 'col-form-label col-sm-2' + .col-sm-4 + = password_field_tag :personal_access_token, nil, class: 'form-control gl-form-input' + .form-actions + = render Pajamas::ButtonComponent.new(type: :submit, variant: :confirm) do + = _('List your Gitea repositories') diff --git a/locale/gitlab.pot b/locale/gitlab.pot index cc2fe88051bb9db4232ac98ed25f0621e1920424..f791caabfaed314d89609679c2ffb3c1432e5803 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26460,6 +26460,9 @@ msgstr "" msgid "GithubImport|Import failed because of a GitHub error: %{original} (HTTP %{code})" msgstr "" +msgid "GithubImport|Learn more about %{linkStart}Gitea personal access tokens%{linkEnd}." +msgstr "" + msgid "GithubImport|Namespace or group to import repository into does not exist." msgstr "" @@ -45954,6 +45957,9 @@ msgstr "" msgid "ProjectsNew|Get started with one of our popular project templates." msgstr "" +msgid "ProjectsNew|Gitea host URL" +msgstr "" + msgid "ProjectsNew|Group name" msgstr "" @@ -45963,6 +45969,9 @@ msgstr "" msgid "ProjectsNew|Import project" msgstr "" +msgid "ProjectsNew|Import projects from Gitea" +msgstr "" + msgid "ProjectsNew|Include a Getting Started README" msgstr "" @@ -45996,6 +46005,12 @@ msgstr "" msgid "ProjectsNew|Pick a group or namespace where you want to create this project." msgstr "" +msgid "ProjectsNew|Please enter a valid Gitea host URL." +msgstr "" + +msgid "ProjectsNew|Please enter a valid personal access token." +msgstr "" + msgid "ProjectsNew|Project Configuration" msgstr "" diff --git a/spec/frontend/import/gitea/import_from_gitea_root_spec.js b/spec/frontend/import/gitea/import_from_gitea_root_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..d27bcbb760b113dcd322fdfef67ee89340340f65 --- /dev/null +++ b/spec/frontend/import/gitea/import_from_gitea_root_spec.js @@ -0,0 +1,111 @@ +import { nextTick } from 'vue'; +import { GlFormInput } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ImportFromGiteaRoot from '~/import/gitea/import_from_gitea_root.vue'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); + +describe('Import from Gitea app', () => { + let wrapper; + + const defaultProps = { + backButtonPath: '/projects/new#import_project', + namespaceId: '1', + formPath: '/import/gitea/personal_access_token', + }; + + const createComponent = () => { + wrapper = shallowMountExtended(ImportFromGiteaRoot, { + propsData: { + ...defaultProps, + }, + stubs: { + GlFormInput, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); + const findForm = () => wrapper.find('form'); + const findGiteaHostUrlInput = () => wrapper.findByTestId('gitea-host-url-input'); + const findPersonalAccessTokenInput = () => wrapper.findByTestId('personal-access-token-input'); + const findBackButton = () => wrapper.findByTestId('back-button'); + const findNextButton = () => wrapper.findByTestId('next-button'); + + describe('form', () => { + it('renders the multi step form correctly', () => { + expect(findMultiStepForm().props()).toMatchObject({ + currentStep: 3, + stepsTotal: 4, + }); + }); + + it('renders the form element correctly', () => { + const form = findForm(); + + expect(form.attributes('action')).toBe(defaultProps.formPath); + expect(form.find('input[type=hidden][name=authenticity_token]').attributes('value')).toBe( + 'mock-csrf-token', + ); + }); + + it('does not submit the form without required fields', () => { + const submitSpy = jest.spyOn(findForm().element, 'submit'); + + findForm().trigger('submit'); + expect(submitSpy).not.toHaveBeenCalled(); + }); + + it('submits the form with valid form data', async () => { + const submitSpy = jest.spyOn(findForm().element, 'submit'); + + await findGiteaHostUrlInput().setValue('https://test.gitea.cloud/'); + await findGiteaHostUrlInput().trigger('blur'); + await findPersonalAccessTokenInput().setValue('863638293ddkdl29'); + await findPersonalAccessTokenInput().trigger('blur'); + await nextTick(); + + findForm().trigger('submit'); + expect(submitSpy).toHaveBeenCalledWith(); + }); + }); + + describe('validation', () => { + it('shows an error message when url is cleared', async () => { + findGiteaHostUrlInput().setValue(''); + findGiteaHostUrlInput().trigger('blur'); + await nextTick(); + + const formGroup = wrapper.findByTestId('gitea-host-url-group'); + expect(formGroup.vm.$attrs['invalid-feedback']).toBe('Please enter a valid Gitea host URL.'); + }); + + it('shows an error message when token is cleared', async () => { + findPersonalAccessTokenInput().setValue(''); + findPersonalAccessTokenInput().trigger('blur'); + await nextTick(); + + const formGroup = wrapper.findByTestId('personal-access-token-group'); + expect(formGroup.vm.$attrs['invalid-feedback']).toBe( + 'Please enter a valid personal access token.', + ); + }); + }); + + describe('back button', () => { + it('renders a back button', () => { + expect(findBackButton().attributes('href')).toBe(defaultProps.backButtonPath); + }); + }); + + describe('next button', () => { + it('renders a next button', () => { + expect(findNextButton().attributes('type')).toBe('submit'); + }); + }); +});