backfill_mixpanel_user_properties.mjs 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. // @ts-check
  2. import '../app/src/models/User.js'
  3. import { batchedUpdateWithResultHandling } from '@overleaf/mongo-utils/batchedUpdate.js'
  4. import { promiseMapWithLimit } from '@overleaf/promise-utils'
  5. import { getQueue } from '../app/src/infrastructure/Queues.js'
  6. import SubscriptionLocator from '../app/src/Features/Subscription/SubscriptionLocator.js'
  7. import PlansLocator from '../app/src/Features/Subscription/PlansLocator.js'
  8. import FeaturesHelper from '../app/src/Features/Subscription/FeaturesHelper.js'
  9. import { db } from '../app/src/infrastructure/mongodb.js'
  10. const WRITE_CONCURRENCY = parseInt(process.env.WRITE_CONCURRENCY || '10', 10)
  11. const mixpanelSinkQueue = getQueue('analytics-mixpanel-sink')
  12. async function processUser(user) {
  13. const analyticsId = user.analyticsId || user._id
  14. await _sendPropertyToQueue(analyticsId, 'user-id', user._id)
  15. await _sendPropertyToQueue(analyticsId, 'analytics-id', analyticsId)
  16. await _sendPropertyToQueue(analyticsId, 'created-at', user.signUpDate)
  17. if (user.alphaProgram !== undefined) {
  18. await _sendPropertyToQueue(analyticsId, 'alpha-program', user.alphaProgram)
  19. }
  20. if (user.betaProgram !== undefined) {
  21. await _sendPropertyToQueue(analyticsId, 'beta-program', user.betaProgram)
  22. }
  23. const groupSubscriptionPlanCode = await _getGroupSubscriptionPlanCode(
  24. user._id
  25. )
  26. if (groupSubscriptionPlanCode) {
  27. await _sendPropertyToQueue(
  28. analyticsId,
  29. 'group-subscription-plan-code',
  30. groupSubscriptionPlanCode
  31. )
  32. }
  33. const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(user.features)
  34. if (matchedFeatureSet !== 'personal') {
  35. await _sendPropertyToQueue(analyticsId, 'feature-set', matchedFeatureSet)
  36. }
  37. if (user.splitTests) {
  38. for (const splitTestName of Object.keys(user.splitTests)) {
  39. const assignments = user.splitTests[splitTestName]
  40. if (Array.isArray(assignments)) {
  41. for (const assignment of assignments) {
  42. await _sendPropertyToQueue(
  43. analyticsId,
  44. `split-test-${splitTestName}-${assignment.versionNumber}`,
  45. `${assignment.variantName}`
  46. )
  47. }
  48. }
  49. }
  50. }
  51. }
  52. async function _getGroupSubscriptionPlanCode(userId) {
  53. const subscriptions =
  54. await SubscriptionLocator.promises.getMemberSubscriptions(userId)
  55. let bestPlanCode = null
  56. let bestFeatures = {}
  57. for (const subscription of subscriptions) {
  58. const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
  59. if (
  60. plan &&
  61. plan.features &&
  62. FeaturesHelper.isFeatureSetBetter(plan.features, bestFeatures)
  63. ) {
  64. bestPlanCode = plan.planCode
  65. bestFeatures = plan.features
  66. }
  67. }
  68. return bestPlanCode
  69. }
  70. async function _sendPropertyToQueue(
  71. analyticsId,
  72. propertyName,
  73. propertyValue,
  74. createdAt = new Date()
  75. ) {
  76. if (propertyValue == null) {
  77. return
  78. }
  79. await mixpanelSinkQueue.add('user-property', {
  80. analyticsId,
  81. propertyName,
  82. propertyValue,
  83. createdAt,
  84. })
  85. }
  86. async function processBatch(_, users) {
  87. await promiseMapWithLimit(WRITE_CONCURRENCY, users, async user => {
  88. await processUser(user)
  89. })
  90. }
  91. batchedUpdateWithResultHandling(
  92. db.users,
  93. {
  94. $nor: [
  95. { thirdPartyIdentifiers: { $exists: false } },
  96. { thirdPartyIdentifiers: { $size: 0 } },
  97. ],
  98. },
  99. processBatch,
  100. {
  101. _id: true,
  102. analyticsId: true,
  103. signUpDate: true,
  104. splitTests: true,
  105. alphaProgram: true,
  106. betaProgram: true,
  107. }
  108. )