Commit af72f143 authored by Brandon Labuschagne's avatar Brandon Labuschagne

Migrate bootstrap modal to vue

This is being done for the blob dropdown
zone functionality within the project
breadcrumbs
parent 60444b92
......@@ -5,6 +5,7 @@ import {
GlDropdownSectionHeader,
GlDropdownItem,
GlIcon,
GlModalDirective,
} from '@gitlab/ui';
import permissionsQuery from 'shared_queries/repository/permissions.query.graphql';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
......@@ -12,12 +13,15 @@ import { __ } from '../../locale';
import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql';
import projectShortPathQuery from '../queries/project_short_path.query.graphql';
import UploadBlobModal from './upload_blob_modal.vue';
const ROW_TYPES = {
header: 'header',
divider: 'divider',
};
const UPLOAD_BLOB_MODAL_ID = 'modal-upload-blob';
export default {
components: {
GlDropdown,
......@@ -25,6 +29,7 @@ export default {
GlDropdownSectionHeader,
GlDropdownItem,
GlIcon,
UploadBlobModal,
},
apollo: {
projectShortPath: {
......@@ -46,6 +51,9 @@ export default {
},
},
},
directives: {
GlModal: GlModalDirective,
},
mixins: [getRefMixin],
props: {
currentPath: {
......@@ -63,6 +71,21 @@ export default {
required: false,
default: false,
},
canPushCode: {
type: Boolean,
required: false,
default: false,
},
selectedBranch: {
type: String,
required: false,
default: '',
},
originalBranch: {
type: String,
required: false,
default: '',
},
newBranchPath: {
type: String,
required: false,
......@@ -93,7 +116,13 @@ export default {
required: false,
default: null,
},
uploadPath: {
type: String,
required: false,
default: '',
},
},
uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
data() {
return {
projectShortPath: '',
......@@ -126,7 +155,10 @@ export default {
);
},
canCreateMrFromFork() {
return this.userPermissions.forkProject && this.userPermissions.createMergeRequestIn;
return this.userPermissions?.forkProject && this.userPermissions?.createMergeRequestIn;
},
showUploadModal() {
return this.canEditTree && !this.$apollo.queries.userPermissions.loading;
},
dropdownItems() {
const items = [];
......@@ -149,10 +181,9 @@ export default {
{
attrs: {
href: '#modal-upload-blob',
'data-target': '#modal-upload-blob',
'data-toggle': 'modal',
},
text: __('Upload file'),
modalId: UPLOAD_BLOB_MODAL_ID,
},
{
attrs: {
......@@ -253,12 +284,26 @@ export default {
<gl-icon name="chevron-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">
<component
:is="getComponent(item.type)"
:key="i"
v-bind="item.attrs"
v-gl-modal="item.modalId || null"
>
{{ item.text }}
</component>
</template>
</gl-dropdown>
</li>
</ol>
<upload-blob-modal
v-if="showUploadModal"
:modal-id="$options.uploadBlobModalId"
:commit-message="__('Upload New File')"
:target-branch="selectedBranch"
:original-branch="originalBranch"
:can-push-code="canPushCode"
:path="uploadPath"
/>
</nav>
</template>
import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import { escapeFileUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import initWebIdeLink from '~/pages/projects/shared/web_ide_link';
import { parseBoolean } from '../lib/utils/common_utils';
import { escapeFileUrl } from '../lib/utils/url_utility';
import { __ } from '../locale';
import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
......@@ -55,6 +55,8 @@ export default function setupVueRepositoryList() {
const {
canCollaborate,
canEditTree,
canPushCode,
selectedBranch,
newBranchPath,
newTagPath,
newBlobPath,
......@@ -65,8 +67,7 @@ export default function setupVueRepositoryList() {
newDirPath,
} = breadcrumbEl.dataset;
router.afterEach(({ params: { path = '/' } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, path);
router.afterEach(({ params: { path } }) => {
updateFormAction('.js-create-dir-form', newDirPath, path);
});
......@@ -81,12 +82,16 @@ export default function setupVueRepositoryList() {
currentPath: this.$route.params.path,
canCollaborate: parseBoolean(canCollaborate),
canEditTree: parseBoolean(canEditTree),
canPushCode: parseBoolean(canPushCode),
originalBranch: ref,
selectedBranch,
newBranchPath,
newTagPath,
newBlobPath,
forkNewBlobPath,
forkNewDirectoryPath,
forkUploadBlobPath,
uploadPath,
},
});
},
......
......@@ -131,6 +131,8 @@ module TreeHelper
def breadcrumb_data_attributes
attrs = {
selected_branch: selected_branch,
can_push_code: can?(current_user, :push_code, @project).to_s,
can_collaborate: can_collaborate_with_project?(@project).to_s,
new_blob_path: project_new_blob_path(@project, @ref),
upload_path: project_create_blob_path(@project, @ref),
......
......@@ -21,5 +21,4 @@
#js-tree-list{ data: vue_file_list_data(project, ref) }
- 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'
---
title: Migrate bootstrap modal to GlModal for repo single file uploads
merge_request: 55587
author:
type: changed
......@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe 'Projects > Files > User uploads files' do
include DropzoneHelper
let(:user) { create(:user) }
let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
......@@ -17,36 +15,15 @@ RSpec.describe 'Projects > Files > User uploads files' do
context 'when a user has write access' do
before do
visit(project_tree_path(project))
wait_for_requests
end
include_examples 'it uploads and commit a new text file'
include_examples 'it uploads and commit a new image file'
it 'uploads a file to a sub-directory', :js do
click_link 'files'
page.within('.repo-breadcrumb') do
expect(page).to have_content('files')
end
find('.add-to-tree').click
click_link('Upload file')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
end
click_button('Upload file')
expect(page).to have_content('New commit message')
page.within('.repo-breadcrumb') do
expect(page).to have_content('files')
expect(page).to have_content('doc_sample.txt')
end
end
include_examples 'it uploads a file to a sub-directory'
end
context 'when a user does not have write access' do
......
......@@ -17,11 +17,15 @@ RSpec.describe 'Projects > Show > User uploads files' do
context 'when a user has write access' do
before do
visit(project_path(project))
wait_for_requests
end
include_examples 'it uploads and commit a new text file'
include_examples 'it uploads and commit a new image file'
include_examples 'it uploads a file to a sub-directory'
end
context 'when a user does not have write access' do
......
import { GlDropdown } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
let vm;
function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
},
});
}
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
describe('Repository breadcrumbs component', () => {
let wrapper;
const factory = (currentPath, extraProps = {}) => {
const $apollo = {
queries: {
userPermissions: {
loading: true,
},
},
};
wrapper = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
},
mocks: { $apollo },
});
};
const findUploadBlobModal = () => wrapper.find(UploadBlobModal);
afterEach(() => {
vm.destroy();
wrapper.destroy();
});
it.each`
......@@ -30,13 +42,13 @@ describe('Repository breadcrumbs component', () => {
`('renders $linkCount links for path $path', ({ path, linkCount }) => {
factory(path);
expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount);
expect(wrapper.findAll(RouterLinkStub).length).toEqual(linkCount);
});
it('escapes hash in directory path', () => {
factory('app/assets/javascripts#');
expect(vm.findAll(RouterLinkStub).at(3).props('to')).toEqual(
expect(wrapper.findAll(RouterLinkStub).at(3).props('to')).toEqual(
'/-/tree/app/assets/javascripts%23',
);
});
......@@ -44,26 +56,44 @@ describe('Repository breadcrumbs component', () => {
it('renders last link as active', () => {
factory('app/assets');
expect(vm.findAll(RouterLinkStub).at(2).attributes('aria-current')).toEqual('page');
expect(wrapper.findAll(RouterLinkStub).at(2).attributes('aria-current')).toEqual('page');
});
it('does not render add to tree dropdown when permissions are false', () => {
it('does not render add to tree dropdown when permissions are false', async () => {
factory('/', { canCollaborate: false });
vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
wrapper.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
return vm.vm.$nextTick(() => {
expect(vm.find(GlDropdown).exists()).toBe(false);
});
await wrapper.vm.$nextTick();
expect(wrapper.find(GlDropdown).exists()).toBe(false);
});
it('renders add to tree dropdown when permissions are true', () => {
it('renders add to tree dropdown when permissions are true', async () => {
factory('/', { canCollaborate: true });
vm.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
wrapper.setData({ userPermissions: { forkProject: true, createMergeRequestIn: true } });
await wrapper.vm.$nextTick();
expect(wrapper.find(GlDropdown).exists()).toBe(true);
});
describe('renders the upload blob modal', () => {
beforeEach(() => {
factory('/', { canEditTree: true });
});
it('does not render the modal while loading', () => {
expect(findUploadBlobModal().exists()).toBe(false);
});
it('renders the modal once loaded', async () => {
wrapper.setData({ $apollo: { queries: { userPermissions: { loading: false } } } });
await wrapper.vm.$nextTick();
return vm.vm.$nextTick(() => {
expect(vm.find(GlDropdown).exists()).toBe(true);
expect(findUploadBlobModal().exists()).toBe(true);
});
});
});
......@@ -10,7 +10,7 @@ RSpec.shared_examples 'it uploads and commit a new text file' do
wait_for_requests
end
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
......@@ -42,7 +42,7 @@ RSpec.shared_examples 'it uploads and commit a new image file' do
wait_for_requests
end
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'))
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'logo_sample.svg'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
......@@ -70,9 +70,11 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
expect(page).to have_content(fork_message)
wait_for_all_requests
find('.add-to-tree').click
click_link('Upload file')
drop_in_dropzone(File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'))
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
......@@ -95,6 +97,33 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
end
end
RSpec.shared_examples 'it uploads a file to a sub-directory' do
it 'uploads a file to a sub-directory', :js do
click_link 'files'
page.within('.repo-breadcrumb') do
expect(page).to have_content('files')
end
find('.add-to-tree').click
click_link('Upload file')
attach_file('upload_file', File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt'), make_visible: true)
page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message')
end
click_button('Upload file')
expect(page).to have_content('New commit message')
page.within('.repo-breadcrumb') do
expect(page).to have_content('files')
expect(page).to have_content('doc_sample.txt')
end
end
end
RSpec.shared_examples 'uploads and commits a new text file via "upload file" button' do
it 'uploads and commits a new text file via "upload file" button', :js do
find('[data-testid="upload-file-button"]').click
......
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