Commit c907a1ab authored by Brandon Labuschagne's avatar Brandon Labuschagne Committed by Mayra Cabrera

Migrate upload file experiment

The migration is from the old bootstrap modal
to the new gitlab ui GlModal based component.

This experiment is behind a feature flag.
parent 9b101bf3
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
import Dropzone from 'dropzone'; import Dropzone from 'dropzone';
import $ from 'jquery'; import $ from 'jquery';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import { trackUploadFileFormSubmitted } from '~/projects/upload_file_experiment';
import { HIDDEN_CLASS } from '../lib/utils/constants'; import { HIDDEN_CLASS } from '../lib/utils/constants';
import csrf from '../lib/utils/csrf'; import csrf from '../lib/utils/csrf';
import { visitUrl } from '../lib/utils/url_utility'; import { visitUrl } from '../lib/utils/url_utility';
...@@ -85,8 +84,6 @@ export default class BlobFileDropzone { ...@@ -85,8 +84,6 @@ export default class BlobFileDropzone {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
trackUploadFileFormSubmitted();
if (dropzone[0].dropzone.getQueuedFiles().length === 0) { if (dropzone[0].dropzone.getQueuedFiles().length === 0) {
// eslint-disable-next-line no-alert // eslint-disable-next-line no-alert
alert(__('Please select a file')); alert(__('Please select a file'));
......
...@@ -4,7 +4,6 @@ import $ from 'jquery'; ...@@ -4,7 +4,6 @@ import $ from 'jquery';
import initPopover from '~/blob/suggest_gitlab_ci_yml'; import initPopover from '~/blob/suggest_gitlab_ci_yml';
import { deprecatedCreateFlash as createFlash } from '~/flash'; import { deprecatedCreateFlash as createFlash } from '~/flash';
import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils'; import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils';
import { initUploadFileTrigger } from '~/projects/upload_file_experiment';
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import BlobFileDropzone from '../blob/blob_file_dropzone'; import BlobFileDropzone from '../blob/blob_file_dropzone';
import NewCommitForm from '../new_commit_form'; import NewCommitForm from '../new_commit_form';
...@@ -48,7 +47,6 @@ export const initUploadForm = () => { ...@@ -48,7 +47,6 @@ export const initUploadForm = () => {
new NewCommitForm(uploadBlobForm); new NewCommitForm(uploadBlobForm);
disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file'); disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file');
initUploadFileTrigger();
} }
}; };
......
...@@ -5,6 +5,7 @@ import BlobViewer from '~/blob/viewer/index'; ...@@ -5,6 +5,7 @@ import BlobViewer from '~/blob/viewer/index';
import { initUploadForm } from '~/blob_edit/blob_bundle'; import { initUploadForm } from '~/blob_edit/blob_bundle';
import leaveByUrl from '~/namespaces/leave_by_url'; import leaveByUrl from '~/namespaces/leave_by_url';
import initVueNotificationsDropdown from '~/notifications'; import initVueNotificationsDropdown from '~/notifications';
import { initUploadFileTrigger } from '~/projects/upload_file_experiment';
import initReadMore from '~/read_more'; import initReadMore from '~/read_more';
import UserCallout from '~/user_callout'; import UserCallout from '~/user_callout';
import Star from '../../../star'; import Star from '../../../star';
...@@ -41,3 +42,5 @@ leaveByUrl('project'); ...@@ -41,3 +42,5 @@ leaveByUrl('project');
initVueNotificationsDropdown(); initVueNotificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
initUploadFileTrigger();
<script> <script>
import { GlButton, GlModalDirective } from '@gitlab/ui'; import { GlButton, GlModalDirective } from '@gitlab/ui';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue'; import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import { trackFileUploadEvent } from '../upload_file_experiment_tracking';
const UPLOAD_BLOB_MODAL_ID = 'details-modal-upload-blob'; const UPLOAD_BLOB_MODAL_ID = 'details-modal-upload-blob';
...@@ -16,7 +17,7 @@ export default { ...@@ -16,7 +17,7 @@ export default {
targetBranch: { targetBranch: {
default: '', default: '',
}, },
origionalBranch: { originalBranch: {
default: '', default: '',
}, },
canPushCode: { canPushCode: {
...@@ -29,19 +30,28 @@ export default { ...@@ -29,19 +30,28 @@ export default {
default: '', default: '',
}, },
}, },
methods: {
trackOpenModal() {
trackFileUploadEvent('click_upload_modal_trigger');
},
},
uploadBlobModalId: UPLOAD_BLOB_MODAL_ID, uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
}; };
</script> </script>
<template> <template>
<span> <span>
<gl-button v-gl-modal="$options.uploadBlobModalId" icon="upload">{{ <gl-button
__('Upload File') v-gl-modal="$options.uploadBlobModalId"
}}</gl-button> icon="upload"
data-testid="upload-file-button"
@click="trackOpenModal"
>{{ __('Upload File') }}</gl-button
>
<upload-blob-modal <upload-blob-modal
:modal-id="$options.uploadBlobModalId" :modal-id="$options.uploadBlobModalId"
:commit-message="__('Upload New File')" :commit-message="__('Upload New File')"
:target-branch="targetBranch" :target-branch="targetBranch"
:origional-branch="origionalBranch" :original-branch="originalBranch"
:can-push-code="canPushCode" :can-push-code="canPushCode"
:path="path" :path="path"
/> />
......
import ExperimentTracking from '~/experimentation/experiment_tracking'; import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import createRouter from '~/repository/router';
import UploadButton from './details/upload_button.vue';
function trackEvent(eventName) { export const initUploadFileTrigger = () => {
const isEmpty = Boolean(document.querySelector('.project-home-panel.empty-project'));
const property = isEmpty ? 'empty' : 'nonempty';
const label = 'blob-upload-modal';
const Tracking = new ExperimentTracking('empty_repo_upload', { label, property });
Tracking.event(eventName);
}
export function initUploadFileTrigger() {
const uploadFileTriggerEl = document.querySelector('.js-upload-file-experiment-trigger'); const uploadFileTriggerEl = document.querySelector('.js-upload-file-experiment-trigger');
if (uploadFileTriggerEl) { if (!uploadFileTriggerEl) return false;
uploadFileTriggerEl.addEventListener('click', () => {
trackEvent('click_upload_modal_trigger'); const {
}); targetBranch,
} originalBranch,
} canPushCode,
path,
projectPath,
} = uploadFileTriggerEl.dataset;
export function trackUploadFileFormSubmitted() { return new Vue({
trackEvent('click_upload_modal_form_submit'); el: uploadFileTriggerEl,
} router: createRouter(projectPath, originalBranch),
provide: {
targetBranch,
originalBranch,
canPushCode: parseBoolean(canPushCode),
path,
projectPath,
},
render(h) {
return h(UploadButton);
},
});
};
import ExperimentTracking from '~/experimentation/experiment_tracking';
export const trackFileUploadEvent = (eventName) => {
const isEmpty = Boolean(document.querySelector('.project-home-panel.empty-project'));
const property = isEmpty ? 'empty' : 'nonempty';
const label = 'blob-upload-modal';
const FileUploadTracking = new ExperimentTracking('empty_repo_upload', { label, property });
FileUploadTracking.event(eventName);
};
...@@ -15,6 +15,7 @@ import { ContentTypeMultipartFormData } from '~/lib/utils/headers'; ...@@ -15,6 +15,7 @@ import { ContentTypeMultipartFormData } from '~/lib/utils/headers';
import { numberToHumanSize } from '~/lib/utils/number_utils'; import { numberToHumanSize } from '~/lib/utils/number_utils';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
const PRIMARY_OPTIONS_TEXT = __('Upload file'); const PRIMARY_OPTIONS_TEXT = __('Upload file');
...@@ -62,7 +63,7 @@ export default { ...@@ -62,7 +63,7 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
origionalBranch: { originalBranch: {
type: String, type: String,
required: true, required: true,
}, },
...@@ -113,7 +114,7 @@ export default { ...@@ -113,7 +114,7 @@ export default {
return numberToHumanSize(this.file.size); return numberToHumanSize(this.file.size);
}, },
showCreateNewMrToggle() { showCreateNewMrToggle() {
return this.canPushCode && this.target !== this.origionalBranch; return this.canPushCode && this.target !== this.originalBranch;
}, },
formCompleted() { formCompleted() {
return this.file && this.commit && this.target; return this.file && this.commit && this.target;
...@@ -158,6 +159,7 @@ export default { ...@@ -158,6 +159,7 @@ export default {
}, },
}) })
.then((response) => { .then((response) => {
trackFileUploadEvent('click_upload_modal_form_submit');
visitUrl(response.data.filePath); visitUrl(response.data.filePath);
}) })
.catch(() => { .catch(() => {
......
...@@ -253,8 +253,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -253,8 +253,11 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
nil, nil,
nil, nil,
{ {
'toggle' => 'modal', 'target_branch' => default_branch_or_master,
'target' => '#modal-upload-blob' 'original_branch' => default_branch_or_master,
'can_push_code' => 'true',
'path' => project_create_blob_path(project, default_branch_or_master),
'project_path' => project.path
} }
) )
end end
......
import $ from 'jquery'; import $ from 'jquery';
import BlobFileDropzone from '~/blob/blob_file_dropzone'; import BlobFileDropzone from '~/blob/blob_file_dropzone';
import { trackUploadFileFormSubmitted } from '~/projects/upload_file_experiment';
jest.mock('~/projects/upload_file_experiment', () => ({
trackUploadFileFormSubmitted: jest.fn(),
}));
describe('BlobFileDropzone', () => { describe('BlobFileDropzone', () => {
let dropzone; let dropzone;
...@@ -45,13 +40,5 @@ describe('BlobFileDropzone', () => { ...@@ -45,13 +40,5 @@ describe('BlobFileDropzone', () => {
expect(replaceFileButton.is(':disabled')).toEqual(true); expect(replaceFileButton.is(':disabled')).toEqual(true);
expect(dropzone.processQueue).toHaveBeenCalled(); expect(dropzone.processQueue).toHaveBeenCalled();
}); });
it('calls the tracking event', () => {
jest.spyOn(window, 'alert').mockImplementation(() => {});
replaceFileButton.click();
expect(trackUploadFileFormSubmitted).toHaveBeenCalled();
});
}); });
}); });
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import UploadButton from '~/projects/details/upload_button.vue'; import UploadButton from '~/projects/details/upload_button.vue';
import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue'; import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
jest.mock('~/projects/upload_file_experiment_tracking');
const MODAL_ID = 'details-modal-upload-blob'; const MODAL_ID = 'details-modal-upload-blob';
describe('UploadButton', () => { describe('UploadButton', () => {
...@@ -47,6 +50,10 @@ describe('UploadButton', () => { ...@@ -47,6 +50,10 @@ describe('UploadButton', () => {
wrapper.find(GlButton).vm.$emit('click'); wrapper.find(GlButton).vm.$emit('click');
}); });
it('tracks the click_upload_modal_trigger event', () => {
expect(trackFileUploadEvent).toHaveBeenCalledWith('click_upload_modal_trigger');
});
it('opens the modal', () => { it('opens the modal', () => {
expect(glModalDirective).toHaveBeenCalledWith(MODAL_ID); expect(glModalDirective).toHaveBeenCalledWith(MODAL_ID);
}); });
......
import ExperimentTracking from '~/experimentation/experiment_tracking'; import ExperimentTracking from '~/experimentation/experiment_tracking';
import * as UploadFileExperiment from '~/projects/upload_file_experiment'; import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking';
jest.mock('~/experimentation/experiment_tracking'); jest.mock('~/experimentation/experiment_tracking');
const fixture = `<a class='js-upload-file-experiment-trigger' data-toggle='modal' data-target='#modal-upload-blob'></a><div id='modal-upload-blob'></div><div class='project-home-panel empty-project'></div>`; const eventName = 'click_upload_modal_form_submit';
const findModal = () => document.querySelector('[aria-modal="true"]'); const fixture = `<a class='js-upload-file-experiment-trigger'></a><div class='project-home-panel empty-project'></div>`;
const findTrigger = () => document.querySelector('.js-upload-file-experiment-trigger');
beforeEach(() => { beforeEach(() => {
document.body.innerHTML = fixture; document.body.innerHTML = fixture;
...@@ -15,23 +14,26 @@ afterEach(() => { ...@@ -15,23 +14,26 @@ afterEach(() => {
document.body.innerHTML = ''; document.body.innerHTML = '';
}); });
describe('trackUploadFileFormSubmitted', () => { describe('trackFileUploadEvent', () => {
it('initializes ExperimentTracking with the correct arguments and calls the tracking event with correct arguments', () => { it('initializes ExperimentTracking with the correct tracking event', () => {
UploadFileExperiment.trackUploadFileFormSubmitted(); trackFileUploadEvent(eventName);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(eventName);
});
it('calls ExperimentTracking with the correct arguments', () => {
trackFileUploadEvent(eventName);
expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', { expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', {
label: 'blob-upload-modal', label: 'blob-upload-modal',
property: 'empty', property: 'empty',
}); });
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(
'click_upload_modal_form_submit',
);
}); });
it('initializes ExperimentTracking with the correct arguments when the project is not empty', () => { it('calls ExperimentTracking with the correct arguments when the project is not empty', () => {
document.querySelector('.empty-project').remove(); document.querySelector('.empty-project').remove();
UploadFileExperiment.trackUploadFileFormSubmitted(); trackFileUploadEvent(eventName);
expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', { expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', {
label: 'blob-upload-modal', label: 'blob-upload-modal',
...@@ -39,14 +41,3 @@ describe('trackUploadFileFormSubmitted', () => { ...@@ -39,14 +41,3 @@ describe('trackUploadFileFormSubmitted', () => {
}); });
}); });
}); });
describe('initUploadFileTrigger', () => {
it('calls modal and tracks event', () => {
UploadFileExperiment.initUploadFileTrigger();
expect(findModal()).not.toExist();
findTrigger().click();
expect(findModal()).toExist();
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith('click_upload_modal_trigger');
});
});
...@@ -6,9 +6,11 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -6,9 +6,11 @@ import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash'; import createFlash from '~/flash';
import httpStatusCodes from '~/lib/utils/http_status'; import httpStatusCodes from '~/lib/utils/http_status';
import { visitUrl } from '~/lib/utils/url_utility'; import { visitUrl } from '~/lib/utils/url_utility';
import { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking';
import UploadBlobModal from '~/repository/components/upload_blob_modal.vue'; import UploadBlobModal from '~/repository/components/upload_blob_modal.vue';
import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
jest.mock('~/projects/upload_file_experiment_tracking');
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/lib/utils/url_utility', () => ({ jest.mock('~/lib/utils/url_utility', () => ({
visitUrl: jest.fn(), visitUrl: jest.fn(),
...@@ -19,7 +21,7 @@ const initialProps = { ...@@ -19,7 +21,7 @@ const initialProps = {
modalId: 'upload-blob', modalId: 'upload-blob',
commitMessage: 'Upload New File', commitMessage: 'Upload New File',
targetBranch: 'master', targetBranch: 'master',
origionalBranch: 'master', originalBranch: 'master',
canPushCode: true, canPushCode: true,
path: 'new_upload', path: 'new_upload',
}; };
...@@ -160,6 +162,10 @@ describe('UploadBlobModal', () => { ...@@ -160,6 +162,10 @@ describe('UploadBlobModal', () => {
await waitForPromises(); await waitForPromises();
}); });
it('tracks the click_upload_modal_trigger event when opening the modal', () => {
expect(trackFileUploadEvent).toHaveBeenCalledWith('click_upload_modal_form_submit');
});
it('redirects to the uploaded file', () => { it('redirects to the uploaded file', () => {
expect(visitUrl).toHaveBeenCalled(); expect(visitUrl).toHaveBeenCalled();
}); });
...@@ -179,6 +185,10 @@ describe('UploadBlobModal', () => { ...@@ -179,6 +185,10 @@ describe('UploadBlobModal', () => {
await waitForPromises(); await waitForPromises();
}); });
it('does not track an event', () => {
expect(trackFileUploadEvent).not.toHaveBeenCalled();
});
it('creates a flash error', () => { it('creates a flash error', () => {
expect(createFlash).toHaveBeenCalledWith('Error uploading file. Please try again.'); expect(createFlash).toHaveBeenCalledWith('Error uploading file. Please try again.');
}); });
......
...@@ -563,6 +563,51 @@ RSpec.describe ProjectPresenter do ...@@ -563,6 +563,51 @@ RSpec.describe ProjectPresenter do
end end
end end
end end
describe '#upload_anchor_data' do
context 'with empty_repo_upload enabled' do
before do
stub_experiments(empty_repo_upload: :candidate)
end
context 'user can push to branch' do
before do
project.add_developer(user)
end
it 'returns upload_anchor_data' do
expect(presenter.upload_anchor_data).to have_attributes(
is_link: false,
label: a_string_including('Upload file'),
data: {
"can_push_code" => "true",
"original_branch" => "master",
"path" => "/#{project.full_path}/-/create/master",
"project_path" => project.path,
"target_branch" => "master"
}
)
end
end
context 'user cannot push to branch' do
it 'returns nil' do
expect(presenter.upload_anchor_data).to be_nil
end
end
end
context 'with empty_repo_upload disabled' do
before do
stub_experiments(empty_repo_upload: :control)
project.add_developer(user)
end
it 'returns nil' do
expect(presenter.upload_anchor_data).to be_nil
end
end
end
end end
describe '#statistics_buttons' do describe '#statistics_buttons' do
......
...@@ -97,18 +97,16 @@ end ...@@ -97,18 +97,16 @@ 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('.js-upload-file-experiment-trigger', text: 'Upload file').click find('[data-testid="upload-file-button"]').click
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('#details-modal-upload-blob') do
fill_in(:commit_message, with: 'New commit message') fill_in(:commit_message, with: 'New commit message')
end end
click_button('Upload file') click_button('Upload file')
wait_for_requests
expect(page).to have_content('New commit message') expect(page).to have_content('New commit message')
expect(page).to have_content('Lorem ipsum dolor sit amet') expect(page).to have_content('Lorem ipsum dolor sit amet')
expect(page).to have_content('Sed ut perspiciatis unde omnis') expect(page).to have_content('Sed ut perspiciatis unde omnis')
......
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