Commit 6c20cebd authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'jswain_new_repo' into 'master'

Add "New Repo" experiment to test familiarity [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!55818
parents fd9486f1 d8d007da
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; import { GlBreadcrumb, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { experiment } from '~/experimentation/utils';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import { NEW_REPO_EXPERIMENT } from '../constants';
import blankProjectIllustration from '../illustrations/blank-project.svg'; import blankProjectIllustration from '../illustrations/blank-project.svg';
import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg'; import ciCdProjectIllustration from '../illustrations/ci-cd-project.svg';
import createFromTemplateIllustration from '../illustrations/create-from-template.svg'; import createFromTemplateIllustration from '../illustrations/create-from-template.svg';
...@@ -13,8 +14,10 @@ import WelcomePage from './welcome.vue'; ...@@ -13,8 +14,10 @@ import WelcomePage from './welcome.vue';
const BLANK_PANEL = 'blank_project'; const BLANK_PANEL = 'blank_project';
const CI_CD_PANEL = 'cicd_for_external_repo'; const CI_CD_PANEL = 'cicd_for_external_repo';
const LAST_ACTIVE_TAB_KEY = 'new_project_last_active_tab'; const LAST_ACTIVE_TAB_KEY = 'new_project_last_active_tab';
const PANELS = [ const PANELS = [
{ {
key: 'blank',
name: BLANK_PANEL, name: BLANK_PANEL,
selector: '#blank-project-pane', selector: '#blank-project-pane',
title: s__('ProjectsNew|Create blank project'), title: s__('ProjectsNew|Create blank project'),
...@@ -24,6 +27,7 @@ const PANELS = [ ...@@ -24,6 +27,7 @@ const PANELS = [
illustration: blankProjectIllustration, illustration: blankProjectIllustration,
}, },
{ {
key: 'template',
name: 'create_from_template', name: 'create_from_template',
selector: '#create-from-template-pane', selector: '#create-from-template-pane',
title: s__('ProjectsNew|Create from template'), title: s__('ProjectsNew|Create from template'),
...@@ -33,6 +37,7 @@ const PANELS = [ ...@@ -33,6 +37,7 @@ const PANELS = [
illustration: createFromTemplateIllustration, illustration: createFromTemplateIllustration,
}, },
{ {
key: 'import',
name: 'import_project', name: 'import_project',
selector: '#import-project-pane', selector: '#import-project-pane',
title: s__('ProjectsNew|Import project'), title: s__('ProjectsNew|Import project'),
...@@ -42,6 +47,7 @@ const PANELS = [ ...@@ -42,6 +47,7 @@ const PANELS = [
illustration: importProjectIllustration, illustration: importProjectIllustration,
}, },
{ {
key: 'ci',
name: CI_CD_PANEL, name: CI_CD_PANEL,
selector: '#ci-cd-project-pane', selector: '#ci-cd-project-pane',
title: s__('ProjectsNew|Run CI/CD for external repository'), title: s__('ProjectsNew|Run CI/CD for external repository'),
...@@ -86,11 +92,27 @@ export default { ...@@ -86,11 +92,27 @@ export default {
computed: { computed: {
availablePanels() { availablePanels() {
const PANEL_TITLES = experiment(NEW_REPO_EXPERIMENT, {
use: () => ({
blank: s__('ProjectsNew|Create blank project'),
import: s__('ProjectsNew|Import project'),
}),
try: () => ({
blank: s__('ProjectsNew|Create blank project/repository'),
import: s__('ProjectsNew|Import project/repository'),
}),
});
const updatedPanels = PANELS.map(({ key, title, ...el }) => ({
...el,
title: PANEL_TITLES[key] !== undefined ? PANEL_TITLES[key] : title,
}));
if (this.isCiCdAvailable) { if (this.isCiCdAvailable) {
return PANELS; return updatedPanels;
} }
return PANELS.filter((p) => p.name !== CI_CD_PANEL); return updatedPanels.filter((p) => p.name !== CI_CD_PANEL);
}, },
activePanel() { activePanel() {
......
<script> <script>
/* eslint-disable vue/no-v-html */ /* eslint-disable vue/no-v-html */
import Tracking from '~/tracking'; import Tracking from '~/tracking';
import { NEW_REPO_EXPERIMENT } from '../constants';
import NewProjectPushTipPopover from './new_project_push_tip_popover.vue'; import NewProjectPushTipPopover from './new_project_push_tip_popover.vue';
const trackingMixin = Tracking.mixin(gon.tracking_data); const trackingMixin = Tracking.mixin({ ...gon.tracking_data, experiment: NEW_REPO_EXPERIMENT });
export default { export default {
components: { components: {
......
...@@ -74,6 +74,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -74,6 +74,7 @@ class ProjectsController < Projects::ApplicationController
@project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute @project = ::Projects::CreateService.new(current_user, project_params(attributes: project_params_create_attributes)).execute
if @project.saved? if @project.saved?
experiment(:new_repo, user: current_user).track(:project_created)
experiment(:new_project_readme, actor: current_user).track( experiment(:new_project_readme, actor: current_user).track(
:created, :created,
property: active_new_project_tab, property: active_new_project_tab,
......
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
= stylesheet_link_tag 'performance_bar' if performance_bar_enabled? = stylesheet_link_tag 'performance_bar' if performance_bar_enabled?
-# Rendering this above Gon, to use in JS later
= render 'layouts/header/new_repo_experiment'
= Gon::Base.render_data(nonce: content_security_policy_nonce) = Gon::Base.render_data(nonce: content_security_policy_nonce)
= javascript_include_tag locale_path unless I18n.locale == :en = javascript_include_tag locale_path unless I18n.locale == :en
......
%li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown", track_value: "" } } %li.header-new.dropdown{ data: { track_label: "new_dropdown", track_event: "click_dropdown", track_experiment: "new_repo" } }
= link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do = link_to new_project_path, class: "header-new-dropdown-toggle has-tooltip qa-new-menu-toggle", id: "js-onboarding-new-project-link", title: _("New..."), ref: 'tooltip', aria: { label: _("New...") }, data: { toggle: 'dropdown', placement: 'bottom', container: 'body', display: 'static' } do
= sprite_icon('plus-square') = sprite_icon('plus-square')
= sprite_icon('chevron-down', css_class: 'caret-down') = sprite_icon('chevron-down', css_class: 'caret-down')
...@@ -37,8 +37,7 @@ ...@@ -37,8 +37,7 @@
= render 'layouts/header/project_invite_members_new_dropdown_item' = render 'layouts/header/project_invite_members_new_dropdown_item'
%li.divider %li.divider
%li.dropdown-bold-header GitLab %li.dropdown-bold-header GitLab
- if current_user.can_create_project? = content_for :new_repo_experiment
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group? - if current_user.can_create_group?
%li= link_to _('New group'), new_group_path %li= link_to _('New group'), new_group_path
- if current_user.can?(:create_snippet) - if current_user.can?(:create_snippet)
......
- content_for :new_repo_experiment do
- if current_user&.can_create_project?
- experiment(:new_repo, user: current_user) do |e|
- e.use do
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link', data: { track_experiment: 'new_repo', track_event: 'click_link', track_label: 'plus_menu_dropdown' }
- e.try do
%li= link_to _('New project/repository'), new_project_path, class: 'qa-global-new-project-link', data: { track_experiment: 'new_repo', track_event: 'click_link', track_label: 'plus_menu_dropdown' }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
-# https://gitlab.com/gitlab-org/gitlab-foss/issues/49713 for more information. -# https://gitlab.com/gitlab-org/gitlab-foss/issues/49713 for more information.
%ul.list-unstyled.navbar-sub-nav %ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects) - if dashboard_nav_link?(:projects)
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do = nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown", track_experiment: "new_repo" } }) do
%button{ type: 'button', data: { toggle: "dropdown" } } %button{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects') = _('Projects')
= sprite_icon('chevron-down', css_class: 'caret-down') = sprite_icon('chevron-down', css_class: 'caret-down')
......
...@@ -11,14 +11,21 @@ ...@@ -11,14 +11,21 @@
= nav_link(path: 'projects#trending') do = nav_link(path: 'projects#trending') do
= link_to explore_root_path, data: { track_label: "projects_dropdown_explore_projects", track_event: "click_link" } do = link_to explore_root_path, data: { track_label: "projects_dropdown_explore_projects", track_event: "click_link" } do
= _('Explore projects') = _('Explore projects')
= nav_link(path: 'projects/new#blank_project', - experiment(:new_repo, user: current_user) do |e|
html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }, - e.use do
data: { track_label: "projects_dropdown_blank_project", track_event: "click_link" }) do = nav_link(path: 'projects/new#blank_project', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do
= link_to new_project_path(anchor: 'blank_project') do = link_to new_project_path(anchor: 'blank_project'), data: { track_label: "projects_dropdown_blank_project", track_event: "click_link", track_experiment: "new_repo" } do
= _('Create blank project') = _('Create blank project')
= nav_link(path: 'projects/new#import_project') do = nav_link(path: 'projects/new#import_project') do
= link_to new_project_path(anchor: 'import_project'), data: { track_label: "projects_dropdown_import_project", track_event: "click_link" } do = link_to new_project_path(anchor: 'import_project'), data: { track_label: "projects_dropdown_import_project", track_event: "click_link", track_experiment: "new_repo" } do
= _('Import project') = _('Import project')
- e.try do
= nav_link(path: 'projects/new#blank_project', html_options: { class: 'gl-border-0 gl-border-t-1 gl-border-solid gl-border-gray-100' }) do
= link_to new_project_path(anchor: 'blank_project'), data: { track_label: "projects_dropdown_blank_project", track_event: "click_link", track_experiment: "new_repo" } do
= _('Create blank project/repository')
= nav_link(path: 'projects/new#import_project') do
= link_to new_project_path(anchor: 'import_project'), data: { track_label: "projects_dropdown_import_project", track_event: "click_link", track_experiment: "new_repo" } do
= _('Import project/repository')
= nav_link(path: 'projects/new#create_from_template') do = nav_link(path: 'projects/new#create_from_template') do
= link_to new_project_path(anchor: 'create_from_template'), data: { track_label: "projects_dropdown_create_from_template", track_event: "click_link" } do = link_to new_project_path(anchor: 'create_from_template'), data: { track_label: "projects_dropdown_create_from_template", track_event: "click_link" } do
= _('Create from template') = _('Create from template')
......
---
name: new_repo
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55818
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285153
milestone: '13.11'
type: experiment
group: group::adoption
default_enabled: false
...@@ -8948,6 +8948,9 @@ msgstr "" ...@@ -8948,6 +8948,9 @@ msgstr ""
msgid "Create blank project" msgid "Create blank project"
msgstr "" msgstr ""
msgid "Create blank project/repository"
msgstr ""
msgid "Create branch" msgid "Create branch"
msgstr "" msgstr ""
...@@ -15982,6 +15985,9 @@ msgstr "" ...@@ -15982,6 +15985,9 @@ msgstr ""
msgid "Import project members" msgid "Import project members"
msgstr "" msgstr ""
msgid "Import project/repository"
msgstr ""
msgid "Import projects from Bitbucket" msgid "Import projects from Bitbucket"
msgstr "" msgstr ""
...@@ -20920,6 +20926,9 @@ msgstr "" ...@@ -20920,6 +20926,9 @@ msgstr ""
msgid "New project" msgid "New project"
msgstr "" msgstr ""
msgid "New project/repository"
msgstr ""
msgid "New release" msgid "New release"
msgstr "" msgstr ""
...@@ -24794,6 +24803,9 @@ msgstr "" ...@@ -24794,6 +24803,9 @@ msgstr ""
msgid "ProjectsNew|Create blank project" msgid "ProjectsNew|Create blank project"
msgstr "" msgstr ""
msgid "ProjectsNew|Create blank project/repository"
msgstr ""
msgid "ProjectsNew|Create from template" msgid "ProjectsNew|Create from template"
msgstr "" msgstr ""
...@@ -24812,6 +24824,9 @@ msgstr "" ...@@ -24812,6 +24824,9 @@ msgstr ""
msgid "ProjectsNew|Import project" msgid "ProjectsNew|Import project"
msgstr "" msgstr ""
msgid "ProjectsNew|Import project/repository"
msgstr ""
msgid "ProjectsNew|Initialize repository with a README" msgid "ProjectsNew|Initialize repository with a README"
msgstr "" msgstr ""
......
...@@ -5,7 +5,7 @@ module QA ...@@ -5,7 +5,7 @@ module QA
module Dashboard module Dashboard
module Snippet module Snippet
class Index < Page::Base class Index < Page::Base
view 'app/views/layouts/header/_new_dropdown.haml' do view 'app/views/layouts/header/_new_dropdown.html.haml' do
element :new_menu_toggle element :new_menu_toggle
element :global_new_snippet_link element :global_new_snippet_link
end end
......
...@@ -22,7 +22,7 @@ module QA ...@@ -22,7 +22,7 @@ module QA
element :file_tree_table element :file_tree_table
end end
view 'app/views/layouts/header/_new_dropdown.haml' do view 'app/views/layouts/header/_new_dropdown.html.haml' do
element :new_menu_toggle element :new_menu_toggle
element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern element :new_issue_link, "link_to _('New issue'), new_project_issue_path(@project)" # rubocop:disable QA/ElementWithPattern
end end
......
...@@ -448,6 +448,12 @@ RSpec.describe ProjectsController do ...@@ -448,6 +448,12 @@ RSpec.describe ProjectsController do
post :create, params: { project: project_params } post :create, params: { project: project_params }
end end
it 'tracks a created event for the new_repo experiment', :experiment do
expect(experiment(:new_repo, :candidate)).to track(:project_created).on_next_instance
post :create, params: { project: project_params }
end
end end
describe 'POST #archive' do describe 'POST #archive' do
......
...@@ -12,6 +12,72 @@ RSpec.describe 'New project', :js do ...@@ -12,6 +12,72 @@ RSpec.describe 'New project', :js do
sign_in(user) sign_in(user)
end end
context 'new repo experiment', :experiment do
it 'when in control renders "project"' do
stub_experiments(new_repo: :control)
visit new_project_path
find('li.header-new.dropdown').click
page.within('li.header-new.dropdown') do
expect(page).to have_selector('a', text: 'New project')
expect(page).to have_no_selector('a', text: 'New project/repository')
end
expect(page).to have_selector('.blank-state-title', text: 'Create blank project')
expect(page).to have_no_selector('.blank-state-title', text: 'Create blank project/repository')
end
it 'when in candidate renders "project/repository"' do
stub_experiments(new_repo: :candidate)
visit new_project_path
find('li.header-new.dropdown').click
page.within('li.header-new.dropdown') do
expect(page).to have_selector('a', text: 'New project/repository')
end
expect(page).to have_selector('.blank-state-title', text: 'Create blank project/repository')
end
context 'with combined_menu feature disabled' do
before do
stub_feature_flags(combined_menu: false)
end
it 'when in control it renders "project" in the new projects dropdown' do
stub_experiments(new_repo: :control)
visit new_project_path
find('#nav-projects-dropdown').click
page.within('#nav-projects-dropdown') do
expect(page).to have_selector('a', text: 'Create blank project')
expect(page).to have_selector('a', text: 'Import project')
expect(page).to have_no_selector('a', text: 'Create blank project/repository')
expect(page).to have_no_selector('a', text: 'Import project/repository')
end
end
it 'when in candidate it renders "project/repository" in the new projects dropdown' do
stub_experiments(new_repo: :candidate)
visit new_project_path
find('#nav-projects-dropdown').click
page.within('#nav-projects-dropdown') do
expect(page).to have_selector('a', text: 'Create blank project/repository')
expect(page).to have_selector('a', text: 'Import project/repository')
end
end
end
end
it 'shows a message if multiple levels are restricted' do it 'shows a message if multiple levels are restricted' do
Gitlab::CurrentSettings.update!( Gitlab::CurrentSettings.update!(
restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL] restricted_visibility_levels: [Gitlab::VisibilityLevel::PRIVATE, Gitlab::VisibilityLevel::INTERNAL]
......
import { GlBreadcrumb } from '@gitlab/ui'; import { GlBreadcrumb } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { assignGitlabExperiment } from 'helpers/experimentation_helper';
import App from '~/projects/experiment_new_project_creation/components/app.vue'; import App from '~/projects/experiment_new_project_creation/components/app.vue';
import LegacyContainer from '~/projects/experiment_new_project_creation/components/legacy_container.vue'; import LegacyContainer from '~/projects/experiment_new_project_creation/components/legacy_container.vue';
import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue'; import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue';
...@@ -17,6 +18,34 @@ describe('Experimental new project creation app', () => { ...@@ -17,6 +18,34 @@ describe('Experimental new project creation app', () => {
wrapper = null; wrapper = null;
}); });
const findWelcomePage = () => wrapper.findComponent(WelcomePage);
const findPanel = (panelName) =>
findWelcomePage()
.props()
.panels.find((p) => p.name === panelName);
describe('new_repo experiment', () => {
describe('when in the candidate variant', () => {
assignGitlabExperiment('new_repo', 'candidate');
it('has "repository" in the panel title', () => {
createComponent();
expect(findPanel('blank_project').title).toBe('Create blank project/repository');
});
});
describe('when in the control variant', () => {
assignGitlabExperiment('new_repo', 'control');
it('has "project" in the panel title', () => {
createComponent();
expect(findPanel('blank_project').title).toBe('Create blank project');
});
});
});
describe('with empty hash', () => { describe('with empty hash', () => {
beforeEach(() => { beforeEach(() => {
createComponent(); createComponent();
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import { mockTracking } from 'helpers/tracking_helper'; import { mockTracking } from 'helpers/tracking_helper';
import { TRACKING_CONTEXT_SCHEMA } from '~/experimentation/constants';
import { getExperimentData } from '~/experimentation/utils';
import NewProjectPushTipPopover from '~/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue'; import NewProjectPushTipPopover from '~/projects/experiment_new_project_creation/components/new_project_push_tip_popover.vue';
import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue'; import WelcomePage from '~/projects/experiment_new_project_creation/components/welcome.vue';
jest.mock('~/experimentation/utils', () => ({ getExperimentData: jest.fn() }));
describe('Welcome page', () => { describe('Welcome page', () => {
let wrapper; let wrapper;
let trackingSpy; let trackingSpy;
...@@ -14,6 +19,7 @@ describe('Welcome page', () => { ...@@ -14,6 +19,7 @@ describe('Welcome page', () => {
beforeEach(() => { beforeEach(() => {
trackingSpy = mockTracking('_category_', document, jest.spyOn); trackingSpy = mockTracking('_category_', document, jest.spyOn);
trackingSpy.mockImplementation(() => {}); trackingSpy.mockImplementation(() => {});
getExperimentData.mockReturnValue(undefined);
}); });
afterEach(() => { afterEach(() => {
...@@ -22,14 +28,35 @@ describe('Welcome page', () => { ...@@ -22,14 +28,35 @@ describe('Welcome page', () => {
wrapper = null; wrapper = null;
}); });
it('tracks link clicks', () => { it('tracks link clicks', async () => {
createComponent({ panels: [{ name: 'test', href: '#' }] }); createComponent({ panels: [{ name: 'test', href: '#' }] });
wrapper.find('a').trigger('click'); const link = wrapper.find('a');
link.trigger('click');
await nextTick();
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { label: 'test' }); expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', { label: 'test' });
}); });
}); });
it('adds new_repo experiment data if in experiment', async () => {
const mockExperimentData = 'data';
getExperimentData.mockReturnValue(mockExperimentData);
createComponent({ panels: [{ name: 'test', href: '#' }] });
const link = wrapper.find('a');
link.trigger('click');
await nextTick();
return wrapper.vm.$nextTick().then(() => {
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_tab', {
label: 'test',
context: {
data: mockExperimentData,
schema: TRACKING_CONTEXT_SCHEMA,
},
});
});
});
it('renders new project push tip popover', () => { it('renders new project push tip popover', () => {
createComponent({ panels: [{ name: 'test', href: '#' }] }); createComponent({ panels: [{ name: 'test', href: '#' }] });
......
...@@ -163,6 +163,7 @@ RSpec.describe 'layouts/header/_new_dropdown' do ...@@ -163,6 +163,7 @@ RSpec.describe 'layouts/header/_new_dropdown' do
end end
it 'has a "New project" link' do it 'has a "New project" link' do
render('layouts/header/new_repo_experiment')
render render
expect(rendered).to have_link('New project', href: new_project_path) expect(rendered).to have_link('New project', href: new_project_path)
......
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