diff --git a/app/assets/javascripts/import/github/import_from_github_app.vue b/app/assets/javascripts/import/github/import_from_github_app.vue new file mode 100644 index 0000000000000000000000000000000000000000..c18b31daafceb166f2dadf158e955827674282b4 --- /dev/null +++ b/app/assets/javascripts/import/github/import_from_github_app.vue @@ -0,0 +1,204 @@ + + + diff --git a/app/assets/javascripts/import/github/index.js b/app/assets/javascripts/import/github/index.js new file mode 100644 index 0000000000000000000000000000000000000000..6ddeb359bfc181dfe7295fe823caaddd6a5bfd3b --- /dev/null +++ b/app/assets/javascripts/import/github/index.js @@ -0,0 +1,23 @@ +import Vue from 'vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import ImportFromGithubApp from './import_from_github_app.vue'; + +export function initGitHubImportProjectForm() { + const el = document.getElementById('js-vue-import-github-project-app'); + + if (!el) { + return null; + } + + const { viewModel } = el.dataset; + const provide = JSON.parse(viewModel); + + return new Vue({ + el, + name: 'ImportFromGitHubRoot', + provide: convertObjectPropsToCamelCase(provide), + render(createElement) { + return createElement(ImportFromGithubApp); + }, + }); +} diff --git a/app/assets/javascripts/pages/import/github/new/index.js b/app/assets/javascripts/pages/import/github/new/index.js index bacd891336a40db1083bd23a1992adb7c8374184..59aef0810f1dd471463b45f697ea3eec3015922e 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 a2e49cf92180034e6cc3bed4ca3365510a67cfc8..0412ec8560311bce408a774a8c6f5313ed098830 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: { view_model: Gitlab::Json.generate({ + 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?, + is_configured: github_import_configured?, + button_auth_href: status_import_github_path(namespace_id: params[:namespace_id]), + form_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 d7b520050c9825fc31c48227add708bf8db98a7e..3ce9611b7d1078152a07473bfba23fa5c3472a6b 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 22fe1ca2752b467fb7233186b5cbe32051eaf05d..3807842560cd3fb638c0008bf4196f436b4dddda 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 fdc5c97c5ba174a6bbcf65dd488abcee549537f7..fa1a34702b559de525cd53e8f204b73f478179e8 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 58c6ce670675206e0106c572ecace07eae7cea0a..3aea58cc62c1710be666fee6db8c7e4c502af530 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/import_from_github_app_spec.js b/spec/frontend/import/github/import_from_github_app_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..effa88f8d705a3390afed60707df0c85ac192299 --- /dev/null +++ b/spec/frontend/import/github/import_from_github_app_spec.js @@ -0,0 +1,58 @@ +import { GlAlert } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import ImportFromGithubApp from '~/import/github/import_from_github_app.vue'; +import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; + +describe('Import from GitHub app', () => { + let wrapper; + + const createComponent = (provide = {}) => { + wrapper = shallowMountExtended(ImportFromGithubApp, { + provide: { + backButtonPath: 'https://gitlab.com', + namespaceId: '1', + messageAdmin: 'This is an admin alert.', + isCiCdOnly: false, + isConfigured: true, + buttonAuthHref: 'https://gitlab.com/submit', + formPath: 'https://gitlab.com/submit', + ...provide, + }, + }); + }; + + 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); + }); + }); +});