Просмотр исходного кода

Implement new three-way split test for the plans page (#11493)

* Update split test config and infrastructure for plans-page-layout-v3

* Implement view for `old-plans-page-annual` variant of the new split test:
    - Make `annual` the default view for all elements on the old plans page
    - Change the tooltip background to green for monthly/student view

* Implement a new design for the new plans page:
    - switch annual and monthly locations (annual on the left now)
    - change the tooltip background to green color for all choice
    - make the monthly-annual switch has green background if annual is chosen

* Fix mobile view plans page header style

GitOrigin-RevId: b2b3c6ac6adbe26bf6def7e072493f503793cfcb
M Fahru 3 лет назад
Родитель
Сommit
781c1c97c7

+ 19 - 10
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -56,16 +56,23 @@ async function plansPage(req, res) {
     return defaultValue
   }
 
-  const newPlansPageAssignmentV2 =
-    await SplitTestHandler.promises.getAssignment(
+  let plansPageLayoutV3Assignment = { variant: 'default' }
+
+  try {
+    plansPageLayoutV3Assignment = await SplitTestHandler.promises.getAssignment(
       req,
       res,
-      'plans-page-layout-v2-annual'
+      'plans-page-layout-v3'
+    )
+  } catch (error) {
+    logger.error(
+      { err: error },
+      'failed to get "plans-page-layout-v3" split test assignment'
     )
+  }
 
-  const newPlansPageVariantV2 =
-    newPlansPageAssignmentV2 &&
-    newPlansPageAssignmentV2.variant === 'new-plans-page'
+  const showNewPlansPage =
+    plansPageLayoutV3Assignment.variant === 'new-plans-page'
 
   let defaultGroupPlanModalCurrency = 'USD'
   if (validGroupPlanModalOptions.currency.includes(recommendedCurrency)) {
@@ -73,14 +80,16 @@ async function plansPage(req, res) {
   }
   const groupPlanModalDefaults = {
     plan_code: getDefault('plan', 'plan_code', 'collaborator'),
-    size: getDefault('number', 'size', newPlansPageVariantV2 ? '2' : '10'),
+    size: getDefault('number', 'size', showNewPlansPage ? '2' : '10'),
     currency: getDefault('currency', 'currency', defaultGroupPlanModalCurrency),
     usage: getDefault('usage', 'usage', 'enterprise'),
   }
 
-  AnalyticsManager.recordEventForSession(req.session, 'plans-page-view')
+  AnalyticsManager.recordEventForSession(req.session, 'plans-page-view', {
+    'plans-page-layout-v3': plansPageLayoutV3Assignment.variant,
+  })
 
-  const template = newPlansPageVariantV2
+  const template = showNewPlansPage
     ? 'subscriptions/plans-marketing-v2'
     : 'subscriptions/plans-marketing'
 
@@ -96,7 +105,7 @@ async function plansPage(req, res) {
     groupPlans: GroupPlansData,
     groupPlanModalOptions,
     groupPlanModalDefaults,
-    newPlansPageVariantV2,
+    plansPageLayoutV3Variant: plansPageLayoutV3Assignment.variant,
     initialLocalizedGroupPrice:
       SubscriptionHelper.generateInitialLocalizedGroupPrice(
         recommendedCurrency

+ 3 - 3
services/web/app/src/Features/Subscription/plansV2Config.js

@@ -18,7 +18,7 @@ const config = {
     },
     eventTrackingKey: 'plans-page-click',
     additionalEventSegmentation: {
-      'plans-page-layout-v2-annual': 'new-plans-page',
+      'plans-page-layout-v3': 'new-plans-page',
     },
   },
   group: {
@@ -36,7 +36,7 @@ const config = {
     },
     eventTrackingKey: 'plans-page-click',
     additionalEventSegmentation: {
-      'plans-page-layout-v2-annual': 'new-plans-page',
+      'plans-page-layout-v3': 'new-plans-page',
     },
   },
   student: {
@@ -61,7 +61,7 @@ const config = {
     },
     eventTrackingKey: 'plans-page-click',
     additionalEventSegmentation: {
-      'plans-page-layout-v2-annual': 'new-plans-page',
+      'plans-page-layout-v3': 'new-plans-page',
     },
   },
 }

+ 4 - 1
services/web/app/views/subscriptions/plans-marketing.pug

@@ -62,7 +62,10 @@ block content
 								h2 #{translate('compare_plan_features')}
 					.row
 						.col-md-6.col-md-offset-3
-							+plan_switch('table')
+							if (plansPageLayoutV3Variant === 'old-plans-page-annual')
+								+plan_switch_annual_default('table')
+							else
+								+plan_switch('table')
 						.col-md-3.text-right
 							+currency_dropdown
 					.row(event-tracking="features-table-viewed" event-tracking-ga="subscription-funnel" event-tracking-trigger="scroll" event-tracking-send-once="true" event-tracking-label="exp-")

+ 2 - 2
services/web/app/views/subscriptions/plans-marketing/_group_plan_modal.pug

@@ -61,7 +61,7 @@ div.modal.fade(tabindex="-1" role="dialog" data-ol-group-plan-modal)
 										event-tracking-mb="true"
 										event-tracking-trigger="click"
 										event-tracking-element="select"
-										event-segmentation='{"plans-page-layout-v2-annual": "' + (newPlansPageVariantV2 ? 'new-plans-page' : 'default') + '"}'
+										event-segmentation='{"plans-page-layout-v3": "' + (plansPageLayoutV3Variant) + '"}'
 									)
 										for size in groupPlanModalOptions.sizes
 											option(
@@ -89,7 +89,7 @@ div.modal.fade(tabindex="-1" role="dialog" data-ol-group-plan-modal)
 										event-tracking-mb="true"
 										event-tracking-trigger="click"
 										event-tracking-element="checkbox"
-										event-segmentation='{"plans-page-layout-v2-annual": "' + (newPlansPageVariantV2 ? 'new-plans-page' : 'default') + '"}'
+										event-segmentation='{"plans-page-layout-v3": "' + (plansPageLayoutV3Variant) + '"}'
 									)
 									span This license is for educational purposes (applies to students or faculty using Overleaf for teaching)
 

+ 23 - 1
services/web/app/views/subscriptions/plans-marketing/_mixins.pug

@@ -217,11 +217,33 @@ mixin plan_switch(location)
 		li(data-ol-view-tab='student')
 			button.btn-default-outline #{translate("special_price_student")}
 
+mixin plan_switch_annual_default(location)
+	ul.nav.nav-pills(class=(location === 'card' ? "above-cards" : ""))
+		li.active(data-ol-view-tab='annual' class=(location === 'card' ? "annual-saving-tooltip-container" : ""))
+			button.btn-default-outline #{translate("annual")}
+			if (location === 'card')
+				.tooltip.in.bottom.annual-saving-tooltip.annual-selected.green-background(
+					role="tooltip"
+					data-ol-annual-saving-tooltip
+				)
+					.tooltip-arrow
+					.tooltip-inner
+						span(data-ol-view="annual") #{translate("saving_20_percent")}
+						span(hidden data-ol-view="monthly") #{translate("save_20_percent_by_paying_annually")}
+						span(hidden data-ol-view="student") #{translate("save_20_percent_by_paying_annually")}
+		li(data-ol-view-tab='monthly')
+			button.btn-default-outline #{translate("monthly")}
+		li(data-ol-view-tab='student')
+			button.btn-default-outline #{translate("special_price_student")}
+
 mixin allCardsAndControls(controlsRowSpaced, listLocation)
 	- var location = listLocation ? 'card_' + listLocation : 'card'
 	.row.top-switch(class=(controlsRowSpaced ? "row-spaced" : ""))
 		.col-md-6.col-md-offset-3
-			+plan_switch(location)
+			if (plansPageLayoutV3Variant === 'old-plans-page-annual')
+				+plan_switch_annual_default(location)
+			else
+				+plan_switch(location)
 		.col-md-2.text-right
 			+currency_dropdown
 

+ 4 - 4
services/web/app/views/subscriptions/plans-marketing/v2/_cards_controls_tables.pug

@@ -8,7 +8,7 @@ include ./_mixins
 				event-tracking="plans-page-toggle-plan"
 				event-tracking-mb="true"
 				event-tracking-trigger="click"
-				event-segmentation='{"button": "individual", "plans-page-layout-v2-annual": "new-plans-page"}'
+				event-segmentation='{"button": "individual", "plans-page-layout-v3": "new-plans-page"}'
 			)
 				button.btn.btn-default-outline #{translate("indvidual_plans")}
 			li.plans-v2-top-switch-group(
@@ -16,7 +16,7 @@ include ./_mixins
 				event-tracking="plans-page-toggle-plan"
 				event-tracking-mb="true"
 				event-tracking-trigger="click"
-				event-segmentation='{"button": "group", "plans-page-layout-v2-annual": "new-plans-page"}'
+				event-segmentation='{"button": "group", "plans-page-layout-v3": "new-plans-page"}'
 			)
 				button.btn.btn-default-outline(
 					href="#"
@@ -28,13 +28,13 @@ include ./_mixins
 				event-tracking="plans-page-toggle-plan"
 				event-tracking-mb="true"
 				event-tracking-trigger="click"
-				event-segmentation='{"button": "student", "plans-page-layout-v2-annual": "new-plans-page"}'
+				event-segmentation='{"button": "student", "plans-page-layout-v3": "new-plans-page"}'
 			)
 				button.btn.btn-default-outline(
 					href="#"
 				) #{translate("student_plans")}
 
-+monthly_annual_switch("annual", "plans-page-toggle-period", '{"plans-page-layout-v2-annual": "new-plans-page"}')
++monthly_annual_switch("annual", "plans-page-toggle-period", '{"plans-page-layout-v3": "new-plans-page"}')
 
 .row(hidden data-ol-plans-v2-license-picker-container)
 	.col-sm-12

+ 16 - 15
services/web/app/views/subscriptions/plans-marketing/v2/_mixins.pug

@@ -344,7 +344,7 @@ mixin group_plans_license_picker()
 				event-tracking-mb="true"
 				event-tracking-trigger="click"
 				event-tracking-element="select"
-				event-segmentation='{"plans-page-layout-v2-annual": "new-plans-page"}'
+				event-segmentation='{"plans-page-layout-v3": "new-plans-page"}'
 			)
 				option(value="2") 2
 				option(value="3") 3
