Commit 6cc50efd authored by James Fargher's avatar James Fargher

Merge branch 'jswain_promote_empty_repo_upload' into 'master'

Promote empty_repo_upload experiment

See merge request gitlab-org/gitlab!73111
parents b834c42b 396f2c49
...@@ -7,7 +7,7 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; ...@@ -7,7 +7,7 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
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 { initUploadFileTrigger } from '~/projects/upload_file';
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';
......
<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';
...@@ -30,11 +29,6 @@ export default { ...@@ -30,11 +29,6 @@ export default {
default: '', default: '',
}, },
}, },
methods: {
trackOpenModal() {
trackFileUploadEvent('click_upload_modal_trigger');
},
},
uploadBlobModalId: UPLOAD_BLOB_MODAL_ID, uploadBlobModalId: UPLOAD_BLOB_MODAL_ID,
}; };
</script> </script>
...@@ -44,7 +38,6 @@ export default { ...@@ -44,7 +38,6 @@ export default {
v-gl-modal="$options.uploadBlobModalId" v-gl-modal="$options.uploadBlobModalId"
icon="upload" icon="upload"
data-testid="upload-file-button" data-testid="upload-file-button"
@click="trackOpenModal"
>{{ __('Upload File') }}</gl-button >{{ __('Upload File') }}</gl-button
> >
<upload-blob-modal <upload-blob-modal
......
...@@ -4,7 +4,7 @@ import createRouter from '~/repository/router'; ...@@ -4,7 +4,7 @@ import createRouter from '~/repository/router';
import UploadButton from './details/upload_button.vue'; import UploadButton from './details/upload_button.vue';
export const initUploadFileTrigger = () => { export const initUploadFileTrigger = () => {
const uploadFileTriggerEl = document.querySelector('.js-upload-file-experiment-trigger'); const uploadFileTriggerEl = document.querySelector('.js-upload-file-trigger');
if (!uploadFileTriggerEl) return false; if (!uploadFileTriggerEl) return false;
......
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);
};
...@@ -116,15 +116,14 @@ export default { ...@@ -116,15 +116,14 @@ export default {
], ],
}; };
}, },
/* eslint-disable dot-notation */
showCreateNewMrToggle() { showCreateNewMrToggle() {
return this.canPushCode && this.form.fields['branch_name'].value !== this.originalBranch; return this.canPushCode && this.form.fields.branch_name.value !== this.originalBranch;
}, },
formCompleted() { formCompleted() {
return this.form.fields['commit_message'].value && this.form.fields['branch_name'].value; return this.form.fields.commit_message.value && this.form.fields.branch_name.value;
}, },
showHint() { showHint() {
const splitCommitMessageByLineBreak = this.form.fields['commit_message'].value const splitCommitMessageByLineBreak = this.form.fields.commit_message.value
.trim() .trim()
.split('\n'); .split('\n');
const [firstLine, ...otherLines] = splitCommitMessageByLineBreak; const [firstLine, ...otherLines] = splitCommitMessageByLineBreak;
...@@ -136,7 +135,7 @@ export default { ...@@ -136,7 +135,7 @@ export default {
otherLines.some((text) => text.length > COMMIT_MESSAGE_BODY_MAX_LENGTH); otherLines.some((text) => text.length > COMMIT_MESSAGE_BODY_MAX_LENGTH);
return ( return (
!this.form.fields['commit_message'].feedback && !this.form.fields.commit_message.feedback &&
(hasFirstLineExceedMaxLength || hasOtherLineExceedMaxLength) (hasFirstLineExceedMaxLength || hasOtherLineExceedMaxLength)
); );
}, },
...@@ -173,9 +172,7 @@ export default { ...@@ -173,9 +172,7 @@ export default {
<input type="hidden" name="_method" value="delete" /> <input type="hidden" name="_method" value="delete" />
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> <input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<template v-if="emptyRepo"> <template v-if="emptyRepo">
<!-- Once "empty_repo_upload_experiment" is made available, will need to add class 'js-branch-name' <input type="hidden" name="branch_name" :value="originalBranch" class="js-branch-name" />
Follow-up issue: https://gitlab.com/gitlab-org/gitlab/-/issues/335721 -->
<input type="hidden" name="branch_name" :value="originalBranch" />
</template> </template>
<template v-else> <template v-else>
<input type="hidden" name="original_branch" :value="originalBranch" /> <input type="hidden" name="original_branch" :value="originalBranch" />
......
...@@ -15,7 +15,6 @@ import { ContentTypeMultipartFormData } from '~/lib/utils/headers'; ...@@ -15,7 +15,6 @@ 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';
import { import {
SECONDARY_OPTIONS_TEXT, SECONDARY_OPTIONS_TEXT,
...@@ -165,9 +164,6 @@ export default { ...@@ -165,9 +164,6 @@ export default {
}, },
}) })
.then((response) => { .then((response) => {
if (!this.replacePath) {
trackFileUploadEvent('click_upload_modal_form_submit');
}
visitUrl(response.data.filePath); visitUrl(response.data.filePath);
}) })
.catch(() => { .catch(() => {
......
...@@ -336,11 +336,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -336,11 +336,6 @@ class ProjectsController < Projects::ApplicationController
if can?(current_user, :download_code, @project) if can?(current_user, :download_code, @project)
return render 'projects/no_repo' unless @project.repository_exists? return render 'projects/no_repo' unless @project.repository_exists?
if @project.can_current_user_push_to_default_branch?
property = @project.empty_repo? ? 'empty' : 'nonempty'
experiment(:empty_repo_upload, project: @project).track(:view_project_show, property: property)
end
render 'projects/empty' if @project.empty_repo? render 'projects/empty' if @project.empty_repo?
else else
if can?(current_user, :read_wiki, @project) if can?(current_user, :read_wiki, @project)
......
...@@ -249,33 +249,23 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated ...@@ -249,33 +249,23 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
strong_memoize(:upload_anchor_data) do strong_memoize(:upload_anchor_data) do
next unless can_current_user_push_to_default_branch? next unless can_current_user_push_to_default_branch?
experiment(:empty_repo_upload, project: project) do |e| AnchorData.new(false,
e.use {} statistic_icon('upload') + _('Upload file'),
e.try do '#modal-upload-blob',
AnchorData.new(false, 'js-upload-file-trigger',
statistic_icon('upload') + _('Upload file'), nil,
'#modal-upload-blob', nil,
'js-upload-file-experiment-trigger', {
nil, 'target_branch' => default_branch_or_main,
nil, 'original_branch' => default_branch_or_main,
{ 'can_push_code' => 'true',
'target_branch' => default_branch_or_main, 'path' => project_create_blob_path(project, default_branch_or_main),
'original_branch' => default_branch_or_main, 'project_path' => project.full_path
'can_push_code' => 'true', }
'path' => project_create_blob_path(project, default_branch_or_main), )
'project_path' => project.full_path
}
)
end
e.run
end
end end
end end
def empty_repo_upload_experiment?
upload_anchor_data.present?
end
def new_file_anchor_data def new_file_anchor_data
if can_current_user_push_to_default_branch? if can_current_user_push_to_default_branch?
new_file_path = empty_repo? ? ide_edit_path(project, default_branch_or_main) : project_new_blob_path(project, default_branch_or_main) new_file_path = empty_repo? ? ide_edit_path(project, default_branch_or_main) : project_new_blob_path(project, default_branch_or_main)
......
...@@ -77,5 +77,5 @@ ...@@ -77,5 +77,5 @@
git push -u origin --all git push -u origin --all
git push -u origin --tags git push -u origin --tags
- if @project.empty_repo_upload_experiment? - if @project.upload_anchor_data.present?
= render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, default_branch_name), ref: default_branch_name, method: :post = render 'projects/blob/upload', title: _('Upload New File'), placeholder: _('Upload New File'), button_title: _('Upload file'), form_path: project_create_blob_path(@project, default_branch_name), ref: default_branch_name, method: :post
...@@ -5,9 +5,7 @@ ...@@ -5,9 +5,7 @@
- if project.empty_repo? - if project.empty_repo?
- ref = local_assigns[:ref] || @ref - ref = local_assigns[:ref] || @ref
- branch_name_class = project.empty_repo_upload_experiment? ? 'js-branch-name' : nil = hidden_field_tag 'branch_name', ref, class: 'js-branch-name'
= hidden_field_tag 'branch_name', ref, class: branch_name_class
- else - else
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
.form-group.row.branch .form-group.row.branch
......
...@@ -128,7 +128,6 @@ class PostReceive ...@@ -128,7 +128,6 @@ class PostReceive
end end
def after_project_changes_hooks(project, user, refs, changes) def after_project_changes_hooks(project, user, refs, changes)
experiment(:empty_repo_upload, project: project).track_initial_write
repository_update_hook_data = Gitlab::DataBuilder::Repository.update(project, user, changes, refs) repository_update_hook_data = Gitlab::DataBuilder::Repository.update(project, user, changes, refs)
SystemHooksService.new.execute_hooks(repository_update_hook_data, :repository_update_hooks) SystemHooksService.new.execute_hooks(repository_update_hook_data, :repository_update_hooks)
Gitlab::UsageDataCounters::SourceCodeCounter.count(:pushes) Gitlab::UsageDataCounters::SourceCodeCounter.count(:pushes)
......
---
name: empty_repo_upload
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52755
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285296
milestone: '13.9'
type: experiment
group: group::adoption
default_enabled: false
...@@ -213,21 +213,6 @@ RSpec.describe ProjectsController do ...@@ -213,21 +213,6 @@ RSpec.describe ProjectsController do
before do before do
sign_in(user) sign_in(user)
allow(controller).to receive(:record_experiment_user)
end
context 'when user can push to default branch', :experiment do
let(:user) { empty_project.owner }
it 'creates an "view_project_show" experiment tracking event' do
expect(experiment(:empty_repo_upload)).to track(
:view_project_show,
property: 'empty'
).on_next_instance
get :show, params: { namespace_id: empty_project.namespace, id: empty_project }
end
end end
User.project_views.keys.each do |project_view| User.project_views.keys.each do |project_view|
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EmptyRepoUploadExperiment, :experiment do
subject { described_class.new(project: project) }
let(:project) { create(:project, :repository) }
describe '#track_initial_write' do
context 'when experiment is turned on' do
before do
stub_experiments(empty_repo_upload: :control)
end
it "tracks an event for the first commit on a project" do
expect(subject).to receive(:commit_count_for).with(project, max_count: described_class::INITIAL_COMMIT_COUNT, experiment: 'empty_repo_upload').and_return(1)
expect(subject).to receive(:track).with(:initial_write, project: project).and_call_original
subject.track_initial_write
end
it "doesn't track an event for projects with a commit count more than 1" do
expect(subject).to receive(:commit_count_for).and_return(2)
expect(subject).not_to receive(:track)
subject.track_initial_write
end
it "doesn't track if the project is older" do
expect(project).to receive(:created_at).and_return(described_class::TRACKING_START_DATE - 1.minute)
expect(subject).not_to receive(:track)
subject.track_initial_write
end
end
context 'when experiment is turned off' do
it "doesn't track when we generally shouldn't" do
expect(subject).not_to receive(:track)
subject.track_initial_write
end
end
end
end
...@@ -44,27 +44,27 @@ RSpec.describe 'Projects > Show > User uploads files' do ...@@ -44,27 +44,27 @@ RSpec.describe 'Projects > Show > User uploads files' do
end end
end end
context 'when in the empty_repo_upload experiment' do context 'with an empty repo' do
before do let(:project) { create(:project, :empty_repo, creator: user) }
stub_experiments(empty_repo_upload: :candidate)
before do
visit(project_path(project)) visit(project_path(project))
end end
context 'with an empty repo' do [true, false].each do |value|
let(:project) { create(:project, :empty_repo, creator: user) } include_examples 'uploads and commits a new text file via "upload file" button', drop: value
[true, false].each do |value|
include_examples 'uploads and commits a new text file via "upload file" button', drop: value
end
end end
end
context 'with a nonempty repo' do context 'with a nonempty repo' do
let(:project) { create(:project, :repository, creator: user) } let(:project) { create(:project, :repository, creator: user) }
[true, false].each do |value| before do
include_examples 'uploads and commits a new text file via "upload file" button', drop: value visit(project_path(project))
end end
[true, false].each do |value|
include_examples 'uploads and commits a new text file via "upload file" button', drop: value
end end
end end
end end
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', () => {
...@@ -50,10 +47,6 @@ describe('UploadButton', () => { ...@@ -50,10 +47,6 @@ 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 { trackFileUploadEvent } from '~/projects/upload_file_experiment_tracking';
jest.mock('~/experimentation/experiment_tracking');
const eventName = 'click_upload_modal_form_submit';
const fixture = `<a class='js-upload-file-experiment-trigger'></a><div class='project-home-panel empty-project'></div>`;
beforeEach(() => {
document.body.innerHTML = fixture;
});
afterEach(() => {
document.body.innerHTML = '';
});
describe('trackFileUploadEvent', () => {
it('initializes ExperimentTracking with the correct tracking event', () => {
trackFileUploadEvent(eventName);
expect(ExperimentTracking.prototype.event).toHaveBeenCalledWith(eventName);
});
it('calls ExperimentTracking with the correct arguments', () => {
trackFileUploadEvent(eventName);
expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', {
label: 'blob-upload-modal',
property: 'empty',
});
});
it('calls ExperimentTracking with the correct arguments when the project is not empty', () => {
document.querySelector('.empty-project').remove();
trackFileUploadEvent(eventName);
expect(ExperimentTracking).toHaveBeenCalledWith('empty_repo_upload', {
label: 'blob-upload-modal',
property: 'nonempty',
});
});
});
...@@ -6,11 +6,9 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -6,11 +6,9 @@ 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(),
...@@ -162,10 +160,6 @@ describe('UploadBlobModal', () => { ...@@ -162,10 +160,6 @@ 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();
}); });
...@@ -185,10 +179,6 @@ describe('UploadBlobModal', () => { ...@@ -185,10 +179,6 @@ 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({ expect(createFlash).toHaveBeenCalledWith({
message: 'Error uploading file. Please try again.', message: 'Error uploading file. Please try again.',
......
...@@ -567,44 +567,27 @@ RSpec.describe ProjectPresenter do ...@@ -567,44 +567,27 @@ RSpec.describe ProjectPresenter do
end end
describe '#upload_anchor_data' do describe '#upload_anchor_data' do
context 'with empty_repo_upload enabled' do context 'when a user can push to the default branch' do
before do before do
stub_experiments(empty_repo_upload: :candidate) project.add_developer(user)
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.full_path,
"target_branch" => "master"
}
)
end
end end
context 'user cannot push to branch' do it 'returns upload_anchor_data' do
it 'returns nil' do expect(presenter.upload_anchor_data).to have_attributes(
expect(presenter.upload_anchor_data).to be_nil is_link: false,
end label: a_string_including('Upload file'),
data: {
"can_push_code" => "true",
"original_branch" => "master",
"path" => "/#{project.full_path}/-/create/master",
"project_path" => project.full_path,
"target_branch" => "master"
}
)
end end
end end
context 'with empty_repo_upload disabled' do context 'when the user cannot push to default branch' do
before do
stub_experiments(empty_repo_upload: :control)
project.add_developer(user)
end
it 'returns nil' do it 'returns nil' do
expect(presenter.upload_anchor_data).to be_nil expect(presenter.upload_anchor_data).to be_nil
end end
...@@ -666,7 +649,6 @@ RSpec.describe ProjectPresenter do ...@@ -666,7 +649,6 @@ RSpec.describe ProjectPresenter do
context 'for a developer' do context 'for a developer' do
before do before do
project.add_developer(user) project.add_developer(user)
stub_experiments(empty_repo_upload: :candidate)
end end
it 'orders the items correctly' do it 'orders the items correctly' do
...@@ -680,16 +662,6 @@ RSpec.describe ProjectPresenter do ...@@ -680,16 +662,6 @@ RSpec.describe ProjectPresenter do
a_string_including('CI/CD') a_string_including('CI/CD')
) )
end end
context 'when not in the upload experiment' do
before do
stub_experiments(empty_repo_upload: :control)
end
it 'does not include upload button' do
expect(empty_repo_statistics_buttons.map(&:label)).not_to start_with(a_string_including('Upload'))
end
end
end end
end end
...@@ -781,20 +753,4 @@ RSpec.describe ProjectPresenter do ...@@ -781,20 +753,4 @@ RSpec.describe ProjectPresenter do
it { is_expected.to match(/code_quality_walkthrough=true.*template=Code-Quality/) } it { is_expected.to match(/code_quality_walkthrough=true.*template=Code-Quality/) }
end end
describe 'empty_repo_upload_experiment?' do
subject { presenter.empty_repo_upload_experiment? }
it 'returns false when upload_anchor_data is nil' do
allow(presenter).to receive(:upload_anchor_data).and_return(nil)
expect(subject).to be false
end
it 'returns true when upload_anchor_data exists' do
allow(presenter).to receive(:upload_anchor_data).and_return(true)
expect(subject).to be true
end
end
end end
...@@ -91,14 +91,6 @@ RSpec.describe PostReceive do ...@@ -91,14 +91,6 @@ RSpec.describe PostReceive do
perform perform
end end
it 'tracks an event for the empty_repo_upload experiment', :experiment do
expect_next_instance_of(EmptyRepoUploadExperiment) do |e|
expect(e).to receive(:track_initial_write)
end
perform
end
end end
shared_examples 'not updating remote mirrors' do shared_examples 'not updating remote mirrors' do
......
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