Merge pull request #34130 from overleaf/rh-enterprise-cio
Expose enterprise indicators and previous_plan_type for first subscriptions to customer.io GitOrigin-RevId: 693db7f796609f00ecd31216a6d6be32c1f569c8
This commit is contained in:
@@ -28,6 +28,7 @@ import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
|
||||
import SplitTestSessionHandler from '../SplitTests/SplitTestSessionHandler.mjs'
|
||||
import TutorialHandler from '../Tutorial/TutorialHandler.mjs'
|
||||
import SubscriptionHelper from '../Subscription/SubscriptionHelper.mjs'
|
||||
import CustomerIoPlanHelpers from '../Subscription/CustomerIoPlanHelpers.mjs'
|
||||
import PermissionsManager from '../Authorization/PermissionsManager.mjs'
|
||||
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
|
||||
import { OnboardingDataCollection } from '../../models/OnboardingDataCollection.mjs'
|
||||
@@ -585,6 +586,7 @@ async function projectListPage(req, res, next) {
|
||||
...(usedLatex && { used_latex: usedLatex }),
|
||||
...(countryCode && { country: countryCode }),
|
||||
...(commonsInstitution && { commons_institution: commonsInstitution }),
|
||||
...CustomerIoPlanHelpers.getAffiliationProperties(userEmails),
|
||||
...(groupRole && { group_role: groupRole }),
|
||||
is_managed_user: Boolean(user.enrollment?.managedBy),
|
||||
...(user.email && { email: user.email }),
|
||||
|
||||
@@ -7,6 +7,7 @@ import FeaturesHelper from './FeaturesHelper.mjs'
|
||||
* @typedef {InstanceType<typeof import('../../models/Subscription.mjs').Subscription>} MongoSubscription
|
||||
* @typedef {import('../../../../types/subscription/plan').Plan} Plan
|
||||
* @typedef {import('../../../../modules/subscriptions/app/src/PaymentService.mjs').PaymentRecord} PaymentRecord
|
||||
* @typedef {import('../../../../types/user-email').UserEmailData} UserEmailData
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -540,6 +541,28 @@ function getGroupRole(
|
||||
return 'member'
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer.io properties derived from a user's institutional affiliations.
|
||||
*
|
||||
* @param {UserEmailData[]} userEmails - email data from UserGetter.getUserFullEmails
|
||||
* @returns {{ enterprise_commons: boolean, domain_capture: boolean }}
|
||||
*/
|
||||
function getAffiliationProperties(userEmails) {
|
||||
const enterpriseCommons = userEmails.some(
|
||||
emailData =>
|
||||
emailData.emailHasInstitutionLicence &&
|
||||
emailData.affiliation?.institution?.commonsAccount &&
|
||||
emailData.affiliation?.institution?.enterpriseCommons
|
||||
)
|
||||
const domainCapture = userEmails.some(
|
||||
emailData => emailData.affiliation?.group?.domainCaptureEnabled
|
||||
)
|
||||
return {
|
||||
enterprise_commons: enterpriseCommons,
|
||||
domain_capture: domainCapture,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute plan-related user properties for sending to customer.io.
|
||||
*
|
||||
@@ -657,4 +680,5 @@ export default {
|
||||
shouldUseCommonsBestSubscription,
|
||||
getGroupRole,
|
||||
getPlanProperties,
|
||||
getAffiliationProperties,
|
||||
}
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ const userEmailData: UserEmailData & { affiliation: Affiliation } = {
|
||||
department: 'Art History',
|
||||
institution: {
|
||||
commonsAccount: false,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 1,
|
||||
|
||||
+2
@@ -24,6 +24,7 @@ const userData1: UserEmailData & { affiliation: Affiliation } = {
|
||||
department: null,
|
||||
institution: {
|
||||
commonsAccount: false,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 1,
|
||||
@@ -55,6 +56,7 @@ const userData2: UserEmailData & { affiliation: Affiliation } = {
|
||||
department: 'Art History',
|
||||
institution: {
|
||||
commonsAccount: false,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 1,
|
||||
|
||||
@@ -29,6 +29,7 @@ export const professionalUserData: UserEmailData & {
|
||||
department: 'Art History',
|
||||
institution: {
|
||||
commonsAccount: false,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 1,
|
||||
@@ -62,6 +63,7 @@ export const unconfirmedCommonsUserData: UserEmailData & {
|
||||
department: 'Art History',
|
||||
institution: {
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 1,
|
||||
@@ -92,6 +94,7 @@ export const ssoUserData: UserEmailData = {
|
||||
department: 'Art History',
|
||||
institution: {
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: false,
|
||||
writefullCommonsAccount: false,
|
||||
confirmed: true,
|
||||
id: 2,
|
||||
|
||||
+2
@@ -12,6 +12,7 @@ const memberships: Institution[] = [
|
||||
id: 9258,
|
||||
name: 'Test University',
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: false,
|
||||
isUniversity: true,
|
||||
confirmed: true,
|
||||
ssoBeta: false,
|
||||
@@ -23,6 +24,7 @@ const memberships: Institution[] = [
|
||||
id: 9259,
|
||||
name: 'Example Institution',
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: false,
|
||||
isUniversity: true,
|
||||
confirmed: true,
|
||||
ssoBeta: false,
|
||||
|
||||
@@ -553,6 +553,107 @@ describe('ProjectListController', function () {
|
||||
)
|
||||
})
|
||||
|
||||
it('should send enterprise_commons=true when user has commons from an enterprise_commons institution', async function (ctx) {
|
||||
ctx.Features.hasFeature.withArgs('saas').returns(true)
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test@overleaf.com',
|
||||
emailHasInstitutionLicence: true,
|
||||
affiliation: {
|
||||
institution: {
|
||||
id: 1,
|
||||
name: 'Overleaf',
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
ctx.res.render = () => {}
|
||||
|
||||
await ctx.ProjectListController.projectListPage(ctx.req, ctx.res)
|
||||
|
||||
expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
||||
'setUserProperties',
|
||||
ctx.user._id,
|
||||
sinon.match({ enterprise_commons: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('should send enterprise_commons=false when affiliated with an enterprise_commons institution but without active commons access', async function (ctx) {
|
||||
ctx.Features.hasFeature.withArgs('saas').returns(true)
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test@overleaf.com',
|
||||
emailHasInstitutionLicence: false,
|
||||
affiliation: {
|
||||
institution: {
|
||||
id: 1,
|
||||
name: 'Overleaf',
|
||||
commonsAccount: true,
|
||||
enterpriseCommons: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
ctx.res.render = () => {}
|
||||
|
||||
await ctx.ProjectListController.projectListPage(ctx.req, ctx.res)
|
||||
|
||||
expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
||||
'setUserProperties',
|
||||
ctx.user._id,
|
||||
sinon.match({ enterprise_commons: false })
|
||||
)
|
||||
})
|
||||
|
||||
it('should send domain_capture=true when user has an affiliation with domain capture enabled', async function (ctx) {
|
||||
ctx.Features.hasFeature.withArgs('saas').returns(true)
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test@overleaf.com',
|
||||
affiliation: {
|
||||
institution: { id: 1, name: 'Overleaf' },
|
||||
group: {
|
||||
_id: 'g1',
|
||||
domainCaptureEnabled: true,
|
||||
managedUsersEnabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
])
|
||||
ctx.res.render = () => {}
|
||||
|
||||
await ctx.ProjectListController.projectListPage(ctx.req, ctx.res)
|
||||
|
||||
expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
||||
'setUserProperties',
|
||||
ctx.user._id,
|
||||
sinon.match({ domain_capture: true })
|
||||
)
|
||||
})
|
||||
|
||||
it('should send domain_capture=false when no affiliation has domain capture enabled', async function (ctx) {
|
||||
ctx.Features.hasFeature.withArgs('saas').returns(true)
|
||||
ctx.UserGetter.promises.getUserFullEmails.resolves([
|
||||
{
|
||||
email: 'test@overleaf.com',
|
||||
affiliation: {
|
||||
institution: { id: 1, name: 'Overleaf' },
|
||||
},
|
||||
},
|
||||
])
|
||||
ctx.res.render = () => {}
|
||||
|
||||
await ctx.ProjectListController.projectListPage(ctx.req, ctx.res)
|
||||
|
||||
expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
|
||||
'setUserProperties',
|
||||
ctx.user._id,
|
||||
sinon.match({ domain_capture: false })
|
||||
)
|
||||
})
|
||||
|
||||
it('should show INR Banner for Indian users with free account', async function (ctx) {
|
||||
// usersBestSubscription is only available when saas feature is present
|
||||
ctx.Features.hasFeature.withArgs('saas').returns(true)
|
||||
|
||||
@@ -80,4 +80,66 @@ describe('CustomerIoPlanHelpers', function () {
|
||||
expect(properties.past_due).to.equal(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getAffiliationProperties', function () {
|
||||
it('sets enterprise_commons=true when the user has active commons access at an enterprise_commons institution', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([
|
||||
{
|
||||
emailHasInstitutionLicence: true,
|
||||
affiliation: {
|
||||
institution: { commonsAccount: true, enterpriseCommons: true },
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(properties.enterprise_commons).to.equal(true)
|
||||
})
|
||||
|
||||
it('sets enterprise_commons=false when affiliated with an enterprise_commons institution but without active commons access', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([
|
||||
{
|
||||
emailHasInstitutionLicence: false,
|
||||
affiliation: {
|
||||
institution: { commonsAccount: true, enterpriseCommons: true },
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(properties.enterprise_commons).to.equal(false)
|
||||
})
|
||||
|
||||
it('sets enterprise_commons=false when active access is not at an enterprise_commons institution', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([
|
||||
{
|
||||
emailHasInstitutionLicence: true,
|
||||
affiliation: {
|
||||
institution: { commonsAccount: true, enterpriseCommons: false },
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(properties.enterprise_commons).to.equal(false)
|
||||
})
|
||||
|
||||
it('sets enterprise_commons=false when there are no emails', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([])
|
||||
expect(properties.enterprise_commons).to.equal(false)
|
||||
})
|
||||
|
||||
it('sets domain_capture=true when an affiliation has domain capture enabled', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([
|
||||
{
|
||||
affiliation: {
|
||||
institution: {},
|
||||
group: { domainCaptureEnabled: true },
|
||||
},
|
||||
},
|
||||
])
|
||||
expect(properties.domain_capture).to.equal(true)
|
||||
})
|
||||
|
||||
it('sets domain_capture=false when no affiliation has domain capture enabled', function () {
|
||||
const properties = CustomerIoPlanHelpers.getAffiliationProperties([
|
||||
{ affiliation: { institution: {} } },
|
||||
])
|
||||
expect(properties.domain_capture).to.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Nullable } from './utils'
|
||||
|
||||
export type Institution = {
|
||||
commonsAccount: boolean
|
||||
enterpriseCommons: boolean
|
||||
writefullCommonsAccount: boolean
|
||||
confirmed: boolean
|
||||
id: number
|
||||
|
||||
Reference in New Issue
Block a user