From c6289cc67f0e16b24805c8a65cac9f28b2e23345 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:47:20 +0300 Subject: [PATCH] Merge pull request #14773 from overleaf/ii-modify-design-system-update-split-test [web] Modify design-system-update split test GitOrigin-RevId: f28aeef5ba782006afd30fd2862d0ad129077f6c --- .../Features/StaticPages/HomeController.js | 18 ++++- .../Subscription/RecurlyEventHandler.js | 16 +++++ .../Subscription/SubscriptionController.js | 22 ++++++- .../web/app/src/Features/User/UserCreator.js | 16 +++++ .../dashboard/states/active/active.tsx | 23 ++++++- .../active/cancel-subscription-button.tsx | 65 +++++-------------- .../states/active/subscription-remainder.tsx | 39 +++++++++++ .../new/checkout/checkout-panel.tsx | 7 ++ .../subscription/context/payment-context.tsx | 7 ++ .../web/frontend/js/utils/splitTestUtils.ts | 4 ++ .../Subscription/RecurlyEventHandlerTests.js | 17 ++++- 11 files changed, 179 insertions(+), 55 deletions(-) create mode 100644 services/web/frontend/js/features/subscription/components/dashboard/states/active/subscription-remainder.tsx 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(