From 9b51d3ccd0e22b6f636a34ae4690ac4332a3140d Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Tue, 4 Feb 2025 16:00:16 +0100 Subject: [PATCH 1/2] Add navigation between first two steps to the new project creation form --- .../projects/new_v2/components/app.vue | 112 +++++++++++++++--- .../new_v2/components/blank_project_form.vue | 37 ++++++ .../new_v2/components/ci_cd_project_form.vue | 37 ++++++ .../new_v2/components/form_breadcrumb.vue | 15 +++ .../new_v2/components/import_project_form.vue | 37 ++++++ .../components/template_project_form.vue | 37 ++++++ .../new_v2/components/form_breadcrumb_spec.js | 34 +++++- 7 files changed, 288 insertions(+), 21 deletions(-) create mode 100644 app/assets/javascripts/projects/new_v2/components/blank_project_form.vue create mode 100644 app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue create mode 100644 app/assets/javascripts/projects/new_v2/components/import_project_form.vue create mode 100644 app/assets/javascripts/projects/new_v2/components/template_project_form.vue diff --git a/app/assets/javascripts/projects/new_v2/components/app.vue b/app/assets/javascripts/projects/new_v2/components/app.vue index 6f912ed7a548db..815d71b575774e 100644 --- a/app/assets/javascripts/projects/new_v2/components/app.vue +++ b/app/assets/javascripts/projects/new_v2/components/app.vue @@ -9,6 +9,10 @@ import SingleChoiceSelectorItem from '~/vue_shared/components/single_choice_sele import NewProjectDestinationSelect from './project_destination_select.vue'; import Breadcrumb from './form_breadcrumb.vue'; import CommandLine from './command_line.vue'; +import BlankProjectForm from './blank_project_form.vue'; +import TemplateProjectForm from './template_project_form.vue'; +import CiCdProjectForm from './ci_cd_project_form.vue'; +import ImportProjectForm from './import_project_form.vue'; const OPTIONS = { blank: { @@ -44,6 +48,7 @@ const OPTIONS = { description: s__( 'ProjectsNew|Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab.', ), + icons: ['tanuki', 'github', 'bitbucket', 'gitea'], disabledMessage: s__( 'ProjectsNew|Contact an administrator to enable options for importing your project', ), @@ -54,11 +59,13 @@ const OPTIONS = { selector: '#transfer-project-pane', title: s__('ProjectsNew|Direct transfer projects with a top-level Group'), description: s__('ProjectsNew|Migrate your data from another GitLab instance.'), + disabled: true, disabledMessage: s__('ProjectsNew|Available only for projects within groups'), }, }; export default { + OPTIONS, components: { GlButton, GlButtonGroup, @@ -71,6 +78,10 @@ export default { NewProjectDestinationSelect, Breadcrumb, CommandLine, + BlankProjectForm, + TemplateProjectForm, + CiCdProjectForm, + ImportProjectForm, }, directives: { SafeHtml, @@ -152,8 +163,10 @@ export default { selectedNamespace: this.namespaceId && this.canSelectNamespace ? this.namespaceId : this.userNamespaceId, rootUrl: this.rootPath, + choosenProjectTypeName: null, }; }, + computed: { isPersonalProject() { return this.selectedNamespace === this.userNamespaceId; @@ -177,6 +190,29 @@ export default { }, ); }, + choosenProjectType() { + return this.availableProjectTypes.find((p) => p.value === this.choosenProjectTypeName); + }, + availableProjectTypes() { + const types = [OPTIONS.blank, OPTIONS.template]; + + if (this.canImportProjects && this.importSourcesEnabled) types.push(OPTIONS.import); + + if (this.isCiCdAvailable) types.push(OPTIONS.ci); + + // this option should be the last in a row + types.push(OPTIONS.transfer); + + return types; + }, + }, + + created() { + this.handleLocationHashChange(); + + window.addEventListener('hashchange', () => { + this.handleLocationHashChange(); + }); }, methods: { choosePersonalNamespace() { @@ -185,16 +221,38 @@ export default { chooseGroupNamespace() { this.selectedNamespace = null; }, + handleLocationHashChange() { + this.choosenProjectTypeName = window.location.hash.substring(1) || null; + }, + showSecondStepForm(type) { + return this.choosenProjectTypeName && this.choosenProjectType.value === type; + }, + goToFirstStep() { + this.choosenProjectTypeName = null; + window.location.hash = ''; + }, + goToSecondStep() { + const formElement = this.$refs.projectTypeSelector.$el; + const projectType = formElement.querySelector('input:checked').value; + + if (projectType && this.availableProjectTypes.find((p) => p.value === projectType)) { + this.choosenProjectTypeName = projectType; + window.location.hash = `#${projectType}`; + } + }, }, - OPTIONS, }; diff --git a/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue b/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue new file mode 100644 index 00000000000000..cdebe6392422d8 --- /dev/null +++ b/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue b/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue new file mode 100644 index 00000000000000..cdebe6392422d8 --- /dev/null +++ b/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue index 8979b802373ba6..235ba5ff38d07c 100644 --- a/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue +++ b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue @@ -10,6 +10,13 @@ export default { SuperSidebarToggle, }, inject: ['rootPath', 'projectsUrl', 'parentGroupUrl', 'parentGroupName'], + props: { + choosenProjectType: { + type: Object, + required: false, + default: null, + }, + }, computed: { breadcrumbs() { const breadcrumbs = this.parentGroupUrl @@ -19,6 +26,14 @@ export default { { text: s__('ProjectsNew|Projects'), href: this.projectsUrl }, ]; breadcrumbs.push({ text: s__('ProjectsNew|New project'), href: '#' }); + + if (this.choosenProjectType) { + breadcrumbs.push({ + text: this.choosenProjectType.title, + href: this.choosenProjectType.value, + }); + } + return breadcrumbs; }, }, diff --git a/app/assets/javascripts/projects/new_v2/components/import_project_form.vue b/app/assets/javascripts/projects/new_v2/components/import_project_form.vue new file mode 100644 index 00000000000000..f6958c0a687492 --- /dev/null +++ b/app/assets/javascripts/projects/new_v2/components/import_project_form.vue @@ -0,0 +1,37 @@ + + + diff --git a/app/assets/javascripts/projects/new_v2/components/template_project_form.vue b/app/assets/javascripts/projects/new_v2/components/template_project_form.vue new file mode 100644 index 00000000000000..fc00b843c4113d --- /dev/null +++ b/app/assets/javascripts/projects/new_v2/components/template_project_form.vue @@ -0,0 +1,37 @@ + + + diff --git a/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js b/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js index 42f7c833b01589..73c05001bd0c19 100644 --- a/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js +++ b/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js @@ -5,12 +5,15 @@ import FormBreadcrumb from '~/projects/new_v2/components/form_breadcrumb.vue'; describe('New project form breadcrumbs', () => { let wrapper; - const createComponent = (props = {}) => { + const createComponent = (props = {}, provide = {}) => { wrapper = shallowMountExtended(FormBreadcrumb, { + props: { + ...props, + }, provide: { rootPath: '/', projectsUrl: '/dashboard/projects', - ...props, + ...provide, }, }); }; @@ -18,7 +21,7 @@ describe('New project form breadcrumbs', () => { const findBreadcrumb = () => wrapper.findComponent(GlBreadcrumb); it('renders personal namespace breadcrumbs', () => { - createComponent({ parentGroupUrl: null, parentGroupName: null }); + createComponent({}, { parentGroupUrl: null, parentGroupName: null }); expect(findBreadcrumb().props('items')).toStrictEqual([ { text: 'Your work', href: '/' }, @@ -28,11 +31,34 @@ describe('New project form breadcrumbs', () => { }); it('renders group namespace breadcrumbs', () => { - createComponent({ parentGroupUrl: '/group/projects', parentGroupName: 'test group' }); + createComponent({}, { parentGroupUrl: '/group/projects', parentGroupName: 'test group' }); expect(findBreadcrumb().props('items')).toStrictEqual([ { text: 'test group', href: '/group/projects' }, { text: 'New project', href: '#' }, ]); }); + + it('renders a breadcrumb for a hash', () => { + createComponent( + { + choosenProjectType: { + key: 'blank', + value: 'blank_project', + selector: '#blank-project-pane', + title: 'Create blank project', + description: + 'Create a blank project to store your files, plan your work, and collaborate on code, among other things.', + }, + }, + { parentGroupUrl: null, parentGroupName: null }, + ); + + expect(findBreadcrumb().props('items')).toStrictEqual([ + { text: 'Your work', href: '/' }, + { text: 'Projects', href: '/dashboard/projects' }, + { text: 'New project', href: '#' }, + { text: 'Create blank project', href: 'blank_project' }, + ]); + }); }); -- GitLab From dadeb1b4df0104941c0916714a972aab3ed3a515 Mon Sep 17 00:00:00 2001 From: Julia Miocene Date: Wed, 5 Feb 2025 11:39:11 +0100 Subject: [PATCH 2/2] Apply suggestions --- .../projects/new_v2/components/app.vue | 139 +++--------------- .../new_v2/components/blank_project_form.vue | 7 +- .../new_v2/components/ci_cd_project_form.vue | 7 +- .../new_v2/components/form_breadcrumb.vue | 15 -- .../new_v2/components/import_project_form.vue | 7 +- .../components/template_project_form.vue | 9 +- .../javascripts/projects/new_v2/constants.js | 51 +++++++ .../components/single_choice_selector.vue | 14 +- .../new_v2/components/form_breadcrumb_spec.js | 34 +---- 9 files changed, 85 insertions(+), 198 deletions(-) create mode 100644 app/assets/javascripts/projects/new_v2/constants.js diff --git a/app/assets/javascripts/projects/new_v2/components/app.vue b/app/assets/javascripts/projects/new_v2/components/app.vue index 815d71b575774e..7de862a21aeafe 100644 --- a/app/assets/javascripts/projects/new_v2/components/app.vue +++ b/app/assets/javascripts/projects/new_v2/components/app.vue @@ -6,6 +6,7 @@ import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_templ import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue'; import SingleChoiceSelectorItem from '~/vue_shared/components/single_choice_selector_item.vue'; +import { OPTIONS } from '../constants'; import NewProjectDestinationSelect from './project_destination_select.vue'; import Breadcrumb from './form_breadcrumb.vue'; import CommandLine from './command_line.vue'; @@ -14,56 +15,6 @@ import TemplateProjectForm from './template_project_form.vue'; import CiCdProjectForm from './ci_cd_project_form.vue'; import ImportProjectForm from './import_project_form.vue'; -const OPTIONS = { - blank: { - key: 'blank', - value: 'blank_project', - selector: '#blank-project-pane', - title: s__('ProjectsNew|Create blank project'), - description: s__( - 'ProjectsNew|Create a blank project to store your files, plan your work, and collaborate on code, among other things.', - ), - }, - template: { - key: 'template', - value: 'create_from_template', - selector: '#create-from-template-pane', - title: s__('ProjectsNew|Create from template'), - description: s__( - 'ProjectsNew|Create a project pre-populated with the necessary files to get you started quickly.', - ), - }, - ci: { - key: 'ci', - value: 'cicd_for_external_repo', - selector: '#ci-cd-project-pane', - title: s__('ProjectsNew|Run CI/CD for external repository'), - description: s__('ProjectsNew|Connect your external repository to GitLab CI/CD.'), - }, - import: { - key: 'import', - value: 'import_project', - selector: '#import-project-pane', - title: s__('ProjectsNew|Import project'), - description: s__( - 'ProjectsNew|Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab.', - ), - icons: ['tanuki', 'github', 'bitbucket', 'gitea'], - disabledMessage: s__( - 'ProjectsNew|Contact an administrator to enable options for importing your project', - ), - }, - transfer: { - key: 'transfer', - value: 'transfer_project', - selector: '#transfer-project-pane', - title: s__('ProjectsNew|Direct transfer projects with a top-level Group'), - description: s__('ProjectsNew|Migrate your data from another GitLab instance.'), - disabled: true, - disabledMessage: s__('ProjectsNew|Available only for projects within groups'), - }, -}; - export default { OPTIONS, components: { @@ -163,7 +114,6 @@ export default { selectedNamespace: this.namespaceId && this.canSelectNamespace ? this.namespaceId : this.userNamespaceId, rootUrl: this.rootPath, - choosenProjectTypeName: null, }; }, @@ -190,30 +140,17 @@ export default { }, ); }, - choosenProjectType() { - return this.availableProjectTypes.find((p) => p.value === this.choosenProjectTypeName); - }, availableProjectTypes() { - const types = [OPTIONS.blank, OPTIONS.template]; - - if (this.canImportProjects && this.importSourcesEnabled) types.push(OPTIONS.import); - - if (this.isCiCdAvailable) types.push(OPTIONS.ci); - - // this option should be the last in a row - types.push(OPTIONS.transfer); - - return types; + return [ + OPTIONS.blank, + OPTIONS.template, + ...(this.canImportProjects && this.importSourcesEnabled ? [OPTIONS.import] : []), + ...(this.isCiCdAvailable ? [OPTIONS.ci] : []), + OPTIONS.transfer, + ]; }, }, - created() { - this.handleLocationHashChange(); - - window.addEventListener('hashchange', () => { - this.handleLocationHashChange(); - }); - }, methods: { choosePersonalNamespace() { this.selectedNamespace = this.userNamespaceId; @@ -221,43 +158,17 @@ export default { chooseGroupNamespace() { this.selectedNamespace = null; }, - handleLocationHashChange() { - this.choosenProjectTypeName = window.location.hash.substring(1) || null; - }, - showSecondStepForm(type) { - return this.choosenProjectTypeName && this.choosenProjectType.value === type; - }, - goToFirstStep() { - this.choosenProjectTypeName = null; - window.location.hash = ''; - }, - goToSecondStep() { - const formElement = this.$refs.projectTypeSelector.$el; - const projectType = formElement.querySelector('input:checked').value; - - if (projectType && this.availableProjectTypes.find((p) => p.value === projectType)) { - this.choosenProjectTypeName = projectType; - window.location.hash = `#${projectType}`; - } - }, }, }; - - - - + + + + diff --git a/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue b/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue index cdebe6392422d8..c7a98d71ab745f 100644 --- a/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue +++ b/app/assets/javascripts/projects/new_v2/components/blank_project_form.vue @@ -13,11 +13,6 @@ export default { required: true, }, }, - methods: { - goToFirstStep() { - this.$parent.goToFirstStep(); - }, - }, }; @@ -29,7 +24,7 @@ export default { diff --git a/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue b/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue index cdebe6392422d8..c7a98d71ab745f 100644 --- a/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue +++ b/app/assets/javascripts/projects/new_v2/components/ci_cd_project_form.vue @@ -13,11 +13,6 @@ export default { required: true, }, }, - methods: { - goToFirstStep() { - this.$parent.goToFirstStep(); - }, - }, }; @@ -29,7 +24,7 @@ export default { diff --git a/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue index 235ba5ff38d07c..8979b802373ba6 100644 --- a/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue +++ b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue @@ -10,13 +10,6 @@ export default { SuperSidebarToggle, }, inject: ['rootPath', 'projectsUrl', 'parentGroupUrl', 'parentGroupName'], - props: { - choosenProjectType: { - type: Object, - required: false, - default: null, - }, - }, computed: { breadcrumbs() { const breadcrumbs = this.parentGroupUrl @@ -26,14 +19,6 @@ export default { { text: s__('ProjectsNew|Projects'), href: this.projectsUrl }, ]; breadcrumbs.push({ text: s__('ProjectsNew|New project'), href: '#' }); - - if (this.choosenProjectType) { - breadcrumbs.push({ - text: this.choosenProjectType.title, - href: this.choosenProjectType.value, - }); - } - return breadcrumbs; }, }, diff --git a/app/assets/javascripts/projects/new_v2/components/import_project_form.vue b/app/assets/javascripts/projects/new_v2/components/import_project_form.vue index f6958c0a687492..e3d7df53a68c7e 100644 --- a/app/assets/javascripts/projects/new_v2/components/import_project_form.vue +++ b/app/assets/javascripts/projects/new_v2/components/import_project_form.vue @@ -13,11 +13,6 @@ export default { required: true, }, }, - methods: { - goToFirstStep() { - this.$parent.goToFirstStep(); - }, - }, }; @@ -29,7 +24,7 @@ export default { diff --git a/app/assets/javascripts/projects/new_v2/components/template_project_form.vue b/app/assets/javascripts/projects/new_v2/components/template_project_form.vue index fc00b843c4113d..97bf6d5744adc9 100644 --- a/app/assets/javascripts/projects/new_v2/components/template_project_form.vue +++ b/app/assets/javascripts/projects/new_v2/components/template_project_form.vue @@ -13,23 +13,18 @@ export default { required: true, }, }, - methods: { - goToFirstStep() { - this.$parent.goToFirstStep(); - }, - }, };