mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[web] avoid UserAnalyticsIdCache (#33121)
* [web] always provide an analyticsId when registering new users * [web] add recordEventForMongoUser and use it during login * [web] use recordEventForMongoUser/recordEventForSession where possible * [web] use session cache for analyticsId when refreshing features * [web] add setUserPropertyForMongoUser and use it where possible * [web] use setUserPropertyForSessionInBackground where possible * [web] use mongo user for analyticsId when setting utm user properties * [web] admin-panel: user updated user when sending events * [web] enforce labsProgram being populated on user object Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com> * [web] revert FeaturesUpdater.refreshFeatures change * [web] labs: move session maintenance into handler * [web] analyticsMiddleware: ensure that all users have labsProgram field * [web] migrate a few more events to recordEventForSession * [migrations] mark back-fill of db.users.analyticsId as required * [web] setUserPropertyForSessionInBackground: throw early --------- Co-authored-by: Andrew Rumble <andrew.rumble@overleaf.com> GitOrigin-RevId: 19c26bc36abd8c11f0a8f458d25abfb7cbda0bb7
This commit is contained in:
committed by
Copybot
co-authored by
Andrew Rumble
parent
7035a2e025
commit
6323fddbd8
@@ -82,7 +82,7 @@ async function recordEventForUser(userId, event, segmentation) {
|
|||||||
userId,
|
userId,
|
||||||
event,
|
event,
|
||||||
segmentation,
|
segmentation,
|
||||||
isLabsUser: Boolean(labsProgram),
|
isLabsUser: labsProgram,
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -97,6 +97,31 @@ function recordEventForUserInBackground(userId, event, segmentation) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function recordEventForMongoUser(user, event, segmentation) {
|
||||||
|
const { userId, analyticsId, labsProgram } = _getIdsFromMongoUser(user)
|
||||||
|
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_recordEvent({
|
||||||
|
analyticsId,
|
||||||
|
userId,
|
||||||
|
event,
|
||||||
|
segmentation,
|
||||||
|
isLabsUser: labsProgram,
|
||||||
|
isLoggedIn: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function recordEventForMongoUserInBackground(user, event, segmentation) {
|
||||||
|
const { userId } = _getIdsFromMongoUser(user) // throw before going into the background.
|
||||||
|
recordEventForMongoUser(user, event, segmentation).catch(err => {
|
||||||
|
logger.warn(
|
||||||
|
{ err, userId, event, segmentation },
|
||||||
|
'failed to record event for user'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
function recordEventForSession(session, event, segmentation) {
|
function recordEventForSession(session, event, segmentation) {
|
||||||
const { analyticsId, userId } = getIdsFromSession(session)
|
const { analyticsId, userId } = getIdsFromSession(session)
|
||||||
if (!analyticsId) {
|
if (!analyticsId) {
|
||||||
@@ -142,7 +167,7 @@ async function setUserPropertyForUser(userId, propertyName, propertyValue) {
|
|||||||
if (analyticsId) {
|
if (analyticsId) {
|
||||||
await _setUserProperty({
|
await _setUserProperty({
|
||||||
analyticsId,
|
analyticsId,
|
||||||
isLabsUser: Boolean(labsProgram),
|
isLabsUser: labsProgram,
|
||||||
propertyName,
|
propertyName,
|
||||||
propertyValue,
|
propertyValue,
|
||||||
})
|
})
|
||||||
@@ -158,6 +183,41 @@ function setUserPropertyForUserInBackground(userId, property, value) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
|
||||||
|
* @param {string} propertyName
|
||||||
|
* @param {any} propertyValue
|
||||||
|
* @return {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function setUserPropertyForMongoUser(user, propertyName, propertyValue) {
|
||||||
|
const { userId, analyticsId, labsProgram } = _getIdsFromMongoUser(user)
|
||||||
|
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_checkPropertyValue(propertyValue)
|
||||||
|
await _setUserProperty({
|
||||||
|
analyticsId,
|
||||||
|
isLabsUser: labsProgram,
|
||||||
|
propertyName,
|
||||||
|
propertyValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
|
||||||
|
* @param {string} property
|
||||||
|
* @param {any} value
|
||||||
|
*/
|
||||||
|
function setUserPropertyForMongoUserInBackground(user, property, value) {
|
||||||
|
const { userId } = _getIdsFromMongoUser(user) // throw before going into the background.
|
||||||
|
setUserPropertyForMongoUser(user, property, value).catch(err => {
|
||||||
|
logger.warn(
|
||||||
|
{ err, userId, property, value },
|
||||||
|
'failed to set user property for user'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function setUserPropertyForAnalyticsId(
|
async function setUserPropertyForAnalyticsId(
|
||||||
analyticsId,
|
analyticsId,
|
||||||
propertyName,
|
propertyName,
|
||||||
@@ -192,8 +252,8 @@ async function setUserPropertyForSession(session, propertyName, propertyValue) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setUserPropertyForSessionInBackground(session, property, value) {
|
function setUserPropertyForSessionInBackground(session, property, value) {
|
||||||
|
const { analyticsId, userId } = getIdsFromSession(session) // throw before going into the background.
|
||||||
setUserPropertyForSession(session, property, value).catch(err => {
|
setUserPropertyForSession(session, property, value).catch(err => {
|
||||||
const { analyticsId, userId } = getIdsFromSession(session)
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ err, analyticsId, userId, property, value },
|
{ err, analyticsId, userId, property, value },
|
||||||
'failed to set user property for session'
|
'failed to set user property for session'
|
||||||
@@ -452,6 +512,26 @@ function _isAnalyticsDisabled() {
|
|||||||
return !(Settings.analytics && Settings.analytics.enabled)
|
return !(Settings.analytics && Settings.analytics.enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{_id: ObjectId, analyticsId: string, labsProgram: boolean}} user
|
||||||
|
* @return {{userId: string, analyticsId: string, labsProgram: boolean}}
|
||||||
|
*/
|
||||||
|
function _getIdsFromMongoUser(user) {
|
||||||
|
const userId = user?._id?.toString()
|
||||||
|
if (!userId) {
|
||||||
|
throw new Error('bug: include db.users._id in projection')
|
||||||
|
}
|
||||||
|
const analyticsId = user?.analyticsId
|
||||||
|
if (!analyticsId) {
|
||||||
|
throw new Error('bug: include db.users.analyticsId in projection')
|
||||||
|
}
|
||||||
|
const labsProgram = user?.labsProgram
|
||||||
|
if (typeof labsProgram !== 'boolean') {
|
||||||
|
throw new Error('bug: include db.users.labsProgram in projection')
|
||||||
|
}
|
||||||
|
return { userId, analyticsId, labsProgram }
|
||||||
|
}
|
||||||
|
|
||||||
function _checkPropertyValue(propertyValue) {
|
function _checkPropertyValue(propertyValue) {
|
||||||
if (propertyValue === undefined) {
|
if (propertyValue === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -498,13 +578,15 @@ async function analyticsIdMiddleware(req, res, next) {
|
|||||||
if (sessionUser) {
|
if (sessionUser) {
|
||||||
// For old sessions, session.analyticsId is the anon id immediately after login. Do not use it!
|
// For old sessions, session.analyticsId is the anon id immediately after login. Do not use it!
|
||||||
session.analyticsId = sessionUser.analyticsId
|
session.analyticsId = sessionUser.analyticsId
|
||||||
if (!session.analyticsId) {
|
if (!session.analyticsId || typeof sessionUser.labsProgram !== 'boolean') {
|
||||||
session.analyticsId = sessionUser.analyticsId =
|
const { analyticsId, labsProgram } =
|
||||||
await UserAnalyticsDataCache.getAnalyticsId(
|
await UserAnalyticsDataCache.getAnalyticsData(
|
||||||
sessionUser._id,
|
sessionUser._id,
|
||||||
// Do not drill down further, this middleware is on all endpoints.
|
// Do not drill down further, this middleware is on all endpoints.
|
||||||
'analyticsIdMiddleware'
|
'analyticsIdMiddleware'
|
||||||
)
|
)
|
||||||
|
session.analyticsId = sessionUser.analyticsId = analyticsId
|
||||||
|
sessionUser.labsProgram = labsProgram
|
||||||
}
|
}
|
||||||
} else if (!session.analyticsId) {
|
} else if (!session.analyticsId) {
|
||||||
// generate an `analyticsId` if needed
|
// generate an `analyticsId` if needed
|
||||||
@@ -521,9 +603,13 @@ export default {
|
|||||||
recordEventForSession,
|
recordEventForSession,
|
||||||
recordEventForUser,
|
recordEventForUser,
|
||||||
recordEventForUserInBackground,
|
recordEventForUserInBackground,
|
||||||
|
recordEventForMongoUser,
|
||||||
|
recordEventForMongoUserInBackground,
|
||||||
emitPackageUsage,
|
emitPackageUsage,
|
||||||
setUserPropertyForUser,
|
setUserPropertyForUser,
|
||||||
setUserPropertyForUserInBackground,
|
setUserPropertyForUserInBackground,
|
||||||
|
setUserPropertyForMongoUser,
|
||||||
|
setUserPropertyForMongoUserInBackground,
|
||||||
setUserPropertyForSession,
|
setUserPropertyForSession,
|
||||||
setUserPropertyForSessionInBackground,
|
setUserPropertyForSessionInBackground,
|
||||||
setUserPropertyForAnalyticsId,
|
setUserPropertyForAnalyticsId,
|
||||||
|
|||||||
@@ -25,32 +25,32 @@ function clearInbound(session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addUserProperties(userId, session) {
|
function addUserProperties(user, session) {
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.required_login_from_product_medium) {
|
if (session.required_login_from_product_medium) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-product-medium`,
|
`registered-from-product-medium`,
|
||||||
session.required_login_from_product_medium
|
session.required_login_from_product_medium
|
||||||
)
|
)
|
||||||
if (session.required_login_from_product_source) {
|
if (session.required_login_from_product_source) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-product-source`,
|
`registered-from-product-source`,
|
||||||
session.required_login_from_product_source
|
session.required_login_from_product_source
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (session.referal_id) {
|
} else if (session.referal_id) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-bonus-scheme`,
|
`registered-from-bonus-scheme`,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-product-medium`,
|
`registered-from-product-medium`,
|
||||||
'bonus-scheme'
|
'bonus-scheme'
|
||||||
)
|
)
|
||||||
@@ -58,16 +58,16 @@ function addUserProperties(userId, session) {
|
|||||||
|
|
||||||
if (session.inbound) {
|
if (session.inbound) {
|
||||||
if (session.inbound.referrer && session.inbound.referrer.medium) {
|
if (session.inbound.referrer && session.inbound.referrer.medium) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-referrer-medium`,
|
`registered-from-referrer-medium`,
|
||||||
`${session.inbound.referrer.medium
|
`${session.inbound.referrer.medium
|
||||||
.charAt(0)
|
.charAt(0)
|
||||||
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
|
.toUpperCase()}${session.inbound.referrer.medium.slice(1)}`
|
||||||
)
|
)
|
||||||
if (session.inbound.referrer.source) {
|
if (session.inbound.referrer.source) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-referrer-source`,
|
`registered-from-referrer-source`,
|
||||||
session.inbound.referrer.source
|
session.inbound.referrer.source
|
||||||
)
|
)
|
||||||
@@ -77,8 +77,8 @@ function addUserProperties(userId, session) {
|
|||||||
if (session.inbound.utm) {
|
if (session.inbound.utm) {
|
||||||
for (const utmKey of RequestHelper.REGISTRATION_UTM_KEYS) {
|
for (const utmKey of RequestHelper.REGISTRATION_UTM_KEYS) {
|
||||||
if (session.inbound.utm[utmKey]) {
|
if (session.inbound.utm[utmKey]) {
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
`registered-from-${utmKey.replace('_', '-')}`,
|
`registered-from-${utmKey.replace('_', '-')}`,
|
||||||
session.inbound.utm[utmKey]
|
session.inbound.utm[utmKey]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ function _loginAsyncHandlers(req, user, anonymousAnalyticsId, isNewUser) {
|
|||||||
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
|
LoginRateLimiter.recordSuccessfulLogin(user.email, () => {})
|
||||||
AuthenticationController._recordSuccessfulLogin(user._id, () => {})
|
AuthenticationController._recordSuccessfulLogin(user._id, () => {})
|
||||||
AuthenticationController.ipMatchCheck(req, user)
|
AuthenticationController.ipMatchCheck(req, user)
|
||||||
Analytics.recordEventForUserInBackground(user._id, 'user-logged-in', {
|
Analytics.recordEventForMongoUserInBackground(user, 'user-logged-in', {
|
||||||
source: req.session.saml
|
source: req.session.saml
|
||||||
? 'saml'
|
? 'saml'
|
||||||
: req.user_info?.auth_provider || 'email-password',
|
: req.user_info?.auth_provider || 'email-password',
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { expressify } from '@overleaf/promise-utils'
|
|||||||
|
|
||||||
async function optIn(req, res) {
|
async function optIn(req, res) {
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
await BetaProgramHandler.promises.optIn(userId)
|
await BetaProgramHandler.promises.optIn(req.session, userId)
|
||||||
try {
|
try {
|
||||||
await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
|
await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -22,7 +22,7 @@ async function optIn(req, res) {
|
|||||||
|
|
||||||
async function optOut(req, res) {
|
async function optOut(req, res) {
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
await BetaProgramHandler.promises.optOut(userId)
|
await BetaProgramHandler.promises.optOut(req.session, userId)
|
||||||
try {
|
try {
|
||||||
await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
|
await SplitTestSessionHandler.promises.sessionMaintenance(req, null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -3,23 +3,23 @@ import metrics from '@overleaf/metrics'
|
|||||||
import UserUpdater from '../User/UserUpdater.mjs'
|
import UserUpdater from '../User/UserUpdater.mjs'
|
||||||
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
|
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
|
||||||
|
|
||||||
async function optIn(userId) {
|
async function optIn(session, userId) {
|
||||||
await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } })
|
await UserUpdater.promises.updateUser(userId, { $set: { betaProgram: true } })
|
||||||
metrics.inc('beta-program.opt-in')
|
metrics.inc('beta-program.opt-in')
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||||
userId,
|
session,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function optOut(userId) {
|
async function optOut(session, userId) {
|
||||||
await UserUpdater.promises.updateUser(userId, {
|
await UserUpdater.promises.updateUser(userId, {
|
||||||
$set: { betaProgram: false },
|
$set: { betaProgram: false },
|
||||||
})
|
})
|
||||||
metrics.inc('beta-program.opt-out')
|
metrics.inc('beta-program.opt-out')
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||||
userId,
|
session,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -493,17 +493,13 @@ async function acceptInvite(req, res) {
|
|||||||
} else if (invite.privileges === PrivilegeLevels.READ_ONLY) {
|
} else if (invite.privileges === PrivilegeLevels.READ_ONLY) {
|
||||||
editMode = 'view'
|
editMode = 'view'
|
||||||
}
|
}
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(req.session, 'project-joined', {
|
||||||
currentUser._id,
|
projectId,
|
||||||
'project-joined',
|
ownerId: invite.sendingUserId, // only owner can invite others
|
||||||
{
|
mode: editMode,
|
||||||
projectId,
|
role: invite.privileges,
|
||||||
ownerId: invite.sendingUserId, // only owner can invite others
|
source: urlToken ? 'email-invite' : 'sharing-link',
|
||||||
mode: editMode,
|
})
|
||||||
role: invite.privileges,
|
|
||||||
source: urlToken ? 'email-invite' : 'sharing-link',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (req.xhr) {
|
if (req.xhr) {
|
||||||
res.sendStatus(204) // Done async via project page notification
|
res.sendStatus(204) // Done async via project page notification
|
||||||
|
|||||||
@@ -98,14 +98,14 @@ async function exportProjectConversion(req, res) {
|
|||||||
type,
|
type,
|
||||||
{ compileFromHistory, rootResourcePath }
|
{ compileFromHistory, rootResourcePath }
|
||||||
)
|
)
|
||||||
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
|
AnalyticsManager.recordEventForSession(req.session, 'convert-format', {
|
||||||
sourceFormat: 'latex',
|
sourceFormat: 'latex',
|
||||||
targetFormat: type,
|
targetFormat: type,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
operation: 'export',
|
operation: 'export',
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
|
AnalyticsManager.recordEventForSession(req.session, 'convert-format', {
|
||||||
sourceFormat: 'latex',
|
sourceFormat: 'latex',
|
||||||
targetFormat: type,
|
targetFormat: type,
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
|
|||||||
@@ -82,13 +82,9 @@ async function createLinkedFile(req, res, next) {
|
|||||||
userId
|
userId
|
||||||
)
|
)
|
||||||
if (name.endsWith('.bib')) {
|
if (name.endsWith('.bib')) {
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(req.session, 'linked-bib-file', {
|
||||||
userId,
|
integration: provider,
|
||||||
'linked-bib-file',
|
})
|
||||||
{
|
|
||||||
integration: provider,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return res.json({ new_file_id: newFileId })
|
return res.json({ new_file_id: newFileId })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -756,8 +756,8 @@ const _ProjectController = {
|
|||||||
planLimit,
|
planLimit,
|
||||||
exceedAtLimit,
|
exceedAtLimit,
|
||||||
}
|
}
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'project-opened',
|
'project-opened',
|
||||||
projectOpenedSegmentation
|
projectOpenedSegmentation
|
||||||
)
|
)
|
||||||
@@ -843,8 +843,8 @@ const _ProjectController = {
|
|||||||
userIsMemberOfGroupSubscription
|
userIsMemberOfGroupSubscription
|
||||||
)
|
)
|
||||||
|
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||||
userId,
|
req.session,
|
||||||
'customer-io-integration',
|
'customer-io-integration',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -273,8 +273,8 @@ async function projectListPage(req, res, next) {
|
|||||||
|
|
||||||
customerIoEnabled = true
|
customerIoEnabled = true
|
||||||
|
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForSessionInBackground(
|
||||||
userId,
|
req.session,
|
||||||
'customer-io-integration',
|
'customer-io-integration',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -39,10 +39,14 @@ function featuresEpochIsCurrent(user) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh features for the given user
|
* Refresh features for the given user
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {string} reason
|
||||||
*/
|
*/
|
||||||
async function refreshFeatures(userId, reason) {
|
async function refreshFeatures(userId, reason) {
|
||||||
const user = await UserGetter.promises.getUser(userId, {
|
const user = await UserGetter.promises.getUser(userId, {
|
||||||
_id: 1,
|
_id: 1,
|
||||||
|
analyticsId: 1,
|
||||||
|
labsProgram: 1,
|
||||||
features: 1,
|
features: 1,
|
||||||
email: 1,
|
email: 1,
|
||||||
})
|
})
|
||||||
@@ -51,8 +55,8 @@ async function refreshFeatures(userId, reason) {
|
|||||||
logger.debug({ userId, features, reason }, 'updating user features')
|
logger.debug({ userId, features, reason }, 'updating user features')
|
||||||
|
|
||||||
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
const matchedFeatureSet = FeaturesHelper.getMatchedFeatureSet(features)
|
||||||
AnalyticsManager.setUserPropertyForUserInBackground(
|
AnalyticsManager.setUserPropertyForMongoUserInBackground(
|
||||||
userId,
|
user,
|
||||||
'feature-set',
|
'feature-set',
|
||||||
matchedFeatureSet
|
matchedFeatureSet
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -427,8 +427,8 @@ async function pauseSubscription(req, res, next) {
|
|||||||
const { subscription } =
|
const { subscription } =
|
||||||
await LimitationsManager.promises.userHasSubscription(user)
|
await LimitationsManager.promises.userHasSubscription(user)
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
user._id,
|
req.session,
|
||||||
'subscription-pause-scheduled',
|
'subscription-pause-scheduled',
|
||||||
{
|
{
|
||||||
pause_length: pauseCycles,
|
pause_length: pauseCycles,
|
||||||
|
|||||||
@@ -342,7 +342,7 @@ async function grantTokenAccessReadAndWrite(req, res, next) {
|
|||||||
...(pendingEditor && { pendingEditor: true }),
|
...(pendingEditor && { pendingEditor: true }),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
AnalyticsManager.recordEventForUserInBackground(userId, 'project-joined', {
|
AnalyticsManager.recordEventForSession(req.session, 'project-joined', {
|
||||||
role: pendingEditor
|
role: pendingEditor
|
||||||
? PrivilegeLevels.READ_ONLY
|
? PrivilegeLevels.READ_ONLY
|
||||||
: PrivilegeLevels.READ_AND_WRITE,
|
: PrivilegeLevels.READ_AND_WRITE,
|
||||||
@@ -452,7 +452,8 @@ async function grantTokenAccessReadOnly(req, res, next) {
|
|||||||
await TokenAccessHandler.promises.addReadOnlyUserToProject(
|
await TokenAccessHandler.promises.addReadOnlyUserToProject(
|
||||||
userId,
|
userId,
|
||||||
project._id,
|
project._id,
|
||||||
project.owner_ref
|
project.owner_ref,
|
||||||
|
req.session
|
||||||
)
|
)
|
||||||
|
|
||||||
return res.json({
|
return res.json({
|
||||||
|
|||||||
@@ -154,13 +154,13 @@ const TokenAccessHandler = {
|
|||||||
throw new Error('invalid token type')
|
throw new Error('invalid token type')
|
||||||
},
|
},
|
||||||
|
|
||||||
async addReadOnlyUserToProject(userId, projectId, ownerId) {
|
async addReadOnlyUserToProject(userId, projectId, ownerId, session) {
|
||||||
if (!Features.hasFeature('link-sharing')) {
|
if (!Features.hasFeature('link-sharing')) {
|
||||||
throw new Error('link sharing is disabled')
|
throw new Error('link sharing is disabled')
|
||||||
}
|
}
|
||||||
userId = new ObjectId(userId.toString())
|
userId = new ObjectId(userId.toString())
|
||||||
projectId = new ObjectId(projectId.toString())
|
projectId = new ObjectId(projectId.toString())
|
||||||
Analytics.recordEventForUserInBackground(userId, 'project-joined', {
|
Analytics.recordEventForSession(session, 'project-joined', {
|
||||||
role: PrivilegeLevels.READ_ONLY,
|
role: PrivilegeLevels.READ_ONLY,
|
||||||
projectId: projectId.toString(),
|
projectId: projectId.toString(),
|
||||||
source: 'link-sharing',
|
source: 'link-sharing',
|
||||||
|
|||||||
@@ -210,16 +210,12 @@ async function importDocument(req, res, next) {
|
|||||||
archivePath
|
archivePath
|
||||||
)
|
)
|
||||||
await ProjectOptionsHandler.promises.setCompiler(project._id, 'lualatex')
|
await ProjectOptionsHandler.promises.setCompiler(project._id, 'lualatex')
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(req.session, 'convert-format', {
|
||||||
userId,
|
sourceFormat: conversionType,
|
||||||
'convert-format',
|
targetFormat: 'latex',
|
||||||
{
|
status: 'success',
|
||||||
sourceFormat: conversionType,
|
operation: 'import',
|
||||||
targetFormat: 'latex',
|
})
|
||||||
status: 'success',
|
|
||||||
operation: 'import',
|
|
||||||
}
|
|
||||||
)
|
|
||||||
res.json({ success: true, project_id: project._id })
|
res.json({ success: true, project_id: project._id })
|
||||||
} finally {
|
} finally {
|
||||||
await fsPromises.unlink(archivePath).catch(unlinkErr => {
|
await fsPromises.unlink(archivePath).catch(unlinkErr => {
|
||||||
@@ -230,7 +226,7 @@ async function importDocument(req, res, next) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
|
AnalyticsManager.recordEventForSession(req.session, 'convert-format', {
|
||||||
sourceFormat: conversionType,
|
sourceFormat: conversionType,
|
||||||
targetFormat: 'latex',
|
targetFormat: 'latex',
|
||||||
status: 'failure',
|
status: 'failure',
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ async function recordRegistrationEvent(user) {
|
|||||||
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
|
if (user.thirdPartyIdentifiers && user.thirdPartyIdentifiers.length > 0) {
|
||||||
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
|
segmentation.provider = user.thirdPartyIdentifiers[0].providerId
|
||||||
}
|
}
|
||||||
Analytics.recordEventForUserInBackground(
|
Analytics.recordEventForMongoUserInBackground(
|
||||||
user._id,
|
user,
|
||||||
'user-registered',
|
'user-registered',
|
||||||
segmentation
|
segmentation
|
||||||
)
|
)
|
||||||
@@ -110,11 +110,11 @@ async function createNewUser(attributes, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await recordRegistrationEvent(user)
|
await recordRegistrationEvent(user)
|
||||||
await Analytics.setUserPropertyForUser(user._id, 'created-at', new Date())
|
await Analytics.setUserPropertyForMongoUser(user, 'created-at', new Date())
|
||||||
await Analytics.setUserPropertyForUser(user._id, 'user-id', user._id)
|
await Analytics.setUserPropertyForMongoUser(user, 'user-id', user._id)
|
||||||
if (attributes.analyticsId) {
|
if (attributes.analyticsId) {
|
||||||
await Analytics.setUserPropertyForUser(
|
await Analytics.setUserPropertyForMongoUser(
|
||||||
user._id,
|
user,
|
||||||
'analytics-id',
|
'analytics-id',
|
||||||
attributes.analyticsId
|
attributes.analyticsId
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -247,15 +247,11 @@ const _checkConfirmationCode =
|
|||||||
|
|
||||||
delete req.session[sessionKey]
|
delete req.session[sessionKey]
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(req.session, 'email-verified', {
|
||||||
user._id,
|
provider: 'email',
|
||||||
'email-verified',
|
verification_type: 'token',
|
||||||
{
|
isPrimary: user.email === emailToCheck,
|
||||||
provider: 'email',
|
})
|
||||||
verification_type: 'token',
|
|
||||||
isPrimary: user.email === emailToCheck,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const redirectUrl =
|
const redirectUrl =
|
||||||
AuthenticationController.getRedirectFromSession(req) || '/project'
|
AuthenticationController.getRedirectFromSession(req) || '/project'
|
||||||
@@ -391,16 +387,14 @@ const resendExistingSecondaryEmailConfirmationCode = _resendConfirmationCode(
|
|||||||
)
|
)
|
||||||
|
|
||||||
async function confirmSecondaryEmailPage(req, res) {
|
async function confirmSecondaryEmailPage(req, res) {
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
|
||||||
|
|
||||||
if (!req.session.pendingSecondaryEmail) {
|
if (!req.session.pendingSecondaryEmail) {
|
||||||
const redirectURL =
|
const redirectURL =
|
||||||
AuthenticationController.getRedirectFromSession(req) || '/project'
|
AuthenticationController.getRedirectFromSession(req) || '/project'
|
||||||
return res.redirect(redirectURL)
|
return res.redirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'confirm-secondary-email-page-displayed'
|
'confirm-secondary-email-page-displayed'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -421,8 +415,8 @@ async function addSecondaryEmailPage(req, res) {
|
|||||||
return res.redirect(redirectURL)
|
return res.redirect(redirectURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'add-secondary-email-page-displayed'
|
'add-secondary-email-page-displayed'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -442,8 +436,8 @@ async function primaryEmailCheckPage(req, res) {
|
|||||||
return res.redirect('/project')
|
return res.redirect('/project')
|
||||||
}
|
}
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'primary-email-check-page-displayed'
|
'primary-email-check-page-displayed'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -456,8 +450,8 @@ async function primaryEmailCheck(req, res) {
|
|||||||
$set: { lastPrimaryEmailCheck: new Date() },
|
$set: { lastPrimaryEmailCheck: new Date() },
|
||||||
})
|
})
|
||||||
|
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'primary-email-check-done'
|
'primary-email-check-done'
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -687,7 +681,7 @@ const UserEmailsController = {
|
|||||||
}
|
}
|
||||||
UserGetter.getUser(
|
UserGetter.getUser(
|
||||||
userData.userId,
|
userData.userId,
|
||||||
{ email: 1 },
|
{ _id: 1, email: 1, analyticsId: 1, labsProgram: 1 },
|
||||||
function (error, user) {
|
function (error, user) {
|
||||||
if (error) {
|
if (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -696,8 +690,8 @@ const UserEmailsController = {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const isPrimary = user?.email === userData.email
|
const isPrimary = user?.email === userData.email
|
||||||
AnalyticsManager.recordEventForUserInBackground(
|
AnalyticsManager.recordEventForMongoUserInBackground(
|
||||||
userData.userId,
|
user,
|
||||||
'email-verified',
|
'email-verified',
|
||||||
{
|
{
|
||||||
provider: 'email',
|
provider: 'email',
|
||||||
|
|||||||
@@ -14,24 +14,28 @@ async function schedulePostRegistrationAnalytics(user) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function postRegistrationAnalytics(userId) {
|
async function postRegistrationAnalytics(userId) {
|
||||||
const user = await UserGetter.promises.getUser({ _id: userId }, { email: 1 })
|
const user = await UserGetter.promises.getUser(
|
||||||
|
{ _id: userId },
|
||||||
|
{ email: 1, _id: 1, analyticsId: 1, labsProgram: 1 }
|
||||||
|
)
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await checkAffiliations(userId)
|
await checkAffiliations(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkAffiliations(userId) {
|
async function checkAffiliations(user) {
|
||||||
const affiliationsData =
|
const affiliationsData = await InstitutionsAPI.promises.getUserAffiliations(
|
||||||
await InstitutionsAPI.promises.getUserAffiliations(userId)
|
user._id.toString()
|
||||||
|
)
|
||||||
const hasCommonsAccountAffiliation = affiliationsData.some(
|
const hasCommonsAccountAffiliation = affiliationsData.some(
|
||||||
affiliationData =>
|
affiliationData =>
|
||||||
affiliationData.institution && affiliationData.institution.commonsAccount
|
affiliationData.institution && affiliationData.institution.commonsAccount
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hasCommonsAccountAffiliation) {
|
if (hasCommonsAccountAffiliation) {
|
||||||
await AnalyticsManager.setUserPropertyForUser(
|
await AnalyticsManager.setUserPropertyForMongoUser(
|
||||||
userId,
|
user,
|
||||||
'registered-from-commons-account',
|
'registered-from-commons-account',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -166,10 +166,11 @@ export default class TokenUsageRateLimiter {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} userId
|
* @param {string} userId
|
||||||
|
* @param {import('express').Request} req
|
||||||
* @param {import('express').Response} res
|
* @param {import('express').Response} res
|
||||||
* @param {{ auditLogTool?: string }} [options] - if `auditLogTool` is set, an `ai-quota-breach` audit log entry is written with `{ tool }` in the info payload when the request is blocked
|
* @param {{ auditLogTool?: string }} [options] - if `auditLogTool` is set, an `ai-quota-breach` audit log entry is written with `{ tool }` in the info payload when the request is blocked
|
||||||
*/
|
*/
|
||||||
async checkUsage(userId, res, options = {}) {
|
async checkUsage(userId, req, res, options = {}) {
|
||||||
const allowance = await this._getAllowance(userId)
|
const allowance = await this._getAllowance(userId)
|
||||||
const currentUsage = await this.getCurrentUsage(userId)
|
const currentUsage = await this.getCurrentUsage(userId)
|
||||||
const periodStart = currentUsage.periodStart ?? new Date()
|
const periodStart = currentUsage.periodStart ?? new Date()
|
||||||
@@ -190,8 +191,8 @@ export default class TokenUsageRateLimiter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await AnalyticsManager.recordEventForUser(
|
AnalyticsManager.recordEventForSession(
|
||||||
userId,
|
req.session,
|
||||||
'ai-token-usage-limit-exceeded'
|
'ai-token-usage-limit-exceeded'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ async function getIEEEUsers() {
|
|||||||
_id: 1,
|
_id: 1,
|
||||||
teamName: 1,
|
teamName: 1,
|
||||||
'member_details._id': 1,
|
'member_details._id': 1,
|
||||||
|
'member_details.analyticsId': 1,
|
||||||
|
'member_details.labsProgram': 1,
|
||||||
'member_details.email': 1,
|
'member_details.email': 1,
|
||||||
'member_details.emails.email': 1,
|
'member_details.emails.email': 1,
|
||||||
},
|
},
|
||||||
@@ -122,8 +124,8 @@ async function main() {
|
|||||||
index = index + 1
|
index = index + 1
|
||||||
|
|
||||||
if (COMMIT) {
|
if (COMMIT) {
|
||||||
await AnalyticsManager.setUserPropertyForUser(
|
await AnalyticsManager.setUserPropertyForMongoUser(
|
||||||
userDetails._id.toString(),
|
userDetails,
|
||||||
'ieee-retirement',
|
'ieee-retirement',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -438,6 +438,7 @@ describe('AnalyticsManager', function () {
|
|||||||
ctx.req.session.user = {
|
ctx.req.session.user = {
|
||||||
_id: ctx.userId,
|
_id: ctx.userId,
|
||||||
analyticsId: ctx.analyticsId,
|
analyticsId: ctx.analyticsId,
|
||||||
|
labsProgram: false,
|
||||||
}
|
}
|
||||||
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
||||||
assert.equal(ctx.analyticsId, ctx.req.session.analyticsId)
|
assert.equal(ctx.analyticsId, ctx.req.session.analyticsId)
|
||||||
@@ -445,10 +446,14 @@ describe('AnalyticsManager', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('sets session.analyticsId with a legacy user session without an analyticsId', async function (ctx) {
|
it('sets session.analyticsId with a legacy user session without an analyticsId', async function (ctx) {
|
||||||
ctx.UserAnalyticsDataCache.getAnalyticsId.resolves(ctx.userId)
|
ctx.UserAnalyticsDataCache.getAnalyticsData.resolves({
|
||||||
|
analyticsId: ctx.userId,
|
||||||
|
labsProgram: false,
|
||||||
|
})
|
||||||
ctx.req.session.user = {
|
ctx.req.session.user = {
|
||||||
_id: ctx.userId,
|
_id: ctx.userId,
|
||||||
analyticsId: undefined,
|
analyticsId: undefined,
|
||||||
|
labsProgram: false,
|
||||||
}
|
}
|
||||||
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
||||||
assert.equal(ctx.userId, ctx.req.session.analyticsId)
|
assert.equal(ctx.userId, ctx.req.session.analyticsId)
|
||||||
@@ -456,10 +461,14 @@ describe('AnalyticsManager', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('updates session.analyticsId with a legacy user session without an analyticsId if different', async function (ctx) {
|
it('updates session.analyticsId with a legacy user session without an analyticsId if different', async function (ctx) {
|
||||||
ctx.UserAnalyticsDataCache.getAnalyticsId.resolves(ctx.userId)
|
ctx.UserAnalyticsDataCache.getAnalyticsData.resolves({
|
||||||
|
analyticsId: ctx.userId,
|
||||||
|
labsProgram: false,
|
||||||
|
})
|
||||||
ctx.req.session.user = {
|
ctx.req.session.user = {
|
||||||
_id: ctx.userId,
|
_id: ctx.userId,
|
||||||
analyticsId: undefined,
|
analyticsId: undefined,
|
||||||
|
labsProgram: false,
|
||||||
}
|
}
|
||||||
ctx.req.analyticsId = 'foo'
|
ctx.req.analyticsId = 'foo'
|
||||||
ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
||||||
@@ -467,11 +476,30 @@ describe('AnalyticsManager', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('updates session.user.labsProgram when not defined', async function (ctx) {
|
||||||
|
ctx.UserAnalyticsDataCache.getAnalyticsData.resolves({
|
||||||
|
analyticsId: ctx.userId,
|
||||||
|
labsProgram: false,
|
||||||
|
})
|
||||||
|
ctx.req.session.user = {
|
||||||
|
_id: ctx.userId,
|
||||||
|
analyticsId: ctx.userId,
|
||||||
|
}
|
||||||
|
ctx.req.session.analyticsId = ctx.userId
|
||||||
|
ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
||||||
|
assert.equal(ctx.req.session.user.labsProgram, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('does not update session.analyticsId with a legacy user session without an analyticsId if same', async function (ctx) {
|
it('does not update session.analyticsId with a legacy user session without an analyticsId if same', async function (ctx) {
|
||||||
ctx.UserAnalyticsDataCache.getAnalyticsId.resolves(ctx.userId)
|
ctx.UserAnalyticsDataCache.getAnalyticsData.resolves({
|
||||||
|
analyticsId: ctx.userId,
|
||||||
|
labsProgram: false,
|
||||||
|
})
|
||||||
ctx.req.session.user = {
|
ctx.req.session.user = {
|
||||||
_id: ctx.userId,
|
_id: ctx.userId,
|
||||||
analyticsId: undefined,
|
analyticsId: undefined,
|
||||||
|
labsProgram: false,
|
||||||
}
|
}
|
||||||
ctx.req.analyticsId = ctx.userId
|
ctx.req.analyticsId = ctx.userId
|
||||||
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
await ctx.AnalyticsManager.analyticsIdMiddleware(ctx.req, ctx.res, () => {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ describe('AuthenticationController', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: (ctx.AnalyticsManager = {
|
default: (ctx.AnalyticsManager = {
|
||||||
recordEventForUserInBackground: sinon.stub(),
|
recordEventForMongoUserInBackground: sinon.stub(),
|
||||||
identifyUser: sinon.stub(),
|
identifyUser: sinon.stub(),
|
||||||
getIdsFromSession: sinon.stub().returns({ userId: ctx.user._id }),
|
getIdsFromSession: sinon.stub().returns({ userId: ctx.user._id }),
|
||||||
}),
|
}),
|
||||||
@@ -1646,8 +1646,8 @@ describe('AuthenticationController', function () {
|
|||||||
|
|
||||||
it('should track the login event', function (ctx) {
|
it('should track the login event', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground,
|
ctx.AnalyticsManager.recordEventForMongoUserInBackground,
|
||||||
ctx.user._id,
|
ctx.user,
|
||||||
'user-logged-in'
|
'user-logged-in'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ describe('BetaProgramHandler', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: (ctx.AnalyticsManager = {
|
default: (ctx.AnalyticsManager = {
|
||||||
setUserPropertyForUserInBackground: sinon.stub(),
|
setUserPropertyForSessionInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ctx.session = {}
|
||||||
ctx.handler = (await import(modulePath)).default
|
ctx.handler = (await import(modulePath)).default
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ describe('BetaProgramHandler', function () {
|
|||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.user.betaProgram = false
|
ctx.user.betaProgram = false
|
||||||
ctx.call = callback => {
|
ctx.call = callback => {
|
||||||
ctx.handler.optIn(ctx.user_id, callback)
|
ctx.handler.optIn(ctx.session, ctx.user_id, callback)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,8 +66,8 @@ describe('BetaProgramHandler', function () {
|
|||||||
ctx.call(err => {
|
ctx.call(err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.setUserPropertyForUserInBackground,
|
ctx.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||||
ctx.user_id,
|
ctx.session,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@@ -105,7 +106,7 @@ describe('BetaProgramHandler', function () {
|
|||||||
beforeEach(function (ctx) {
|
beforeEach(function (ctx) {
|
||||||
ctx.user.betaProgram = true
|
ctx.user.betaProgram = true
|
||||||
ctx.call = callback => {
|
ctx.call = callback => {
|
||||||
ctx.handler.optOut(ctx.user_id, callback)
|
ctx.handler.optOut(ctx.session, ctx.user_id, callback)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -124,8 +125,8 @@ describe('BetaProgramHandler', function () {
|
|||||||
ctx.call(err => {
|
ctx.call(err => {
|
||||||
expect(err).to.not.exist
|
expect(err).to.not.exist
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.setUserPropertyForUserInBackground,
|
ctx.AnalyticsManager.setUserPropertyForSessionInBackground,
|
||||||
ctx.user_id,
|
ctx.session,
|
||||||
'beta-program',
|
'beta-program',
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ describe('CollaboratorsInviteController', function () {
|
|||||||
getSessionUser: sinon.stub().returns(ctx.currentUser),
|
getSessionUser: sinon.stub().returns(ctx.currentUser),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.AnalyticsManger = { recordEventForUserInBackground: sinon.stub() }
|
ctx.AnalyticsManger = { recordEventForSession: sinon.stub() }
|
||||||
|
|
||||||
ctx.rateLimiter = {
|
ctx.rateLimiter = {
|
||||||
consume: sinon.stub().resolves(),
|
consume: sinon.stub().resolves(),
|
||||||
@@ -1756,8 +1756,8 @@ describe('CollaboratorsInviteController', function () {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.AnalyticsManger.recordEventForUserInBackground.should.have.been.calledWith(
|
ctx.AnalyticsManger.recordEventForSession.should.have.been.calledWith(
|
||||||
ctx.currentUser._id,
|
ctx.req.session,
|
||||||
'project-joined',
|
'project-joined',
|
||||||
sinon.match({ source: 'sharing-link' })
|
sinon.match({ source: 'sharing-link' })
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ describe('ProjectDownloadsController', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager.mjs',
|
'../../../../app/src/Features/Analytics/AnalyticsManager.mjs',
|
||||||
() => ({
|
() => ({
|
||||||
default: (ctx.AnalyticsManager = {
|
default: (ctx.AnalyticsManager = {
|
||||||
|
recordEventForSession: sinon.stub(),
|
||||||
recordEventForUserInBackground: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
@@ -369,8 +370,8 @@ describe('ProjectDownloadsController', function () {
|
|||||||
|
|
||||||
it('should record a successful convert-format analytics event', function (ctx) {
|
it('should record a successful convert-format analytics event', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground,
|
ctx.AnalyticsManager.recordEventForSession,
|
||||||
ctx.userId,
|
ctx.req.session,
|
||||||
'convert-format',
|
'convert-format',
|
||||||
{
|
{
|
||||||
sourceFormat: 'latex',
|
sourceFormat: 'latex',
|
||||||
|
|||||||
@@ -439,8 +439,10 @@ describe('ProjectController', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: {
|
default: {
|
||||||
|
recordEventForSession: () => {},
|
||||||
recordEventForUserInBackground: () => {},
|
recordEventForUserInBackground: () => {},
|
||||||
setUserPropertyForUserInBackground: () => {},
|
setUserPropertyForUserInBackground: () => {},
|
||||||
|
setUserPropertyForSessionInBackground: () => {},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ vi.mock('../../../../app/src/Features/Analytics/AnalyticsManager.mjs', () => {
|
|||||||
return {
|
return {
|
||||||
default: {
|
default: {
|
||||||
setUserPropertyForUserInBackground: () => {},
|
setUserPropertyForUserInBackground: () => {},
|
||||||
|
setUserPropertyForSessionInBackground: () => {},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ describe('FeaturesUpdater', function () {
|
|||||||
.resolves(ctx.user)
|
.resolves(ctx.user)
|
||||||
|
|
||||||
ctx.AnalyticsManager = {
|
ctx.AnalyticsManager = {
|
||||||
setUserPropertyForUserInBackground: sinon.stub(),
|
setUserPropertyForMongoUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
ctx.Modules = {
|
ctx.Modules = {
|
||||||
promises: { hooks: { fire: sinon.stub().resolves([]) } },
|
promises: { hooks: { fire: sinon.stub().resolves([]) } },
|
||||||
@@ -443,8 +443,8 @@ describe('FeaturesUpdater', function () {
|
|||||||
|
|
||||||
it('should send the corresponding feature set user property', function (ctx) {
|
it('should send the corresponding feature set user property', function (ctx) {
|
||||||
expect(
|
expect(
|
||||||
ctx.AnalyticsManager.setUserPropertyForUserInBackground
|
ctx.AnalyticsManager.setUserPropertyForMongoUserInBackground
|
||||||
).to.have.been.calledWith(ctx.user._id, 'feature-set', 'all')
|
).to.have.been.calledWith(ctx.user, 'feature-set', 'all')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should sync subscription properties to customer.io', function (ctx) {
|
it('should sync subscription properties to customer.io', function (ctx) {
|
||||||
@@ -919,8 +919,8 @@ describe('FeaturesUpdater', function () {
|
|||||||
|
|
||||||
it('should send mixed feature set user property', function (ctx) {
|
it('should send mixed feature set user property', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.setUserPropertyForUserInBackground,
|
ctx.AnalyticsManager.setUserPropertyForMongoUserInBackground,
|
||||||
ctx.user._id,
|
ctx.user,
|
||||||
'feature-set',
|
'feature-set',
|
||||||
'mixed'
|
'mixed'
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -329,8 +329,8 @@ describe('TokenAccessController', function () {
|
|||||||
|
|
||||||
it('records a project-joined event for the user', function (ctx) {
|
it('records a project-joined event for the user', function (ctx) {
|
||||||
expect(
|
expect(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground
|
ctx.AnalyticsManager.recordEventForSession
|
||||||
).to.have.been.calledWith(ctx.user._id, 'project-joined', {
|
).to.have.been.calledWith(ctx.req.session, 'project-joined', {
|
||||||
mode: 'edit',
|
mode: 'edit',
|
||||||
projectId: ctx.project._id.toString(),
|
projectId: ctx.project._id.toString(),
|
||||||
ownerId: ctx.project.owner_ref.toString(),
|
ownerId: ctx.project.owner_ref.toString(),
|
||||||
@@ -406,8 +406,8 @@ describe('TokenAccessController', function () {
|
|||||||
|
|
||||||
it('records a project-joined event for the user', function (ctx) {
|
it('records a project-joined event for the user', function (ctx) {
|
||||||
expect(
|
expect(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground
|
ctx.AnalyticsManager.recordEventForSession
|
||||||
).to.have.been.calledWith(ctx.user._id, 'project-joined', {
|
).to.have.been.calledWith(ctx.req.session, 'project-joined', {
|
||||||
mode: 'view',
|
mode: 'view',
|
||||||
projectId: ctx.project._id.toString(),
|
projectId: ctx.project._id.toString(),
|
||||||
pendingEditor: true,
|
pendingEditor: true,
|
||||||
@@ -863,7 +863,8 @@ describe('TokenAccessController', function () {
|
|||||||
).to.have.been.calledWith(
|
).to.have.been.calledWith(
|
||||||
ctx.user._id,
|
ctx.user._id,
|
||||||
ctx.project._id,
|
ctx.project._id,
|
||||||
ctx.project.owner_ref
|
ctx.project.owner_ref,
|
||||||
|
ctx.req.session
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ describe('TokenAccessHandler', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: (ctx.Analytics = {
|
default: (ctx.Analytics = {
|
||||||
recordEventForUserInBackground: sinon.stub(),
|
recordEventForSession: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -153,7 +153,8 @@ describe('TokenAccessHandler', function () {
|
|||||||
await ctx.TokenAccessHandler.promises.addReadOnlyUserToProject(
|
await ctx.TokenAccessHandler.promises.addReadOnlyUserToProject(
|
||||||
ctx.userId,
|
ctx.userId,
|
||||||
ctx.projectId,
|
ctx.projectId,
|
||||||
ctx.project.owner_ref
|
ctx.project.owner_ref,
|
||||||
|
ctx.req.session
|
||||||
)
|
)
|
||||||
expect(ctx.Project.updateOne.callCount).to.equal(1)
|
expect(ctx.Project.updateOne.callCount).to.equal(1)
|
||||||
expect(
|
expect(
|
||||||
@@ -165,8 +166,8 @@ describe('TokenAccessHandler', function () {
|
|||||||
'tokenAccessReadOnly_refs'
|
'tokenAccessReadOnly_refs'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.Analytics.recordEventForUserInBackground,
|
ctx.Analytics.recordEventForSession,
|
||||||
ctx.userId,
|
ctx.req.session,
|
||||||
'project-joined',
|
'project-joined',
|
||||||
{
|
{
|
||||||
mode: 'view',
|
mode: 'view',
|
||||||
@@ -516,7 +517,8 @@ describe('TokenAccessHandler', function () {
|
|||||||
ctx.TokenAccessHandler.promises.addReadOnlyUserToProject(
|
ctx.TokenAccessHandler.promises.addReadOnlyUserToProject(
|
||||||
ctx.userId,
|
ctx.userId,
|
||||||
ctx.projectId,
|
ctx.projectId,
|
||||||
ctx.project.owner_ref
|
ctx.project.owner_ref,
|
||||||
|
ctx.req.session
|
||||||
)
|
)
|
||||||
).to.be.rejectedWith('link sharing is disabled')
|
).to.be.rejectedWith('link sharing is disabled')
|
||||||
expect(ctx.Project.updateOne.callCount).to.equal(0)
|
expect(ctx.Project.updateOne.callCount).to.equal(0)
|
||||||
|
|||||||
@@ -120,6 +120,7 @@ describe('ProjectUploadController', function () {
|
|||||||
)
|
)
|
||||||
|
|
||||||
ctx.AnalyticsManager = {
|
ctx.AnalyticsManager = {
|
||||||
|
recordEventForSession: sinon.stub(),
|
||||||
recordEventForUserInBackground: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
}
|
}
|
||||||
vi.doMock(
|
vi.doMock(
|
||||||
@@ -545,8 +546,8 @@ describe('ProjectUploadController', function () {
|
|||||||
|
|
||||||
it('should record a successful convert-format analytics event', function (ctx) {
|
it('should record a successful convert-format analytics event', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground,
|
ctx.AnalyticsManager.recordEventForSession,
|
||||||
ctx.user_id,
|
ctx.req.session,
|
||||||
'convert-format',
|
'convert-format',
|
||||||
{
|
{
|
||||||
sourceFormat: 'docx',
|
sourceFormat: 'docx',
|
||||||
@@ -615,8 +616,8 @@ describe('ProjectUploadController', function () {
|
|||||||
|
|
||||||
it('should record a successful convert-format analytics event', function (ctx) {
|
it('should record a successful convert-format analytics event', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground,
|
ctx.AnalyticsManager.recordEventForSession,
|
||||||
ctx.user_id,
|
ctx.req.session,
|
||||||
'convert-format',
|
'convert-format',
|
||||||
{
|
{
|
||||||
sourceFormat: 'markdown',
|
sourceFormat: 'markdown',
|
||||||
@@ -687,8 +688,8 @@ describe('ProjectUploadController', function () {
|
|||||||
|
|
||||||
it('should record a failed convert-format analytics event', function (ctx) {
|
it('should record a failed convert-format analytics event', function (ctx) {
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground,
|
ctx.AnalyticsManager.recordEventForSession,
|
||||||
ctx.user_id,
|
ctx.req.session,
|
||||||
'convert-format',
|
'convert-format',
|
||||||
{
|
{
|
||||||
sourceFormat: 'docx',
|
sourceFormat: 'docx',
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const modulePath = '../../../../app/src/Features/User/UserCreator.mjs'
|
|||||||
describe('UserCreator', function () {
|
describe('UserCreator', function () {
|
||||||
beforeEach(async function (ctx) {
|
beforeEach(async function (ctx) {
|
||||||
const self = ctx
|
const self = ctx
|
||||||
ctx.user = { _id: '12390i', ace: {} }
|
ctx.user = { _id: '12390i', ace: {}, analyticsId: 'uuid' }
|
||||||
ctx.user.save = sinon.stub().resolves(self.user)
|
ctx.user.save = sinon.stub().resolves(self.user)
|
||||||
ctx.UserModel = class Project {
|
ctx.UserModel = class Project {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -64,8 +64,9 @@ describe('UserCreator', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: (ctx.Analytics = {
|
default: (ctx.Analytics = {
|
||||||
|
recordEventForMongoUserInBackground: sinon.stub(),
|
||||||
recordEventForUserInBackground: sinon.stub(),
|
recordEventForUserInBackground: sinon.stub(),
|
||||||
setUserPropertyForUser: sinon.stub(),
|
setUserPropertyForMongoUser: sinon.stub(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -320,13 +321,13 @@ describe('UserCreator', function () {
|
|||||||
)
|
)
|
||||||
assert.equal(user.email, ctx.email)
|
assert.equal(user.email, ctx.email)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.Analytics.recordEventForUserInBackground,
|
ctx.Analytics.recordEventForMongoUserInBackground,
|
||||||
user._id,
|
user,
|
||||||
'user-registered'
|
'user-registered'
|
||||||
)
|
)
|
||||||
sinon.assert.calledWith(
|
sinon.assert.calledWith(
|
||||||
ctx.Analytics.setUserPropertyForUser,
|
ctx.Analytics.setUserPropertyForMongoUser,
|
||||||
user._id,
|
user,
|
||||||
'created-at'
|
'created-at'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ describe('UserEmailsController', function () {
|
|||||||
}
|
}
|
||||||
ctx.HttpErrorHandler = { conflict: vi.fn() }
|
ctx.HttpErrorHandler = { conflict: vi.fn() }
|
||||||
ctx.AnalyticsManager = {
|
ctx.AnalyticsManager = {
|
||||||
recordEventForUserInBackground: vi.fn(),
|
recordEventForSession: vi.fn(),
|
||||||
|
recordEventForMongoUserInBackground: vi.fn(),
|
||||||
}
|
}
|
||||||
ctx.UserAuditLogHandler = {
|
ctx.UserAuditLogHandler = {
|
||||||
addEntry: vi.fn((userId, op, initiatorId, ip, info, callback) =>
|
addEntry: vi.fn((userId, op, initiatorId, ip, info, callback) =>
|
||||||
@@ -810,13 +811,15 @@ describe('UserEmailsController', function () {
|
|||||||
ctx.req,
|
ctx.req,
|
||||||
{ json: vi.fn() }
|
{ json: vi.fn() }
|
||||||
)
|
)
|
||||||
expect(
|
expect(ctx.AnalyticsManager.recordEventForSession).toHaveBeenCalledWith(
|
||||||
ctx.AnalyticsManager.recordEventForUserInBackground
|
ctx.req.session,
|
||||||
).toHaveBeenCalledWith(ctx.user._id, 'email-verified', {
|
'email-verified',
|
||||||
provider: 'email',
|
{
|
||||||
verification_type: 'token',
|
provider: 'email',
|
||||||
isPrimary: ctx.user.email === ctx.email,
|
verification_type: 'token',
|
||||||
})
|
isPrimary: ctx.user.email === ctx.email,
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('removes pendingExistingEmail from session', async function (ctx) {
|
it('removes pendingExistingEmail from session', async function (ctx) {
|
||||||
|
|||||||
@@ -15,16 +15,17 @@ describe('UserPostRegistrationAnalyticsManager', function () {
|
|||||||
getUser: sinon.stub().resolves(),
|
getUser: sinon.stub().resolves(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
ctx.fakeUser = { _id: ctx.fakeUserId, analyticsId: 'uuid' }
|
||||||
ctx.UserGetter.promises.getUser
|
ctx.UserGetter.promises.getUser
|
||||||
.withArgs({ _id: ctx.fakeUserId })
|
.withArgs({ _id: ctx.fakeUserId })
|
||||||
.resolves({ _id: ctx.fakeUserId })
|
.resolves(ctx.fakeUser)
|
||||||
ctx.InstitutionsAPI = {
|
ctx.InstitutionsAPI = {
|
||||||
promises: {
|
promises: {
|
||||||
getUserAffiliations: sinon.stub().resolves([]),
|
getUserAffiliations: sinon.stub().resolves([]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
ctx.AnalyticsManager = {
|
ctx.AnalyticsManager = {
|
||||||
setUserPropertyForUser: sinon.stub().resolves(),
|
setUserPropertyForMongoUser: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
|
|
||||||
vi.doMock('../../../../app/src/infrastructure/Queues', () => ({
|
vi.doMock('../../../../app/src/infrastructure/Queues', () => ({
|
||||||
@@ -77,7 +78,7 @@ describe('UserPostRegistrationAnalyticsManager', function () {
|
|||||||
)
|
)
|
||||||
expect(ctx.InstitutionsAPI.promises.getUserAffiliations).not.to.have.been
|
expect(ctx.InstitutionsAPI.promises.getUserAffiliations).not.to.have.been
|
||||||
.called
|
.called
|
||||||
expect(ctx.AnalyticsManager.setUserPropertyForUser).not.to.have.been
|
expect(ctx.AnalyticsManager.setUserPropertyForMongoUser).not.to.have.been
|
||||||
.called
|
.called
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -99,9 +100,9 @@ describe('UserPostRegistrationAnalyticsManager', function () {
|
|||||||
ctx.fakeUserId
|
ctx.fakeUserId
|
||||||
)
|
)
|
||||||
expect(
|
expect(
|
||||||
ctx.AnalyticsManager.setUserPropertyForUser
|
ctx.AnalyticsManager.setUserPropertyForMongoUser
|
||||||
).to.have.been.calledWith(
|
).to.have.been.calledWith(
|
||||||
ctx.fakeUserId,
|
ctx.fakeUser,
|
||||||
'registered-from-commons-account',
|
'registered-from-commons-account',
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
@@ -118,7 +119,7 @@ describe('UserPostRegistrationAnalyticsManager', function () {
|
|||||||
await ctx.UserPostRegistrationAnalyticsManager.postRegistrationAnalytics(
|
await ctx.UserPostRegistrationAnalyticsManager.postRegistrationAnalytics(
|
||||||
ctx.fakeUserId
|
ctx.fakeUserId
|
||||||
)
|
)
|
||||||
expect(ctx.AnalyticsManager.setUserPropertyForUser).not.to.have.been
|
expect(ctx.AnalyticsManager.setUserPropertyForMongoUser).not.to.have.been
|
||||||
.called
|
.called
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
() => ({
|
() => ({
|
||||||
default: {
|
default: {
|
||||||
recordEventForUser: sinon.stub(),
|
recordEventForSession: sinon.stub(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -164,6 +164,7 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
describe('with no data', function () {
|
describe('with no data', function () {
|
||||||
beforeEach(async function (ctx) {
|
beforeEach(async function (ctx) {
|
||||||
await UserFeatureUsage.deleteMany({}).exec()
|
await UserFeatureUsage.deleteMany({}).exec()
|
||||||
|
ctx.req = { session: {} }
|
||||||
ctx.res = {
|
ctx.res = {
|
||||||
set: sinon.stub(),
|
set: sinon.stub(),
|
||||||
headersSent: false,
|
headersSent: false,
|
||||||
@@ -172,12 +173,16 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
|
|
||||||
it('should not throw', async function (ctx) {
|
it('should not throw', async function (ctx) {
|
||||||
await expect(
|
await expect(
|
||||||
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.req, ctx.res)
|
||||||
).to.eventually.be.fulfilled
|
).to.eventually.be.fulfilled
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets rate limit headers', async function (ctx) {
|
it('sets rate limit headers', async function (ctx) {
|
||||||
await ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
await ctx.WorkbenchRateLimiter.checkUsage(
|
||||||
|
ctx.alphaUserId,
|
||||||
|
ctx.req,
|
||||||
|
ctx.res
|
||||||
|
)
|
||||||
expect(ctx.res.set).to.have.been.calledWith(
|
expect(ctx.res.set).to.have.been.calledWith(
|
||||||
'Token-RateLimit-Limit',
|
'Token-RateLimit-Limit',
|
||||||
'8000000'
|
'8000000'
|
||||||
@@ -197,6 +202,7 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
describe('with existing usage', function () {
|
describe('with existing usage', function () {
|
||||||
beforeEach(async function (ctx) {
|
beforeEach(async function (ctx) {
|
||||||
await UserFeatureUsage.deleteMany({}).exec()
|
await UserFeatureUsage.deleteMany({}).exec()
|
||||||
|
ctx.req = { session: {} }
|
||||||
ctx.res = {
|
ctx.res = {
|
||||||
set: sinon.stub(),
|
set: sinon.stub(),
|
||||||
headersSent: false,
|
headersSent: false,
|
||||||
@@ -215,12 +221,16 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
|
|
||||||
it('should not throw if under limit', async function (ctx) {
|
it('should not throw if under limit', async function (ctx) {
|
||||||
await expect(
|
await expect(
|
||||||
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.req, ctx.res)
|
||||||
).to.eventually.be.fulfilled
|
).to.eventually.be.fulfilled
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets rate limit headers', async function (ctx) {
|
it('sets rate limit headers', async function (ctx) {
|
||||||
await ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
await ctx.WorkbenchRateLimiter.checkUsage(
|
||||||
|
ctx.alphaUserId,
|
||||||
|
ctx.req,
|
||||||
|
ctx.res
|
||||||
|
)
|
||||||
expect(ctx.res.set).to.have.been.calledWith(
|
expect(ctx.res.set).to.have.been.calledWith(
|
||||||
'Token-RateLimit-Limit',
|
'Token-RateLimit-Limit',
|
||||||
'8000000'
|
'8000000'
|
||||||
@@ -243,7 +253,7 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
await usageRecord.save()
|
await usageRecord.save()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.req, ctx.res)
|
||||||
).to.eventually.be.rejectedWith(/rate limit exceeded/i)
|
).to.eventually.be.rejectedWith(/rate limit exceeded/i)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -269,12 +279,16 @@ describe('WorkbenchRateLimiter', function () {
|
|||||||
|
|
||||||
it('should not throw', async function (ctx) {
|
it('should not throw', async function (ctx) {
|
||||||
await expect(
|
await expect(
|
||||||
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.req, ctx.res)
|
||||||
).to.eventually.be.fulfilled
|
).to.eventually.be.fulfilled
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets rate limit headers', async function (ctx) {
|
it('sets rate limit headers', async function (ctx) {
|
||||||
await ctx.WorkbenchRateLimiter.checkUsage(ctx.alphaUserId, ctx.res)
|
await ctx.WorkbenchRateLimiter.checkUsage(
|
||||||
|
ctx.alphaUserId,
|
||||||
|
ctx.req,
|
||||||
|
ctx.res
|
||||||
|
)
|
||||||
expect(ctx.res.set).to.have.been.calledWith(
|
expect(ctx.res.set).to.have.been.calledWith(
|
||||||
'Token-RateLimit-Limit',
|
'Token-RateLimit-Limit',
|
||||||
'8000000'
|
'8000000'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
|
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
|
||||||
|
|
||||||
const tags = ['server-ce', 'server-pro', 'saas', 'nonblocking']
|
const tags = ['server-ce', 'server-pro', 'saas']
|
||||||
|
|
||||||
const migrate = async client => {
|
const migrate = async client => {
|
||||||
const { db } = client
|
const { db } = client
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { db } from './lib/mongodb.mjs'
|
import { db } from './lib/mongodb.mjs'
|
||||||
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
|
import { batchedUpdate } from '@overleaf/mongo-utils/batchedUpdate.js'
|
||||||
|
|
||||||
const tags = ['saas', 'server-ce', 'server-pro', 'nonblocking']
|
const tags = ['saas', 'server-ce', 'server-pro']
|
||||||
|
|
||||||
const migrate = async () => {
|
const migrate = async () => {
|
||||||
await batchedUpdate(db.users, { analyticsId: { $exists: false } }, [
|
await batchedUpdate(db.users, { analyticsId: { $exists: false } }, [
|
||||||
|
|||||||
Reference in New Issue
Block a user