Merge pull request #4116 from overleaf/ab-paywall-prompt-events

Add paywall prompt events

GitOrigin-RevId: 6b1b3b384590f14828f37210b2e14047e2ee33d6
This commit is contained in:
Alexandre Bourdin 2021-06-10 10:04:21 +02:00 committed by Copybot
parent e2d116e8be
commit c634f51eee
13 changed files with 125 additions and 43 deletions

View file

@ -583,13 +583,13 @@ script(type="text/ng-template", id="trackChangesUpgradeModalTemplate")
div(ng-show="user.allowedFreeTrial" ng-controller="FreeTrialModalController")
a.btn.btn-success(
href
ng-click="startFreeTrial('real-time-track-changes')"
ng-click="startFreeTrial('track-changes')"
ng-show="project.owner._id == user.id"
) #{translate("try_it_for_free")}
div(ng-show="!user.allowedFreeTrial" ng-controller="UpgradeModalController")
a.btn.btn-success(
href
ng-click="upgradePlan('projectMembers')"
ng-click="upgradePlan('project-sharing')"
ng-show="project.owner._id == user.id"
) #{translate("upgrade")}
p(ng-show="project.owner._id != user.id"): strong #{translate("please_ask_the_project_owner_to_upgrade_to_track_changes")}

View file

@ -64,13 +64,13 @@ export default function AddCollaboratorsUpgrade() {
<StartFreeTrialButton
buttonStyle="success"
setStartedFreeTrial={setStartedFreeTrial}
source="projectMembers"
source="project-sharing"
/>
) : (
<Button
bsStyle="success"
onClick={() => {
upgradePlan('projectMembers')
upgradePlan('project-sharing')
setStartedFreeTrial(true)
}}
>

View file

@ -35,6 +35,7 @@ import './components/historyLabelsList'
import './components/historyLabel'
import './components/historyFileTree'
import './components/historyFileEntity'
import { paywallPrompt } from '../../../../frontend/js/main/account-upgrade'
let HistoryManager
export default HistoryManager = (function () {
@ -939,6 +940,7 @@ export default HistoryManager = (function () {
'editor-click-feature',
'history'
)
paywallPrompt('history')
}
break
}

View file

@ -441,6 +441,7 @@ App.controller(
'editor-click-feature',
'compile-timeout'
)
eventTracking.sendMB('compile-timeout-paywall-prompt')
}
} else if (response.status === 'terminated') {
$scope.pdf.view = 'errors'

View file

@ -917,6 +917,7 @@ export default App.controller(
'editor-click-feature',
'real-time-track-changes'
)
eventTracking.sendMB('track-changes-paywall-prompt')
}
const _setUserTCState = function (userId, newValue, isLocal) {

View file

@ -1,4 +1,7 @@
import { postJSON } from './fetch-json'
import sessionStorage from '../infrastructure/session-storage'
const CACHE_KEY = 'mbEvents'
export function send(category, action, label, value) {
if (typeof window.ga === 'function') {
@ -6,10 +9,29 @@ export function send(category, action, label, value) {
}
}
export function sendMB(key, body = {}) {
postJSON(`/event/${key}`, { body, keepalive: true }).catch(() => {
// ignore errors
})
export function sendMB(key, segmentation = {}) {
postJSON(`/event/${key}`, { body: segmentation, keepalive: true }).catch(
() => {
// ignore errors
}
)
}
export function sendMBOnce(key, segmentation = {}) {
let eventCache = sessionStorage.getItem(CACHE_KEY)
// Initialize as an empy object if the event cache is still empty.
if (eventCache == null) {
eventCache = {}
sessionStorage.setItem(CACHE_KEY, eventCache)
}
const isEventInCache = eventCache[key] || false
if (!isEventInCache) {
eventCache[key] = true
sessionStorage.setItem(CACHE_KEY, eventCache)
sendMB(key, segmentation)
}
}
export function sendMBSampled(key, body = {}, rate = 0.01) {

View file

@ -0,0 +1,45 @@
/**
* sessionStorage can throw browser exceptions, for example if it is full.
* We don't use sessionStorage for anything critical, so in that case just fail gracefully.
*/
/**
* Catch, log and otherwise ignore errors.
*
* @param {function} fn sessionStorage function to call
* @param {string?} key Key passed to the sessionStorage function (if any)
* @param {any?} value Value passed to the sessionStorage function (if any)
*/
const callSafe = function (fn, key, value) {
try {
return fn(key, value)
} catch (e) {
console.error('sessionStorage exception', e)
return null
}
}
const getItem = function (key) {
return JSON.parse(sessionStorage.getItem(key))
}
const setItem = function (key, value) {
sessionStorage.setItem(key, JSON.stringify(value))
}
const clear = function () {
sessionStorage.clear()
}
const removeItem = function (key) {
return sessionStorage.removeItem(key)
}
const customSessionStorage = {
getItem: key => callSafe(getItem, key),
setItem: (key, value) => callSafe(setItem, key, value),
clear: () => callSafe(clear),
removeItem: key => callSafe(removeItem, key),
}
export default customSessionStorage

View file

@ -1,13 +1,14 @@
import App from '../base'
import { startFreeTrial, upgradePlan } from './account-upgrade'
import { startFreeTrial, upgradePlan, paywallPrompt } from './account-upgrade'
App.controller('FreeTrialModalController', function ($scope, eventTracking) {
App.controller('FreeTrialModalController', function ($scope) {
$scope.buttonClass = 'btn-primary'
$scope.startFreeTrial = (source, version) =>
startFreeTrial(source, version, $scope, eventTracking)
startFreeTrial(source, version, $scope)
$scope.paywallPrompt = source => paywallPrompt(source)
})
App.controller('UpgradeModalController', function ($scope, eventTracking) {
App.controller('UpgradeModalController', function ($scope) {
$scope.buttonClass = 'btn-primary'
$scope.upgradePlan = source => upgradePlan(source, $scope)
})

View file

@ -1,4 +1,6 @@
function startFreeTrial(source, version, $scope, eventTracking) {
import * as eventTracking from '../infrastructure/event-tracking'
function startFreeTrial(source, version, $scope) {
const plan = 'collaborator_free_trial_7_days'
const w = window.open()
@ -7,6 +9,8 @@ function startFreeTrial(source, version, $scope, eventTracking) {
if (typeof ga === 'function') {
ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
}
eventTracking.sendMB(`${source}-paywall-click`)
url = `/user/subscription/new?planCode=${plan}&ssp=true`
url = `${url}&itm_campaign=${source}`
if (version) {
@ -17,10 +21,6 @@ function startFreeTrial(source, version, $scope, eventTracking) {
$scope.startedFreeTrial = true
}
if (eventTracking) {
eventTracking.sendMB('subscription-start-trial', { source, plan })
}
w.location = url
}
@ -45,4 +45,8 @@ function upgradePlan(source, $scope) {
go()
}
export { startFreeTrial, upgradePlan }
function paywallPrompt(source) {
eventTracking.sendMB(`${source}-paywall-prompt`)
}
export { startFreeTrial, upgradePlan, paywallPrompt }

View file

@ -1,15 +1,17 @@
angular
.module('sessionStorage', [])
.value('sessionStorage', function (...args) {
/*
sessionStorage can throw browser exceptions, for example if it is full
We don't use sessionStorage for anything critical, on in that case just
fail gracefully.
*/
try {
return $.sessionStorage(...args)
} catch (e) {
console.error('sessionStorage exception', e)
return null
}
})
angular.module('sessionStorage', []).value('sessionStorage', sessionStorage)
/*
sessionStorage can throw browser exceptions, for example if it is full
We don't use sessionStorage for anything critical, on in that case just
fail gracefully.
*/
function sessionStorage(...args) {
try {
return $.sessionStorage(...args)
} catch (e) {
console.error('sessionStorage exception', e)
return null
}
}
export default sessionStorage

View file

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { useCallback, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap'
import PropTypes from 'prop-types'
@ -13,25 +13,23 @@ export default function StartFreeTrialButton({
}) {
const { t } = useTranslation()
useEffect(() => {
eventTracking.sendMB(`${source}-paywall-prompt`)
}, [source])
const handleClick = useCallback(
event => {
event.preventDefault()
eventTracking.send('subscription-funnel', 'upgraded-free-trial', source)
const planCode = 'collaborator_free_trial_7_days'
eventTracking.sendMB('subscription-start-trial', {
source,
plan: planCode,
})
eventTracking.sendMB(`${source}-paywall-click`)
if (setStartedFreeTrial) {
setStartedFreeTrial(true)
}
const params = new URLSearchParams({
planCode,
planCode: 'collaborator_free_trial_7_days',
ssp: 'true',
itm_campaign: source,
})

View file

@ -639,6 +639,8 @@ describe('<ShareProjectModal/>', function () {
})
it('displays a message when the collaborator limit is reached', async function () {
fetchMock.post('/event/project-sharing-paywall-prompt', {})
renderWithEditorContext(
<ShareProjectModal
{...modalProps}

View file

@ -157,11 +157,15 @@ export default describe('HistoryV2Manager', function () {
$http: $http,
$filter: $filter,
}
this.eventTracking = {
sendMB: () => {},
}
this.localStorage = sinon.stub().returns(null)
this.historyManager = new HistoryV2Manager(
this.ide,
this.$scope,
this.localStorage
this.localStorage,
this.eventTracking
)
done()
})