Browse Source

Merge pull request #6393 from overleaf/ta-backfill-user-properties

Backfill User Properties

GitOrigin-RevId: ea2ffeaaf71d5ce41c76a1bfb2f0014006ef0d08
Jakob Ackermann 4 years ago
parent
commit
cfaec97a3d

+ 16 - 1
services/web/app/src/Features/Subscription/FeaturesHelper.js

@@ -1,4 +1,5 @@
 const _ = require('lodash')
+const Settings = require('@overleaf/settings')
 
 /**
  * Merge feature sets coming from different sources
@@ -96,4 +97,18 @@ function compareFeatures(currentFeatures, expectedFeatures) {
   return mismatchReasons
 }
 
-module.exports = { mergeFeatures, isFeatureSetBetter, compareFeatures }
+function getMatchedFeatureSet(features) {
+  for (const [name, featureSet] of Object.entries(Settings.features)) {
+    if (_.isEqual(features, featureSet)) {
+      return name
+    }
+  }
+  return 'mixed'
+}
+
+module.exports = {
+  mergeFeatures,
+  isFeatureSetBetter,
+  compareFeatures,
+  getMatchedFeatureSet,
+}

+ 1 - 10
services/web/app/src/Features/Subscription/FeaturesUpdater.js

@@ -41,7 +41,7 @@ async function refreshFeatures(userId, reason) {
   const features = await computeFeatures(userId)
   logger.log({ userId, features }, 'updating user features')
 
-  const matchedFeatureSet = _getMatchedFeatureSet(features)
+  const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
   AnalyticsManager.setUserPropertyForUser(
     userId,
     'feature-set',
@@ -189,15 +189,6 @@ async function doSyncFromV1(v1UserId) {
   return refreshFeatures(user._id, 'sync-v1')
 }
 
-function _getMatchedFeatureSet(features) {
-  for (const [name, featureSet] of Object.entries(Settings.features)) {
-    if (_.isEqual(features, featureSet)) {
-      return name
-    }
-  }
-  return 'mixed'
-}
-
 module.exports = {
   featuresEpochIsCurrent,
   computeFeatures: callbackify(computeFeatures),

+ 60 - 0
services/web/scripts/backfill_user_properties.js

@@ -0,0 +1,60 @@
+const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY, 10) || 10
+
+const { batchedUpdateWithResultHandling } = require('./helpers/batchedUpdate')
+const { promiseMapWithLimit } = require('../app/src/util/promises')
+const SubscriptionLocator = require('../app/src/features/Subscription/SubscriptionLocator')
+const PlansLocator = require('../app/src/features/Subscription/PlansLocator')
+const FeaturesHelper = require('../app/src/features/Subscription/FeaturesHelper')
+const AnalyticsManager = require('../app/src/features/Analytics/AnalyticsManager')
+
+async function getGroupSubscriptionPlanCode(userId) {
+  const subscriptions =
+    await SubscriptionLocator.promises.getMemberSubscriptions(userId)
+  let bestPlanCode = null
+  let bestFeatures = {}
+  for (const subscription of subscriptions) {
+    const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
+    if (
+      plan &&
+      FeaturesHelper.isFeatureSetBetter(plan.features, bestFeatures)
+    ) {
+      bestPlanCode = plan.planCode
+      bestFeatures = plan.features
+    }
+  }
+  return bestPlanCode
+}
+
+async function processUser(user) {
+  const analyticsId = user.analyticsId || user._id
+
+  const groupSubscriptionPlanCode = await getGroupSubscriptionPlanCode(user._id)
+  if (groupSubscriptionPlanCode) {
+    await AnalyticsManager.setUserPropertyForAnalyticsId(
+      analyticsId,
+      'group-subscription-plan-code',
+      groupSubscriptionPlanCode
+    )
+  }
+
+  const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(user.features)
+  if (matchedFeatureSet !== 'personal') {
+    await AnalyticsManager.setUserPropertyForAnalyticsId(
+      analyticsId,
+      'feature-set',
+      matchedFeatureSet
+    )
+  }
+}
+
+async function processBatch(_, users) {
+  await promiseMapWithLimit(WRITE_CONCURRENCY, users, async user => {
+    await processUser(user)
+  })
+}
+
+batchedUpdateWithResultHandling('users', {}, processBatch, {
+  _id: true,
+  analyticsId: true,
+  features: true,
+})

+ 8 - 2
services/web/scripts/helpers/batchedUpdate.js

@@ -95,8 +95,14 @@ async function batchedUpdate(
   return updated
 }
 
-function batchedUpdateWithResultHandling(collection, query, update) {
-  batchedUpdate(collection, query, update)
+function batchedUpdateWithResultHandling(
+  collection,
+  query,
+  update,
+  projection,
+  options
+) {
+  batchedUpdate(collection, query, update, projection, options)
     .then(updated => {
       console.error({ updated })
       process.exit(0)

+ 0 - 4
services/web/test/unit/src/Subscription/FeaturesUpdaterTests.js

@@ -105,15 +105,11 @@ describe('FeaturesUpdater', function () {
     this.Modules = {
       promises: { hooks: { fire: sinon.stub().resolves() } },
     }
-    this.FeaturesHelper = {
-      mergeFeatures: sinon.stub().callsFake((a, b) => ({ ...a, ...b })),
-    }
 
     this.FeaturesUpdater = SandboxedModule.require(MODULE_PATH, {
       requires: {
         './UserFeaturesUpdater': this.UserFeaturesUpdater,
         './SubscriptionLocator': this.SubscriptionLocator,
-        './FeaturesHelper': this.FeaturesHelper,
         '@overleaf/settings': this.Settings,
         '../Referal/ReferalFeatures': this.ReferalFeatures,
         './V1SubscriptionManager': this.V1SubscriptionManager,