Преглед на файлове

Merge pull request #31678 from overleaf/rh-cio-subscription-status

Sync subscription type and features to customer.io

GitOrigin-RevId: 4c23a6b4ec9f103e73b26203b0d43f177e56bb6e
roo hutton преди 5 месеца
родител
ревизия
8fb5b0ed05

+ 31 - 0
services/web/app/src/Features/Subscription/FeaturesUpdater.mjs

@@ -15,6 +15,7 @@ import UserGetter from '../User/UserGetter.mjs'
 import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
 import Queues from '../../infrastructure/Queues.mjs'
 import Modules from '../../infrastructure/Modules.mjs'
+import SubscriptionViewModelBuilder from './SubscriptionViewModelBuilder.mjs'
 import { AI_ADD_ON_CODE } from './AiHelper.mjs'
 import { fetchNothing } from '@overleaf/fetch-utils'
 
@@ -54,6 +55,20 @@ async function refreshFeatures(userId, reason) {
 
   const { features: newFeatures, featuresChanged } =
     await UserFeaturesUpdater.promises.updateFeatures(userId, features)
+
+  // TODO: this call is quite expensive, so ideally we'd update cio with something
+  // that doesn't require the best subscription to be computed, ie. the plan code (or type)
+  const bestSubscriptionType = await _getBestSubscriptionType(userId)
+
+  Modules.promises.hooks
+    .fire('setUserProperties', userId, {
+      features,
+      'best-subscription-type': bestSubscriptionType,
+    })
+    .catch(err => {
+      logger.error({ err, userId }, 'Failed to sync features to customer.io')
+    })
+
   if (oldFeatures.dropbox === true && features.dropbox === false) {
     logger.debug({ userId }, '[FeaturesUpdater] must unlink dropbox')
     try {
@@ -101,6 +116,22 @@ async function refreshFeatures(userId, reason) {
   return { features: newFeatures, featuresChanged }
 }
 
+async function _getBestSubscriptionType(userId) {
+  try {
+    const { bestSubscription } =
+      await SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails({
+        _id: userId,
+      })
+    return bestSubscription?.type || 'free'
+  } catch (err) {
+    logger.warn(
+      { err, userId },
+      'Failed to calculate best-subscription-type for customer.io'
+    )
+    return 'free'
+  }
+}
+
 /**
  * Return the features that the given user should have.
  */

+ 25 - 0
services/web/test/unit/src/Subscription/FeaturesUpdater.test.mjs

@@ -113,6 +113,13 @@ describe('FeaturesUpdater', function () {
     ctx.Modules = {
       promises: { hooks: { fire: sinon.stub().resolves() } },
     }
+    ctx.SubscriptionViewModelBuilder = {
+      promises: {
+        getUsersSubscriptionDetails: sinon.stub().resolves({
+          bestSubscription: { type: 'individual' },
+        }),
+      },
+    }
     ctx.Queues = {
       getQueue: sinon.stub().returns({
         add: sinon.stub().resolves(),
@@ -170,6 +177,13 @@ describe('FeaturesUpdater', function () {
       default: ctx.Modules,
     }))
 
+    vi.doMock(
+      '../../../../app/src/Features/Subscription/SubscriptionViewModelBuilder',
+      () => ({
+        default: ctx.SubscriptionViewModelBuilder,
+      })
+    )
+
     vi.doMock('../../../../app/src/infrastructure/Queues', () => ({
       default: ctx.Queues,
     }))
@@ -374,6 +388,17 @@ describe('FeaturesUpdater', function () {
           ctx.AnalyticsManager.setUserPropertyForUserInBackground
         ).to.have.been.calledWith(ctx.user._id, 'feature-set', 'all')
       })
+
+      it('should sync features to customer.io', function (ctx) {
+        expect(ctx.Modules.promises.hooks.fire).to.have.been.calledWith(
+          'setUserProperties',
+          ctx.user._id,
+          {
+            features: ctx.Settings.features.all,
+            'best-subscription-type': 'individual',
+          }
+        )
+      })
     })
 
     describe('with a non-standard feature set', async function () {