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

Backfill User Properties

GitOrigin-RevId: ea2ffeaaf71d5ce41c76a1bfb2f0014006ef0d08
This commit is contained in:
Jakob Ackermann 2022-01-25 10:18:07 +00:00 committed by Copybot
parent b2e18c92a6
commit cfaec97a3d
5 changed files with 85 additions and 17 deletions

View file

@ -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,
}

View file

@ -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),

View file

@ -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,
})

View file

@ -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)

View file

@ -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,