Commit 39fd54b8 authored by nicolasdular's avatar nicolasdular

Add paid signup tracking events

This adds the required tracking for the new paid signup flow.
We're using frontend tracking as well because it gives us a user
session. For users who have not activated frontend tracking, we
can fallback to backend tracking.

Two things to be aware of:

1. Users who go to /-/subscriptions/new who are not signed in, get
redirected to /users/sign_up. After they successfully created an
account or signed in, we redirect them to /-/subscriptions/new again.
However, when they go to -/subscriptions/new, we would track the
experiment_start event a second time. Therefore, we added the param
experiment_started to not call track them twice

2. For the sign_up_view event, we only want to track users who got
redirected from /-/subscriptions/new, therefore we have added the
redirected_from=checkout param.
parent 3c1e19ba
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import Tracking from '~/tracking';
document.addEventListener('DOMContentLoaded', () => {
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // 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';
import BillingAddress from './checkout/billing_address.vue';
import PaymentMethod from './checkout/payment_method.vue';
import ConfirmOrder from './checkout/confirm_order.vue';
import Tracking from '~/tracking';
export default {
components: { ProgressBar, SubscriptionDetails, BillingAddress, PaymentMethod, ConfirmOrder },
......@@ -18,6 +19,15 @@ export default {
computed: {
...mapState(['isNewUser']),
},
created() {
document.addEventListener('SnowplowInitialized', () => {
Tracking.event('Growth::Acquisition::Experiment::PaidSignUpFlow', 'start', {
label: null,
property: null,
value: null,
});
});
},
i18n: {
checkout: s__('Checkout|Checkout'),
},
......
......@@ -4,6 +4,7 @@ import createFlash from '~/flash';
import Api from 'ee/api';
import { redirectTo } from '~/lib/utils/url_utility';
import { STEPS, PAYMENT_FORM_ID } from '../constants';
import Tracking from '~/tracking';
export const activateStep = ({ commit }, currentStep) => {
if (STEPS.includes(currentStep)) {
......@@ -184,13 +185,26 @@ export const confirmOrder = ({ getters, dispatch, commit }) => {
Api.confirmOrder(getters.confirmOrderParams)
.then(({ data }) => {
if (data.location) dispatch('confirmOrderSuccess', data.location);
else dispatch('confirmOrderError', JSON.stringify(data.errors));
if (data.location) {
dispatch('confirmOrderSuccess', {
location: data.location,
plan_id: data.plan_id,
quantity: data.quantity,
});
} else {
dispatch('confirmOrderError', JSON.stringify(data.errors));
}
})
.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);
};
......
......@@ -5,6 +5,10 @@ module EE
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
before_action :set_frontend_tracking_data, only: [:new]
end
private
override :user_created_message
......@@ -35,5 +39,9 @@ module EE
super
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
......@@ -24,11 +24,13 @@ class SubscriptionsController < ApplicationController
def new
if experiment_enabled?(:paid_signup_flow)
track_paid_signup_flow_event('start_experiment') unless experiment_already_started?
if current_user
track_paid_signup_flow_event('start')
else
store_location_for :user, request.fullpath
redirect_to new_user_registration_path
store_location_for_user
redirect_to new_user_registration_path(redirect_from: 'checkout')
end
else
redirect_to customer_portal_new_subscription_url
......@@ -65,14 +67,14 @@ class SubscriptionsController < ApplicationController
).execute
if response[:success]
plan_id, quantity = subscription_params.values_at(:plan_id, :quantity)
redirect_location = if params[:selected_group]
group_path(group)
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])
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)
end
......@@ -103,6 +105,15 @@ class SubscriptionsController < ApplicationController
Gitlab::SubscriptionPortal::Client
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
"#{EE::SUBSCRIPTIONS_URL}/subscriptions/new?plan_id=#{params[:plan_id]}&transaction=create_subscription"
end
......
......@@ -42,6 +42,35 @@ describe RegistrationsController do
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
subject { get :welcome }
......
......@@ -14,18 +14,32 @@ describe SubscriptionsController do
stub_experiment_for_user(paid_signup_flow: true)
end
context 'with unauthorized user' do
context 'with unauthenticated user' do
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
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
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
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 authorized user' do
context 'with authenticated user' do
before do
sign_in(user)
end
......@@ -34,6 +48,7 @@ describe SubscriptionsController do
it { is_expected.to render_template :new }
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)
subject
......@@ -197,7 +212,13 @@ describe SubscriptionsController do
it 'returns the group edit location in JSON format' do
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
......@@ -234,7 +255,13 @@ describe SubscriptionsController do
it 'returns the selected group location in JSON format' do
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
......
import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { mockTracking } from 'helpers/tracking_helper';
import createStore from 'ee/subscriptions/new/store';
import Component from 'ee/subscriptions/new/components/checkout.vue';
import ProgressBar from 'ee/subscriptions/new/components/checkout/progress_bar.vue';
......@@ -10,6 +11,7 @@ describe('Checkout', () => {
let store;
let wrapper;
let spy;
const createComponent = () => {
wrapper = shallowMount(Component, {
......@@ -20,6 +22,7 @@ describe('Checkout', () => {
const findProgressBar = () => wrapper.find(ProgressBar);
beforeEach(() => {
spy = mockTracking('Growth::Acquisition::Experiment::PaidSignUpFlow', null, jest.spyOn);
store = createStore();
createComponent();
});
......@@ -28,6 +31,16 @@ describe('Checkout', () => {
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) => {
beforeEach(() => {
store.state.isNewUser = isNewUser;
......
import { mockTracking } from 'helpers/tracking_helper';
import testAction from 'helpers/vuex_action_helper';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
......@@ -587,14 +588,15 @@ describe('Subscriptions Actions', () => {
describe('confirmOrder', () => {
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(
actions.confirmOrder,
null,
{},
[{ type: 'UPDATE_IS_CONFIRMING_ORDER', payload: true }],
[{ type: 'confirmOrderSuccess', payload: 'x' }],
[{ type: 'confirmOrderSuccess', payload: response }],
done,
);
});
......@@ -627,14 +629,30 @@ describe('Subscriptions Actions', () => {
});
describe('confirmOrderSuccess', () => {
const params = { location: 'http://example.com', plan_id: 'x', quantity: 10 };
it('changes the window location', done => {
const spy = jest.spyOn(window.location, 'assign').mockImplementation();
testAction(actions.confirmOrderSuccess, 'http://example.com', {}, [], [], () => {
testAction(actions.confirmOrderSuccess, params, {}, [], [], () => {
expect(spy).toHaveBeenCalledWith('http://example.com');
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', () => {
......
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