From 918aed342c7a188e1039b4312a9696f30dbde211 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Tue, 11 Feb 2025 13:26:54 +0100 Subject: [PATCH 1/2] Migrate an Import from Gitea form to Vue --- app/assets/javascripts/import/gitea/app.vue | 85 +++++++++++++++++++ app/assets/javascripts/import/gitea/index.js | 21 +++++ .../pages/import/gitea/new/index.js | 3 + app/views/import/gitea/new.html.haml | 49 ++++++----- locale/gitlab.pot | 15 ++++ spec/frontend/import/gitea/app_spec.js | 25 ++++++ 6 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 app/assets/javascripts/import/gitea/app.vue create mode 100644 app/assets/javascripts/import/gitea/index.js create mode 100644 app/assets/javascripts/pages/import/gitea/new/index.js create mode 100644 spec/frontend/import/gitea/app_spec.js diff --git a/app/assets/javascripts/import/gitea/app.vue b/app/assets/javascripts/import/gitea/app.vue new file mode 100644 index 00000000000000..b26801f22ac540 --- /dev/null +++ b/app/assets/javascripts/import/gitea/app.vue @@ -0,0 +1,85 @@ + + + diff --git a/app/assets/javascripts/import/gitea/index.js b/app/assets/javascripts/import/gitea/index.js new file mode 100644 index 00000000000000..9496cdd78d3348 --- /dev/null +++ b/app/assets/javascripts/import/gitea/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; +import App from './app.vue'; + +export function initGiteaImportProjectForm() { + const el = document.getElementById('js-vue-import-gitea-project-app'); + + if (!el) { + return null; + } + + const { backButtonPath, namespaceId, formPath } = el.dataset; + + const props = { backButtonPath, namespaceId, formPath }; + + return new Vue({ + el, + render(h) { + return h(App, { 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 00000000000000..d917983432bc08 --- /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 caed978b396141..9783b480c8e18a 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-app{ 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 cc2fe88051bb9d..2dad78f2be2b6d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2054,6 +2054,9 @@ msgstr "" msgid "503|Try refreshing the page, or going back and attempting the action again." msgstr "" +msgid "8d3f016698e..." +msgstr "" + msgid ":%{startLine} to %{endLine}" msgstr "" @@ -26460,6 +26463,9 @@ msgstr "" msgid "GithubImport|Import failed because of a GitHub error: %{original} (HTTP %{code})" msgstr "" +msgid "GithubImport|Learn more about %{linkStart}Gitea personal access token%{linkEnd}." +msgstr "" + msgid "GithubImport|Namespace or group to import repository into does not exist." msgstr "" @@ -45954,6 +45960,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 +45972,9 @@ msgstr "" msgid "ProjectsNew|Import project" msgstr "" +msgid "ProjectsNew|Import projects from Gitea" +msgstr "" + msgid "ProjectsNew|Include a Getting Started README" msgstr "" @@ -68561,6 +68573,9 @@ msgstr "" msgid "https://bamboo.example.com" msgstr "" +msgid "https://gitea.com" +msgstr "" + msgid "https://your-bitbucket-server" msgstr "" diff --git a/spec/frontend/import/gitea/app_spec.js b/spec/frontend/import/gitea/app_spec.js new file mode 100644 index 00000000000000..058877c16cb3bf --- /dev/null +++ b/spec/frontend/import/gitea/app_spec.js @@ -0,0 +1,25 @@ +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import App from '~/import/gitea/app.vue'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('Import from Gitea app', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(App, { + propsData: { + backButtonPath: 'https://gitlab.com', + namespaceId: '1', + ...props, + }, + }); + }; + + const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); + + it('renders a form', () => { + createComponent(); + + expect(findMultiStepForm().exists()).toBe(true); + }); +}); -- GitLab From c6b47489dc48bc5da09d8958b2f6ef4769f4d86e Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Mon, 17 Feb 2025 15:44:04 +0100 Subject: [PATCH 2/2] Apply suggestions --- app/assets/javascripts/import/gitea/app.vue | 85 --------- .../import/gitea/import_from_gitea_root.vue | 166 ++++++++++++++++++ app/assets/javascripts/import/gitea/index.js | 7 +- app/views/import/gitea/new.html.haml | 2 +- locale/gitlab.pot | 14 +- spec/frontend/import/gitea/app_spec.js | 25 --- .../gitea/import_from_gitea_root_spec.js | 111 ++++++++++++ 7 files changed, 289 insertions(+), 121 deletions(-) delete mode 100644 app/assets/javascripts/import/gitea/app.vue create mode 100644 app/assets/javascripts/import/gitea/import_from_gitea_root.vue delete mode 100644 spec/frontend/import/gitea/app_spec.js create mode 100644 spec/frontend/import/gitea/import_from_gitea_root_spec.js diff --git a/app/assets/javascripts/import/gitea/app.vue b/app/assets/javascripts/import/gitea/app.vue deleted file mode 100644 index b26801f22ac540..00000000000000 --- a/app/assets/javascripts/import/gitea/app.vue +++ /dev/null @@ -1,85 +0,0 @@ - - - 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 00000000000000..c7d8a83fe79895 --- /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 index 9496cdd78d3348..d305564679993a 100644 --- a/app/assets/javascripts/import/gitea/index.js +++ b/app/assets/javascripts/import/gitea/index.js @@ -1,8 +1,8 @@ import Vue from 'vue'; -import App from './app.vue'; +import ImportFromGiteaRoot from './import_from_gitea_root.vue'; export function initGiteaImportProjectForm() { - const el = document.getElementById('js-vue-import-gitea-project-app'); + const el = document.getElementById('js-vue-import-gitea-project-root'); if (!el) { return null; @@ -14,8 +14,9 @@ export function initGiteaImportProjectForm() { return new Vue({ el, + name: 'ImportFromGiteaRoot', render(h) { - return h(App, { props }); + return h(ImportFromGiteaRoot, { props }); }, }); } diff --git a/app/views/import/gitea/new.html.haml b/app/views/import/gitea/new.html.haml index 9783b480c8e18a..aeca521203abb1 100644 --- a/app/views/import/gitea/new.html.haml +++ b/app/views/import/gitea/new.html.haml @@ -3,7 +3,7 @@ - add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project') - if Feature.enabled?(:new_project_creation_form, @user) - #js-vue-import-gitea-project-app{ data: { + #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 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2dad78f2be2b6d..f791caabfaed31 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2054,9 +2054,6 @@ msgstr "" msgid "503|Try refreshing the page, or going back and attempting the action again." msgstr "" -msgid "8d3f016698e..." -msgstr "" - msgid ":%{startLine} to %{endLine}" msgstr "" @@ -26463,7 +26460,7 @@ msgstr "" msgid "GithubImport|Import failed because of a GitHub error: %{original} (HTTP %{code})" msgstr "" -msgid "GithubImport|Learn more about %{linkStart}Gitea personal access token%{linkEnd}." +msgid "GithubImport|Learn more about %{linkStart}Gitea personal access tokens%{linkEnd}." msgstr "" msgid "GithubImport|Namespace or group to import repository into does not exist." @@ -46008,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 "" @@ -68573,9 +68576,6 @@ msgstr "" msgid "https://bamboo.example.com" msgstr "" -msgid "https://gitea.com" -msgstr "" - msgid "https://your-bitbucket-server" msgstr "" diff --git a/spec/frontend/import/gitea/app_spec.js b/spec/frontend/import/gitea/app_spec.js deleted file mode 100644 index 058877c16cb3bf..00000000000000 --- a/spec/frontend/import/gitea/app_spec.js +++ /dev/null @@ -1,25 +0,0 @@ -import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; -import App from '~/import/gitea/app.vue'; -import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; - -describe('Import from Gitea app', () => { - let wrapper; - - const createComponent = (props = {}) => { - wrapper = shallowMountExtended(App, { - propsData: { - backButtonPath: 'https://gitlab.com', - namespaceId: '1', - ...props, - }, - }); - }; - - const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); - - it('renders a form', () => { - createComponent(); - - expect(findMultiStepForm().exists()).toBe(true); - }); -}); 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 00000000000000..d27bcbb760b113 --- /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'); + }); + }); +}); -- GitLab