Merge pull request #11147 from overleaf/ii-subscriptions-pages-react-split-test
[web] React subscription split test GitOrigin-RevId: 6656b3895030bc677483a3e30d5e998f5f7d1458
This commit is contained in:
@@ -104,8 +104,106 @@ async function plansPage(req, res) {
|
||||
})
|
||||
}
|
||||
|
||||
// get to show the recurly.js page
|
||||
async function paymentPage(req, res) {
|
||||
try {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-pages-react'
|
||||
)
|
||||
// get to show the recurly.js page
|
||||
if (assignment.variant === 'active') {
|
||||
await _paymentReactPage(req, res)
|
||||
} else {
|
||||
await _paymentAngularPage(req, res)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{ err: error },
|
||||
'failed to get "subscription-pages-react" split test assignment'
|
||||
)
|
||||
await _paymentAngularPage(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("express").Request} req
|
||||
* @param {import("express").Response} res
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function _paymentReactPage(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode)
|
||||
if (!plan) {
|
||||
return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
|
||||
}
|
||||
const hasSubscription =
|
||||
await LimitationsManager.promises.userHasV1OrV2Subscription(user)
|
||||
if (hasSubscription) {
|
||||
res.redirect('/user/subscription?hasSubscription=true')
|
||||
} else {
|
||||
// LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
|
||||
// Recurly as well at this point (we don't do this most places for speed).
|
||||
const valid =
|
||||
await SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
|
||||
user._id
|
||||
)
|
||||
if (!valid) {
|
||||
res.redirect('/user/subscription?hasSubscription=true')
|
||||
} else {
|
||||
let currency = null
|
||||
if (req.query.currency) {
|
||||
const queryCurrency = req.query.currency.toUpperCase()
|
||||
if (GeoIpLookup.isValidCurrencyParam(queryCurrency)) {
|
||||
currency = queryCurrency
|
||||
}
|
||||
}
|
||||
const { currencyCode: recommendedCurrency, countryCode } =
|
||||
await GeoIpLookup.promises.getCurrencyCode(
|
||||
(req.query ? req.query.ip : undefined) || req.ip
|
||||
)
|
||||
if (recommendedCurrency && currency == null) {
|
||||
currency = recommendedCurrency
|
||||
}
|
||||
|
||||
const refreshedPaymentPageAssignment =
|
||||
await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'payment-page-refresh'
|
||||
)
|
||||
const useRefreshedPaymentPage =
|
||||
refreshedPaymentPageAssignment &&
|
||||
refreshedPaymentPageAssignment.variant === 'refreshed-payment-page'
|
||||
|
||||
await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'student-check-modal'
|
||||
)
|
||||
|
||||
// TODO
|
||||
const template = useRefreshedPaymentPage
|
||||
? 'subscriptions/new-react'
|
||||
: 'subscriptions/new-react'
|
||||
|
||||
res.render(template, {
|
||||
title: 'subscribe',
|
||||
currency,
|
||||
countryCode,
|
||||
plan,
|
||||
recurlyConfig: JSON.stringify({
|
||||
currency,
|
||||
subdomain: Settings.apis.recurly.subdomain,
|
||||
}),
|
||||
showCouponField: !!req.query.scf,
|
||||
showVatField: !!req.query.svf,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function _paymentAngularPage(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode)
|
||||
if (!plan) {
|
||||
@@ -177,6 +275,84 @@ async function paymentPage(req, res) {
|
||||
}
|
||||
|
||||
async function userSubscriptionPage(req, res) {
|
||||
try {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-pages-react'
|
||||
)
|
||||
if (assignment.variant === 'active') {
|
||||
await _userSubscriptionReactPage(req, res)
|
||||
} else {
|
||||
await _userSubscriptionAngularPage(req, res)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{ err: error },
|
||||
'failed to get "subscription-pages-react" split test assignment'
|
||||
)
|
||||
await _userSubscriptionAngularPage(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("express").Request} req
|
||||
* @param {import("express").Response} res
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function _userSubscriptionReactPage(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const results =
|
||||
await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
user
|
||||
)
|
||||
const {
|
||||
personalSubscription,
|
||||
memberGroupSubscriptions,
|
||||
managedGroupSubscriptions,
|
||||
currentInstitutionsWithLicence,
|
||||
managedInstitutions,
|
||||
managedPublishers,
|
||||
v1SubscriptionStatus,
|
||||
} = results
|
||||
const hasSubscription =
|
||||
await LimitationsManager.promises.userHasV1OrV2Subscription(user)
|
||||
const fromPlansPage = req.query.hasSubscription
|
||||
const plans = SubscriptionViewModelBuilder.buildPlansList(
|
||||
personalSubscription ? personalSubscription.plan : undefined
|
||||
)
|
||||
|
||||
AnalyticsManager.recordEventForSession(req.session, 'subscription-page-view')
|
||||
|
||||
const cancelButtonAssignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-cancel-button'
|
||||
)
|
||||
|
||||
const cancelButtonNewCopy = cancelButtonAssignment?.variant === 'new-copy'
|
||||
|
||||
const data = {
|
||||
title: 'your_subscription',
|
||||
plans,
|
||||
groupPlans: GroupPlansData,
|
||||
user,
|
||||
hasSubscription,
|
||||
fromPlansPage,
|
||||
personalSubscription,
|
||||
memberGroupSubscriptions,
|
||||
managedGroupSubscriptions,
|
||||
managedInstitutions,
|
||||
managedPublishers,
|
||||
v1SubscriptionStatus,
|
||||
currentInstitutionsWithLicence,
|
||||
groupPlanModalOptions,
|
||||
cancelButtonNewCopy,
|
||||
}
|
||||
res.render('subscriptions/dashboard-react', data)
|
||||
}
|
||||
|
||||
async function _userSubscriptionAngularPage(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const results =
|
||||
await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
@@ -302,6 +478,32 @@ async function createSubscription(req, res) {
|
||||
}
|
||||
|
||||
async function successfulSubscription(req, res) {
|
||||
try {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-pages-react'
|
||||
)
|
||||
if (assignment.variant === 'active') {
|
||||
await _successfulSubscriptionReact(req, res)
|
||||
} else {
|
||||
await _successfulSubscriptionAngular(req, res)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{ err: error },
|
||||
'failed to get "subscription-pages-react" split test assignment'
|
||||
)
|
||||
await _successfulSubscriptionAngular(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("express").Request} req
|
||||
* @param {import("express").Response} res
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function _successfulSubscriptionReact(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const { personalSubscription } =
|
||||
await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
@@ -313,7 +515,27 @@ async function successfulSubscription(req, res) {
|
||||
if (!personalSubscription) {
|
||||
res.redirect('/user/subscription/plans')
|
||||
} else {
|
||||
res.render('subscriptions/successful_subscription', {
|
||||
res.render('subscriptions/successful-subscription-react', {
|
||||
title: 'thank_you',
|
||||
personalSubscription,
|
||||
postCheckoutRedirect,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function _successfulSubscriptionAngular(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const { personalSubscription } =
|
||||
await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
user
|
||||
)
|
||||
|
||||
const postCheckoutRedirect = req.session?.postCheckoutRedirect
|
||||
|
||||
if (!personalSubscription) {
|
||||
res.redirect('/user/subscription/plans')
|
||||
} else {
|
||||
res.render('subscriptions/successful-subscription', {
|
||||
title: 'thank_you',
|
||||
personalSubscription,
|
||||
postCheckoutRedirect,
|
||||
@@ -337,8 +559,41 @@ function cancelSubscription(req, res, next) {
|
||||
})
|
||||
}
|
||||
|
||||
function canceledSubscription(req, res, next) {
|
||||
return res.render('subscriptions/canceled_subscription', {
|
||||
async function canceledSubscription(req, res, next) {
|
||||
try {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-pages-react'
|
||||
)
|
||||
if (assignment.variant === 'active') {
|
||||
await _canceledSubscriptionReact(req, res, next)
|
||||
} else {
|
||||
await _canceledSubscriptionAngular(req, res, next)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{ err: error },
|
||||
'failed to get "subscription-pages-react" split test assignment'
|
||||
)
|
||||
await _canceledSubscriptionAngular(req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("express").Request} req
|
||||
* @param {import("express").Response} res
|
||||
* @param {import("express").NextFunction} next
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function _canceledSubscriptionReact(req, res, next) {
|
||||
return res.render('subscriptions/canceled-subscription-react', {
|
||||
title: 'subscription_canceled',
|
||||
})
|
||||
}
|
||||
|
||||
function _canceledSubscriptionAngular(req, res, next) {
|
||||
return res.render('subscriptions/canceled-subscription', {
|
||||
title: 'subscription_canceled',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,42 +16,99 @@ const Errors = require('../Errors/Errors')
|
||||
const EmailHelper = require('../Helpers/EmailHelper')
|
||||
const { csvAttachment } = require('../../infrastructure/Response')
|
||||
const { UserIsManagerError } = require('./UserMembershipErrors')
|
||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||
const CSVParser = require('json2csv').Parser
|
||||
const logger = require('@overleaf/logger')
|
||||
|
||||
async function index(req, res, next) {
|
||||
try {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'subscription-pages-react'
|
||||
)
|
||||
if (assignment.variant === 'active') {
|
||||
await _indexReact(req, res, next)
|
||||
} else {
|
||||
await _indexAngular(req, res, next)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(
|
||||
{ err: error },
|
||||
'failed to get "subscription-pages-react" split test assignment'
|
||||
)
|
||||
await _indexAngular(req, res, next)
|
||||
}
|
||||
}
|
||||
|
||||
function _indexReact(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
return entity.fetchV1Data(function (error, entity) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return UserMembershipHandler.getUsers(
|
||||
entity,
|
||||
entityConfig,
|
||||
function (error, users) {
|
||||
let entityName
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
const entityPrimaryKey =
|
||||
entity[entityConfig.fields.primaryKey].toString()
|
||||
if (entityConfig.fields.name) {
|
||||
entityName = entity[entityConfig.fields.name]
|
||||
}
|
||||
return res.render('user_membership/index-react', {
|
||||
name: entityName,
|
||||
users,
|
||||
groupSize: entityConfig.hasMembersLimit
|
||||
? entity.membersLimit
|
||||
: undefined,
|
||||
translations: entityConfig.translations,
|
||||
paths: entityConfig.pathsFor(entityPrimaryKey),
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function _indexAngular(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
return entity.fetchV1Data(function (error, entity) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return UserMembershipHandler.getUsers(
|
||||
entity,
|
||||
entityConfig,
|
||||
function (error, users) {
|
||||
let entityName
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
const entityPrimaryKey =
|
||||
entity[entityConfig.fields.primaryKey].toString()
|
||||
if (entityConfig.fields.name) {
|
||||
entityName = entity[entityConfig.fields.name]
|
||||
}
|
||||
return res.render('user_membership/index', {
|
||||
name: entityName,
|
||||
users,
|
||||
groupSize: entityConfig.hasMembersLimit
|
||||
? entity.membersLimit
|
||||
: undefined,
|
||||
translations: entityConfig.translations,
|
||||
paths: entityConfig.pathsFor(entityPrimaryKey),
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
index(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
return entity.fetchV1Data(function (error, entity) {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
return UserMembershipHandler.getUsers(
|
||||
entity,
|
||||
entityConfig,
|
||||
function (error, users) {
|
||||
let entityName
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
const entityPrimaryKey =
|
||||
entity[entityConfig.fields.primaryKey].toString()
|
||||
if (entityConfig.fields.name) {
|
||||
entityName = entity[entityConfig.fields.name]
|
||||
}
|
||||
return res.render('user_membership/index', {
|
||||
name: entityName,
|
||||
users,
|
||||
groupSize: entityConfig.hasMembersLimit
|
||||
? entity.membersLimit
|
||||
: undefined,
|
||||
translations: entityConfig.translations,
|
||||
paths: entityConfig.pathsFor(entityPrimaryKey),
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
index,
|
||||
add(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
const email = EmailHelper.parseEmail(req.body.email)
|
||||
@@ -96,7 +153,6 @@ module.exports = {
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
remove(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
const { userId } = req.params
|
||||
@@ -135,7 +191,6 @@ module.exports = {
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
exportCsv(req, res, next) {
|
||||
const { entity, entityConfig } = req
|
||||
const fields = ['email', 'last_logged_in_at', 'last_active_at']
|
||||
@@ -152,14 +207,12 @@ module.exports = {
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
new(req, res, next) {
|
||||
return res.render('user_membership/new', {
|
||||
entityName: req.params.name,
|
||||
entityId: req.params.id,
|
||||
})
|
||||
},
|
||||
|
||||
create(req, res, next) {
|
||||
const entityId = req.params.id
|
||||
const entityConfig = req.entityConfig
|
||||
|
||||
Reference in New Issue
Block a user