@@ -364,7 +364,7 @@ mixin group_plans_license_picker()
 					event-tracking-mb="true"
 					event-tracking-trigger="click"
 					event-tracking-element="checkbox"
-					event-segmentation='{"plans-page-layout-v2-annual": "new-plans-page"}'
+					event-segmentation='{"plans-page-layout-v3": "new-plans-page"}'
 				)
 				span #{translate("apply_educational_discount")}
 			//- will only appear on screen width >= 768px (using CSS)
@@ -573,10 +573,21 @@ mixin table_sticky_header_all(plansV2Config)
 		+plans_v2_table_sticky_header(true, plansV2Config.student)
 
 mixin monthly_annual_switch(initialState, eventTracking, eventSegmentation)
-	- var monthlyAnnualToggleChecked = initialState === 'annual'
+	- var monthlyAnnualToggleChecked = initialState === 'monthly'
 	.row
 		.col-md-4.col-md-offset-4.text-centered.plans-v2-m-a-switch-container(data-ol-plans-v2-m-a-switch-container)
-			span.underline(data-ol-plans-v2-m-a-switch-text='monthly') #{translate("monthly")}
+			.plans-v2-m-a-switch-annual-text-container
+				span.underline(data-ol-plans-v2-m-a-switch-text='annual') #{translate("annual")}
+				.tooltip.in.left.plans-v2-m-a-tooltip(
+					role="tooltip"
+					data-ol-plans-v2-m-a-tooltip
+					class=monthlyAnnualToggleChecked ? 'plans-v2-m-a-tooltip-monthly-selected' : ''
+				)
+					.tooltip-arrow
+					.tooltip-inner
+						span(hidden=!monthlyAnnualToggleChecked data-ol-tooltip-period='monthly') #{translate("save_20_percent_by_paying_annually")}
+						span(hidden=monthlyAnnualToggleChecked data-ol-tooltip-period='annual') #{translate("saving_20_percent")}
+
 			label.plans-v2-m-a-switch(data-ol-plans-v2-m-a-switch)
 				input(
 					type="checkbox"
@@ -590,14 +601,4 @@ mixin monthly_annual_switch(initialState, eventTracking, eventSegmentation)
 					event-segmentation=eventSegmentation
 				)
 				span
