diff --git a/services/web/app/src/Features/StaticPages/HomeController.js b/services/web/app/src/Features/StaticPages/HomeController.js
index 4f80645356..42428b809e 100644
--- a/services/web/app/src/Features/StaticPages/HomeController.js
+++ b/services/web/app/src/Features/StaticPages/HomeController.js
@@ -43,7 +43,23 @@ module.exports = HomeController = {
async home(req, res) {
if (Features.hasFeature('homepage') && homepageExists) {
- return res.render('external/home/v2')
+ let designSystemUpdatesAssignment = { variant: 'default' }
+ try {
+ designSystemUpdatesAssignment =
+ await SplitTestHandler.promises.getAssignment(
+ req,
+ res,
+ 'design-system-updates'
+ )
+ } catch (error) {
+ logger.error(
+ { err: error },
+ 'failed to get "design-system-updates" split test assignment'
+ )
+ }
+ return res.render('external/home/v2', {
+ designSystemUpdatesVariant: designSystemUpdatesAssignment.variant,
+ })
} else {
return res.redirect('/login')
}
diff --git a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js
index aed2671dac..a92af99f4f 100644
--- a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js
+++ b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js
@@ -1,6 +1,8 @@
const AnalyticsManager = require('../Analytics/AnalyticsManager')
const SubscriptionEmailHandler = require('./SubscriptionEmailHandler')
const { ObjectID } = require('mongodb')
+const SplitTestHandler = require('../SplitTests/SplitTestHandler')
+const logger = require('@overleaf/logger')
const INVOICE_SUBSCRIPTION_LIMIT = 10
@@ -99,11 +101,25 @@ async function _sendSubscriptionUpdatedEvent(userId, eventData) {
async function _sendSubscriptionCancelledEvent(userId, eventData) {
const { planCode, quantity, state, isTrial, subscriptionId } =
_getSubscriptionData(eventData)
+ let designSystemUpdatesAssignment = { variant: 'default' }
+ try {
+ designSystemUpdatesAssignment =
+ await SplitTestHandler.promises.getAssignmentForUser(
+ userId,
+ 'design-system-updates'
+ )
+ } catch (error) {
+ logger.error(
+ { err: error },
+ 'failed to get "design-system-updates" split test assignment'
+ )
+ }
AnalyticsManager.recordEventForUser(userId, 'subscription-cancelled', {
plan_code: planCode,
quantity,
is_trial: isTrial,
subscriptionId,
+ 'split-test-design-system-updates': designSystemUpdatesAssignment.variant,
})
AnalyticsManager.setUserPropertyForUser(userId, 'subscription-state', state)
AnalyticsManager.setUserPropertyForUser(
diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js
index 53da877df6..aa015eadbf 100644
--- a/services/web/app/src/Features/Subscription/SubscriptionController.js
+++ b/services/web/app/src/Features/Subscription/SubscriptionController.js
@@ -280,8 +280,28 @@ async function userSubscriptionPage(req, res) {
SubscriptionViewModelBuilder.buildPlansListForSubscriptionDash(
personalSubscription?.plan
)
+ let designSystemUpdatesAssignment = { variant: 'default' }
+ try {
+ designSystemUpdatesAssignment =
+ await SplitTestHandler.promises.getAssignment(
+ req,
+ res,
+ 'design-system-updates'
+ )
+ } catch (error) {
+ logger.error(
+ { err: error },
+ 'failed to get "design-system-updates" split test assignment'
+ )
+ }
- AnalyticsManager.recordEventForSession(req.session, 'subscription-page-view')
+ AnalyticsManager.recordEventForSession(
+ req.session,
+ 'subscription-page-view',
+ {
+ 'split-test-design-system-updates': designSystemUpdatesAssignment.variant,
+ }
+ )
const cancelButtonAssignment = await SplitTestHandler.promises.getAssignment(
req,
diff --git a/services/web/app/src/Features/User/UserCreator.js b/services/web/app/src/Features/User/UserCreator.js
index 226b0a7826..73e891e86f 100644
--- a/services/web/app/src/Features/User/UserCreator.js
+++ b/services/web/app/src/Features/User/UserCreator.js
@@ -7,6 +7,7 @@ const UserDeleter = require('./UserDeleter')
const UserGetter = require('./UserGetter')
const UserUpdater = require('./UserUpdater')
const Analytics = require('../Analytics/AnalyticsManager')
+const SplitTestHandler = require('../SplitTests/SplitTestHandler')
const UserOnboardingEmailManager = require('./UserOnboardingEmailManager')
const UserPostRegistrationAnalyticsManager = require('./UserPostRegistrationAnalyticsManager')
const OError = require('@overleaf/o-error')
@@ -36,9 +37,24 @@ async function _addAffiliation(user, affiliationOptions) {
}
async function recordRegistrationEvent(user) {
+ let designSystemUpdatesAssignment = { variant: 'default' }
+ try {
+ designSystemUpdatesAssignment =
+ await SplitTestHandler.promises.getAssignmentForUser(
+ user._id,
+ 'design-system-updates'
+ )
+ } catch (error) {
+ logger.error(
+ { err: error },
+ 'failed to get "design-system-updates" split test assignment'
+ )
+ }
+
try {
const segmentation = {
'home-registration': 'default',
+ 'split-test-design-system-updates': designSystemUpdatesAssignment.variant,
}
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
index 89b0ea14cd..f96c499e0c 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active.tsx
@@ -8,11 +8,13 @@ import { PendingPlanChange } from './pending-plan-change'
import { TrialEnding } from './trial-ending'
import { PendingAdditionalLicenses } from './pending-additional-licenses'
import { ContactSupportToChangeGroupPlan } from './contact-support-to-change-group-plan'
+import SubscriptionRemainder from './subscription-remainder'
import isInFreeTrial from '../../../../util/is-in-free-trial'
import { ChangePlanModal } from './change-plan/modals/change-plan-modal'
import { ConfirmChangePlanModal } from './change-plan/modals/confirm-change-plan-modal'
import { KeepCurrentPlanModal } from './change-plan/modals/keep-current-plan-modal'
import { ChangeToGroupModal } from './change-plan/modals/change-to-group-modal'
+import { isSplitTestEnabled } from '../../../../../../../../frontend/js/utils/splitTestUtils'
export function ActiveSubscription({
subscription,
@@ -23,6 +25,10 @@ export function ActiveSubscription({
const { recurlyLoadError, setModalIdShown, showCancellation } =
useSubscriptionDashboardContext()
+ const isDesignSystemUpdatesEnabled = isSplitTestEnabled(
+ 'design-system-updates'
+ )
+
if (showCancellation) return
return (
@@ -115,10 +121,25 @@ export function ActiveSubscription({
>
{t('view_your_invoices')}
+ {!recurlyLoadError && isDesignSystemUpdatesEnabled && (
+
+ )}
{!recurlyLoadError && (
-
+ <>
+
+ {!isDesignSystemUpdatesEnabled && (
+
+
+
+ )}
+
+
+
+
+
+ >
)}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx
index 1b6d8150ae..4bcf50d4ff 100644
--- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-subscription-button.tsx
@@ -1,67 +1,32 @@
-import { useTranslation, Trans } from 'react-i18next'
+import { useTranslation } from 'react-i18next'
import * as eventTracking from '../../../../../../infrastructure/event-tracking'
-import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
import { useSubscriptionDashboardContext } from '../../../../context/subscription-dashboard-context'
+import { getSplitTestVariant } from '../../../../../../../../frontend/js/utils/splitTestUtils'
-export function CancelSubscriptionButton({
- subscription,
-}: {
- subscription: RecurlySubscription
-}) {
+export function CancelSubscriptionButton(
+ props: React.ComponentProps<'button'>
+) {
const { t } = useTranslation()
const { recurlyLoadError, setShowCancellation } =
useSubscriptionDashboardContext()
- const stillInATrial =
- subscription.recurly.trialEndsAtFormatted &&
- subscription.recurly.trial_ends_at &&
- new Date(subscription.recurly.trial_ends_at).getTime() > Date.now()
+ const designSystemUpdatesVariant = getSplitTestVariant(
+ 'design-system-updates',
+ 'default'
+ )
function handleCancelSubscriptionClick() {
- eventTracking.sendMB('subscription-page-cancel-button-click')
+ eventTracking.sendMB('subscription-page-cancel-button-click', {
+ 'split-test-design-system-updates': designSystemUpdatesVariant,
+ })
setShowCancellation(true)
}
if (recurlyLoadError) return null
return (
- <>
-
-
-
-
-
-
- {stillInATrial ? (
- ,
- ]}
- />
- ) : (
- ,
- ]}
- />
- )}
-
-
- >
+
)
}
diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx
new file mode 100644
index 0000000000..c619aabe8a
--- /dev/null
+++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx
@@ -0,0 +1,39 @@
+import { Trans } from 'react-i18next'
+import { RecurlySubscription } from '../../../../../../../../types/subscription/dashboard/subscription'
+
+type SubscriptionRemainderProps = {
+ subscription: RecurlySubscription
+}
+
+function SubscriptionRemainder({ subscription }: SubscriptionRemainderProps) {
+ const stillInATrial =
+ subscription.recurly.trialEndsAtFormatted &&
+ subscription.recurly.trial_ends_at &&
+ new Date(subscription.recurly.trial_ends_at).getTime() > Date.now()
+
+ return stillInATrial ? (
+ ,
+ ]}
+ />
+ ) : (
+ ,
+ ]}
+ />
+ )
+}
+
+export default SubscriptionRemainder
diff --git a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx b/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
index c0bf30c2ac..dc2b94c407 100644
--- a/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
+++ b/services/web/frontend/js/features/subscription/components/new/checkout/checkout-panel.tsx
@@ -17,6 +17,7 @@ import TosAgreementNotice from './tos-agreement-notice'
import SubmitButton from './submit-button'
import ThreeDSecure from './three-d-secure'
import getMeta from '../../../../../utils/meta'
+import { getSplitTestVariant } from '../../../../../../../frontend/js/utils/splitTestUtils'
import { postJSON } from '../../../../../infrastructure/fetch-json'
import * as eventTracking from '../../../../../infrastructure/event-tracking'
import classnames from 'classnames'
@@ -69,6 +70,10 @@ function CheckoutPanel() {
'#add-company-details-checkbox'
)?.checked
)
+ const designSystemUpdatesVariant = getSplitTestVariant(
+ 'design-system-updates',
+ 'default'
+ )
const completeSubscription = useCallback(
async (
@@ -140,6 +145,7 @@ function CheckoutPanel() {
plan_code: postData.subscriptionDetails.plan_code,
coupon_code: postData.subscriptionDetails.coupon_code,
isPaypal: postData.subscriptionDetails.isPaypal,
+ 'split-test-design-system-updates': designSystemUpdatesVariant,
})
eventTracking.send(
'subscription-funnel',
@@ -172,6 +178,7 @@ function CheckoutPanel() {
}
},
[
+ designSystemUpdatesVariant,
ITMCampaign,
ITMContent,
ITMReferrer,
diff --git a/services/web/frontend/js/features/subscription/context/payment-context.tsx b/services/web/frontend/js/features/subscription/context/payment-context.tsx
index 0231995334..ad6b0adf1b 100644
--- a/services/web/frontend/js/features/subscription/context/payment-context.tsx
+++ b/services/web/frontend/js/features/subscription/context/payment-context.tsx
@@ -11,6 +11,7 @@ import {
import { currencies, CurrencyCode, CurrencySymbol } from '../data/currency'
import { useTranslation } from 'react-i18next'
import getMeta from '../../../utils/meta'
+import { getSplitTestVariant } from '../../../../../frontend/js/utils/splitTestUtils'
import * as eventTracking from '../../../infrastructure/event-tracking'
import {
PaymentContextValue,
@@ -38,6 +39,10 @@ function usePayment({ publicKey }: RecurlyOptions) {
'ol-recommendedCurrency'
)
const planCode: string = getMeta('ol-planCode')
+ const designSystemUpdatesVariant = getSplitTestVariant(
+ 'design-system-updates',
+ 'default'
+ )
const [planName, setPlanName] = useState(plan.name)
const [recurlyLoading, setRecurlyLoading] = useState(true)
@@ -98,6 +103,7 @@ function usePayment({ publicKey }: RecurlyOptions) {
eventTracking.sendMB('payment-page-view', {
plan: planCode,
currency: currencyCode,
+ 'split-test-design-system-updates': designSystemUpdatesVariant,
})
eventTracking.send(
'subscription-funnel',
@@ -142,6 +148,7 @@ function usePayment({ publicKey }: RecurlyOptions) {
setupPricing()
}, [
+ designSystemUpdatesVariant,
initialCountry,
initialCouponCode,
initiallySelectedCurrencyCode,
diff --git a/services/web/frontend/js/utils/splitTestUtils.ts b/services/web/frontend/js/utils/splitTestUtils.ts
index a31ac9db79..22a220f836 100644
--- a/services/web/frontend/js/utils/splitTestUtils.ts
+++ b/services/web/frontend/js/utils/splitTestUtils.ts
@@ -4,6 +4,10 @@ export function isSplitTestEnabled(name: string) {
return getMeta('ol-splitTestVariants')?.[name] === 'enabled'
}
+export function getSplitTestVariant(name: string, fallback?: string) {
+ return getMeta('ol-splitTestVariants')?.[name] || fallback
+}
+
export function parseIntFromSplitTest(name: string, defaultValue: number) {
const v = getMeta('ol-splitTestVariants')?.[name]
const n = parseInt(v, 10)
diff --git a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js
index 101a5a15c0..0d70676bdb 100644
--- a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js
+++ b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js
@@ -34,6 +34,13 @@ describe('RecurlyEventHandler', function () {
recordEventForUser: sinon.stub(),
setUserPropertyForUser: sinon.stub(),
}),
+ '../SplitTests/SplitTestHandler': (this.SplitTestHandler = {
+ promises: {
+ getAssignmentForUser: sinon.stub().resolves({
+ variant: 'default',
+ }),
+ },
+ }),
},
})
})
@@ -159,12 +166,17 @@ describe('RecurlyEventHandler', function () {
)
})
- it('with canceled_subscription_notification', function () {
+ it('with canceled_subscription_notification', async function () {
this.eventData.subscription.state = 'cancelled'
- this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
+ await this.RecurlyEventHandler.sendRecurlyAnalyticsEvent(
'canceled_subscription_notification',
this.eventData
)
+ sinon.assert.calledWith(
+ this.SplitTestHandler.promises.getAssignmentForUser,
+ this.userId,
+ 'design-system-updates'
+ )
sinon.assert.calledWith(
this.AnalyticsManager.recordEventForUser,
this.userId,
@@ -174,6 +186,7 @@ describe('RecurlyEventHandler', function () {
quantity: 1,
is_trial: true,
subscriptionId: this.eventData.subscription.uuid,
+ 'split-test-design-system-updates': 'default',
}
)
sinon.assert.calledWith(