Просмотр исходного кода

Merge pull request #7806 from overleaf/ab-group-memberships-bigquery

[web] [analytics] Send group memberships information to analytics & import baseline data

GitOrigin-RevId: a15c3131f7b55225ae3949933578475668a195a0
Alexandre Bourdin 4 лет назад
Родитель
Сommit
5c00fe09e4

+ 68 - 2
services/web/app/src/Features/Subscription/SubscriptionUpdater.js

@@ -64,6 +64,11 @@ async function addUserToGroup(subscriptionId, userId) {
   ).exec()
   await FeaturesUpdater.promises.refreshFeatures(userId, 'add-to-group')
   await _sendUserGroupPlanCodeUserProperty(userId)
+  await _sendSubscriptionEvent(
+    userId,
+    subscriptionId,
+    'group-subscription-joined'
+  )
 }
 
 async function removeUserFromGroup(subscriptionId, userId) {
@@ -76,6 +81,11 @@ async function removeUserFromGroup(subscriptionId, userId) {
     'remove-user-from-group'
   )
   await _sendUserGroupPlanCodeUserProperty(userId)
+  await _sendSubscriptionEvent(
+    userId,
+    subscriptionId,
+    'group-subscription-left'
+  )
 }
 
 async function removeUserFromAllGroups(userId) {
@@ -94,6 +104,13 @@ async function removeUserFromAllGroups(userId) {
     userId,
     'remove-user-from-groups'
   )
+  for (const subscriptionId of subscriptionIds) {
+    await _sendSubscriptionEvent(
+      userId,
+      subscriptionId,
+      'group-subscription-left'
+    )
+  }
   await _sendUserGroupPlanCodeUserProperty(userId)
 }
 
@@ -105,10 +122,16 @@ async function deleteSubscription(subscription, deleterData) {
   // 1. create deletedSubscription
   await createDeletedSubscription(subscription, deleterData)
 
-  // 2. remove subscription
+  // 2. notify analytics that members left the subscription
+  await _sendSubscriptionEventForAllMembers(
+    subscription._id,
+    'group-subscription-left'
+  )
+
+  // 3. remove subscription
   await Subscription.deleteOne({ _id: subscription._id }).exec()
 
-  // 3. refresh users features
+  // 4. refresh users features
   await _scheduleRefreshFeatures(subscription)
 }
 
@@ -132,6 +155,12 @@ async function restoreSubscription(subscriptionId) {
   await DeletedSubscription.deleteOne({
     'subscription._id': subscription._id,
   }).exec()
+
+  // 4. notify analytics that members rejoined the subscription
+  await _sendSubscriptionEventForAllMembers(
+    subscriptionId,
+    'group-subscription-left'
+  )
 }
 
 async function refreshUsersFeatures(subscription) {
@@ -276,6 +305,43 @@ async function _sendUserGroupPlanCodeUserProperty(userId) {
   }
 }
 
+async function _sendSubscriptionEvent(userId, subscriptionId, event) {
+  const subscription = await Subscription.findOne(
+    { _id: subscriptionId },
+    { recurlySubscription_id: 1, groupPlan: 1 }
+  )
+  if (!subscription || !subscription.groupPlan) {
+    return
+  }
+  AnalyticsManager.recordEventForUser(userId, event, {
+    groupId: subscription._id.toString(),
+    subscriptionId: subscription.recurlySubscription_id,
+  })
+}
+
+async function _sendSubscriptionEventForAllMembers(subscriptionId, event) {
+  const subscription = await Subscription.findOne(
+    { _id: subscriptionId },
+    {
+      recurlySubscription_id: 1,
+      member_ids: 1,
+      groupPlan: 1,
+    }
+  )
+  if (!subscription) {
+    return
+  }
+  const userIds = (subscription.member_ids || []).filter(Boolean)
+  for (const userId of userIds) {
+    if (userId) {
+      AnalyticsManager.recordEventForUser(userId, event, {
+        groupId: subscription._id.toString(),
+        subscriptionId: subscription.recurlySubscription_id,
+      })
+    }
+  }
+}
+
 module.exports = {
   updateAdmin: callbackify(updateAdmin),
   syncSubscription: callbackify(syncSubscription),

+ 111 - 10
services/web/test/unit/src/Subscription/SubscriptionUpdaterTests.js

@@ -25,6 +25,7 @@ describe('SubscriptionUpdater', function () {
       member_ids: [],
       save: sinon.stub().resolves(),
       planCode: 'student_or_something',
+      recurlySubscription_id: 'abc123def456fab789',
     }
     this.user_id = this.adminuser_id
 
@@ -36,6 +37,7 @@ describe('SubscriptionUpdater', function () {
       save: sinon.stub().resolves(),
       groupPlan: true,
       planCode: 'group_subscription',
+      recurlySubscription_id: '456fab789abc123def',
     }
     this.betterGroupSubscription = {
       _id: '999999999999999999999999',
@@ -45,6 +47,7 @@ describe('SubscriptionUpdater', function () {
       save: sinon.stub().resolves(),
       groupPlan: true,
       planCode: 'better_group_subscription',
+      recurlySubscription_id: '123def456fab789abc',
     }
 
     const subscription = this.subscription
@@ -66,6 +69,7 @@ describe('SubscriptionUpdater', function () {
     this.SubscriptionModel.updateOne = sinon
       .stub()
       .returns({ exec: sinon.stub().resolves() })
+    this.SubscriptionModel.findOne = sinon.stub().resolves()
     this.SubscriptionModel.updateMany = sinon
       .stub()
       .returns({ exec: sinon.stub().resolves() })
@@ -135,6 +139,7 @@ describe('SubscriptionUpdater', function () {
     }
 
     this.AnalyticsManager = {
+      recordEventForUser: sinon.stub().resolves(),
       setUserPropertyForUser: sinon.stub(),
     }
 
@@ -378,17 +383,30 @@ describe('SubscriptionUpdater', function () {
 
   describe('addUserToGroup', function () {
     it('should add the user ids to the group as a set', async function () {
+      this.SubscriptionModel.findOne = sinon
+        .stub()
+        .resolves(this.groupSubscription)
+
       await this.SubscriptionUpdater.promises.addUserToGroup(
-        this.subscription._id,
+        this.groupSubscription._id,
         this.otherUserId
       )
-      const searchOps = { _id: this.subscription._id }
+      const searchOps = { _id: this.groupSubscription._id }
       const insertOperation = {
         $addToSet: { member_ids: this.otherUserId },
       }
       this.SubscriptionModel.updateOne
         .calledWith(searchOps, insertOperation)
         .should.equal(true)
+      sinon.assert.calledWith(
+        this.AnalyticsManager.recordEventForUser,
+        this.otherUserId,
+        'group-subscription-joined',
+        {
+          groupId: this.groupSubscription._id,
+          subscriptionId: this.groupSubscription.recurlySubscription_id,
+        }
+      )
     })
 
     it('should update the users features', async function () {
@@ -450,9 +468,17 @@ describe('SubscriptionUpdater', function () {
     })
   })
 
-  describe('removeUserFromGroups', function () {
+  describe('removeUserFromGroup', function () {
     beforeEach(function () {
-      this.fakeSubscriptions = [{ _id: 'fake-id-1' }, { _id: 'fake-id-2' }]
+      this.fakeSubscriptions = [
+        {
+          _id: 'fake-id-1',
+        },
+        {
+          _id: 'fake-id-2',
+        },
+      ]
+      this.SubscriptionModel.findOne.resolves(this.groupSubscription)
       this.SubscriptionLocator.promises.getMemberSubscriptions.resolves(
         this.fakeSubscriptions
       )
@@ -469,6 +495,22 @@ describe('SubscriptionUpdater', function () {
         .should.equal(true)
     })
 
+    it('should send a group-subscription-left event', async function () {
+      await this.SubscriptionUpdater.promises.removeUserFromGroup(
+        this.groupSubscription._id,
+        this.otherUserId
+      )
+      sinon.assert.calledWith(
+        this.AnalyticsManager.recordEventForUser,
+        this.otherUserId,
+        'group-subscription-left',
+        {
+          groupId: this.groupSubscription._id,
+          subscriptionId: this.groupSubscription.recurlySubscription_id,
+        }
+      )
+    })
+
     it('should set the group plan code user property when removing user from group', async function () {
       await this.SubscriptionUpdater.promises.removeUserFromGroup(
         this.subscription._id,
@@ -482,6 +524,29 @@ describe('SubscriptionUpdater', function () {
       )
     })
 
+    it('should update the users features', async function () {
+      await this.SubscriptionUpdater.promises.removeUserFromGroup(
+        this.subscription._id,
+        this.otherUserId
+      )
+      this.FeaturesUpdater.promises.refreshFeatures
+        .calledWith(this.otherUserId)
+        .should.equal(true)
+    })
+  })
+
+  describe('removeUserFromAllGroups', function () {
+    beforeEach(function () {
+      this.SubscriptionLocator.promises.getMemberSubscriptions.resolves([
+        {
+          _id: 'fake-id-1',
+        },
+        {
+          _id: 'fake-id-2',
+        },
+      ])
+    })
+
     it('should set the group plan code user property when removing user from all groups', async function () {
       await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
         this.otherUserId
@@ -507,14 +572,50 @@ describe('SubscriptionUpdater', function () {
       )
     })
 
-    it('should update the users features', async function () {
-      await this.SubscriptionUpdater.promises.removeUserFromGroup(
-        this.subscription._id,
+    it('should send a group-subscription-left event for each group', async function () {
+      this.fakeSub1 = {
+        _id: 'fake-id-1',
+        groupPlan: true,
+        recurlySubscription_id: 'fake-sub-1',
+      }
+      this.fakeSub2 = {
+        _id: 'fake-id-2',
+        groupPlan: true,
+        recurlySubscription_id: 'fake-sub-2',
+      }
+      this.SubscriptionModel.findOne
+        .withArgs(
+          { _id: 'fake-id-1' },
+          { recurlySubscription_id: 1, groupPlan: 1 }
+        )
+        .resolves(this.fakeSub1)
+        .withArgs(
+          { _id: 'fake-id-2' },
+          { recurlySubscription_id: 1, groupPlan: 1 }
+        )
+        .resolves(this.fakeSub2)
+
+      await this.SubscriptionUpdater.promises.removeUserFromAllGroups(
         this.otherUserId
       )
-      this.FeaturesUpdater.promises.refreshFeatures
-        .calledWith(this.otherUserId)
-        .should.equal(true)
+      sinon.assert.calledWith(
+        this.AnalyticsManager.recordEventForUser,
+        this.otherUserId,
+        'group-subscription-left',
+        {
+          groupId: 'fake-id-1',
+          subscriptionId: 'fake-sub-1',
+        }
+      )
+      sinon.assert.calledWith(
+        this.AnalyticsManager.recordEventForUser,
+        this.otherUserId,
+        'group-subscription-left',
+        {
+          groupId: 'fake-id-2',
+          subscriptionId: 'fake-sub-2',
+        }
+      )
     })
   })