-			.plans-v2-m-a-switch-annual-text-container
-				span(data-ol-plans-v2-m-a-switch-text='annual') #{translate("annual")}
-				.tooltip.in.right.plans-v2-m-a-tooltip(
-					role="tooltip"
-					data-ol-plans-v2-m-a-tooltip
-					class=monthlyAnnualToggleChecked ? 'plans-v2-m-a-tooltip-annual-selected' : ''
-				)
-					.tooltip-arrow
-					.tooltip-inner
-						span(hidden=monthlyAnnualToggleChecked data-ol-tooltip-period='monthly') #{translate("save_20_percent_by_paying_annually")}
-						span(hidden=!monthlyAnnualToggleChecked data-ol-tooltip-period='annual') #{translate("saving_20_percent")}
+			span(data-ol-plans-v2-m-a-switch-text='monthly') #{translate("monthly")}

+ 4 - 4
services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-m-a-switch.js

@@ -12,7 +12,7 @@ export function toggleMonthlyAnnualSwitching(
   containerEl.classList.toggle('disabled', view === 'group')
 
   checkbox.disabled = view === 'group'
-  checkbox.checked = currentMonthlyAnnualSwitchValue === 'annual'
+  checkbox.checked = currentMonthlyAnnualSwitchValue === 'monthly'
 
   switchMonthlyAnnual(currentMonthlyAnnualSwitchValue)
 }
