[WEB] Add analytics events for importing and exporting to different file types (#33614)

* adding events for success and failure for import and export from latex

* adding the operation property to capture the import/export keyword

GitOrigin-RevId: 2e5482b3c7517b402fc151966975ca8718729683
This commit is contained in:
Davinder Singh
2026-05-13 13:20:22 +01:00
committed by Copybot
parent 75a12dda17
commit a3a508d193
4 changed files with 117 additions and 6 deletions

View File

@@ -7,6 +7,7 @@ import { prepareZipAttachment } from '../../infrastructure/Response.mjs'
import SessionManager from '../Authentication/SessionManager.mjs'
import ProjectAuditLogHandler from '../Project/ProjectAuditLogHandler.mjs'
import DocumentConversionManager from '../Uploads/DocumentConversionManager.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
import Validation from '../../infrastructure/Validation.mjs'
import { expressify } from '@overleaf/promise-utils'
import { pipeline } from 'node:stream/promises'
@@ -78,12 +79,30 @@ async function exportProjectConversion(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session)
Metrics.inc('document-exports', 1, { type })
const { conversionId, buildId, clsiServerId, file } =
await DocumentConversionManager.promises.convertProjectToDocument(
projectId,
userId,
type
)
let conversionResult
try {
conversionResult =
await DocumentConversionManager.promises.convertProjectToDocument(
projectId,
userId,
type
)
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
sourceFormat: 'latex',
targetFormat: type,
status: 'success',
operation: 'export',
})
} catch (error) {
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
sourceFormat: 'latex',
targetFormat: type,
status: 'failure',
operation: 'export',
})
throw error
}
const { conversionId, buildId, clsiServerId, file } = conversionResult
ProjectAuditLogHandler.addEntryInBackground(
projectId,
`project-exported-${type}`,

View File

@@ -16,6 +16,7 @@ import { expressify } from '@overleaf/promise-utils'
import { DuplicateNameError, FileTooLargeError } from '../Errors/Errors.js'
import DocumentConversionManager from './DocumentConversionManager.mjs'
import ProjectOptionsHandler from '../Project/ProjectOptionsHandler.mjs'
import AnalyticsManager from '../Analytics/AnalyticsManager.mjs'
const defaultsDeep = lodash.defaultsDeep
@@ -202,6 +203,16 @@ async function importDocument(req, res, next) {
archivePath
)
await ProjectOptionsHandler.promises.setCompiler(project._id, 'lualatex')
AnalyticsManager.recordEventForUserInBackground(
userId,
'convert-format',
{
sourceFormat: conversionType,
targetFormat: 'latex',
status: 'success',
operation: 'import',
}
)
res.json({ success: true, project_id: project._id })
} finally {
await fsPromises.unlink(archivePath).catch(unlinkErr => {
@@ -213,6 +224,12 @@ async function importDocument(req, res, next) {
}
} catch (error) {
logger.error({ error }, 'error importing document file')
AnalyticsManager.recordEventForUserInBackground(userId, 'convert-format', {
sourceFormat: conversionType,
targetFormat: 'latex',
status: 'failure',
operation: 'import',
})
if (
error instanceof FileTooLargeError ||
error?.name === 'FileTooLargeError'

View File

@@ -92,6 +92,15 @@ describe('ProjectDownloadsController', function () {
pipeline: (ctx.pipeline = sinon.stub().resolves()),
}))
vi.doMock(
'../../../../app/src/Features/Analytics/AnalyticsManager.mjs',
() => ({
default: (ctx.AnalyticsManager = {
recordEventForUserInBackground: sinon.stub(),
}),
})
)
ctx.ProjectDownloadsController = (await import(modulePath)).default
})
@@ -349,6 +358,20 @@ describe('ProjectDownloadsController', function () {
it('should stream the document to the response', function (ctx) {
sinon.assert.calledWith(ctx.pipeline, ctx.exportStream, ctx.res)
})
it('should record a successful convert-format analytics event', function (ctx) {
sinon.assert.calledWith(
ctx.AnalyticsManager.recordEventForUserInBackground,
ctx.userId,
'convert-format',
{
sourceFormat: 'latex',
targetFormat: 'docx',
status: 'success',
operation: 'export',
}
)
})
})
describe('with responseFormat=json', function () {

View File

@@ -112,6 +112,16 @@ describe('ProjectUploadController', function () {
})
)
ctx.AnalyticsManager = {
recordEventForUserInBackground: sinon.stub(),
}
vi.doMock(
'../../../../app/src/Features/Analytics/AnalyticsManager.mjs',
() => ({
default: ctx.AnalyticsManager,
})
)
vi.doMock('node:fs', () => ({
default: (ctx.fs = {}),
}))
@@ -525,6 +535,20 @@ describe('ProjectUploadController', function () {
ctx.req.file.path
)
})
it('should record a successful convert-format analytics event', function (ctx) {
sinon.assert.calledWith(
ctx.AnalyticsManager.recordEventForUserInBackground,
ctx.user_id,
'convert-format',
{
sourceFormat: 'docx',
targetFormat: 'latex',
status: 'success',
operation: 'import',
}
)
})
})
})
@@ -581,6 +605,20 @@ describe('ProjectUploadController', function () {
it('should unlink the uploaded file', function (ctx) {
expect(ctx.fsPromises.unlink).to.have.been.calledWith(ctx.req.file.path)
})
it('should record a successful convert-format analytics event', function (ctx) {
sinon.assert.calledWith(
ctx.AnalyticsManager.recordEventForUserInBackground,
ctx.user_id,
'convert-format',
{
sourceFormat: 'markdown',
targetFormat: 'latex',
status: 'success',
operation: 'import',
}
)
})
})
describe('with an invalid conversionType', async function () {
@@ -639,6 +677,20 @@ describe('ProjectUploadController', function () {
it('should return http 500', function (ctx) {
expect(ctx.res.statusCode).to.equal(500)
})
it('should record a failed convert-format analytics event', function (ctx) {
sinon.assert.calledWith(
ctx.AnalyticsManager.recordEventForUserInBackground,
ctx.user_id,
'convert-format',
{
sourceFormat: 'docx',
targetFormat: 'latex',
status: 'failure',
operation: 'import',
}
)
})
})
describe('when the converted archive is too large', async function () {