Procházet zdrojové kódy

Merge pull request #14773 from overleaf/ii-modify-design-system-update-split-test

[web] Modify design-system-update split test

GitOrigin-RevId: f28aeef5ba782006afd30fd2862d0ad129077f6c
ilkin-overleaf před 2 roky
rodič
revize
c6289cc67f

+ 17 - 1
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')
     }

+ 16 - 0
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(

+ 21 - 1
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,

+ 16 - 0
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

+ 22 - 1
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 <CancelSubscription />
 
   return (
@@ -115,10 +121,25 @@ export function ActiveSubscription({
         >
           {t('view_your_invoices')}
         </a>
+        {!recurlyLoadError && isDesignSystemUpdatesEnabled && (
+          <CancelSubscriptionButton className="btn btn-danger-ghost ms-1" />
+        )}
       </p>
 
       {!recurlyLoadError && (
-        <CancelSubscriptionButton subscription={subscription} />
+        <>
+          <br />
+          {!isDesignSystemUpdatesEnabled && (
+            <p>
+              <CancelSubscriptionButton className="btn btn-danger" />
+            </p>
+          )}
+          <p>
+            <i>
+              <SubscriptionRemainder subscription={subscription} />
+            </i>
+          </p>
+        </>
       )}
 
       <ChangePlanModal />

+ 15 - 50
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 (
-    <>
-      <br />
-      <p>
-        <button
-          className="btn btn-danger"
-          onClick={handleCancelSubscriptionClick}
-        >
-          {t('cancel_your_subscription')}
-        </button>
-      </p>
-      <p>
-        <i>
-          {stillInATrial ? (
-            <Trans
-              i18nKey="subscription_will_remain_active_until_end_of_trial_period_x"
-              values={{
-                terminationDate: subscription.recurly.nextPaymentDueAt,
-              }}
-              components={[
-                // eslint-disable-next-line react/jsx-key
-                <strong />,
-              ]}
-            />
-          ) : (
-            <Trans
-              i18nKey="subscription_will_remain_active_until_end_of_billing_period_x"
-              values={{
-                terminationDate: subscription.recurly.nextPaymentDueAt,
-              }}
-              components={[
-                // eslint-disable-next-line react/jsx-key
-                <strong />,
-              ]}
-            />
-          )}
-        </i>
-      </p>
-    </>
+    <button onClick={handleCancelSubscriptionClick} {...props}>
+      {t('cancel_your_subscription')}
+    </button>
   )
 }

+ 39 - 0
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 ? (
+    <Trans
+      i18nKey="subscription_will_remain_active_until_end_of_trial_period_x"
+      values={{
+        terminationDate: subscription.recurly.nextPaymentDueAt,
+      }}
+      components={[
+        // eslint-disable-next-line react/jsx-key
+        <strong />,
+      ]}
+    />
+  ) : (
+    <Trans
+      i18nKey="subscription_will_remain_active_until_end_of_billing_period_x"
+      values={{
+        terminationDate: subscription.recurly.nextPaymentDueAt,
+      }}
+      components={[
+        // eslint-disable-next-line react/jsx-key
+        <strong />,
+      ]}
+    />
+  )
+}
+
+export default SubscriptionRemainder

+ 7 - 0
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,

+ 7 - 0
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,

+ 4 - 0
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)

+ 15 - 2
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(