Commit c2688ebe authored by Martin Wortschack's avatar Martin Wortschack

Merge branch...

Merge branch '254280-replace-bootstrap-modal-trigger-in-app-assets-javascripts-blob-blob_file_dropzone-js' into 'master'

Migrate bootstrap File Upload modal to GlModal

See merge request gitlab-org/gitlab!55587
parents ec2e6fa9 af72f143
...@@ -5,6 +5,7 @@ import { ...@@ -5,6 +5,7 @@ import {
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
GlModalDirective,
} from '@gitlab/ui'; } from '@gitlab/ui';
import permissionsQuery from 'shared_queries/repository/permissions.query.graphql'; import permissionsQuery from 'shared_queries/repository/permissions.query.graphql';
import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility'; import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
...@@ -12,12 +13,15 @@ import { __ } from '../../locale'; ...@@ -12,12 +13,15 @@ import { __ } from '../../locale';
import getRefMixin from '../mixins/get_ref'; import getRefMixin from '../mixins/get_ref';
import projectPathQuery from '../queries/project_path.query.graphql'; import projectPathQuery from '../queries/project_path.query.graphql';
import projectShortPathQuery from '../queries/project_short_path.query.graphql'; import projectShortPathQuery from '../queries/project_short_path.query.graphql';
import UploadBlobModal from './upload_blob_modal.vue';
const ROW_TYPES = { const ROW_TYPES = {
header: 'header', header: 'header',
divider: 'divider', divider: 'divider',
}; };
const UPLOAD_BLOB_MODAL_ID = 'modal-upload-blob';
export default { export default {
components: { components: {
GlDropdown, GlDropdown,
...@@ -25,6 +29,7 @@ export default { ...@@ -25,6 +29,7 @@ export default {
GlDropdownSectionHeader, GlDropdownSectionHeader,
GlDropdownItem, GlDropdownItem,
GlIcon, GlIcon,
UploadBlobModal,
}, },
apollo: { apollo: {
projectShortPath: { projectShortPath: {
...@@ -46,6 +51,9 @@ export default { ...@@ -46,6 +51,9 @@ export default {
}, },
}, },
}, },
directives: {
GlModal: GlModalDirective,
},
mixins: [getRefMixin], mixins: [getRefMixin],
props: { props: {
currentPath: { currentPath: {
...@@ -63,6 +71,21 @@ export default { ...@@ -63,6 +71,21 @@ export default {
required: false, required: false,
default: false, default: false,
}, },
canPushCode: {
type: Boolean,
required: false,
default: false,
},
selectedBranch: {
type: String,
required: false,
default: '',
},
originalBranch: {
type: String,
required: false,
default: '',
},
newBranchPath: { newBranchPath: {
type: String, type: String,
required: false, required: false,
...@@ -93,7 +116,13 @@ export default { ...@@ -93,7 +116,13 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
uploadPath: {
type: String,
required: false,
default: '',
},
}, },
uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
data() { data() {
return { return {
projectShortPath: '', projectShortPath: '',
...@@ -126,7 +155,10 @@ export default { ...@@ -126,7 +155,10 @@ export default {
); );
}, },
canCreateMrFromFork() { 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() { dropdownItems() {
const items = []; const items = [];
...@@ -149,10 +181,9 @@ export default { ...@@ -149,10 +181,9 @@ export default {
{ {
attrs: { attrs: {
href: '#modal-upload-blob', href: '#modal-upload-blob',
'data-target': '#modal-upload-blob',
'data-toggle': 'modal',
}, },
text: __('Upload file'), text: __('Upload file'),
modalId: UPLOAD_BLOB_MODAL_ID,
}, },
{ {
attrs: { attrs: {
...@@ -253,12 +284,26 @@ export default { ...@@ -253,12 +284,26 @@ export default {
<gl-icon name="chevron-down" :size="16" class="float-left" /> <gl-icon name="chevron-down" :size="16" class="float-left" />
</template> </template>
<template v-for="(item, i) in dropdownItems"> <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 }} {{ item.text }}
</component> </component>
</template> </template>
</gl-dropdown> </gl-dropdown>
</li> </li>
</ol> </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> </nav>
</template> </template>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import Vue from 'vue'; 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 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 App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue'; import Breadcrumbs from './components/breadcrumbs.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue'; import DirectoryDownloadLinks from './components/directory_download_links.vue';
...@@ -55,6 +55,8 @@ export default function setupVueRepositoryList() { ...@@ -55,6 +55,8 @@ export default function setupVueRepositoryList() {
const { const {
canCollaborate, canCollaborate,
canEditTree, canEditTree,
canPushCode,
selectedBranch,
newBranchPath, newBranchPath,
newTagPath, newTagPath,
newBlobPath, newBlobPath,
...@@ -65,8 +67,7 @@ export default function setupVueRepositoryList() { ...@@ -65,8 +67,7 @@ export default function setupVueRepositoryList() {
newDirPath, newDirPath,
} = breadcrumbEl.dataset; } = breadcrumbEl.dataset;
router.afterEach(({ params: { path = '/' } }) => { router.afterEach(({ params: { path } }) => {
updateFormAction('.js-upload-blob-form', uploadPath, path);
updateFormAction('.js-create-dir-form', newDirPath, path); updateFormAction('.js-create-dir-form', newDirPath, path);
}); });
...@@ -81,12 +82,16 @@ export default function setupVueRepositoryList() { ...@@ -81,12 +82,16 @@ export default function setupVueRepositoryList() {
currentPath: this.$route.params.path, currentPath: this.$route.params.path,
canCollaborate: parseBoolean(canCollaborate), canCollaborate: parseBoolean(canCollaborate),
canEditTree: parseBoolean(canEditTree), canEditTree: parseBoolean(canEditTree),
canPushCode: parseBoolean(canPushCode),
originalBranch: ref,
selectedBranch,
newBranchPath, newBranchPath,
newTagPath, newTagPath,
newBlobPath, newBlobPath,
forkNewBlobPath, forkNewBlobPath,
forkNewDirectoryPath, forkNewDirectoryPath,
forkUploadBlobPath, forkUploadBlobPath,
uploadPath,
}, },
}); });
}, },
......
...@@ -131,6 +131,8 @@ module TreeHelper ...@@ -131,6 +131,8 @@ module TreeHelper
def breadcrumb_data_attributes def breadcrumb_data_attributes
attrs = { 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, can_collaborate: can_collaborate_with_project?(@project).to_s,
new_blob_path: project_new_blob_path(@project, @ref), new_blob_path: project_new_blob_path(@project, @ref),
upload_path: project_create_blob_path(@project, @ref), upload_path: project_create_blob_path(@project, @ref),
......
...@@ -21,5 +21,4 @@ ...@@ -21,5 +21,4 @@
#js-tree-list{ data: vue_file_list_data(project, ref) } #js-tree-list{ data: vue_file_list_data(project, ref) }
- if can_edit_tree? - 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' = 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 @@ ...@@ -3,8 +3,6 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe 'Projects > Files > User uploads files' do RSpec.describe 'Projects > Files > User uploads files' do
include DropzoneHelper
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, name: 'Shop', creator: user) } let(:project) { create(:project, :repository, name: 'Shop', creator: user) }
let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') } let(:project2) { create(:project, :repository, name: 'Another Project', path: 'another-project') }
...@@ -17,36 +15,15 @@ RSpec.describe 'Projects > Files > User uploads files' do ...@@ -17,36 +15,15 @@ RSpec.describe 'Projects > Files > User uploads files' do
context 'when a user has write access' do context 'when a user has write access' do
before do before do
visit(project_tree_path(project)) visit(project_tree_path(project))
wait_for_requests
end end
include_examples 'it uploads and commit a new text file' include_examples 'it uploads and commit a new text file'
include_examples 'it uploads and commit a new image file' include_examples 'it uploads and commit a new image file'
it 'uploads a file to a sub-directory', :js do include_examples 'it uploads a file to a sub-directory'
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
end end
context 'when a user does not have write access' do context 'when a user does not have write access' do
......
...@@ -17,11 +17,15 @@ RSpec.describe 'Projects > Show > User uploads files' do ...@@ -17,11 +17,15 @@ RSpec.describe 'Projects > Show > User uploads files' do
context 'when a user has write access' do context 'when a user has write access' do
before do before do
visit(project_path(project)) visit(project_path(project))
wait_for_requests
end end
include_examples 'it uploads and commit a new text file' include_examples 'it uploads and commit a new text file'
include_examples 'it uploads and commit a new image file' include_examples 'it uploads and commit a new image file'
include_examples 'it uploads a file to a sub-directory'
end end
context 'when a user does not have write access' do context 'when a user does not have write access' do
......
import { GlDropdown } from '@gitlab/ui'; import { GlDropdown } from '@gitlab/ui';
import { shallowMount, RouterLinkStub } from '@vue/test-utils'; import { shallowMount, RouterLinkStub } from '@vue/test-utils';
import Breadcrumbs from '~/repository/components/breadcrumbs.vue'; import Breadcrumbs from '~/repository/components/breadcrumbs.vue';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
let vm;
function factory(currentPath, extraProps = {}) {
vm = shallowMount(Breadcrumbs, {
propsData: {
currentPath,
...extraProps,
},
stubs: {
RouterLink: RouterLinkStub,
},
});
}
describe('Repository breadcrumbs component', () => { 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(() => { afterEach(() => {
vm.destroy(); wrapper.destroy();
}); });
it.each` it.each`
...@@ -30,13 +42,13 @@ describe('Repository breadcrumbs component', () => { ...@@ -30,13 +42,13 @@ describe('Repository breadcrumbs component', () => {
`('renders $linkCount links for path $path', ({ path, linkCount }) => { `('renders $linkCount links for path $path', ({ path, linkCount }) => {
factory(path); factory(path);
expect(vm.findAll(RouterLinkStub).length).toEqual(linkCount); expect(wrapper.findAll(RouterLinkStub).length).toEqual(linkCount);
}); });
it('escapes hash in directory path', () => { it('escapes hash in directory path', () => {
factory('app/assets/javascripts#'); 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', '/-/tree/app/assets/javascripts%23',
); );
}); });
...@@ -44,26 +56,44 @@ describe('Repository breadcrumbs component', () => { ...@@ -44,26 +56,44 @@ describe('Repository breadcrumbs component', () => {
it('renders last link as active', () => { it('renders last link as active', () => {
factory('app/assets'); 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 }); factory('/', { canCollaborate: false });
vm.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } }); wrapper.setData({ userPermissions: { forkProject: false, createMergeRequestIn: false } });
return vm.vm.$nextTick(() => { await wrapper.vm.$nextTick();
expect(vm.find(GlDropdown).exists()).toBe(false);
}); 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 }); 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(findUploadBlobModal().exists()).toBe(true);
expect(vm.find(GlDropdown).exists()).toBe(true);
}); });
}); });
}); });
...@@ -10,7 +10,7 @@ RSpec.shared_examples 'it uploads and commit a new text file' do ...@@ -10,7 +10,7 @@ RSpec.shared_examples 'it uploads and commit a new text file' do
wait_for_requests wait_for_requests
end 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 page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
...@@ -42,7 +42,7 @@ RSpec.shared_examples 'it uploads and commit a new image file' do ...@@ -42,7 +42,7 @@ RSpec.shared_examples 'it uploads and commit a new image file' do
wait_for_requests wait_for_requests
end 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 page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') 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 ...@@ -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) expect(page).to have_content(fork_message)
wait_for_all_requests
find('.add-to-tree').click find('.add-to-tree').click
click_link('Upload file') 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 page.within('#modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') 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 ...@@ -95,6 +97,33 @@ RSpec.shared_examples 'it uploads and commit a new file to a forked project' do
end end
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 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 it 'uploads and commits a new text file via "upload file" button', :js do
find('[data-testid="upload-file-button"]').click 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