@@ -20,8 +20,8 @@ export function toggleMonthlyAnnualSwitching(
 export function switchMonthlyAnnual(currentMonthlyAnnualSwitchValue) {
   const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]')
   el.classList.toggle(
-    'plans-v2-m-a-tooltip-annual-selected',
-    currentMonthlyAnnualSwitchValue === 'annual'
+    'plans-v2-m-a-tooltip-monthly-selected',
+    currentMonthlyAnnualSwitchValue === 'monthly'
   )
 
   document.querySelectorAll('[data-ol-tooltip-period]').forEach(el => {
@@ -51,7 +51,7 @@ function changeMonthlyAnnualTooltipPosition() {
   const el = document.querySelector('[data-ol-plans-v2-m-a-tooltip]')
 
   el.classList.toggle('bottom', smallScreen)
-  el.classList.toggle('right', !smallScreen)
+  el.classList.toggle('left', !smallScreen)
 }
 
 // click event listener for monthly-annual switch

+ 6 - 5
services/web/frontend/js/pages/user/subscription/plans-v2/plans-v2-main.js

@@ -19,7 +19,7 @@ import { updateLinkTargets } from '../plans'
 // There's some difference between the monthly and annual UI
 // and since monthly-annual switch is disabled for the group tab,
 // we need to introduce a new variable to store the information
-let currentMonthlyAnnualSwitchValue = 'monthly'
+let currentMonthlyAnnualSwitchValue = 'annual'
 
 function selectTab(viewTab) {
   document.querySelectorAll('[data-ol-plans-v2-view-tab]').forEach(el => {
@@ -105,6 +105,7 @@ function selectViewFromHash() {
     if (view) {
       // make sure the selected view is valid
       if (document.querySelector(`[data-ol-plans-v2-view-tab="${view}"]`)) {
+        // set annual as the default
         currentMonthlyAnnualSwitchValue = 'annual'
         selectTab(view)
         // clear the hash so it doesn't persist when switching plans
@@ -119,14 +120,14 @@ function selectViewFromHash() {
 document
   .querySelector('[data-ol-plans-v2-m-a-switch]')
   .addEventListener('click', () => {
-    const isAnnualPricing = document.querySelector(
+    const isMonthlyPricing = document.querySelector(
       '[data-ol-plans-v2-m-a-switch] input[type="checkbox"]'
     ).checked
 
-    if (isAnnualPricing) {
-      currentMonthlyAnnualSwitchValue = 'annual'
-    } else {
+    if (isMonthlyPricing) {
       currentMonthlyAnnualSwitchValue = 'monthly'
+    } else {
+      currentMonthlyAnnualSwitchValue = 'annual'
     }
 
     switchMonthlyAnnual(currentMonthlyAnnualSwitchValue)

+ 35 - 9
services/web/frontend/js/pages/user/subscription/plans.js

@@ -3,8 +3,6 @@ import '../../../features/plans/group-plan-modal'
 import * as eventTracking from '../../../infrastructure/event-tracking'
 import getMeta from '../../../utils/meta'
 
-const PLANS_PAGE_LAYOUT_V2_ANNUAL = 'plans-page-layout-v2-annual'
-
 let currentView = 'monthly'
 let currentCurrencyCode = getMeta('ol-recommendedCurrency')
 
@@ -16,24 +14,28 @@ function selectView(view) {
       el.classList.remove('active')
     }
   })
+
   document.querySelectorAll('[data-ol-view]').forEach(el => {
     el.hidden = el.getAttribute('data-ol-view') !== view
   })
+
   updateAnnualSavingBanner(view)
   currentView = view
   updateLinkTargets()
 }
 
 function setUpViewSwitching(liEl) {
-  const plansPageV2SplitTestVariant =
-    getMeta('ol-splitTestVariants')?.[PLANS_PAGE_LAYOUT_V2_ANNUAL] ?? 'default'
+  const plansPageLayoutV3Variant =
+    getMeta('ol-splitTestVariants')?.['plans-page-layout-v3'] ?? 'default'
+
   const view = liEl.getAttribute('data-ol-view-tab')
+
   liEl.querySelector('button').addEventListener('click', function (e) {
     e.preventDefault()
     eventTracking.send('subscription-funnel', 'plans-page', `${view}-prices`)
     eventTracking.sendMB('plans-page-toggle', {
       button: view,
-      PLANS_PAGE_LAYOUT_V2_ANNUAL: plansPageV2SplitTestVariant,
+      'plans-page-layout-v3': plansPageLayoutV3Variant,
     })
     selectView(view)
   })
@@ -41,6 +43,7 @@ function setUpViewSwitching(liEl) {
 
 function setUpCurrencySwitching(linkEl) {
   const currencyCode = linkEl.getAttribute('data-ol-currencyCode-switch')
+
   linkEl.addEventListener('click', function (e) {
     e.preventDefault()
     document.querySelectorAll('[data-ol-currencyCode]').forEach(el => {
@@ -53,18 +56,22 @@ function setUpCurrencySwitching(linkEl) {
 }
 
 function setUpSubscriptionTracking(linkEl) {
-  const plansPageV2SplitTestVariant =
-    getMeta('ol-splitTestVariants')?.[PLANS_PAGE_LAYOUT_V2_ANNUAL] ?? 'default'
+  const plansPageLayoutV3Variant =
+    getMeta('ol-splitTestVariants')?.['plans-page-layout-v3'] ?? 'default'
+
   const plan =
     linkEl.getAttribute('data-ol-tracking-plan') ||
     linkEl.getAttribute('data-ol-start-new-subscription')
+
   const location = linkEl.getAttribute('data-ol-location')
   const period = linkEl.getAttribute('data-ol-item-view') || currentView
 
   const DEFAULT_EVENT_TRACKING_KEY = 'plans-page-click'
+
   const eventTrackingKey =
     linkEl.getAttribute('data-ol-event-tracking-key') ||
     DEFAULT_EVENT_TRACKING_KEY
+
   const eventTrackingSegmentation = {
     button: plan,
     location,
@@ -72,8 +79,7 @@ function setUpSubscriptionTracking(linkEl) {
   }
 
   if (eventTrackingKey === DEFAULT_EVENT_TRACKING_KEY) {
-    eventTrackingSegmentation[PLANS_PAGE_LAYOUT_V2_ANNUAL] =
-      plansPageV2SplitTestVariant
+    eventTrackingSegmentation['plans-page-layout-v3'] = plansPageLayoutV3Variant
   }
 
   linkEl.addEventListener('click', function () {
@@ -131,6 +137,23 @@ function updateAnnualSavingBanner(view) {
   }
 }
 
+function makeAnnualViewAsDefault() {
+  const plansPageLayoutV3Variant =
+    getMeta('ol-splitTestVariants')?.['plans-page-layout-v3'] ?? 'default'
+
+  // there are a handful of html elements that will switch between monthly and annual view
+  // with the `hidden` attribute.
+  // On the variant `old-plans-page-annual`, we want annual as the default.
+  // So, instead of changing the pugfiles directly, we change the default with this
+  if (plansPageLayoutV3Variant === 'old-plans-page-annual') {
+    document.querySelectorAll('[data-ol-view]').forEach(el => {
+      const view = el.getAttribute('data-ol-view')
+
+      el.hidden = view !== 'annual'
+    })
+  }
+}
+
 function selectViewFromHash() {
   try {
     const params = new URLSearchParams(window.location.hash.substring(1))
@@ -159,3 +182,6 @@ updateLinkTargets()
 
 selectViewFromHash()
 window.addEventListener('hashchange', selectViewFromHash)
+
+// only for `old-plans-page-annual` variant of the `plans-page-layout-v3` split test
+makeAnnualViewAsDefault()

+ 15 - 8
services/web/frontend/stylesheets/app/plans-v2.less

@@ -118,6 +118,7 @@
         white-space: unset;
         padding: 4px 8px;
         font-size: 14px;
+        width: 100%;
       }
     }
   }
@@ -183,6 +184,10 @@ button.plans-v2-btn-header {
   }
 
   input + span {
+    background-color: @ol-green;
+  }
+
+  input:checked + span {
     background-color: @ol-blue-gray-4;
   }
 
@@ -225,17 +230,17 @@ button.plans-v2-btn-header {
   width: auto;
   padding: @padding-xs @padding-sm;
   border-radius: 4px;
-  left: 100%;
+  right: 110%;
   top: 0;
   z-index: 0;
 
-  &.plans-v2-m-a-tooltip-annual-selected {
+  &.tooltip.in.left {
     .tooltip-inner {
       background-color: @ol-green;
     }
 
     .tooltip-arrow {
-      border-right-color: @ol-green;
+      border-left-color: @ol-green;
     }
   }
 
@@ -244,22 +249,24 @@ button.plans-v2-btn-header {
   }
 
   @media (max-width: @screen-xs-max) {
-    left: -121%;
+    right: -31%;
     top: unset;
 
-    &.plans-v2-m-a-tooltip-annual-selected {
-      left: -33%;
-
+    &.tooltip.in.bottom {
       .tooltip-inner {
         background-color: @ol-green;
       }
 
       .tooltip-arrow {
-        border-right-color: unset;
+        border-left-color: unset;
         border-bottom-color: @ol-green;
       }
     }
 
+    &.plans-v2-m-a-tooltip-monthly-selected {
+      right: -119%;
+    }
+
     span {
       font-size: @font-size-extra-small;
       position: relative;

+ 8 - 0
services/web/frontend/stylesheets/app/plans.less

@@ -155,6 +155,14 @@
             border-bottom-color: @ol-green;
           }
         }
+        &.green-background {
+          .tooltip-inner {
+            background-color: @ol-green;
+          }
+          .tooltip-arrow {
+            border-bottom-color: @ol-green;
+          }
+        }
         .tooltip-inner {
           max-width: none;
         }