diff --git a/app/assets/javascripts/projects/new_v2/components/app.vue b/app/assets/javascripts/projects/new_v2/components/app.vue index 91b3551c237ed50195cc61692e001c8fccdf016b..6f912ed7a548db1cb7b658ccb00c46f1ab1060f3 100644 --- a/app/assets/javascripts/projects/new_v2/components/app.vue +++ b/app/assets/javascripts/projects/new_v2/components/app.vue @@ -7,6 +7,7 @@ import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector import SingleChoiceSelectorItem from '~/vue_shared/components/single_choice_selector_item.vue'; import NewProjectDestinationSelect from './project_destination_select.vue'; +import Breadcrumb from './form_breadcrumb.vue'; import CommandLine from './command_line.vue'; const OPTIONS = { @@ -68,6 +69,7 @@ export default { SingleChoiceSelector, SingleChoiceSelectorItem, NewProjectDestinationSelect, + Breadcrumb, CommandLine, }, directives: { @@ -189,75 +191,78 @@ export default { </script> <template> - <multi-step-form-template :title="__('Create new project')" :current-step="1"> - <template #form> - <gl-form-group - v-if="canSelectNamespace" - :label="s__('ProjectNew|What do you want to create?')" - > - <gl-button-group class="gl-w-full"> - <gl-button - category="primary" - variant="default" - size="medium" - :selected="isPersonalProject" - class="gl-w-full" - data-testid="personal-namespace-button" - @click="choosePersonalNamespace" - > - {{ s__('ProjectsNew|A personal project') }} - </gl-button> - <gl-button - category="primary" - variant="default" - size="medium" - :selected="!isPersonalProject" - class="gl-w-full" - data-testid="group-namespace-button" - @click="chooseGroupNamespace" - > - {{ s__('ProjectsNew|A project within a group') }} - </gl-button> - </gl-button-group> - </gl-form-group> + <div> + <breadcrumb /> - <gl-form-group v-if="!isPersonalProject" :label="s__('ProjectsNew|Choose a group')"> - <new-project-destination-select - :namespace-full-path="namespaceFullPath" - :namespace-id="namespaceId" - :track-label="trackLabel" - :root-url="rootUrl" - :groups-only="true" - data-testid="group-selector" - /> - </gl-form-group> + <multi-step-form-template :title="__('Create new project')" :current-step="1"> + <template #form> + <gl-form-group + v-if="canSelectNamespace" + :label="s__('ProjectNew|What do you want to create?')" + > + <gl-button-group class="gl-w-full"> + <gl-button + category="primary" + variant="default" + size="medium" + :selected="isPersonalProject" + class="gl-w-full" + data-testid="personal-namespace-button" + @click="choosePersonalNamespace" + > + {{ s__('ProjectsNew|A personal project') }} + </gl-button> + <gl-button + category="primary" + variant="default" + size="medium" + :selected="!isPersonalProject" + class="gl-w-full" + data-testid="group-namespace-button" + @click="chooseGroupNamespace" + > + {{ s__('ProjectsNew|A project within a group') }} + </gl-button> + </gl-button-group> + </gl-form-group> - <single-choice-selector v-if="canChooseOption" checked="blank_project"> - <single-choice-selector-item v-bind="$options.OPTIONS.blank" /> - <single-choice-selector-item v-bind="$options.OPTIONS.template" /> - <single-choice-selector-item - v-if="canImportProjects && importSourcesEnabled" - v-bind="$options.OPTIONS.ci" - /> - <single-choice-selector-item v-if="isCiCdAvailable" v-bind="$options.OPTIONS.import"> - {{ $options.OPTIONS.import.title }} - <div class="gl-flex gl-gap-2"> - <gl-icon name="tanuki" /> - <gl-icon name="github" /> - <gl-icon name="bitbucket" /> - <gl-icon name="gitea" /> - </div> - </single-choice-selector-item> - <single-choice-selector-item v-bind="$options.OPTIONS.transfer" :disabled="true" /> - </single-choice-selector> - <gl-alert v-else variant="danger" :dismissible="false"> - {{ errorMessage }} - </gl-alert> - </template> - <template #footer> - <div v-if="newProjectGuidelines" v-safe-html="newProjectGuidelines" class="gl-mb-6"></div> + <gl-form-group v-if="!isPersonalProject" :label="s__('ProjectsNew|Choose a group')"> + <new-project-destination-select + :namespace-full-path="namespaceFullPath" + :namespace-id="namespaceId" + :track-label="trackLabel" + :root-url="rootUrl" + :groups-only="true" + data-testid="group-selector" + /> + </gl-form-group> - <command-line v-if="isPersonalProject" /> - </template> - </multi-step-form-template> + <single-choice-selector v-if="canChooseOption" checked="blank_project"> + <single-choice-selector-item v-bind="$options.OPTIONS.blank" /> + <single-choice-selector-item v-bind="$options.OPTIONS.template" /> + <single-choice-selector-item + v-if="canImportProjects && importSourcesEnabled" + v-bind="$options.OPTIONS.ci" + /> + <single-choice-selector-item v-if="isCiCdAvailable" v-bind="$options.OPTIONS.import"> + {{ $options.OPTIONS.import.title }} + <div class="gl-flex gl-gap-2"> + <gl-icon name="tanuki" /> + <gl-icon name="github" /> + <gl-icon name="bitbucket" /> + <gl-icon name="gitea" /> + </div> + </single-choice-selector-item> + <single-choice-selector-item v-bind="$options.OPTIONS.transfer" :disabled="true" /> + </single-choice-selector> + <gl-alert v-else variant="danger" :dismissible="false"> + {{ errorMessage }} + </gl-alert> + </template> + <template #footer> + <div v-if="newProjectGuidelines" v-safe-html="newProjectGuidelines" class="gl-mb-6"></div> + <command-line v-if="isPersonalProject" /> + </template> + </multi-step-form-template> + </div> </template> diff --git a/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue new file mode 100644 index 0000000000000000000000000000000000000000..8979b802373ba64ba60b2b2e159259c64cce9cfc --- /dev/null +++ b/app/assets/javascripts/projects/new_v2/components/form_breadcrumb.vue @@ -0,0 +1,36 @@ +<script> +import { GlBreadcrumb } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import SuperSidebarToggle from '~/super_sidebar/components/super_sidebar_toggle.vue'; +import { JS_TOGGLE_EXPAND_CLASS } from '~/super_sidebar/constants'; + +export default { + components: { + GlBreadcrumb, + SuperSidebarToggle, + }, + inject: ['rootPath', 'projectsUrl', 'parentGroupUrl', 'parentGroupName'], + computed: { + breadcrumbs() { + const breadcrumbs = this.parentGroupUrl + ? [{ text: this.parentGroupName, href: this.parentGroupUrl }] + : [ + { text: s__('Navigation|Your work'), href: this.rootPath }, + { text: s__('ProjectsNew|Projects'), href: this.projectsUrl }, + ]; + breadcrumbs.push({ text: s__('ProjectsNew|New project'), href: '#' }); + return breadcrumbs; + }, + }, + JS_TOGGLE_EXPAND_CLASS, +}; +</script> + +<template> + <div class="top-bar-fixed container-fluid" data-testid="top-bar"> + <div class="top-bar-container gl-border-b gl-flex gl-items-center gl-gap-2"> + <super-sidebar-toggle :class="$options.JS_TOGGLE_EXPAND_CLASS" class="xl:gl-hidden" /> + <gl-breadcrumb :items="breadcrumbs" data-testid="breadcrumb-links" class="gl-grow" /> + </div> + </div> +</template> diff --git a/app/assets/javascripts/projects/new_v2/index.js b/app/assets/javascripts/projects/new_v2/index.js index 1e039e3a44b3f9ed089744355ee43288410de483..204451672e9524ac300b053caef0ff7c378063d3 100644 --- a/app/assets/javascripts/projects/new_v2/index.js +++ b/app/assets/javascripts/projects/new_v2/index.js @@ -50,10 +50,15 @@ export function initNewProjectForm() { }; const provide = { - userNamespaceId, canCreateProject: parseBoolean(canCreateProject), projectHelpPath, pushToCreateProjectCommand, + namespaceId, + userNamespaceId, + rootPath, + parentGroupUrl, + parentGroupName, + projectsUrl, }; return new Vue({ diff --git a/spec/frontend/projects/new_v2/components/app_spec.js b/spec/frontend/projects/new_v2/components/app_spec.js index f616fd060320bdfc96f8c667aca7505cf2104238..b8b3ada84a8e3fbeef9d207e12285cb7697c4ab7 100644 --- a/spec/frontend/projects/new_v2/components/app_spec.js +++ b/spec/frontend/projects/new_v2/components/app_spec.js @@ -1,6 +1,7 @@ import { GlAlert } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import App from '~/projects/new_v2/components/app.vue'; +import FormBreadcrumb from '~/projects/new_v2/components/form_breadcrumb.vue'; import CommandLine from '~/projects/new_v2/components/command_line.vue'; import MultiStepFormTemplate from '~/vue_shared/components/multi_step_form_template.vue'; import SingleChoiceSelector from '~/vue_shared/components/single_choice_selector.vue'; @@ -26,10 +27,17 @@ describe('New project creation app', () => { }; const findMultiStepForm = () => wrapper.findComponent(MultiStepFormTemplate); + const findBreadcrumbs = () => wrapper.findComponent(FormBreadcrumb); const findSingleChoiceSelector = () => wrapper.findComponent(SingleChoiceSelector); const findAlert = () => wrapper.findComponent(GlAlert); const findCommandLine = () => wrapper.findComponent(CommandLine); + it('renders breadcrumbs', () => { + createComponent(); + + expect(findBreadcrumbs().exists()).toBe(true); + }); + it('renders a form', () => { createComponent(); diff --git a/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js b/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..42f7c833b01589cc84a8ef4d5e79f82e63199897 --- /dev/null +++ b/spec/frontend/projects/new_v2/components/form_breadcrumb_spec.js @@ -0,0 +1,38 @@ +import { GlBreadcrumb } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import FormBreadcrumb from '~/projects/new_v2/components/form_breadcrumb.vue'; + +describe('New project form breadcrumbs', () => { + let wrapper; + + const createComponent = (props = {}) => { + wrapper = shallowMountExtended(FormBreadcrumb, { + provide: { + rootPath: '/', + projectsUrl: '/dashboard/projects', + ...props, + }, + }); + }; + + const findBreadcrumb = () => wrapper.findComponent(GlBreadcrumb); + + it('renders personal namespace breadcrumbs', () => { + createComponent({ parentGroupUrl: null, parentGroupName: null }); + + expect(findBreadcrumb().props('items')).toStrictEqual([ + { text: 'Your work', href: '/' }, + { text: 'Projects', href: '/dashboard/projects' }, + { text: 'New project', href: '#' }, + ]); + }); + + it('renders group namespace breadcrumbs', () => { + createComponent({ parentGroupUrl: '/group/projects', parentGroupName: 'test group' }); + + expect(findBreadcrumb().props('items')).toStrictEqual([ + { text: 'test group', href: '/group/projects' }, + { text: 'New project', href: '#' }, + ]); + }); +});