From 532799affd27fabf723f5bae20a21fdd9bbbff85 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Mon, 10 Feb 2025 20:45:27 +0100 Subject: [PATCH 1/2] Migrate GitHub auth form to Vue --- app/assets/javascripts/import/github/app.vue | 187 ++++++++++++++++++ app/assets/javascripts/import/github/index.js | 38 ++++ .../pages/import/github/new/index.js | 7 +- app/views/import/github/new.html.haml | 101 +++++----- .../combined_registration_spec.rb | 1 + .../helpers/saas_registration_helpers.rb | 1 + lib/gitlab/gon_helper.rb | 1 + locale/gitlab.pot | 24 +++ spec/frontend/import/github/app_spec.js | 57 ++++++ 9 files changed, 371 insertions(+), 46 deletions(-) create mode 100644 app/assets/javascripts/import/github/app.vue create mode 100644 app/assets/javascripts/import/github/index.js create mode 100644 spec/frontend/import/github/app_spec.js diff --git a/app/assets/javascripts/import/github/app.vue b/app/assets/javascripts/import/github/app.vue new file mode 100644 index 00000000000000..d90e9cf5488310 --- /dev/null +++ b/app/assets/javascripts/import/github/app.vue @@ -0,0 +1,187 @@ + + + diff --git a/app/assets/javascripts/import/github/index.js b/app/assets/javascripts/import/github/index.js new file mode 100644 index 00000000000000..d87041fe9e2c04 --- /dev/null +++ b/app/assets/javascripts/import/github/index.js @@ -0,0 +1,38 @@ +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import App from './app.vue'; + +export function initGitHubImportProjectForm() { + const el = document.getElementById('js-vue-import-github-project-app'); + + if (!el) { + return null; + } + + const { + backButtonPath, + namespaceId, + messageAdmin, + isCiCdOnly, + isConfigured, + buttonAuthHref, + personalAccessTokenImportGithubPath, + } = el.dataset; + + const props = { + backButtonPath, + namespaceId, + messageAdmin, + isCiCdOnly: parseBoolean(isCiCdOnly), + isConfigured: parseBoolean(isConfigured), + buttonAuthHref, + personalAccessTokenImportGithubPath, + }; + + return new Vue({ + el, + render(h) { + return h(App, { props }); + }, + }); +} diff --git a/app/assets/javascripts/pages/import/github/new/index.js b/app/assets/javascripts/pages/import/github/new/index.js index bacd891336a40d..59aef0810f1dd4 100644 --- a/app/assets/javascripts/pages/import/github/new/index.js +++ b/app/assets/javascripts/pages/import/github/new/index.js @@ -1,3 +1,8 @@ +import { initGitHubImportProjectForm } from '~/import/github'; import { initPersonalAccessTokenFormValidation } from './init_personal_access_token_form_validation'; -initPersonalAccessTokenFormValidation(); +if (gon.features.newProjectCreationForm) { + initGitHubImportProjectForm(); +} else { + initPersonalAccessTokenFormValidation(); +} diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index a2e49cf9218003..dc4d1956f8bcbd 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -3,50 +3,61 @@ - header_title _("New project"), new_project_path - add_to_breadcrumbs s_('ProjectsNew|Import project'), new_project_path(anchor: 'import_project') -= render ::Layouts::PageHeadingComponent.new('') do |c| - - c.with_heading do - .gl-flex.gl-gap-3.gl-items-center - = sprite_icon('github', size: 32) - = title - - c.with_description do - = import_github_authorize_message - - if !has_ci_cd_only_params? - .gl-mt-5 - - if github_import_configured? - = render Pajamas::ButtonComponent.new(variant: :confirm, - href: status_import_github_path(namespace_id: params[:namespace_id]), - icon: 'github') do - = title - - else - = render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c| - - c.with_body do - = import_configure_github_admin_message +- if Feature.enabled?(:new_project_creation_form, @user) + #js-vue-import-github-project-app{ data: { + back_button_path: new_project_path(anchor: 'import_project'), + namespace_id: namespace_id_from(params), + message_admin: import_configure_github_admin_message, + is_ci_cd_only: has_ci_cd_only_params?.to_s, + is_configured: github_import_configured?.to_s, + button_auth_href: status_import_github_path(namespace_id: params[:namespace_id]), + personal_access_token_import_github_path: personal_access_token_import_github_path + } } +- else + = render ::Layouts::PageHeadingComponent.new('') do |c| + - c.with_heading do + .gl-flex.gl-gap-3.gl-items-center + = sprite_icon('github', size: 32) + = title + - c.with_description do + = import_github_authorize_message + - if !has_ci_cd_only_params? + .gl-mt-5 + - if github_import_configured? + = render Pajamas::ButtonComponent.new(variant: :confirm, + href: status_import_github_path(namespace_id: params[:namespace_id]), + icon: 'github') do + = title + - else + = render Pajamas::AlertComponent.new(variant: :info, dismissible: false) do |c| + - c.with_body do + = import_configure_github_admin_message -= form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do - .form-group.gl-form-group - %label.col-form-label{ for: 'personal_access_token' }= _('Personal access token') - = hidden_field_tag(:namespace_id, params[:namespace_id]) - = password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' } - %p.invalid-feedback.js-import-github-pat-validation - = _('Personal access token is required.') - %span.form-text.gl-text-subtle - - code_pair = tag_pair(tag.code, :code_start, :code_end) - - github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end) - = safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair) - %br - = safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:')) - %ul - - if has_ci_cd_only_params? - %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair) - - else - %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair) - %li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair) - - docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer') - - docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end) - = safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair) + = form_tag personal_access_token_import_github_path, method: :post, class: 'gl-mt-3' do + .form-group.gl-form-group + %label.col-form-label{ for: 'personal_access_token' }= _('Personal access token') + = hidden_field_tag(:namespace_id, params[:namespace_id]) + = password_field_tag :personal_access_token, '', class: 'form-control gl-form-input js-import-github-pat-field', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { testid: 'personal-access-token-field' } + %p.invalid-feedback.js-import-github-pat-validation + = _('Personal access token is required.') + %span.form-text.gl-text-subtle + - code_pair = tag_pair(tag.code, :code_start, :code_end) + - github_link_tag_pair = tag_pair(link_to('', 'https://github.com/settings/tokens', target: '_blank', rel: 'noopener noreferrer'), :link_start, :link_end) + = safe_format(s_('GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}.'), github_link_tag_pair) + %br + = safe_format(s_('GithubImport|Use a classic GitHub personal access token with the following scopes:')) + %ul + - if has_ci_cd_only_params? + %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to connect to.'), code_pair) + - else + %li= safe_format(s_('GithubImporter|%{code_start}repo%{code_end}: Used to display a list of your public and private repositories that are available to import from.'), code_pair) + %li= safe_format(s_('GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files.'), code_pair) + - docs_link = link_to('', help_page_path('user/project/import/github.md', anchor: 'use-a-github-personal-access-token'), target: '_blank', rel: 'noopener noreferrer') + - docs_link_tag_pair = tag_pair(docs_link, :link_start, :link_end) + = safe_format(s_('GithubImport|%{link_start}Learn more%{link_end}.'), docs_link_tag_pair) - .gl-mt-5.gl-flex.gl-gap-3 - = render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do - = _('Authenticate') - = render Pajamas::ButtonComponent.new(href: new_project_path) do - = _('Cancel') + .gl-mt-5.gl-flex.gl-gap-3 + = render Pajamas::ButtonComponent.new(variant: :confirm, type: :submit, button_options: { class: 'js-import-github-pat-authenticate', data: { testid: 'authenticate-button' } }) do + = _('Authenticate') + = render Pajamas::ButtonComponent.new(href: new_project_path) do + = _('Cancel') diff --git a/ee/spec/features/registrations/combined_registration_spec.rb b/ee/spec/features/registrations/combined_registration_spec.rb index d7b520050c9825..3ce9611b7d1078 100644 --- a/ee/spec/features/registrations/combined_registration_spec.rb +++ b/ee/spec/features/registrations/combined_registration_spec.rb @@ -14,6 +14,7 @@ # Stubbed not to break query budget. Should be safe as the query only happens on SaaS and the result is cached allow(Gitlab::Com).to receive(:gitlab_com_group_member?).and_return(nil) + stub_feature_flags(new_project_creation_form: false) stub_saas_features(onboarding: true) stub_application_setting(import_sources: %w[github gitlab_project]) sign_in(user) diff --git a/ee/spec/support/helpers/saas_registration_helpers.rb b/ee/spec/support/helpers/saas_registration_helpers.rb index 22fe1ca2752b46..3807842560cd3f 100644 --- a/ee/spec/support/helpers/saas_registration_helpers.rb +++ b/ee/spec/support/helpers/saas_registration_helpers.rb @@ -243,6 +243,7 @@ def expect_to_be_in_import_process end def expect_to_see_import_form + stub_feature_flags(new_project_creation_form: false) expect_to_see_group_and_project_creation_form expect(page).to have_content('GitLab export') end diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index fdc5c97c5ba174..fa1a34702b559d 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -91,6 +91,7 @@ def add_gon_variables push_frontend_feature_flag(:work_items_view_preference, current_user) push_frontend_feature_flag(:search_button_top_right, current_user) push_frontend_feature_flag(:merge_request_dashboard, current_user, type: :wip) + push_frontend_feature_flag(:new_project_creation_form, current_user, type: :wip) end # Exposes the state of a feature flag to the frontend code. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 58c6ce67067520..3aea58cc62c171 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -26668,6 +26668,15 @@ msgstr "" msgid "Gitea import" msgstr "" +msgid "GithubImporter|%{codeStart}read:org%{codeEnd} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files." +msgstr "" + +msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to connect to." +msgstr "" + +msgid "GithubImporter|%{codeStart}repo%{codeEnd}: Used to display a list of your public and private repositories that are available to import from." +msgstr "" + msgid "GithubImporter|%{code_start}read:org%{code_end} (optional): Used to import collaborators from GitHub repositories, or if your project has Git LFS files." msgstr "" @@ -26755,9 +26764,15 @@ msgstr "" msgid "GithubImport|\"%{repository_name}\" size (%{repository_size}) is larger than the limit of %{limit}." msgstr "" +msgid "GithubImport|%{linkStart}Learn more%{linkEnd}." +msgstr "" + msgid "GithubImport|%{link_start}Learn more%{link_end}." msgstr "" +msgid "GithubImport|Create and provide your GitHub %{linkStart}personal access token%{linkEnd}." +msgstr "" + msgid "GithubImport|Create and provide your GitHub %{link_start}personal access token%{link_end}." msgstr "" @@ -46236,6 +46251,12 @@ msgstr "" msgid "ProjectsNew|Analyze your source code for known security vulnerabilities." msgstr "" +msgid "ProjectsNew|Authenticate through GitHub" +msgstr "" + +msgid "ProjectsNew|Authenticate with GitHub" +msgstr "" + msgid "ProjectsNew|Available only for projects within groups" msgstr "" @@ -62650,6 +62671,9 @@ msgstr "" msgid "Use one line per URI" msgstr "" +msgid "Use personal access token" +msgstr "" + msgid "Use primary email (%{email})" msgstr "" diff --git a/spec/frontend/import/github/app_spec.js b/spec/frontend/import/github/app_spec.js new file mode 100644 index 00000000000000..e13ac53d662cad --- /dev/null +++ b/spec/frontend/import/github/app_spec.js @@ -0,0 +1,57 @@ +import { GlAlert } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import App from '~/import/github/app.vue'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('Import from GitHub app', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(App, { + propsData: { + backButtonPath: 'https://gitlab.com', + namespaceId: '1', + messageAdmin: 'This is an admin alert.', + isCiCdOnly: false, + isConfigured: true, + buttonAuthHref: 'https://gitlab.com/submit', + ...props, + }, + }); + }; + + const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); + const findGithubAuthButton = () => wrapper.findByTestId('github-auth-button'); + const findAlert = () => wrapper.findComponent(GlAlert); + + it('renders a form', () => { + createComponent(); + + expect(findMultiStepForm().exists()).toBe(true); + }); + + describe('not a ci/cd project', () => { + it('renders a button if github is configured', () => { + createComponent(); + + expect(findGithubAuthButton().exists()).toBe(true); + expect(findAlert().exists()).toBe(false); + }); + + it('renders an alert if github is not configured', () => { + createComponent({ isConfigured: false }); + + expect(findGithubAuthButton().exists()).toBe(false); + expect(findAlert().exists()).toBe(true); + }); + }); + + describe('is a ci/cd project', () => { + it('does not render a github auth button or alert', () => { + createComponent({ isCiCdOnly: true }); + + expect(findGithubAuthButton().exists()).toBe(false); + expect(findAlert().exists()).toBe(false); + }); + }); +}); -- GitLab From 8832343323e55ac88e662209256336fcbce5aaa0 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Thu, 13 Feb 2025 12:53:03 +0100 Subject: [PATCH 2/2] Apply suggestions --- .../{app.vue => import_from_github_app.vue} | 89 +++++++++++-------- app/assets/javascripts/import/github/index.js | 31 ++----- app/views/import/github/new.html.haml | 10 +-- ...spec.js => import_from_github_app_spec.js} | 11 +-- 4 files changed, 72 insertions(+), 69 deletions(-) rename app/assets/javascripts/import/github/{app.vue => import_from_github_app.vue} (70%) rename spec/frontend/import/github/{app_spec.js => import_from_github_app_spec.js} (85%) diff --git a/app/assets/javascripts/import/github/app.vue b/app/assets/javascripts/import/github/import_from_github_app.vue similarity index 70% rename from app/assets/javascripts/import/github/app.vue rename to app/assets/javascripts/import/github/import_from_github_app.vue index d90e9cf5488310..c18b31daafceb1 100644 --- a/app/assets/javascripts/import/github/app.vue +++ b/app/assets/javascripts/import/github/import_from_github_app.vue @@ -1,5 +1,5 @@