mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Send subscription -> salesforce/v1 links to analytics
GitOrigin-RevId: 52c02138f9ef8141850e2f8ce16f2bab1e7463b0
This commit is contained in:
parent
2d87fe036d
commit
24b27e3863
5 changed files with 360 additions and 0 deletions
|
@ -0,0 +1,48 @@
|
||||||
|
export function extractAccountMappingsFromSubscription(
|
||||||
|
subscription,
|
||||||
|
updatedSubscription
|
||||||
|
) {
|
||||||
|
const accountMappings = []
|
||||||
|
if (
|
||||||
|
updatedSubscription.salesforce_id ||
|
||||||
|
updatedSubscription.salesforce_id === ''
|
||||||
|
) {
|
||||||
|
if (subscription.salesforce_id !== updatedSubscription.salesforce_id) {
|
||||||
|
accountMappings.push(
|
||||||
|
generateSubscriptionToSalesforceMapping(
|
||||||
|
subscription.id,
|
||||||
|
updatedSubscription.salesforce_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updatedSubscription.v1_id || updatedSubscription.v1_id === '') {
|
||||||
|
if (subscription.v1_id !== updatedSubscription.v1_id) {
|
||||||
|
accountMappings.push(
|
||||||
|
generateSubscriptionToV1Mapping(
|
||||||
|
subscription.id,
|
||||||
|
updatedSubscription.v1_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return accountMappings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function generateSubscriptionToSalesforceMapping(subscriptionId, salesforceId) {
|
||||||
|
return {
|
||||||
|
source: 'salesforce',
|
||||||
|
sourceEntity: 'account',
|
||||||
|
sourceEntityId: salesforceId,
|
||||||
|
target: 'v2',
|
||||||
|
targetEntity: 'subscription',
|
||||||
|
targetEntityId: subscriptionId,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
extractAccountMappingsFromSubscription,
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ const analyticsEditingSessionsQueue = Queues.getQueue(
|
||||||
const analyticsUserPropertiesQueue = Queues.getQueue(
|
const analyticsUserPropertiesQueue = Queues.getQueue(
|
||||||
'analytics-user-properties'
|
'analytics-user-properties'
|
||||||
)
|
)
|
||||||
|
const analyticsAccountMappingQueue = Queues.getQueue(
|
||||||
|
'analytics-account-mapping'
|
||||||
|
)
|
||||||
|
|
||||||
const ONE_MINUTE_MS = 60 * 1000
|
const ONE_MINUTE_MS = 60 * 1000
|
||||||
|
|
||||||
|
@ -143,6 +146,55 @@ function setUserPropertyForSessionInBackground(session, property, value) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register mapping between two accounts.
|
||||||
|
*
|
||||||
|
* @param {object} payload - The event payload to send to Analytics
|
||||||
|
* @param {string} payload.source - The type of account linked from
|
||||||
|
* @param {string} payload.sourceId - The ID of the account linked from
|
||||||
|
* @param {string} payload.target - The type of account linked to
|
||||||
|
* @param {string} payload.targetId - The ID of the account linked to
|
||||||
|
* @param {Date} payload.createdAt - The date the mapping was created
|
||||||
|
* @property
|
||||||
|
*/
|
||||||
|
function registerAccountMapping({
|
||||||
|
source,
|
||||||
|
sourceEntity,
|
||||||
|
sourceEntityId,
|
||||||
|
target,
|
||||||
|
targetEntity,
|
||||||
|
targetEntityId,
|
||||||
|
createdAt,
|
||||||
|
}) {
|
||||||
|
Metrics.analyticsQueue.inc({
|
||||||
|
status: 'adding',
|
||||||
|
event_type: 'account-mapping',
|
||||||
|
})
|
||||||
|
|
||||||
|
analyticsAccountMappingQueue
|
||||||
|
.add('account-mapping', {
|
||||||
|
source,
|
||||||
|
sourceEntity,
|
||||||
|
sourceEntityId,
|
||||||
|
target,
|
||||||
|
targetEntity,
|
||||||
|
targetEntityId,
|
||||||
|
createdAt: createdAt ?? new Date(),
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
Metrics.analyticsQueue.inc({
|
||||||
|
status: 'added',
|
||||||
|
event_type: 'account-mapping',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Metrics.analyticsQueue.inc({
|
||||||
|
status: 'error',
|
||||||
|
event_type: 'account-mapping',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function updateEditingSession(userId, projectId, countryCode, segmentation) {
|
function updateEditingSession(userId, projectId, countryCode, segmentation) {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return
|
return
|
||||||
|
@ -349,5 +401,6 @@ module.exports = {
|
||||||
setUserPropertyForAnalyticsId,
|
setUserPropertyForAnalyticsId,
|
||||||
updateEditingSession,
|
updateEditingSession,
|
||||||
getIdsFromSession,
|
getIdsFromSession,
|
||||||
|
registerAccountMapping,
|
||||||
analyticsIdMiddleware: expressify(analyticsIdMiddleware),
|
analyticsIdMiddleware: expressify(analyticsIdMiddleware),
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ const QUEUES_JOB_OPTIONS = {
|
||||||
'analytics-editing-sessions': {
|
'analytics-editing-sessions': {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
||||||
},
|
},
|
||||||
|
'analytics-account-mapping': {
|
||||||
|
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
||||||
|
},
|
||||||
'analytics-user-properties': {
|
'analytics-user-properties': {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
||||||
},
|
},
|
||||||
|
@ -38,6 +41,7 @@ const QUEUES_JOB_OPTIONS = {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
},
|
},
|
||||||
|
|
||||||
'group-sso-reminder': {
|
'group-sso-reminder': {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
|
@ -54,6 +58,7 @@ const QUEUE_OPTIONS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const ANALYTICS_QUEUES = [
|
const ANALYTICS_QUEUES = [
|
||||||
|
'analytics-account-mapping',
|
||||||
'analytics-events',
|
'analytics-events',
|
||||||
'analytics-editing-sessions',
|
'analytics-editing-sessions',
|
||||||
'analytics-user-properties',
|
'analytics-user-properties',
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
import path from 'node:path'
|
||||||
|
import esmock from 'esmock'
|
||||||
|
import { expect } from 'chai'
|
||||||
|
import mongodb from 'mongodb-legacy'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const { ObjectId } = mongodb
|
||||||
|
|
||||||
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
||||||
|
|
||||||
|
const MODULE_PATH = path.join(
|
||||||
|
__dirname,
|
||||||
|
'../../../../app/src/Features/Analytics/AccountMappingHelper'
|
||||||
|
)
|
||||||
|
|
||||||
|
describe('AccountMappingHelper', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
this.AccountMappingHelper = await esmock.strict(MODULE_PATH)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('extractAccountMappingsFromSubscription', function () {
|
||||||
|
describe('when the v1 id is the same in the updated subscription and the subscription', function () {
|
||||||
|
describe('when the salesforce id is the same in the updated subscription and the subscription', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.subscription = {
|
||||||
|
id: new ObjectId('abc123abc123abc123abc123'),
|
||||||
|
salesforce_id: 'def456def456def456',
|
||||||
|
}
|
||||||
|
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||||
|
this.result =
|
||||||
|
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||||
|
this.subscription,
|
||||||
|
this.updatedSubscription
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an empty array', function () {
|
||||||
|
expect(this.result).to.be.an('array')
|
||||||
|
expect(this.result).to.have.length(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.subscription = {
|
||||||
|
id: new ObjectId('abc123abc123abc123abc123'),
|
||||||
|
salesforce_id: 'def456def456def456',
|
||||||
|
}
|
||||||
|
this.updatedSubscription = { salesforce_id: 'ghi789ghi789ghi789' }
|
||||||
|
this.result =
|
||||||
|
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||||
|
this.subscription,
|
||||||
|
this.updatedSubscription
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an array with a single item', function () {
|
||||||
|
expect(this.result).to.be.an('array')
|
||||||
|
expect(this.result).to.have.length(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "account" as sourceEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'sourceEntityId',
|
||||||
|
this.updatedSubscription.salesforce_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "subscription" as targetEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntity',
|
||||||
|
'subscription'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the subscriptionId as targetEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntityId',
|
||||||
|
this.subscription.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('when the update subscription has a salesforce id and the subscription has no salesforce_id', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.subscription = { id: new ObjectId('abc123abc123abc123abc123') }
|
||||||
|
this.updatedSubscription = { salesforce_id: 'def456def456def456' }
|
||||||
|
this.result =
|
||||||
|
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||||
|
this.subscription,
|
||||||
|
this.updatedSubscription
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an array with a single item', function () {
|
||||||
|
expect(this.result).to.be.an('array')
|
||||||
|
expect(this.result).to.have.length(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "account" as sourceEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'account')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the salesforceId from the updated subscription as sourceEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'sourceEntityId',
|
||||||
|
this.updatedSubscription.salesforce_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "subscription" as targetEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntity',
|
||||||
|
'subscription'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the subscriptionId as targetEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntityId',
|
||||||
|
this.subscription.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when the v1 id has changed between the subscription and the updated subscription', function () {
|
||||||
|
describe('when the salesforce id has not changed between the subscription and the updated subscription', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.subscription = {
|
||||||
|
id: new ObjectId('abc123abc123abc123abc123'),
|
||||||
|
v1_id: '1',
|
||||||
|
salesforce_id: '',
|
||||||
|
}
|
||||||
|
this.updatedSubscription = { v1_id: '2', salesforce_id: '' }
|
||||||
|
this.result =
|
||||||
|
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||||
|
this.subscription,
|
||||||
|
this.updatedSubscription
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an array with a single item', function () {
|
||||||
|
expect(this.result).to.be.an('array')
|
||||||
|
expect(this.result).to.have.length(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "university" as the sourceEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty('sourceEntity', 'university')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the v1_id from the updated subscription as the sourceEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'sourceEntityId',
|
||||||
|
this.updatedSubscription.v1_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses "subscription" as the targetEntity', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntity',
|
||||||
|
'subscription'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the subscription id as the targetEntityId', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntityId',
|
||||||
|
this.subscription.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('when the salesforce id has changed between the subscription and the updated subscription', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.subscription = {
|
||||||
|
id: new ObjectId('abc123abc123abc123abc123'),
|
||||||
|
v1_id: '',
|
||||||
|
salesforce_id: 'def456def456def456',
|
||||||
|
}
|
||||||
|
this.updatedSubscription = {
|
||||||
|
v1_id: '2',
|
||||||
|
salesforce_id: '',
|
||||||
|
}
|
||||||
|
this.result =
|
||||||
|
this.AccountMappingHelper.extractAccountMappingsFromSubscription(
|
||||||
|
this.subscription,
|
||||||
|
this.updatedSubscription
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns an array with two items', function () {
|
||||||
|
expect(this.result).to.be.an('array')
|
||||||
|
expect(this.result).to.have.length(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the salesforce_id from the updated subscription as the sourceEntityId for the first item', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'sourceEntityId',
|
||||||
|
this.updatedSubscription.salesforce_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the subscription id as the targetEntityId for the first item', function () {
|
||||||
|
expect(this.result[0]).to.haveOwnProperty(
|
||||||
|
'targetEntityId',
|
||||||
|
this.subscription.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the v1_id from the updated subscription as the sourceEntityId for the second item', function () {
|
||||||
|
expect(this.result[1]).to.haveOwnProperty(
|
||||||
|
'sourceEntityId',
|
||||||
|
this.updatedSubscription.v1_id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('uses the subscription id as the targetEntityId for the second item', function () {
|
||||||
|
expect(this.result[1]).to.haveOwnProperty(
|
||||||
|
'targetEntityId',
|
||||||
|
this.subscription.id
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
|
@ -34,6 +34,10 @@ describe('AnalyticsManager', function () {
|
||||||
add: sinon.stub().resolves(),
|
add: sinon.stub().resolves(),
|
||||||
process: sinon.stub().resolves(),
|
process: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
|
this.analyticsAccountMappingQueue = {
|
||||||
|
add: sinon.stub().resolves(),
|
||||||
|
process: sinon.stub().resolves(),
|
||||||
|
}
|
||||||
const self = this
|
const self = this
|
||||||
this.Queues = {
|
this.Queues = {
|
||||||
getQueue: queueName => {
|
getQueue: queueName => {
|
||||||
|
@ -46,6 +50,8 @@ describe('AnalyticsManager', function () {
|
||||||
return self.onboardingEmailsQueue
|
return self.onboardingEmailsQueue
|
||||||
case 'analytics-user-properties':
|
case 'analytics-user-properties':
|
||||||
return self.analyticsUserPropertiesQueue
|
return self.analyticsUserPropertiesQueue
|
||||||
|
case 'analytics-account-mapping':
|
||||||
|
return self.analyticsAccountMappingQueue
|
||||||
default:
|
default:
|
||||||
throw new Error('Unexpected queue name')
|
throw new Error('Unexpected queue name')
|
||||||
}
|
}
|
||||||
|
@ -278,6 +284,24 @@ describe('AnalyticsManager', function () {
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('account mapping', async function () {
|
||||||
|
const message = {
|
||||||
|
source: 'salesforce',
|
||||||
|
sourceEntity: 'account',
|
||||||
|
sourceEntityId: 'abc123abc123abc123',
|
||||||
|
target: 'v1',
|
||||||
|
targetEntity: 'university',
|
||||||
|
targetEntityId: 1,
|
||||||
|
createdAt: '2021-01-01T00:00:00Z',
|
||||||
|
}
|
||||||
|
await this.AnalyticsManager.registerAccountMapping(message)
|
||||||
|
sinon.assert.calledWithMatch(
|
||||||
|
this.analyticsAccountMappingQueue.add,
|
||||||
|
'account-mapping',
|
||||||
|
message
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('AnalyticsIdMiddleware', function () {
|
describe('AnalyticsIdMiddleware', function () {
|
||||||
|
@ -299,6 +323,8 @@ describe('AnalyticsManager', function () {
|
||||||
return self.onboardingEmailsQueue
|
return self.onboardingEmailsQueue
|
||||||
case 'analytics-user-properties':
|
case 'analytics-user-properties':
|
||||||
return self.analyticsUserPropertiesQueue
|
return self.analyticsUserPropertiesQueue
|
||||||
|
case 'analytics-account-mapping':
|
||||||
|
return self.analyticsAccountMappingQueue
|
||||||
default:
|
default:
|
||||||
throw new Error('Unexpected queue name')
|
throw new Error('Unexpected queue name')
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue