mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-15 14:43:46 +01:00
Merge pull request #23939 from overleaf/rh-cio-analytics-split-test
Only send events to customer.io if in campaign split test GitOrigin-RevId: 572ad5efdfc1e86f525722c6a425fa1454f2cf3a
This commit is contained in:
@@ -768,6 +768,15 @@ const _ProjectController = {
|
|||||||
isPaywallChangeCompileTimeoutEnabled &&
|
isPaywallChangeCompileTimeoutEnabled &&
|
||||||
(await ProjectController._getPaywallPlansPrices(req, res))
|
(await ProjectController._getPaywallPlansPrices(req, res))
|
||||||
|
|
||||||
|
const customerIoEnabled =
|
||||||
|
await SplitTestHandler.promises.hasUserBeenAssignedToVariant(
|
||||||
|
req,
|
||||||
|
userId,
|
||||||
|
'customer-io-trial-conversion',
|
||||||
|
'enabled',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
|
||||||
const addonPrices =
|
const addonPrices =
|
||||||
isOverleafAssistBundleEnabled &&
|
isOverleafAssistBundleEnabled &&
|
||||||
(await ProjectController._getAddonPrices(req, res))
|
(await ProjectController._getAddonPrices(req, res))
|
||||||
@@ -883,6 +892,7 @@ const _ProjectController = {
|
|||||||
isPaywallChangeCompileTimeoutEnabled,
|
isPaywallChangeCompileTimeoutEnabled,
|
||||||
isOverleafAssistBundleEnabled,
|
isOverleafAssistBundleEnabled,
|
||||||
paywallPlans,
|
paywallPlans,
|
||||||
|
customerIoEnabled,
|
||||||
addonPrices,
|
addonPrices,
|
||||||
})
|
})
|
||||||
timer.done()
|
timer.done()
|
||||||
|
|||||||
@@ -127,10 +127,97 @@ async function getAssignmentForUser(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if user has already been explicitly assigned to a variant.
|
||||||
|
* This will be false if the user **would** be assigned when calling getAssignment but hasn't yet.
|
||||||
|
*
|
||||||
|
* @param req express request
|
||||||
|
* @param {string} userId the user ID
|
||||||
|
* @param {string} splitTestName the unique name of the split test
|
||||||
|
* @param {string} variant variant name to check
|
||||||
|
* @param {boolean} ignoreVersion users explicitly assigned to a previous version should be treated as if assigned to latest version
|
||||||
|
*/
|
||||||
|
async function hasUserBeenAssignedToVariant(
|
||||||
|
req,
|
||||||
|
userId,
|
||||||
|
splitTestName,
|
||||||
|
variant,
|
||||||
|
ignoreVersion = false
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { session, query = {} } = req
|
||||||
|
|
||||||
|
const splitTest = await _getSplitTest(splitTestName)
|
||||||
|
const currentVersion = SplitTestUtils.getCurrentVersion(splitTest)
|
||||||
|
|
||||||
|
// Check the query string for an override, ignoring an invalid value
|
||||||
|
const queryVariant = query[splitTestName]
|
||||||
|
if (queryVariant === variant) {
|
||||||
|
const variants = await _getVariantNames(splitTestName)
|
||||||
|
if (variants.includes(queryVariant)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow dev toolbar and session cache to override assignment from DB
|
||||||
|
if (Settings.devToolbar.enabled) {
|
||||||
|
const override = session?.splitTestOverrides?.[splitTestName]
|
||||||
|
if (override === variant) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const canUseSessionCache = session && SessionManager.isUserLoggedIn(session)
|
||||||
|
if (canUseSessionCache) {
|
||||||
|
const cachedVariant = SplitTestSessionHandler.getCachedVariant(
|
||||||
|
session,
|
||||||
|
splitTestName,
|
||||||
|
currentVersion
|
||||||
|
)
|
||||||
|
if (cachedVariant === variant) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get variant from db, including explicit assignments from previous versions if requested
|
||||||
|
const assignments = await getActiveAssignmentsForUser(
|
||||||
|
userId,
|
||||||
|
true,
|
||||||
|
ignoreVersion
|
||||||
|
)
|
||||||
|
const testAssignment = assignments[splitTestName]
|
||||||
|
|
||||||
|
if (!testAssignment || !testAssignment.assignedAt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if variant matches and we can use cache, we should persist it in cache
|
||||||
|
if (testAssignment.variantName === variant && testAssignment.assignedAt) {
|
||||||
|
if (canUseSessionCache) {
|
||||||
|
SplitTestSessionHandler.setVariantInCache({
|
||||||
|
session,
|
||||||
|
splitTestName,
|
||||||
|
currentVersion,
|
||||||
|
selectedVariantName: variant,
|
||||||
|
activeForUser: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error({ err: error }, 'Failed to get split test assignment for user')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a mapping of the active split test assignments for the given user
|
* Get a mapping of the active split test assignments for the given user
|
||||||
*/
|
*/
|
||||||
async function getActiveAssignmentsForUser(userId, removeArchived = false) {
|
async function getActiveAssignmentsForUser(
|
||||||
|
userId,
|
||||||
|
removeArchived = false,
|
||||||
|
ignoreVersion = false
|
||||||
|
) {
|
||||||
if (!Features.hasFeature('saas')) {
|
if (!Features.hasFeature('saas')) {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
@@ -156,9 +243,14 @@ async function getActiveAssignmentsForUser(userId, removeArchived = false) {
|
|||||||
}
|
}
|
||||||
const userAssignments = user.splitTests?.[splitTest.name]
|
const userAssignments = user.splitTests?.[splitTest.name]
|
||||||
if (Array.isArray(userAssignments)) {
|
if (Array.isArray(userAssignments)) {
|
||||||
const userAssignment = userAssignments.find(
|
let userAssignment
|
||||||
x => x.versionNumber === versionNumber
|
if (!ignoreVersion) {
|
||||||
)
|
userAssignment = userAssignments.find(
|
||||||
|
x => x.versionNumber === versionNumber
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
userAssignment = userAssignments[0]
|
||||||
|
}
|
||||||
if (userAssignment) {
|
if (userAssignment) {
|
||||||
assignment.assignedAt = userAssignment.assignedAt
|
assignment.assignedAt = userAssignment.assignedAt
|
||||||
}
|
}
|
||||||
@@ -578,6 +670,7 @@ module.exports = {
|
|||||||
getAssignmentForUser: callbackify(getAssignmentForUser),
|
getAssignmentForUser: callbackify(getAssignmentForUser),
|
||||||
getOneTimeAssignment: callbackify(getOneTimeAssignment),
|
getOneTimeAssignment: callbackify(getOneTimeAssignment),
|
||||||
getActiveAssignmentsForUser: callbackify(getActiveAssignmentsForUser),
|
getActiveAssignmentsForUser: callbackify(getActiveAssignmentsForUser),
|
||||||
|
hasUserBeenAssignedToVariant: callbackify(hasUserBeenAssignedToVariant),
|
||||||
setOverrideInSession,
|
setOverrideInSession,
|
||||||
clearOverridesInSession,
|
clearOverridesInSession,
|
||||||
promises: {
|
promises: {
|
||||||
@@ -585,5 +678,6 @@ module.exports = {
|
|||||||
getAssignmentForUser,
|
getAssignmentForUser,
|
||||||
getOneTimeAssignment,
|
getOneTimeAssignment,
|
||||||
getActiveAssignmentsForUser,
|
getActiveAssignmentsForUser,
|
||||||
|
hasUserBeenAssignedToVariant,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,10 +128,17 @@ async function _sendSubscriptionStartedEvent(userId, eventData) {
|
|||||||
|
|
||||||
if (isTrial) {
|
if (isTrial) {
|
||||||
await SubscriptionEmailHandler.sendTrialOnboardingEmail(userId, planCode)
|
await SubscriptionEmailHandler.sendTrialOnboardingEmail(userId, planCode)
|
||||||
await SplitTestHandler.promises.getAssignmentForUser(
|
const cioAssignment = await SplitTestHandler.promises.getAssignmentForUser(
|
||||||
userId,
|
userId,
|
||||||
'customer-io-trial-conversion'
|
'customer-io-trial-conversion'
|
||||||
)
|
)
|
||||||
|
if (cioAssignment.variant === 'enabled') {
|
||||||
|
AnalyticsManager.setUserPropertyForUserInBackground(
|
||||||
|
userId,
|
||||||
|
'customer-io-integration',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ meta(name="ol-shouldLoadHotjar" data-type="boolean" content=shouldLoadHotjar)
|
|||||||
meta(name="ol-isReviewerRoleEnabled" data-type="boolean" content=isReviewerRoleEnabled)
|
meta(name="ol-isReviewerRoleEnabled" data-type="boolean" content=isReviewerRoleEnabled)
|
||||||
meta(name="ol-odcRole" data-type="string" content=odcRole)
|
meta(name="ol-odcRole" data-type="string" content=odcRole)
|
||||||
meta(name="ol-isPaywallChangeCompileTimeoutEnabled" data-type="boolean" content=isPaywallChangeCompileTimeoutEnabled)
|
meta(name="ol-isPaywallChangeCompileTimeoutEnabled" data-type="boolean" content=isPaywallChangeCompileTimeoutEnabled)
|
||||||
|
meta(name='ol-customerIoEnabled' data-type="boolean" content=customerIoEnabled)
|
||||||
if(isPaywallChangeCompileTimeoutEnabled)
|
if(isPaywallChangeCompileTimeoutEnabled)
|
||||||
//- expose plans info to show prices in paywall-change-compile-timeout test
|
//- expose plans info to show prices in paywall-change-compile-timeout test
|
||||||
meta(name="ol-paywallPlans", data-type="json" content=paywallPlans)
|
meta(name="ol-paywallPlans", data-type="json" content=paywallPlans)
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ export function sendMB(key: string, segmentation: Segmentation = {}) {
|
|||||||
segmentation.page = window.location.pathname
|
segmentation.page = window.location.pathname
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getMeta('ol-customerIoEnabled')) {
|
||||||
|
segmentation['customerio-integration'] = 'enabled'
|
||||||
|
}
|
||||||
|
|
||||||
sendBeacon(key, segmentation)
|
sendBeacon(key, segmentation)
|
||||||
|
|
||||||
if (typeof window.gtag !== 'function') return
|
if (typeof window.gtag !== 'function') return
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export interface Meta {
|
|||||||
'ol-currentInstitutionsWithLicence': Institution[]
|
'ol-currentInstitutionsWithLicence': Institution[]
|
||||||
'ol-currentManagedUserAdminEmail': string
|
'ol-currentManagedUserAdminEmail': string
|
||||||
'ol-currentUrl': string
|
'ol-currentUrl': string
|
||||||
|
'ol-customerIoEnabled': boolean
|
||||||
'ol-debugPdfDetach': boolean
|
'ol-debugPdfDetach': boolean
|
||||||
'ol-detachRole': 'detached' | 'detacher' | ''
|
'ol-detachRole': 'detached' | 'detacher' | ''
|
||||||
'ol-dictionariesRoot': 'string'
|
'ol-dictionariesRoot': 'string'
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ describe('ProjectController', function () {
|
|||||||
promises: {
|
promises: {
|
||||||
getAssignment: sinon.stub().resolves({ variant: 'default' }),
|
getAssignment: sinon.stub().resolves({ variant: 'default' }),
|
||||||
getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
|
getAssignmentForUser: sinon.stub().resolves({ variant: 'default' }),
|
||||||
|
hasUserBeenAssignedToVariant: sinon.stub().resolves(false),
|
||||||
},
|
},
|
||||||
getAssignment: sinon.stub().yields(null, { variant: 'default' }),
|
getAssignment: sinon.stub().yields(null, { variant: 'default' }),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ const MODULE_PATH = Path.join(
|
|||||||
describe('SplitTestHandler', function () {
|
describe('SplitTestHandler', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.splitTests = [
|
this.splitTests = [
|
||||||
makeSplitTest('active-test'),
|
makeSplitTest('active-test', { versionNumber: 2 }),
|
||||||
makeSplitTest('not-active-test', { active: false }),
|
makeSplitTest('not-active-test', { active: false }),
|
||||||
makeSplitTest('legacy-test'),
|
makeSplitTest('legacy-test'),
|
||||||
makeSplitTest('no-analytics-test-1', { analyticsEnabled: false }),
|
makeSplitTest('no-analytics-test-1', { analyticsEnabled: false }),
|
||||||
@@ -109,6 +109,27 @@ describe('SplitTestHandler', function () {
|
|||||||
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
||||||
this.user._id
|
this.user._id
|
||||||
)
|
)
|
||||||
|
this.explicitAssignments =
|
||||||
|
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
||||||
|
this.user._id,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
this.assignedToActiveTest =
|
||||||
|
await this.SplitTestHandler.promises.hasUserBeenAssignedToVariant(
|
||||||
|
this.req,
|
||||||
|
this.user._id,
|
||||||
|
'active-test',
|
||||||
|
'variant-1'
|
||||||
|
)
|
||||||
|
this.assignedToActiveTestAnyVersion =
|
||||||
|
await this.SplitTestHandler.promises.hasUserBeenAssignedToVariant(
|
||||||
|
this.req,
|
||||||
|
this.user._id,
|
||||||
|
'active-test',
|
||||||
|
'variant-1',
|
||||||
|
true
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles the legacy assignment format', function () {
|
it('handles the legacy assignment format', function () {
|
||||||
@@ -123,7 +144,15 @@ describe('SplitTestHandler', function () {
|
|||||||
expect(this.assignments['active-test']).to.deep.equal({
|
expect(this.assignments['active-test']).to.deep.equal({
|
||||||
variantName: 'variant-1',
|
variantName: 'variant-1',
|
||||||
phase: 'release',
|
phase: 'release',
|
||||||
versionNumber: 1,
|
versionNumber: 2,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the explicit assignment for each active test', function () {
|
||||||
|
expect(this.explicitAssignments['active-test']).to.deep.equal({
|
||||||
|
variantName: 'variant-1',
|
||||||
|
phase: 'release',
|
||||||
|
versionNumber: 2,
|
||||||
assignedAt: 'active-test-assigned-at',
|
assignedAt: 'active-test-assigned-at',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -144,6 +173,14 @@ describe('SplitTestHandler', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows user has been assigned to previous version of variant', function () {
|
||||||
|
expect(this.assignedToActiveTestAnyVersion).to.be.true
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows user has not been explicitly assigned to current version of variant', function () {
|
||||||
|
expect(this.assignedToActiveTest).to.be.false
|
||||||
|
})
|
||||||
|
|
||||||
it('does not return assignments for unknown tests', function () {
|
it('does not return assignments for unknown tests', function () {
|
||||||
expect(this.assignments).not.to.have.property('unknown-test')
|
expect(this.assignments).not.to.have.property('unknown-test')
|
||||||
})
|
})
|
||||||
@@ -171,6 +208,19 @@ describe('SplitTestHandler', function () {
|
|||||||
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
||||||
this.user._id
|
this.user._id
|
||||||
)
|
)
|
||||||
|
this.explicitAssignments =
|
||||||
|
await this.SplitTestHandler.promises.getActiveAssignmentsForUser(
|
||||||
|
this.user._id,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
this.assignedToActiveTest =
|
||||||
|
await this.SplitTestHandler.promises.hasUserBeenAssignedToVariant(
|
||||||
|
this.req,
|
||||||
|
this.user._id,
|
||||||
|
'active-test',
|
||||||
|
'variant-1'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns current assignments', function () {
|
it('returns current assignments', function () {
|
||||||
@@ -178,7 +228,7 @@ describe('SplitTestHandler', function () {
|
|||||||
'active-test': {
|
'active-test': {
|
||||||
phase: 'release',
|
phase: 'release',
|
||||||
variantName: 'variant-1',
|
variantName: 'variant-1',
|
||||||
versionNumber: 1,
|
versionNumber: 2,
|
||||||
},
|
},
|
||||||
'legacy-test': {
|
'legacy-test': {
|
||||||
phase: 'release',
|
phase: 'release',
|
||||||
@@ -202,6 +252,10 @@ describe('SplitTestHandler', function () {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows user not assigned to variant', function () {
|
||||||
|
expect(this.assignedToActiveTest).to.be.false
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with settings overrides', function () {
|
describe('with settings overrides', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user