Procházet zdrojové kódy

[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
Davinder Singh před 5 měsíci
rodič
revize
a3a508d193

+ 25 - 6
services/web/app/src/Features/Downloads/ProjectDownloadsController.mjs

@@ -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}`,

+ 17 - 0
services/web/app/src/Features/Uploads/ProjectUploadController.mjs

@@ -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'

+ 23 - 0
services/web/test/unit/src/Downloads/ProjectDownloadsController.test.mjs

@@ -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 () {

+ 52 - 0
services/web/test/unit/src/Uploads/ProjectUploadController.test.mjs

@@ -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 () {