Commit 7a25206a authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch 'nicolasdular/paid-signup-flow-events' into 'master'

Add paid signup tracking events

See merge request gitlab-org/gitlab!26811
parents 42e47a2f 39fd54b8
import LengthValidator from '~/pages/sessions/new/length_validator'; import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator'; import UsernameValidator from '~/pages/sessions/new/username_validator';
import NoEmojiValidator from '~/emoji/no_emoji_validator'; import NoEmojiValidator from '~/emoji/no_emoji_validator';
import Tracking from '~/tracking';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new
}); });
document.addEventListener('SnowplowInitialized', () => {
if (gon.tracking_data) {
const { category, action } = gon.tracking_data;
if (category && action) {
Tracking.event(category, action);
}
}
});
...@@ -7,6 +7,7 @@ import SubscriptionDetails from './checkout/subscription_details.vue'; ...@@ -7,6 +7,7 @@ import SubscriptionDetails from './checkout/subscription_details.vue';
import BillingAddress from './checkout/billing_address.vue'; import BillingAddress from './checkout/billing_address.vue';
import PaymentMethod from './checkout/payment_method.vue'; import PaymentMethod from './checkout/payment_method.vue';
import ConfirmOrder from './checkout/confirm_order.vue'; import ConfirmOrder from './checkout/confirm_order.vue';
import Tracking from '~/tracking';
export default { export default {
components: { ProgressBar, SubscriptionDetails, BillingAddress, PaymentMethod, ConfirmOrder }, components: { ProgressBar, SubscriptionDetails, BillingAddress, PaymentMethod, ConfirmOrder },
...@@ -18,6 +19,15 @@ export default { ...@@ -18,6 +19,15 @@ export default {
computed: { computed: {
...mapState(['isNewUser']), ...mapState(['isNewUser']),
}, },
created() {
document.addEventListener('SnowplowInitialized', () => {
Tracking.event('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start', {
label: null,
property: null,
value: null,
});
});
},
i18n: { i18n: {
checkout: s__('Checkout|Checkout'), checkout: s__('Checkout|Checkout'),
}, },
......
...@@ -4,6 +4,7 @@ import createFlash from '~/flash'; ...@@ -4,6 +4,7 @@ import createFlash from '~/flash';
import Api from 'ee/api'; import Api from 'ee/api';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import { STEPS, PAYMENT_FORM_ID } from '../constants'; import { STEPS, PAYMENT_FORM_ID } from '../constants';
import Tracking from '~/tracking';
export const activateStep = ({ commit }, currentStep) => { export const activateStep = ({ commit }, currentStep) => {
if (STEPS.includes(currentStep)) { if (STEPS.includes(currentStep)) {
...@@ -184,13 +185,26 @@ export const confirmOrder = ({ getters, dispatch, commit }) => { ...@@ -184,13 +185,26 @@ export const confirmOrder = ({ getters, dispatch, commit }) => {
Api.confirmOrder(getters.confirmOrderParams) Api.confirmOrder(getters.confirmOrderParams)
.then(({ data }) => { .then(({ data }) => {
if (data.location) dispatch('confirmOrderSuccess', data.location); if (data.location) {
else dispatch('confirmOrderError', JSON.stringify(data.errors)); dispatch('confirmOrderSuccess', {
location: data.location,
plan_id: data.plan_id,
quantity: data.quantity,
});
} else {
dispatch('confirmOrderError', JSON.stringify(data.errors));
}
}) })
.catch(() => dispatch('confirmOrderError')); .catch(() => dispatch('confirmOrderError'));
}; };
export const confirmOrderSuccess = (_, location) => { export const confirmOrderSuccess = (_, { location, plan_id, quantity }) => {
Tracking.event('Growth::Acquisition::Experiment::PaidSignUpFlow', 'end', {
label: plan_id,
property: null,
value: quantity,
});
redirectTo(location); redirectTo(location);
}; };
......
...@@ -5,6 +5,10 @@ module EE ...@@ -5,6 +5,10 @@ module EE
extend ActiveSupport::Concern extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override extend ::Gitlab::Utils::Override
prepended do
before_action :set_frontend_tracking_data, only: [:new]
end
private private
override :user_created_message override :user_created_message
...@@ -35,5 +39,9 @@ module EE ...@@ -35,5 +39,9 @@ module EE
super super
end end
end end
def set_frontend_tracking_data
frontend_experimentation_tracking_data(:paid_signup_flow, 'sign_up_page_view') if params[:redirect_from] == 'checkout'
end
end end
end end
...@@ -24,11 +24,13 @@ class SubscriptionsController < ApplicationController ...@@ -24,11 +24,13 @@ class SubscriptionsController < ApplicationController
def new def new
if experiment_enabled?(:paid_signup_flow) if experiment_enabled?(:paid_signup_flow)
track_paid_signup_flow_event('start_experiment') unless experiment_already_started?
if current_user if current_user
track_paid_signup_flow_event('start') track_paid_signup_flow_event('start')
else else
store_location_for :user, request.fullpath store_location_for_user
redirect_to new_user_registration_path redirect_to new_user_registration_path(redirect_from: 'checkout')
end end
else else
redirect_to customer_portal_new_subscription_url redirect_to customer_portal_new_subscription_url
...@@ -65,14 +67,14 @@ class SubscriptionsController < ApplicationController ...@@ -65,14 +67,14 @@ class SubscriptionsController < ApplicationController
).execute ).execute
if response[:success] if response[:success]
plan_id, quantity = subscription_params.values_at(:plan_id, :quantity)
redirect_location = if params[:selected_group] redirect_location = if params[:selected_group]
group_path(group) group_path(group)
else else
plan_id, quantity = subscription_params.values_at(:plan_id, :quantity)
edit_subscriptions_group_path(group.path, plan_id: plan_id, quantity: quantity, new_user: params[:new_user]) edit_subscriptions_group_path(group.path, plan_id: plan_id, quantity: quantity, new_user: params[:new_user])
end end
response[:data] = { location: redirect_location } response[:data] = { location: redirect_location, plan_id: plan_id, quantity: quantity }
track_paid_signup_flow_event('end', label: plan_id, value: quantity) track_paid_signup_flow_event('end', label: plan_id, value: quantity)
end end
...@@ -103,6 +105,15 @@ class SubscriptionsController < ApplicationController ...@@ -103,6 +105,15 @@ class SubscriptionsController < ApplicationController
Gitlab::SubscriptionPortal::Client Gitlab::SubscriptionPortal::Client
end end
def store_location_for_user
redirect_url = url_for(safe_params.merge(experiment_started: true))
store_location_for :user, redirect_url
end
def experiment_already_started?
params[:experiment_started].present?
end
def customer_portal_new_subscription_url def customer_portal_new_subscription_url
"#{EE::SUBSCRIPTIONS_URL}/subscriptions/new?plan_id=#{params[:plan_id]}&transaction=create_subscription" "#{EE::SUBSCRIPTIONS_URL}/subscriptions/new?plan_id=#{params[:plan_id]}&transaction=create_subscription"
end end
......
...@@ -42,6 +42,35 @@ describe RegistrationsController do ...@@ -42,6 +42,35 @@ describe RegistrationsController do
end end
end end
describe '#new' do
before do
stub_experiment(signup_flow: true, paid_signup_flow: true)
stub_experiment_for_user(signup_flow: true, paid_signup_flow: true)
end
context 'when not redirected from checkout page' do
it 'does not push tracking data to gon' do
get :new
expect(Gon.tracking_data).to eq(nil)
end
end
context 'when redirect from checkout page' do
it 'pushes tracking data to gon' do
get :new, params: { redirect_from: 'checkout' }
expect(Gon.tracking_data).to include(
{
category: 'Growth::Acquisition::Experiment::PaidSignUpFlow',
action: 'sign_up_page_view',
property: 'experimental_group'
}
)
end
end
end
describe '#welcome' do describe '#welcome' do
subject { get :welcome } subject { get :welcome }
......
...@@ -14,18 +14,32 @@ describe SubscriptionsController do ...@@ -14,18 +14,32 @@ describe SubscriptionsController do
stub_experiment_for_user(paid_signup_flow: true) stub_experiment_for_user(paid_signup_flow: true)
end end
context 'with unauthorized user' do context 'with unauthenticated user' do
it { is_expected.to have_gitlab_http_status(:redirect) } it { is_expected.to have_gitlab_http_status(:redirect) }
it { is_expected.to redirect_to new_user_registration_path } it { is_expected.to redirect_to new_user_registration_path(redirect_from: 'checkout') }
it 'stores subscription URL for later' do it 'stores subscription URL for later' do
subject subject
expect(controller.stored_location_for(:user)).to eq(new_subscriptions_path(plan_id: 'bronze_id')) expected_subscription_path = new_subscriptions_path(experiment_started: true, plan_id: 'bronze_id')
expect(controller.stored_location_for(:user)).to eq(expected_subscription_path)
end end
it 'tracks the event when experiment starts' do
expect(Gitlab::Tracking).to receive(:event).with('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start_experiment', label: nil, value: nil)
subject
end end
context 'with authorized user' do it 'does not track event when user got redirected to the subscription page again' do
get :new, params: { plan_id: 'bronze_id', experiment_started: 'true' }
expect(Gitlab::Tracking).not_to receive(:event).with('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start_experiment', label: nil, value: nil)
end
end
context 'with authenticated user' do
before do before do
sign_in(user) sign_in(user)
end end
...@@ -34,6 +48,7 @@ describe SubscriptionsController do ...@@ -34,6 +48,7 @@ describe SubscriptionsController do
it { is_expected.to render_template :new } it { is_expected.to render_template :new }
it 'tracks the event with the right parameters' do it 'tracks the event with the right parameters' do
expect(Gitlab::Tracking).to receive(:event).with('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start_experiment', label: nil, value: nil)
expect(Gitlab::Tracking).to receive(:event).with('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start', label: nil, value: nil) expect(Gitlab::Tracking).to receive(:event).with('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start', label: nil, value: nil)
subject subject
...@@ -197,7 +212,13 @@ describe SubscriptionsController do ...@@ -197,7 +212,13 @@ describe SubscriptionsController do
it 'returns the group edit location in JSON format' do it 'returns the group edit location in JSON format' do
subject subject
expect(response.body).to eq({ location: "/-/subscriptions/groups/#{group.path}/edit?plan_id=x&quantity=2" }.to_json) expected_response = {
location: "/-/subscriptions/groups/#{group.path}/edit?plan_id=x&quantity=2",
plan_id: 'x',
quantity: 2
}
expect(response.body).to eq(expected_response.to_json)
end end
end end
...@@ -234,7 +255,13 @@ describe SubscriptionsController do ...@@ -234,7 +255,13 @@ describe SubscriptionsController do
it 'returns the selected group location in JSON format' do it 'returns the selected group location in JSON format' do
subject subject
expect(response.body).to eq({ location: "/#{selected_group.path}" }.to_json) expected_response = {
location: "/#{selected_group.path}",
plan_id: 'x',
quantity: 1
}
expect(response.body).to eq(expected_response.to_json)
end end
end end
......
import Vuex from 'vuex'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { shallowMount, createLocalVue } from '@vue/test-utils';
import { mockTracking } from 'helpers/tracking_helper';
import createStore from 'ee/subscriptions/new/store'; import createStore from 'ee/subscriptions/new/store';
import Component from 'ee/subscriptions/new/components/checkout.vue'; import Component from 'ee/subscriptions/new/components/checkout.vue';
import ProgressBar from 'ee/subscriptions/new/components/checkout/progress_bar.vue'; import ProgressBar from 'ee/subscriptions/new/components/checkout/progress_bar.vue';
...@@ -10,6 +11,7 @@ describe('Checkout', () => { ...@@ -10,6 +11,7 @@ describe('Checkout', () => {
let store; let store;
let wrapper; let wrapper;
let spy;
const createComponent = () => { const createComponent = () => {
wrapper = shallowMount(Component, { wrapper = shallowMount(Component, {
...@@ -20,6 +22,7 @@ describe('Checkout', () => { ...@@ -20,6 +22,7 @@ describe('Checkout', () => {
const findProgressBar = () => wrapper.find(ProgressBar); const findProgressBar = () => wrapper.find(ProgressBar);
beforeEach(() => { beforeEach(() => {
spy = mockTracking('Growth::Acquisition::Experiment::PaidSignUpFlow', null, jest.spyOn);
store = createStore(); store = createStore();
createComponent(); createComponent();
}); });
...@@ -28,6 +31,16 @@ describe('Checkout', () => { ...@@ -28,6 +31,16 @@ describe('Checkout', () => {
wrapper.destroy(); wrapper.destroy();
}); });
it('sends tracking event when snowplow got initialized', () => {
document.dispatchEvent(new Event('SnowplowInitialized'));
expect(spy).toHaveBeenCalledWith('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start', {
label: null,
property: null,
value: null,
});
});
describe.each([[true, true], [false, false]])('when isNewUser=%s', (isNewUser, visible) => { describe.each([[true, true], [false, false]])('when isNewUser=%s', (isNewUser, visible) => {
beforeEach(() => { beforeEach(() => {
store.state.isNewUser = isNewUser; store.state.isNewUser = isNewUser;
......
import { mockTracking } from 'helpers/tracking_helper';
import testAction from 'helpers/vuex_action_helper'; import testAction from 'helpers/vuex_action_helper';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
...@@ -587,14 +588,15 @@ describe('Subscriptions Actions', () => { ...@@ -587,14 +588,15 @@ describe('Subscriptions Actions', () => {
describe('confirmOrder', () => { describe('confirmOrder', () => {
it('calls confirmOrderSuccess with a redirect location on success', done => { it('calls confirmOrderSuccess with a redirect location on success', done => {
mock.onPost(confirmOrderPath).replyOnce(200, { location: 'x' }); const response = { location: 'x', plan_id: 'id', quantity: 1 };
mock.onPost(confirmOrderPath).replyOnce(200, response);
testAction( testAction(
actions.confirmOrder, actions.confirmOrder,
null, null,
{}, {},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }], [{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }],
[{ type: 'confirmOrderSuccess', payload: 'x' }], [{ type: 'confirmOrderSuccess', payload: response }],
done, done,
); );
}); });
...@@ -627,14 +629,30 @@ describe('Subscriptions Actions', () => { ...@@ -627,14 +629,30 @@ describe('Subscriptions Actions', () => {
}); });
describe('confirmOrderSuccess', () => { describe('confirmOrderSuccess', () => {
const params = { location: 'http://example.com', plan_id: 'x', quantity: 10 };
it('changes the window location', done => { it('changes the window location', done => {
const spy = jest.spyOn(window.location, 'assign').mockImplementation(); const spy = jest.spyOn(window.location, 'assign').mockImplementation();
testAction(actions.confirmOrderSuccess, 'http://example.com', {}, [], [], () => { testAction(actions.confirmOrderSuccess, params, {}, [], [], () => {
expect(spy).toHaveBeenCalledWith('http://example.com'); expect(spy).toHaveBeenCalledWith('http://example.com');
done(); done();
}); });
}); });
it('sends tracking event', done => {
const spy = mockTracking('Growth::Acquisition::Experiment::PaidSignUpFlow', null, jest.spyOn);
jest.spyOn(window.location, 'assign').mockImplementation();
testAction(actions.confirmOrderSuccess, params, {}, [], [], () => {
expect(spy).toHaveBeenCalledWith('Growth::Acquisition::Experiment::PaidSignUpFlow', 'end', {
label: 'x',
property: null,
value: 10,
});
done();
});
});
}); });
describe('confirmOrderError', () => { describe('confirmOrderError', () => {
......
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