mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #4113 from overleaf/ta-post-registration-analytics
Add Post Registration Analytics Job GitOrigin-RevId: f0d83eeea2e32915782e916cb40a768d5c1b6116
This commit is contained in:
parent
ca1e828ea7
commit
5af039eef0
5 changed files with 195 additions and 2 deletions
|
@ -8,6 +8,7 @@ const UserGetter = require('./UserGetter')
|
||||||
const UserUpdater = require('./UserUpdater')
|
const UserUpdater = require('./UserUpdater')
|
||||||
const Analytics = require('../Analytics/AnalyticsManager')
|
const Analytics = require('../Analytics/AnalyticsManager')
|
||||||
const UserOnboardingEmailQueueManager = require('./UserOnboardingEmailManager')
|
const UserOnboardingEmailQueueManager = require('./UserOnboardingEmailManager')
|
||||||
|
const UserPostRegistrationAnalyticsManager = require('./UserPostRegistrationAnalyticsManager')
|
||||||
const OError = require('@overleaf/o-error')
|
const OError = require('@overleaf/o-error')
|
||||||
|
|
||||||
async function _addAffiliation(user, affiliationOptions) {
|
async function _addAffiliation(user, affiliationOptions) {
|
||||||
|
@ -89,6 +90,9 @@ async function createNewUser(attributes, options = {}) {
|
||||||
if (Features.hasFeature('saas')) {
|
if (Features.hasFeature('saas')) {
|
||||||
try {
|
try {
|
||||||
await UserOnboardingEmailQueueManager.scheduleOnboardingEmail(user)
|
await UserOnboardingEmailQueueManager.scheduleOnboardingEmail(user)
|
||||||
|
await UserPostRegistrationAnalyticsManager.schedulePostRegistrationAnalytics(
|
||||||
|
user
|
||||||
|
)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
OError.tag(error, 'Failed to schedule sending of onboarding email', {
|
OError.tag(error, 'Failed to schedule sending of onboarding email', {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
const Queues = require('../../infrastructure/Queues')
|
||||||
|
const UserGetter = require('./UserGetter')
|
||||||
|
const {
|
||||||
|
promises: InstitutionsAPIPromises,
|
||||||
|
} = require('../Institutions/InstitutionsAPI')
|
||||||
|
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||||
|
|
||||||
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
class UserPostRegistrationAnalyticsManager {
|
||||||
|
constructor() {
|
||||||
|
this.queue = Queues.getPostRegistrationAnalyticsQueue()
|
||||||
|
this.queue.process(async job => {
|
||||||
|
const { userId } = job.data
|
||||||
|
await postRegistrationAnalytics(userId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async schedulePostRegistrationAnalytics(user) {
|
||||||
|
await this.queue.add({ userId: user._id }, { delay: ONE_DAY_MS })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function postRegistrationAnalytics(userId) {
|
||||||
|
const user = await UserGetter.promises.getUser({ _id: userId }, { email: 1 })
|
||||||
|
if (!user) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
await checkAffiliations(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAffiliations(userId) {
|
||||||
|
const affiliationsData = await InstitutionsAPIPromises.getUserAffiliations(
|
||||||
|
userId
|
||||||
|
)
|
||||||
|
const hasCommonsAccountAffiliation = affiliationsData.some(
|
||||||
|
affiliationData =>
|
||||||
|
affiliationData.institution && affiliationData.institution.commonsAccount
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasCommonsAccountAffiliation) {
|
||||||
|
await AnalyticsManager.setUserProperty(
|
||||||
|
userId,
|
||||||
|
'registered-from-commons-account',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new UserPostRegistrationAnalyticsManager()
|
|
@ -31,6 +31,10 @@ function getOnboardingEmailsQueue() {
|
||||||
return getOrCreateQueue('emails-onboarding')
|
return getOrCreateQueue('emails-onboarding')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPostRegistrationAnalyticsQueue() {
|
||||||
|
return getOrCreateQueue('post-registration-analytics')
|
||||||
|
}
|
||||||
|
|
||||||
function getOrCreateQueue(queueName, defaultJobOptions) {
|
function getOrCreateQueue(queueName, defaultJobOptions) {
|
||||||
if (!queues[queueName]) {
|
if (!queues[queueName]) {
|
||||||
queues[queueName] = new Queue(queueName, {
|
queues[queueName] = new Queue(queueName, {
|
||||||
|
@ -54,4 +58,5 @@ module.exports = {
|
||||||
getAnalyticsEditingSessionsQueue,
|
getAnalyticsEditingSessionsQueue,
|
||||||
getAnalyticsUserPropertiesQueue,
|
getAnalyticsUserPropertiesQueue,
|
||||||
getOnboardingEmailsQueue,
|
getOnboardingEmailsQueue,
|
||||||
|
getPostRegistrationAnalyticsQueue,
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,9 @@ describe('UserCreator', function () {
|
||||||
'./UserOnboardingEmailManager': (this.UserOnboardingEmailManager = {
|
'./UserOnboardingEmailManager': (this.UserOnboardingEmailManager = {
|
||||||
scheduleOnboardingEmail: sinon.stub(),
|
scheduleOnboardingEmail: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
|
'./UserPostRegistrationAnalyticsManager': (this.UserPostRegistrationAnalyticsManager = {
|
||||||
|
schedulePostRegistrationAnalytics: sinon.stub(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -279,7 +282,7 @@ describe('UserCreator', function () {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should schedule an onboarding email on registration with saas feature', async function () {
|
it('should schedule post registration jobs on registration with saas feature', async function () {
|
||||||
this.Features.hasFeature = sinon.stub().withArgs('saas').returns(true)
|
this.Features.hasFeature = sinon.stub().withArgs('saas').returns(true)
|
||||||
const user = await this.UserCreator.promises.createNewUser({
|
const user = await this.UserCreator.promises.createNewUser({
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
@ -289,14 +292,23 @@ describe('UserCreator', function () {
|
||||||
this.UserOnboardingEmailManager.scheduleOnboardingEmail,
|
this.UserOnboardingEmailManager.scheduleOnboardingEmail,
|
||||||
user
|
user
|
||||||
)
|
)
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
this.UserPostRegistrationAnalyticsManager
|
||||||
|
.schedulePostRegistrationAnalytics,
|
||||||
|
user
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not add schedule onboarding email when without saas feature', async function () {
|
it('should not schedule post registration checks when without saas feature', async function () {
|
||||||
const attributes = { email: this.email }
|
const attributes = { email: this.email }
|
||||||
await this.UserCreator.promises.createNewUser(attributes)
|
await this.UserCreator.promises.createNewUser(attributes)
|
||||||
sinon.assert.notCalled(
|
sinon.assert.notCalled(
|
||||||
this.UserOnboardingEmailManager.scheduleOnboardingEmail
|
this.UserOnboardingEmailManager.scheduleOnboardingEmail
|
||||||
)
|
)
|
||||||
|
sinon.assert.notCalled(
|
||||||
|
this.UserPostRegistrationAnalyticsManager
|
||||||
|
.schedulePostRegistrationAnalytics
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
const SandboxedModule = require('sandboxed-module')
|
||||||
|
const path = require('path')
|
||||||
|
const sinon = require('sinon')
|
||||||
|
|
||||||
|
const MODULE_PATH = path.join(
|
||||||
|
__dirname,
|
||||||
|
'../../../../app/src/Features/User/UserPostRegistrationAnalyticsManager'
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('UserPostRegistrationAnalyticsManager', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.fakeUserId = '123abc'
|
||||||
|
this.postRegistrationAnalyticsQueue = {
|
||||||
|
add: sinon.stub().resolves(),
|
||||||
|
process: callback => {
|
||||||
|
this.queueProcessFunction = callback
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const self = this
|
||||||
|
this.Queues = {
|
||||||
|
getPostRegistrationAnalyticsQueue: () => {
|
||||||
|
return self.postRegistrationAnalyticsQueue
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.UserGetter = {
|
||||||
|
promises: {
|
||||||
|
getUser: sinon.stub().resolves({ _id: this.fakeUserId }),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.InstitutionsAPI = {
|
||||||
|
promises: {
|
||||||
|
getUserAffiliations: sinon.stub().resolves([]),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.AnalyticsManager = {
|
||||||
|
setUserProperty: sinon.stub().resolves(),
|
||||||
|
}
|
||||||
|
this.UserPostRegistrationAnalyticsManager = SandboxedModule.require(
|
||||||
|
MODULE_PATH,
|
||||||
|
{
|
||||||
|
globals: {
|
||||||
|
console: console,
|
||||||
|
},
|
||||||
|
requires: {
|
||||||
|
'../../infrastructure/Queues': this.Queues,
|
||||||
|
'./UserGetter': this.UserGetter,
|
||||||
|
'../Institutions/InstitutionsAPI': this.InstitutionsAPI,
|
||||||
|
'../Analytics/AnalyticsManager': this.AnalyticsManager,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('schedule jobs', function () {
|
||||||
|
it('should schedule delayed job on queue', function () {
|
||||||
|
this.UserPostRegistrationAnalyticsManager.schedulePostRegistrationAnalytics(
|
||||||
|
{
|
||||||
|
_id: this.fakeUserId,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
sinon.assert.calledWithMatch(
|
||||||
|
this.postRegistrationAnalyticsQueue.add,
|
||||||
|
{ userId: this.fakeUserId },
|
||||||
|
{ delay: 24 * 60 * 60 * 1000 }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('process jobs', function () {
|
||||||
|
it('stops without errors if user is not found', async function () {
|
||||||
|
this.UserGetter.promises.getUser.resolves(null)
|
||||||
|
await this.queueProcessFunction({ data: { userId: this.fakeUserId } })
|
||||||
|
sinon.assert.calledWith(this.UserGetter.promises.getUser, {
|
||||||
|
_id: this.fakeUserId,
|
||||||
|
})
|
||||||
|
sinon.assert.notCalled(this.InstitutionsAPI.promises.getUserAffiliations)
|
||||||
|
sinon.assert.notCalled(this.AnalyticsManager.setUserProperty)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets user property if user has commons account affiliationd', async function () {
|
||||||
|
this.InstitutionsAPI.promises.getUserAffiliations.resolves([
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
institution: {
|
||||||
|
commonsAccount: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
institution: {
|
||||||
|
commonsAccount: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
await this.queueProcessFunction({ data: { userId: this.fakeUserId } })
|
||||||
|
sinon.assert.calledWith(this.UserGetter.promises.getUser, {
|
||||||
|
_id: this.fakeUserId,
|
||||||
|
})
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
this.InstitutionsAPI.promises.getUserAffiliations,
|
||||||
|
this.fakeUserId
|
||||||
|
)
|
||||||
|
sinon.assert.calledWith(
|
||||||
|
this.AnalyticsManager.setUserProperty,
|
||||||
|
this.fakeUserId,
|
||||||
|
'registered-from-commons-account',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not set user property if user has no commons account affiliation', async function () {
|
||||||
|
this.InstitutionsAPI.promises.getUserAffiliations.resolves([
|
||||||
|
{
|
||||||
|
institution: {
|
||||||
|
commonsAccount: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
await this.queueProcessFunction({ data: { userId: this.fakeUserId } })
|
||||||
|
sinon.assert.notCalled(this.AnalyticsManager.setUserProperty)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue