Files
overleaf-cep/services/web/scripts/backfill_mixpanel_user_properties.mjs
Anna Claire Fields 6113c6c291 Enable TS noImplicitAny in web (#31636)
GitOrigin-RevId: 18881694770f2476c475f8fef4c6a2678a2a12fe
2026-03-27 09:05:30 +00:00

137 lines
3.6 KiB
JavaScript

// @ts-check
import '../app/src/models/User.mjs'
import { batchedUpdateWithResultHandling } from '@overleaf/mongo-utils/batchedUpdate.js'
import { promiseMapWithLimit } from '@overleaf/promise-utils'
import Queues from '../app/src/infrastructure/Queues.mjs'
import SubscriptionLocator from '../app/src/Features/Subscription/SubscriptionLocator.mjs'
import PlansLocator from '../app/src/Features/Subscription/PlansLocator.mjs'
import FeaturesHelper from '../app/src/Features/Subscription/FeaturesHelper.mjs'
import { db } from '../app/src/infrastructure/mongodb.mjs'
const { getQueue } = Queues
const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY || '10', 10)
const mixpanelSinkQueue = getQueue('analytics-mixpanel-sink')
/**
* @param {any} user
*/
async function processUser(user) {
const analyticsId = user.analyticsId || user._id
await _sendPropertyToQueue(analyticsId, 'user-id', user._id)
await _sendPropertyToQueue(analyticsId, 'analytics-id', analyticsId)
await _sendPropertyToQueue(analyticsId, 'created-at', user.signUpDate)
if (user.alphaProgram !== undefined) {
await _sendPropertyToQueue(analyticsId, 'alpha-program', user.alphaProgram)
}
if (user.betaProgram !== undefined) {
await _sendPropertyToQueue(analyticsId, 'beta-program', user.betaProgram)
}
const groupSubscriptionPlanCode = await _getGroupSubscriptionPlanCode(
user._id
)
if (groupSubscriptionPlanCode) {
await _sendPropertyToQueue(
analyticsId,
'group-subscription-plan-code',
groupSubscriptionPlanCode
)
}
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(user.features)
if (matchedFeatureSet !== 'personal') {
await _sendPropertyToQueue(analyticsId, 'feature-set', matchedFeatureSet)
}
if (user.splitTests) {
for (const splitTestName of Object.keys(user.splitTests)) {
const assignments = user.splitTests[splitTestName]
if (Array.isArray(assignments)) {
for (const assignment of assignments) {
await _sendPropertyToQueue(
analyticsId,
`split-test-${splitTestName}-${assignment.versionNumber}`,
`${assignment.variantName}`
)
}
}
}
}
}
/**
* @param {any} userId
*/
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 &&
plan.features &&
FeaturesHelper.isFeatureSetBetter(plan.features, bestFeatures)
) {
bestPlanCode = plan.planCode
bestFeatures = plan.features
}
}
return bestPlanCode
}
/**
* @param {any} analyticsId
* @param {any} propertyName
* @param {any} propertyValue
* @param {any} [createdAt]
*/
async function _sendPropertyToQueue(
analyticsId,
propertyName,
propertyValue,
createdAt = new Date()
) {
if (propertyValue == null) {
return
}
await mixpanelSinkQueue.add('user-property', {
analyticsId,
propertyName,
propertyValue,
createdAt,
})
}
/**
* @param {any} _
* @param {any} users
*/
async function processBatch(_, users) {
await promiseMapWithLimit(WRITE_CONCURRENCY, users, async user => {
await processUser(user)
})
}
batchedUpdateWithResultHandling(
db.users,
{
$nor: [
{ thirdPartyIdentifiers: { $exists: false } },
{ thirdPartyIdentifiers: { $size: 0 } },
],
},
processBatch,
{
_id: true,
analyticsId: true,
signUpDate: true,
splitTests: true,
alphaProgram: true,
betaProgram: true,
}
)