Commit 9b14a29f authored by Alper Akgun's avatar Alper Akgun Committed by Stan Hu

Trial onboarding in signup

Growth conversion experiment
parent d6805f4f
......@@ -45,7 +45,12 @@ Rails.application.routes.draw do
# Sign up
scope path: '/users/sign_up', module: :registrations, as: :users_sign_up do
resource :welcome, only: [:show, :update], controller: 'welcome'
resource :welcome, only: [:show, :update], controller: 'welcome' do
Gitlab.ee do
get :trial_getting_started, on: :collection
end
end
resource :experience_level, only: [:show, :update]
Gitlab.ee do
......
......@@ -5,6 +5,13 @@ module EE
module WelcomeController
extend ::Gitlab::Utils::Override
def trial_getting_started
project = learn_gitlab_project
return access_denied! unless current_user.id == project.creator_id
render locals: { learn_gitlab_project: learn_gitlab_project }
end
private
override :update_params
......@@ -21,6 +28,10 @@ module EE
clean_params
end
def learn_gitlab_project
::Project.find(params[:learn_gitlab_project_id])
end
end
end
end
......@@ -18,23 +18,31 @@ module Registrations
def create
@group = Groups::CreateService.new(current_user, group_params).execute
render_new && return unless @group.persisted?
trial = params[:trial] == 'true'
url_params = { namespace_id: @group.id, trial: trial }
if @group.persisted?
if helpers.in_trial_onboarding_flow?
render_new && return unless apply_trial
url_params[:trial_onboarding_flow] = true
else
record_experiment_user(:trial_during_signup, trial_chosen: trial)
if experiment_enabled?(:trial_during_signup)
if trial && create_lead && apply_trial
if trial
render_new && return unless create_lead && apply_trial
record_experiment_conversion_event(:trial_during_signup)
end
else
invite_members(@group)
end
redirect_to new_users_sign_up_project_path(namespace_id: @group.id, trial: trial)
else
render action: :new
end
redirect_to new_users_sign_up_project_path(url_params)
end
private
......@@ -51,6 +59,10 @@ module Registrations
params.require(:group).permit(:name, :path, :visibility_level)
end
def render_new
render action: :new
end
def create_lead
trial_params = {
trial_user: params.permit(
......@@ -71,20 +83,24 @@ module Registrations
)
}
result = GitlabSubscriptions::CreateLeadService.new.execute(trial_params)
result[:success]
flash[:alert] = result&.dig(:errors) unless result&.dig(:success)
result&.dig(:success)
end
def apply_trial
apply_trial_params = {
uid: current_user.id,
trial_user: {
trial_user: params.permit(:glm_source, :glm_content).merge({
namespace_id: @group.id,
gitlab_com_trial: true,
sync_to_gl: true
}
})
}
result = GitlabSubscriptions::ApplyTrialService.new.execute(apply_trial_params)
flash[:alert] = result&.dig(:errors) unless result&.dig(:success)
result&.dig(:success)
end
end
......
......@@ -17,8 +17,20 @@ module Registrations
@project = ::Projects::CreateService.new(current_user, project_params).execute
if @project.saved?
create_learn_gitlab_project
redirect_to users_sign_up_experience_level_path(namespace_path: @project.namespace)
learn_gitlab_project = create_learn_gitlab_project
if helpers.in_trial_onboarding_flow?
trial_onboarding_context = {
namespace_id: learn_gitlab_project.namespace_id,
project_id: @project.id,
learn_gitlab_project_id: learn_gitlab_project.id
}
record_experiment_user(:trial_onboarding_issues, trial_onboarding_context)
redirect_to trial_getting_started_users_sign_up_welcome_path(learn_gitlab_project_id: learn_gitlab_project.id)
else
redirect_to users_sign_up_experience_level_path(namespace_path: @project.namespace, trial_onboarding_flow: params[:trial_onboarding_flow])
end
else
render :new
end
......@@ -27,20 +39,26 @@ module Registrations
private
def create_learn_gitlab_project
title, filename = if helpers.in_trial_onboarding_flow?
[s_('Learn GitLab - Gold trial'), 'learn_gitlab_gold_trial.tar.gz']
else
[s_('Learn GitLab'), 'learn_gitlab.tar.gz']
end
learn_gitlab_template_path = Rails.root.join('vendor', 'project_templates', filename)
learn_gitlab_project = File.open(learn_gitlab_template_path) do |archive|
::Projects::GitlabProjectsImportService.new(
current_user,
namespace_id: @project.namespace_id,
file: archive,
name: s_('Learn GitLab')
name: title
).execute
end
cookies[:onboarding_issues_settings] = { 'groups#show' => true, 'projects#show' => true, 'issues#index' => true }.to_json if learn_gitlab_project.saved?
end
cookies[:onboarding_issues_settings] = { 'groups#show' => true, 'projects#show' => true, 'issues#index' => true }.to_json if learn_gitlab_project.saved? && !helpers.in_trial_onboarding_flow?
def learn_gitlab_template_path
Rails.root.join('vendor', 'project_templates', 'learn_gitlab.tar.gz')
learn_gitlab_project
end
def check_experiment_enabled
......
......@@ -22,13 +22,17 @@ class TrialsController < ApplicationController
end
def create_lead
url_params = { glm_source: params[:glm_source], glm_content: params[:glm_content] }
@result = GitlabSubscriptions::CreateLeadService.new.execute({ trial_user: company_params })
if @result[:success]
redirect_to select_trials_url(glm_source: params[:glm_source], glm_content: params[:glm_content])
else
render :new
render(:new) && return unless @result[:success]
if params[:glm_source] == 'about.gitlab.com'
record_experiment_user(:trial_onboarding_issues)
return redirect_to(new_users_sign_up_group_path(url_params.merge(trial_onboarding_flow: true))) if experiment_enabled?(:trial_onboarding_issues)
end
redirect_to select_trials_url(url_params)
end
def apply
......
......@@ -12,6 +12,10 @@ module EE
redirect_path == new_trial_path
end
def in_trial_onboarding_flow?
params[:trial_onboarding_flow] == 'true'
end
def in_invitation_flow?
redirect_path.present? && redirect_path.starts_with?('/-/invites/')
end
......
- page_title _('Your GitLab group')
- form_params = { trial_onboarding_flow: params[:trial_onboarding_flow], glm_source: params[:glm_source], glm_content: params[:glm_content] }
.row.flex-grow-1
.d-flex.flex-column.align-items-center.w-100.p-3.gl-bg-gray-10
.edit-group.d-flex.flex-column.align-items-center.pt-5
#progress-bar
- unless in_trial_onboarding_flow?
#progress-bar
%h2.center= _('Create your group')
%p
%div= _('A group represents your organization in GitLab. Groups allow you to manage users and collaborate across multiple projects.')
= form_for @group, url: users_sign_up_groups_path, html: { class: 'gl-show-field-errors card w-100 p-3' } do |f|
= form_for @group, url: users_sign_up_groups_path(form_params), html: { class: 'gl-show-field-errors card gl-w-full gl-p-4' } do |f|
= form_errors(@group)
= render 'layouts/flash'
.row
.form-group.group-name-holder.col-sm-12
= f.label :name, class: 'label-bold' do
......@@ -40,7 +42,7 @@
.row
.form-group.col-sm-12
= render partial: 'shared/groups/visibility_level', locals: { f: f }
- if experiment_enabled?(:trial_during_signup)
- if !in_trial_onboarding_flow? && experiment_enabled?(:trial_during_signup)
= render partial: 'shared/groups/trial_form'
- else
= render partial: 'shared/groups/invite_members'
......
......@@ -8,7 +8,8 @@
.row.flex-grow-1.bg-gray-light
.d-flex.flex-column.align-items-center.w-100.p-3
.new-project.d-flex.flex-column.align-items-center.pt-5
#progress-bar
- unless in_trial_onboarding_flow?
#progress-bar
%h2.center= _('Create/import your first project')
%p
.center= html_escape(_('This project will live in your group %{strong_open}%{namespace}%{strong_close}. A project is where you house your files (repository), plan your work (issues), publish your documentation (wiki), and so much more.')) % { namespace: html_escape(@project.namespace.name), strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe }
......@@ -24,7 +25,7 @@
.tab-content.gitlab-tab-content.bg-white
#blank-project-pane.tab-pane.js-toggle-container.active{ role: 'tabpanel' }
= form_for @project, url: users_sign_up_projects_path, html: { class: 'new_project' } do |f|
= form_for @project, url: users_sign_up_projects_path(trial_onboarding_flow: params[:trial_onboarding_flow]), html: { class: 'new_project' } do |f|
= form_errors(@project)
= f.hidden_field :namespace_id, value: @project.namespace_id
#blank-project-name.row
......
- return unless learn_gitlab_project
- page_title _('Get started with GitLab')
.row.gl-flex-grow-1.gl-bg-gray-10
.gl-display-flex.gl-flex-direction-column.gl-align-items-center.gl-w-full
.gl-display-flex.gl-flex-direction-column.gl-align-items-center.gl-pt-6.gl-w-half
= image_tag 'learn-gitlab-avatar.jpg', width: '90'
%h2.gl-text-center.gl-mt-5= _('Get started with GitLab')
%p.gl-text-center.gl-mb-5= _('We created a sandbox project that will help you learn the basics of GitLab. You’ll be guided by issues in an issue board. You can go through the issues at your own pace.')
= image_tag 'trial-learn-gitlab-gold-board.png', class: 'gl-w-full'
= link_to s_("Ok, let's go"), project_boards_path(learn_gitlab_project), class: 'btn btn-success gl-button gl-mt-5'
......@@ -52,13 +52,16 @@ RSpec.describe Registrations::GroupsController do
end
describe 'POST #create' do
let(:group_params) do
{ name: 'Group name', path: 'group-path', visibility_level: Gitlab::VisibilityLevel::PRIVATE, emails: ['', ''] }
let_it_be(:glm_params) { {} }
let_it_be(:trial_form_params) { { trial: 'false' } }
let_it_be(:trial_onboarding_issues_enabled) { false }
let_it_be(:trial_onboarding_flow_params) { {} }
let(:group_params) { { name: 'Group name', path: 'group-path', visibility_level: Gitlab::VisibilityLevel::PRIVATE, emails: ['', ''] } }
let(:params) do
{ group: group_params }.merge(glm_params).merge(trial_form_params).merge(trial_onboarding_flow_params)
end
subject { post :create, params: { group: group_params }.merge(trial_form_params) }
let_it_be(:trial_form_params) { { trial: 'false' } }
subject { post :create, params: params }
context 'with an unauthenticated user' do
it { is_expected.to have_gitlab_http_status(:redirect) }
......@@ -68,7 +71,7 @@ RSpec.describe Registrations::GroupsController do
context 'with an authenticated user' do
before do
sign_in(user)
stub_experiment_for_subject(onboarding_issues: true)
stub_experiment_for_subject(onboarding_issues: true, trial_onboarding_issues: trial_onboarding_issues_enabled)
end
it 'creates a group' do
......@@ -86,6 +89,7 @@ RSpec.describe Registrations::GroupsController do
context 'in experiment group for trial_during_signup' do
let_it_be(:group) { create(:group) }
let_it_be(:glm_params) { { glm_source: 'gitlab.com', glm_content: 'content' } }
let_it_be(:trial_form_params) do
{
trial: 'true',
......@@ -119,11 +123,15 @@ RSpec.describe Registrations::GroupsController do
let_it_be(:apply_trial_params) do
{
uid: user.id,
trial_user: {
namespace_id: group.id,
gitlab_com_trial: true,
sync_to_gl: true
}
trial_user: ActionController::Parameters.new(
{
glm_source: 'gitlab.com',
glm_content: 'content',
namespace_id: group.id,
gitlab_com_trial: true,
sync_to_gl: true
}
).permit!
}
end
......@@ -166,6 +174,36 @@ RSpec.describe Registrations::GroupsController do
it_behaves_like GroupInviteMembers
context 'when the trial onboarding is active' do
let_it_be(:group) { create(:group) }
let_it_be(:trial_onboarding_flow_params) { { trial_onboarding_flow: true, glm_source: 'about.gitlab.com', glm_content: 'content' } }
let_it_be(:trial_onboarding_issues_enabled) { true }
let_it_be(:apply_trial_params) do
{
uid: user.id,
trial_user: ActionController::Parameters.new(
{
glm_source: 'about.gitlab.com',
glm_content: 'content',
namespace_id: group.id,
gitlab_com_trial: true,
sync_to_gl: true
}
).permit!
}
end
it 'applies the trial to the group and redirects to the project path' do
expect_next_instance_of(::Groups::CreateService) do |service|
expect(service).to receive(:execute).and_return(group)
end
expect_next_instance_of(GitlabSubscriptions::ApplyTrialService) do |service|
expect(service).to receive(:execute).with(apply_trial_params).and_return({ success: true })
end
is_expected.to redirect_to(new_users_sign_up_project_path(namespace_id: group.id, trial: false, trial_onboarding_flow: true))
end
end
context 'when the group cannot be saved' do
let(:group_params) { { name: '', path: '' } }
......@@ -183,6 +221,15 @@ RSpec.describe Registrations::GroupsController do
it { is_expected.to have_gitlab_http_status(:ok) }
it { is_expected.to render_template(:new) }
context 'when the trial onboarding is active' do
let_it_be(:group) { create(:group) }
let_it_be(:trial_onboarding_flow_params) { { trial_onboarding_flow: true } }
let_it_be(:trial_onboarding_issues_enabled) { true }
it { is_expected.not_to receive(:apply_trial) }
it { is_expected.to render_template(:new) }
end
end
context 'with the experiment not enabled for user' do
......
......@@ -48,8 +48,9 @@ RSpec.describe Registrations::ProjectsController do
end
describe 'POST #create' do
subject { post :create, params: { project: params } }
subject { post :create, params: { project: params }.merge(trial_onboarding_flow_params) }
let_it_be(:trial_onboarding_flow_params) { {} }
let(:params) { { namespace_id: namespace.id, name: 'New project', path: 'project-path', visibility_level: Gitlab::VisibilityLevel::PRIVATE } }
context 'with an unauthenticated user' do
......@@ -57,24 +58,51 @@ RSpec.describe Registrations::ProjectsController do
it { is_expected.to redirect_to(new_user_session_path) }
end
context 'with an authenticated user' do
context 'with an authenticated user', :sidekiq_inline do
let_it_be(:trial_onboarding_issues_enabled) { false }
before do
namespace.add_owner(user)
sign_in(user)
stub_experiment_for_subject(onboarding_issues: true)
stub_experiment_for_subject(onboarding_issues: true, trial_onboarding_issues: trial_onboarding_issues_enabled)
end
it 'creates a new project, a "Learn GitLab" project, sets a cookie and redirects to the experience level page' do
expect { subject }.to change { namespace.projects.pluck(:name) }.from([]).to(['New project', s_('Learn GitLab')])
Sidekiq::Worker.drain_all
expect(subject).to have_gitlab_http_status(:redirect)
expect(subject).to redirect_to(users_sign_up_experience_level_path(namespace_path: namespace.to_param))
expect(namespace.projects.find_by_name(s_('Learn GitLab'))).to be_import_finished
expect(cookies[:onboarding_issues_settings]).not_to be_nil
end
context 'when the trial onboarding is active' do
let_it_be(:trial_onboarding_flow_params) { { trial_onboarding_flow: true } }
let_it_be(:trial_onboarding_issues_enabled) { true }
let_it_be(:project) { create(:project) }
let_it_be(:first_project) { create(:project) }
let_it_be(:trial_onboarding_context) do
{ learn_gitlab_project_id: project.id, namespace_id: project.namespace_id, project_id: first_project.id }
end
it 'creates a new project, a "Learn GitLab - Gold trial" project, does not set a cookie' do
expect { subject }.to change { namespace.projects.pluck(:name) }.from([]).to(['New project', s_('Learn GitLab - Gold trial')])
expect(subject).to have_gitlab_http_status(:redirect)
expect(namespace.projects.find_by_name(s_('Learn GitLab - Gold trial'))).to be_import_finished
expect(cookies[:onboarding_issues_settings]).to be_nil
end
it 'records context and redirects to the trial getting started page' do
expect_next_instance_of(::Projects::CreateService) do |service|
expect(service).to receive(:execute).and_return(first_project)
end
expect_next_instance_of(::Projects::GitlabProjectsImportService) do |service|
expect(service).to receive(:execute).and_return(project)
end
expect(controller).to receive(:record_experiment_user).with(:trial_onboarding_issues, trial_onboarding_context)
expect(subject).to redirect_to(trial_getting_started_users_sign_up_welcome_path(learn_gitlab_project_id: project.id))
end
end
context 'when the project cannot be saved' do
let(:params) { { name: '', path: '' } }
......
......@@ -4,6 +4,42 @@ require 'spec_helper'
RSpec.describe Registrations::WelcomeController do
let_it_be(:user) { create(:user) }
let_it_be(:another_user) { create(:user) }
let_it_be(:project) { create(:project, creator: user) }
describe '#trial_getting_started' do
subject(:trial_getting_started) do
get :trial_getting_started, params: { learn_gitlab_project_id: project.id }
end
context 'without a signed in user' do
it { is_expected.to redirect_to new_user_session_path }
end
context 'with the creator user signed' do
before do
sign_in(user)
end
it 'sets the learn_gitlab_project and renders' do
subject
is_expected.to render_template(:trial_getting_started)
end
end
context 'with any other user signed in except the creator' do
before do
sign_in(another_user)
end
it 'sets the learn_gitlab_project and renders' do
subject
is_expected.to have_gitlab_http_status(:not_found)
end
end
end
describe '#update' do
let(:setup_for_company) { 'false' }
......
......@@ -95,6 +95,26 @@ RSpec.describe TrialsController do
let(:create_lead_result) { true }
it { is_expected.to redirect_to(select_trials_url) }
context 'coming from about.gitlab.com' do
let(:post_params) { { glm_source: 'about.gitlab.com' } }
it 'records trial_onboarding_issues experiment users but does not redirect to onboarding' do
expect(controller).to receive(:record_experiment_user).with(:trial_onboarding_issues)
is_expected.to redirect_to(select_trials_url(glm_source: 'about.gitlab.com'))
end
context 'when experiment trial_onboarding_issues is enabled' do
before do
stub_experiment_for_subject(trial_onboarding_issues: true)
end
it 'records trial_onboarding_issues experiment users and redirects to onboarding' do
expect(controller).to receive(:record_experiment_user).with(:trial_onboarding_issues)
is_expected.to redirect_to(new_users_sign_up_group_path(glm_source: 'about.gitlab.com', trial_onboarding_flow: true))
end
end
end
end
context 'on failure' do
......
......@@ -266,4 +266,20 @@ RSpec.describe EE::WelcomeHelper do
expect(helper.skip_setup_for_company?).to be false
end
end
describe '#in_trial_onboarding_flow?' do
subject { helper.in_trial_onboarding_flow? }
it 'returns true if query param trial_flow is set to true' do
allow(helper).to receive(:params).and_return({ trial_onboarding_flow: 'true' })
is_expected.to eq(true)
end
it 'returns true if query param trial_flow is not set' do
allow(helper).to receive(:params).and_return({})
is_expected.to eq(false)
end
end
end
......@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe 'registrations/groups/new' do
let_it_be(:user) { create(:user) }
let_it_be(:trial_during_signup) { false }
let_it_be(:group) { create(:group) }
let_it_be(:trial_onboarding_flow) { false }
before do
assign(:group, group)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:experiment_enabled?).with(:trial_during_signup).and_return(trial_during_signup)
@group = create(:group)
allow(view).to receive(:in_trial_onboarding_flow?).and_return(trial_onboarding_flow)
render
end
......@@ -23,12 +26,33 @@ RSpec.describe 'registrations/groups/new' do
is_expected.to have_content('Company name')
is_expected.not_to have_selector('.js-invite-members')
end
context 'in trial onboarding' do
let_it_be(:trial_onboarding_flow) { true }
it 'hides trial form and shows invite members' do
is_expected.not_to have_content('Company name')
is_expected.to have_selector('.js-invite-members')
end
end
end
context 'feature flag trial_during_signup is disabled' do
it 'shows trial form and hides invite members' do
it 'hides trial form and shows invite members' do
is_expected.not_to have_content('Company name')
is_expected.to have_selector('.js-invite-members')
end
end
it 'shows the progress bar' do
expect(rendered).to have_selector('#progress-bar')
end
context 'in trial onboarding' do
let_it_be(:trial_onboarding_flow) { true }
it 'hides the progress bar' do
expect(rendered).not_to have_selector('#progress-bar')
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'registrations/projects/new' do
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace) }
let_it_be(:trial_onboarding_flow) { false }
before do
assign(:project, project)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:in_trial_onboarding_flow?).and_return(trial_onboarding_flow)
allow(view).to receive(:import_sources_enabled?).and_return(false)
render
end
it 'shows the progress bar' do
expect(rendered).to have_selector('#progress-bar')
end
context 'in trial onboarding' do
let_it_be(:trial_onboarding_flow) { true }
it 'hides the progress bar in trial onboarding' do
expect(rendered).not_to have_selector('#progress-bar')
end
end
end
......@@ -99,6 +99,9 @@ module Gitlab
},
invite_members_new_dropdown: {
tracking_category: 'Growth::Expansion::Experiment::InviteMembersNewDropdown'
},
trial_onboarding_issues: {
tracking_category: 'Growth::Conversion::Experiment::TrialOnboardingIssues'
}
}.freeze
......
......@@ -12995,6 +12995,9 @@ msgstr ""
msgid "Get started"
msgstr ""
msgid "Get started with GitLab"
msgstr ""
msgid "Get started with a project that follows best practices for setting up GitLab for your own organization, including sample Issues, Merge Requests, and Milestones"
msgstr ""
......@@ -16304,6 +16307,9 @@ msgstr ""
msgid "Learn GitLab"
msgstr ""
msgid "Learn GitLab - Gold trial"
msgstr ""
msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}"
msgstr ""
......@@ -19427,6 +19433,9 @@ msgstr ""
msgid "Oh no!"
msgstr ""
msgid "Ok, let's go"
msgstr ""
msgid "Oldest first"
msgstr ""
......@@ -31169,6 +31178,9 @@ msgstr ""
msgid "We couldn't reach the Prometheus server. Either the server no longer exists or the configuration details need updating."
msgstr ""
msgid "We created a sandbox project that will help you learn the basics of GitLab. You’ll be guided by issues in an issue board. You can go through the issues at your own pace."
msgstr ""
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
......
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