Commit f97a73fa authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'vue-file-list-breadcrumb-dropdown' into 'master'

Added add to tree dropdown to Vue files breadcrumb

Closes #62865

See merge request gitlab-org/gitlab-ce!30177
parents bbbaab1e 1e3877a9
<script> <script>
import { GlDropdown, GlDropdownDivider, GlDropdownHeader, GlDropdownItem } from '@gitlab/ui';
import { __ } from '../../locale';
import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref'; import getRefMixin from '../mixins/get_ref';
import getProjectShortPath from '../queries/getProjectShortPath.query.graphql'; import getProjectShortPath from '../queries/getProjectShortPath.query.graphql';
import getProjectPath from '../queries/getProjectPath.query.graphql';
import getPermissions from '../queries/getPermissions.query.graphql';
const ROW_TYPES = {
header: 'header',
divider: 'divider',
};
export default { export default {
components: {
GlDropdown,
GlDropdownDivider,
GlDropdownHeader,
GlDropdownItem,
Icon,
},
apollo: { apollo: {
projectShortPath: { projectShortPath: {
query: getProjectShortPath, query: getProjectShortPath,
}, },
projectPath: {
query: getProjectPath,
},
userPermissions: {
query: getPermissions,
variables() {
return {
projectPath: this.projectPath,
};
},
update: data => data.project.userPermissions,
},
}, },
mixins: [getRefMixin], mixins: [getRefMixin],
props: { props: {
...@@ -15,10 +44,52 @@ export default { ...@@ -15,10 +44,52 @@ export default {
required: false, required: false,
default: '/', default: '/',
}, },
canCollaborate: {
type: Boolean,
required: false,
default: false,
},
canEditTree: {
type: Boolean,
required: false,
default: false,
},
newBranchPath: {
type: String,
required: false,
default: null,
},
newTagPath: {
type: String,
required: false,
default: null,
},
newBlobPath: {
type: String,
required: false,
default: null,
},
forkNewBlobPath: {
type: String,
required: false,
default: null,
},
forkNewDirectoryPath: {
type: String,
required: false,
default: null,
},
forkUploadBlobPath: {
type: String,
required: false,
default: null,
},
}, },
data() { data() {
return { return {
projectShortPath: '', projectShortPath: '',
projectPath: '',
userPermissions: {},
}; };
}, },
computed: { computed: {
...@@ -39,11 +110,112 @@ export default { ...@@ -39,11 +110,112 @@ export default {
[{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }],
); );
}, },
canCreateMrFromFork() {
return this.userPermissions.forkProject && this.userPermissions.createMergeRequestIn;
},
dropdownItems() {
const items = [];
if (this.canEditTree) {
items.push(
{
type: ROW_TYPES.header,
text: __('This directory'),
},
{
attrs: {
href: this.newBlobPath,
class: 'qa-new-file-option',
},
text: __('New file'),
},
{
attrs: {
href: '#modal-upload-blob',
'data-target': '#modal-upload-blob',
'data-toggle': 'modal',
},
text: __('Upload file'),
},
{
attrs: {
href: '#modal-create-new-dir',
'data-target': '#modal-create-new-dir',
'data-toggle': 'modal',
},
text: __('New directory'),
},
);
} else if (this.canCreateMrFromFork) {
items.push(
{
attrs: {
href: this.forkNewBlobPath,
'data-method': 'post',
},
text: __('New file'),
},
{
attrs: {
href: this.forkUploadBlobPath,
'data-method': 'post',
},
text: __('Upload file'),
},
{
attrs: {
href: this.forkNewDirectoryPath,
'data-method': 'post',
},
text: __('New directory'),
},
);
}
if (this.userPermissions.pushCode) {
items.push(
{
type: ROW_TYPES.divider,
},
{
type: ROW_TYPES.header,
text: __('This repository'),
},
{
attrs: {
href: this.newBranchPath,
},
text: __('New branch'),
},
{
attrs: {
href: this.newTagPath,
},
text: __('New tag'),
},
);
}
return items;
},
renderAddToTreeDropdown() {
return this.canCollaborate || this.canCreateMrFromFork;
},
}, },
methods: { methods: {
isLast(i) { isLast(i) {
return i === this.pathLinks.length - 1; return i === this.pathLinks.length - 1;
}, },
getComponent(type) {
switch (type) {
case ROW_TYPES.divider:
return 'gl-dropdown-divider';
case ROW_TYPES.header:
return 'gl-dropdown-header';
default:
return 'gl-dropdown-item';
}
},
}, },
}; };
</script> </script>
...@@ -56,6 +228,20 @@ export default { ...@@ -56,6 +228,20 @@ export default {
{{ link.name }} {{ link.name }}
</router-link> </router-link>
</li> </li>
<li v-if="renderAddToTreeDropdown" class="breadcrumb-item">
<gl-dropdown toggle-class="add-to-tree qa-add-to-tree ml-1">
<template slot="button-content">
<span class="sr-only">{{ __('Add to tree') }}</span>
<icon name="plus" :size="16" class="float-left" />
<icon name="arrow-down" :size="16" class="float-left" />
</template>
<template v-for="(item, i) in dropdownItems">
<component :is="getComponent(item.type)" :key="i" v-bind="item.attrs">
{{ item.text }}
</component>
</template>
</gl-dropdown>
</li>
</ol> </ol>
</nav> </nav>
</template> </template>
...@@ -5,6 +5,7 @@ import Breadcrumbs from './components/breadcrumbs.vue'; ...@@ -5,6 +5,7 @@ import Breadcrumbs from './components/breadcrumbs.vue';
import LastCommit from './components/last_commit.vue'; import LastCommit from './components/last_commit.vue';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { setTitle } from './utils/title'; import { setTitle } from './utils/title';
import { parseBoolean } from '../lib/utils/common_utils';
export default function setupVueRepositoryList() { export default function setupVueRepositoryList() {
const el = document.getElementById('js-tree-list'); const el = document.getElementById('js-tree-list');
...@@ -36,19 +37,42 @@ export default function setupVueRepositoryList() { ...@@ -36,19 +37,42 @@ export default function setupVueRepositoryList() {
.forEach(elem => elem.classList.toggle('hidden', !isRoot)); .forEach(elem => elem.classList.toggle('hidden', !isRoot));
}); });
// eslint-disable-next-line no-new const breadcrumbEl = document.getElementById('js-repo-breadcrumb');
new Vue({
el: document.getElementById('js-repo-breadcrumb'), if (breadcrumbEl) {
router, const {
apolloProvider, canCollaborate,
render(h) { canEditTree,
return h(Breadcrumbs, { newBranchPath,
props: { newTagPath,
currentPath: this.$route.params.pathMatch, newBlobPath,
}, forkNewBlobPath,
}); forkNewDirectoryPath,
}, forkUploadBlobPath,
}); } = breadcrumbEl.dataset;
// eslint-disable-next-line no-new
new Vue({
el: breadcrumbEl,
router,
apolloProvider,
render(h) {
return h(Breadcrumbs, {
props: {
currentPath: this.$route.params.pathMatch,
canCollaborate: parseBoolean(canCollaborate),
canEditTree: parseBoolean(canEditTree),
newBranchPath,
newTagPath,
newBlobPath,
forkNewBlobPath,
forkNewDirectoryPath,
forkUploadBlobPath,
},
});
},
});
}
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
......
query getPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
userPermissions {
pushCode
forkProject
createMergeRequestIn
}
}
}
...@@ -154,4 +154,36 @@ module TreeHelper ...@@ -154,4 +154,36 @@ module TreeHelper
"logs-path" => logs_path "logs-path" => logs_path
} }
end end
def breadcrumb_data_attributes
attrs = {
can_collaborate: can_collaborate_with_project?(@project).to_s,
new_blob_path: project_new_blob_path(@project, @id),
new_branch_path: new_project_branch_path(@project),
new_tag_path: new_project_tag_path(@project),
can_edit_tree: can_edit_tree?.to_s
}
if can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
continue_param = {
to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now
}
attrs.merge!(
fork_new_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param),
fork_new_directory_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
to: request.fullpath,
notice: _("%{edit_in_new_fork_notice} Try to create a new directory again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
})),
fork_upload_blob_path: project_forks_path(@project, namespace_key: current_user.namespace.id, continue: continue_param.merge({
to: request.fullpath,
notice: _("%{edit_in_new_fork_notice} Try to upload a file again.") % { edit_in_new_fork_notice: edit_in_new_fork_notice }
}))
)
end
attrs
end
end end
...@@ -20,6 +20,9 @@ ...@@ -20,6 +20,9 @@
- if vue_file_list_enabled? - if vue_file_list_enabled?
#js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } } #js-tree-list{ data: { project_path: @project.full_path, project_short_path: @project.path, ref: ref, full_name: @project.name_with_namespace } }
- if can_edit_tree?
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, @id), method: :post
= render 'projects/blob/new_dir'
- if @tree.readme - if @tree.readme
= render "projects/tree/readme", readme: @tree.readme = render "projects/tree/readme", readme: @tree.readme
- else - else
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
- addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' } - addtotree_toggle_attributes = { title: _("You can only add files when you are on a branch"), data: { container: 'body' }, class: 'disabled has-tooltip' }
- if vue_file_list_enabled? - if vue_file_list_enabled?
#js-repo-breadcrumb #js-repo-breadcrumb{ data: breadcrumb_data_attributes }
- else - else
%ul.breadcrumb.repo-breadcrumb %ul.breadcrumb.repo-breadcrumb
%li.breadcrumb-item %li.breadcrumb-item
......
...@@ -138,9 +138,15 @@ msgstr[1] "" ...@@ -138,9 +138,15 @@ msgstr[1] ""
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again." msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
msgstr "" msgstr ""
msgid "%{edit_in_new_fork_notice} Try to create a new directory again."
msgstr ""
msgid "%{edit_in_new_fork_notice} Try to revert this commit again." msgid "%{edit_in_new_fork_notice} Try to revert this commit again."
msgstr "" msgstr ""
msgid "%{edit_in_new_fork_notice} Try to upload a file again."
msgstr ""
msgid "%{filePath} deleted" msgid "%{filePath} deleted"
msgstr "" msgstr ""
...@@ -704,6 +710,9 @@ msgstr "" ...@@ -704,6 +710,9 @@ msgstr ""
msgid "Add to review" msgid "Add to review"
msgstr "" msgstr ""
msgid "Add to tree"
msgstr ""
msgid "Add user(s) to the group:" msgid "Add user(s) to the group:"
msgstr "" msgstr ""
......
import { shallowMount, RouterLinkStub } from '@vue/test-utils'; import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import { GlDropdown } from '@gitlab/ui';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue'; import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm; let vm;
function factory(currentPath) { function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, { vm = shallowMount(Breadcrumbs, {
propsData: { propsData: {
currentPath, currentPath,
...extraProps,
}, },
stubs: { stubs: {
RouterLink: RouterLinkStub, RouterLink: RouterLinkStub,
...@@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => { ...@@ -41,4 +43,20 @@ describe('Repository breadcrumbs component', () => {
.attributes('aria-current'), .attributes('aria-current'),
).toEqual('page'); ).toEqual('page');
}); });
it('does not render add to tree dropdown when permissions are false', () => {
factory('/', { canCollaborate: false });
vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
expect(vm.find(GlDropdown).exists()).toBe(false);
});
it('renders add to tree dropdown when permissions are true', () => {
factory('/', { canCollaborate: true });
vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
expect(vm.find(GlDropdown).exists()).toBe(true);
});
}); });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment