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

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

GitOrigin-RevId: a15c3131f7b55225ae3949933578475668a195a0
This commit is contained in:
Alexandre Bourdin 2022-05-12 11:34:31 +02:00 committed by Copybot
parent fe18fbe157
commit 5c00fe09e4
2 changed files with 179 additions and 12 deletions

View file

@ -64,6 +64,11 @@ async function addUserToGroup(subscriptionId, userId) {
).exec() ).exec()
await FeaturesUpdater.promises.refreshFeatures(userId, 'add-to-group') await FeaturesUpdater.promises.refreshFeatures(userId, 'add-to-group')
await _sendUserGroupPlanCodeUserProperty(userId) await _sendUserGroupPlanCodeUserProperty(userId)
await _sendSubscriptionEvent(
userId,
subscriptionId,
'group-subscription-joined'
)
} }
async function removeUserFromGroup(subscriptionId, userId) { async function removeUserFromGroup(subscriptionId, userId) {
@ -76,6 +81,11 @@ async function removeUserFromGroup(subscriptionId, userId) {
'remove-user-from-group' 'remove-user-from-group'
) )
await _sendUserGroupPlanCodeUserProperty(userId) await _sendUserGroupPlanCodeUserProperty(userId)
await _sendSubscriptionEvent(
userId,
subscriptionId,
'group-subscription-left'
)
} }
async function removeUserFromAllGroups(userId) { async function removeUserFromAllGroups(userId) {
@ -94,6 +104,13 @@ async function removeUserFromAllGroups(userId) {
userId, userId,
'remove-user-from-groups' 'remove-user-from-groups'
) )
for (const subscriptionId of subscriptionIds) {
await _sendSubscriptionEvent(
userId,
subscriptionId,
'group-subscription-left'
)
}
await _sendUserGroupPlanCodeUserProperty(userId) await _sendUserGroupPlanCodeUserProperty(userId)
} }
@ -105,10 +122,16 @@ async function deleteSubscription(subscription, deleterData) {
// 1. create deletedSubscription // 1. create deletedSubscription
await createDeletedSubscription(subscription, deleterData) 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() await Subscription.deleteOne({ _id: subscription._id }).exec()
// 3. refresh users features // 4. refresh users features
await _scheduleRefreshFeatures(subscription) await _scheduleRefreshFeatures(subscription)
} }
@ -132,6 +155,12 @@ async function restoreSubscription(subscriptionId) {
await DeletedSubscription.deleteOne({ await DeletedSubscription.deleteOne({
'subscription._id': subscription._id, 'subscription._id': subscription._id,
}).exec() }).exec()
// 4. notify analytics that members rejoined the subscription
await _sendSubscriptionEventForAllMembers(
subscriptionId,
'group-subscription-left'
)
} }
async function refreshUsersFeatures(subscription) { 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 = { module.exports = {
updateAdmin: callbackify(updateAdmin), updateAdmin: callbackify(updateAdmin),
syncSubscription: callbackify(syncSubscription), syncSubscription: callbackify(syncSubscription),

View file

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