mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #30366 from overleaf/mj-em-package-meta
[web] Add event for package usage GitOrigin-RevId: e994becf01e7e4c8642cd1815ffe05907a5fd63c
This commit is contained in:
@@ -28,6 +28,7 @@ const analyticsAccountMappingQueue = Queues.getQueue(
|
|||||||
'analytics-account-mapping'
|
'analytics-account-mapping'
|
||||||
)
|
)
|
||||||
const analyticsEmailChangeQueue = Queues.getQueue('analytics-email-change')
|
const analyticsEmailChangeQueue = Queues.getQueue('analytics-email-change')
|
||||||
|
const analyticsPackageUsageQueue = Queues.getQueue('analytics-package-usage')
|
||||||
|
|
||||||
const ONE_MINUTE_MS = 60 * 1000
|
const ONE_MINUTE_MS = 60 * 1000
|
||||||
|
|
||||||
@@ -97,6 +98,14 @@ function recordEventForSession(session, event, segmentation) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function emitPackageUsage(projectId, { documentClasses, packages }) {
|
||||||
|
analyticsPackageUsageQueue
|
||||||
|
.add('package-usage', { projectId, documentClasses, packages })
|
||||||
|
.catch(err => {
|
||||||
|
logger.warn({ err, projectId }, 'Failed to emit package usage')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function setUserPropertyForUser(userId, propertyName, propertyValue) {
|
async function setUserPropertyForUser(userId, propertyName, propertyValue) {
|
||||||
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
|
if (_isAnalyticsDisabled() || _isSmokeTestUser(userId)) {
|
||||||
return
|
return
|
||||||
@@ -454,6 +463,7 @@ export default {
|
|||||||
recordEventForSession,
|
recordEventForSession,
|
||||||
recordEventForUser,
|
recordEventForUser,
|
||||||
recordEventForUserInBackground,
|
recordEventForUserInBackground,
|
||||||
|
emitPackageUsage,
|
||||||
setUserPropertyForUser,
|
setUserPropertyForUser,
|
||||||
setUserPropertyForUserInBackground,
|
setUserPropertyForUserInBackground,
|
||||||
setUserPropertyForSession,
|
setUserPropertyForSession,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import EditorRealTimeController from '../Editor/EditorRealTimeController.mjs'
|
|||||||
import MetaHandler from './MetaHandler.mjs'
|
import MetaHandler from './MetaHandler.mjs'
|
||||||
import logger from '@overleaf/logger'
|
import logger from '@overleaf/logger'
|
||||||
import { expressify } from '@overleaf/promise-utils'
|
import { expressify } from '@overleaf/promise-utils'
|
||||||
|
import Analytics from '../Analytics/AnalyticsManager.mjs'
|
||||||
|
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
|
||||||
|
|
||||||
async function getMetadata(req, res) {
|
async function getMetadata(req, res) {
|
||||||
const { project_id: projectId } = req.params
|
const { project_id: projectId } = req.params
|
||||||
@@ -22,9 +24,37 @@ async function getMetadata(req, res) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
'emit-package-usage-statistics'
|
||||||
|
)
|
||||||
|
if (assignment.variant === 'enabled') {
|
||||||
|
sendAnalyticsEventForPackageUsage(projectId, projectMeta)
|
||||||
|
}
|
||||||
|
|
||||||
res.json({ projectId, projectMeta })
|
res.json({ projectId, projectMeta })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendAnalyticsEventForPackageUsage(projectId, projectMeta) {
|
||||||
|
const packagesSet = new Set()
|
||||||
|
const documentClassesSet = new Set()
|
||||||
|
for (const docMeta of Object.values(projectMeta)) {
|
||||||
|
if (docMeta.documentClass != null) {
|
||||||
|
documentClassesSet.add(docMeta.documentClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const packageName of docMeta.packageNames) {
|
||||||
|
if (packageName.match(/^[a-zA-Z0-9-_]+$/)) {
|
||||||
|
packagesSet.add(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const packages = Array.from(packagesSet)
|
||||||
|
const documentClasses = Array.from(documentClassesSet)
|
||||||
|
Analytics.emitPackageUsage(projectId, { documentClasses, packages })
|
||||||
|
}
|
||||||
|
|
||||||
async function broadcastMetadataForDoc(req, res) {
|
async function broadcastMetadataForDoc(req, res) {
|
||||||
const { project_id: projectId } = req.params
|
const { project_id: projectId } = req.params
|
||||||
const { doc_id: docId } = req.params
|
const { doc_id: docId } = req.params
|
||||||
|
|||||||
@@ -7,9 +7,16 @@ import { callbackify } from '@overleaf/promise-utils'
|
|||||||
* labels: string[]
|
* labels: string[]
|
||||||
* packages: Record<string, Record<string, any>>,
|
* packages: Record<string, Record<string, any>>,
|
||||||
* packageNames: string[],
|
* packageNames: string[],
|
||||||
|
* documentClass: string | null
|
||||||
* }} DocMeta
|
* }} DocMeta
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const LABEL_RE = /\\label{(.{0,80}?)}/g
|
||||||
|
const LABEL_OPTION_RE = /\blabel={?(.{0,80}?)[\s},\]]/g
|
||||||
|
const PACKAGE_RE = /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
||||||
|
const REQ_PACKAGE_RE = /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
||||||
|
const DOCUMENT_CLASS_RE = /^\\documentclass(?:\[.{0,80}?])?{(.{0,80}?)}/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string[]} lines
|
* @param {string[]} lines
|
||||||
* @return {Promise<DocMeta>}
|
* @return {Promise<DocMeta>}
|
||||||
@@ -20,31 +27,34 @@ async function extractMetaFromDoc(lines) {
|
|||||||
labels: [],
|
labels: [],
|
||||||
packages: {},
|
packages: {},
|
||||||
packageNames: [],
|
packageNames: [],
|
||||||
|
documentClass: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const labelRe = /\\label{(.{0,80}?)}/g
|
|
||||||
const labelOptionRe = /\blabel={?(.{0,80}?)[\s},\]]/g
|
|
||||||
const packageRe = /^\\usepackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
|
||||||
const reqPackageRe = /^\\RequirePackage(?:\[.{0,80}?])?{(.{0,80}?)}/g
|
|
||||||
|
|
||||||
for (const rawLine of lines) {
|
for (const rawLine of lines) {
|
||||||
const line = getNonCommentedContent(rawLine)
|
const line = getNonCommentedContent(rawLine)
|
||||||
|
|
||||||
for (const label of lineMatches(labelRe, line)) {
|
for (const label of lineMatches(LABEL_RE, line)) {
|
||||||
docMeta.labels.push(label)
|
docMeta.labels.push(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const label of lineMatches(labelOptionRe, line)) {
|
for (const label of lineMatches(LABEL_OPTION_RE, line)) {
|
||||||
docMeta.labels.push(label)
|
docMeta.labels.push(label)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pkg of lineMatches(packageRe, line, ',')) {
|
for (const pkg of lineMatches(PACKAGE_RE, line, ',')) {
|
||||||
docMeta.packageNames.push(pkg)
|
docMeta.packageNames.push(pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pkg of lineMatches(reqPackageRe, line, ',')) {
|
for (const pkg of lineMatches(REQ_PACKAGE_RE, line, ',')) {
|
||||||
docMeta.packageNames.push(pkg)
|
docMeta.packageNames.push(pkg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (docMeta.documentClass == null) {
|
||||||
|
const match = line.match(DOCUMENT_CLASS_RE)
|
||||||
|
if (match != null) {
|
||||||
|
docMeta.documentClass = match[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const packageName of docMeta.packageNames) {
|
for (const packageName of docMeta.packageNames) {
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ const QUEUES_JOB_OPTIONS = {
|
|||||||
'analytics-email-change': {
|
'analytics-email-change': {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
||||||
},
|
},
|
||||||
|
'analytics-package-usage': {
|
||||||
|
removeOnFail: MAX_FAILED_JOBS_RETAINED_ANALYTICS,
|
||||||
|
},
|
||||||
'emails-onboarding': {
|
'emails-onboarding': {
|
||||||
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
removeOnFail: MAX_FAILED_JOBS_RETAINED,
|
||||||
},
|
},
|
||||||
@@ -73,6 +76,7 @@ const ANALYTICS_QUEUES = [
|
|||||||
'analytics-email-change',
|
'analytics-email-change',
|
||||||
'analytics-events',
|
'analytics-events',
|
||||||
'analytics-editing-sessions',
|
'analytics-editing-sessions',
|
||||||
|
'analytics-package-usage',
|
||||||
'analytics-user-properties',
|
'analytics-user-properties',
|
||||||
'analytics-user-exports',
|
'analytics-user-exports',
|
||||||
'post-registration-analytics',
|
'post-registration-analytics',
|
||||||
|
|||||||
@@ -50,6 +50,10 @@ describe('AnalyticsManager', function () {
|
|||||||
add: sinon.stub().resolves(),
|
add: sinon.stub().resolves(),
|
||||||
process: sinon.stub().resolves(),
|
process: sinon.stub().resolves(),
|
||||||
}
|
}
|
||||||
|
ctx.analyticsPackageUsageQueue = {
|
||||||
|
add: sinon.stub().resolves(),
|
||||||
|
process: sinon.stub().resolves(),
|
||||||
|
}
|
||||||
ctx.Queues = {
|
ctx.Queues = {
|
||||||
getQueue: queueName => {
|
getQueue: queueName => {
|
||||||
switch (queueName) {
|
switch (queueName) {
|
||||||
@@ -65,6 +69,8 @@ describe('AnalyticsManager', function () {
|
|||||||
return ctx.analyticsAccountMappingQueue
|
return ctx.analyticsAccountMappingQueue
|
||||||
case 'analytics-email-change':
|
case 'analytics-email-change':
|
||||||
return ctx.analyticsEmailChangeQueue
|
return ctx.analyticsEmailChangeQueue
|
||||||
|
case 'analytics-package-usage':
|
||||||
|
return ctx.analyticsPackageUsageQueue
|
||||||
default:
|
default:
|
||||||
throw new Error('Unexpected queue name')
|
throw new Error('Unexpected queue name')
|
||||||
}
|
}
|
||||||
@@ -372,6 +378,8 @@ describe('AnalyticsManager', function () {
|
|||||||
return ctx.analyticsAccountMappingQueue
|
return ctx.analyticsAccountMappingQueue
|
||||||
case 'analytics-email-change':
|
case 'analytics-email-change':
|
||||||
return ctx.analyticsEmailChangeQueue
|
return ctx.analyticsEmailChangeQueue
|
||||||
|
case 'analytics-package-usage':
|
||||||
|
return ctx.analyticsPackageUsageQueue
|
||||||
default:
|
default:
|
||||||
throw new Error('Unexpected queue name')
|
throw new Error('Unexpected queue name')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ describe('MetaController', function () {
|
|||||||
default: ctx.MetaHandler,
|
default: ctx.MetaHandler,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
vi.doMock(
|
||||||
|
'../../../../app/src/Features/Analytics/AnalyticsManager',
|
||||||
|
() => ({ default: {} })
|
||||||
|
)
|
||||||
|
|
||||||
ctx.MetadataController = (await import(modulePath)).default
|
ctx.MetadataController = (await import(modulePath)).default
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ describe('MetaHandler', function () {
|
|||||||
baz: ctx.packageMapping.baz,
|
baz: ctx.packageMapping.baz,
|
||||||
},
|
},
|
||||||
packageNames: ['foo', 'bar', 'baz'],
|
packageNames: ['foo', 'bar', 'baz'],
|
||||||
|
documentClass: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
ctx.DocumentUpdaterHandler.promises.flushDocToMongo.should.be.calledWith(
|
ctx.DocumentUpdaterHandler.promises.flushDocToMongo.should.be.calledWith(
|
||||||
@@ -161,16 +162,19 @@ describe('MetaHandler', function () {
|
|||||||
labels: ['aaa'],
|
labels: ['aaa'],
|
||||||
packages: {},
|
packages: {},
|
||||||
packageNames: [],
|
packageNames: [],
|
||||||
|
documentClass: null,
|
||||||
},
|
},
|
||||||
id_two: {
|
id_two: {
|
||||||
labels: [],
|
labels: [],
|
||||||
packages: {},
|
packages: {},
|
||||||
packageNames: [],
|
packageNames: [],
|
||||||
|
documentClass: null,
|
||||||
},
|
},
|
||||||
id_three: {
|
id_three: {
|
||||||
labels: ['bbb', 'ccc'],
|
labels: ['bbb', 'ccc'],
|
||||||
packages: {},
|
packages: {},
|
||||||
packageNames: [],
|
packageNames: [],
|
||||||
|
documentClass: null,
|
||||||
},
|
},
|
||||||
id_four: {
|
id_four: {
|
||||||
labels: [],
|
labels: [],
|
||||||
@@ -185,6 +189,7 @@ describe('MetaHandler', function () {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
packageNames: ['baz', 'amsmath'],
|
packageNames: ['baz', 'amsmath'],
|
||||||
|
documentClass: null,
|
||||||
},
|
},
|
||||||
id_five: {
|
id_five: {
|
||||||
labels: ['sec:intro'],
|
labels: ['sec:intro'],
|
||||||
@@ -213,6 +218,7 @@ describe('MetaHandler', function () {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
packageNames: ['foo', 'baz', 'hello'],
|
packageNames: ['foo', 'baz', 'hello'],
|
||||||
|
documentClass: null,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user