Browse Source

Merge pull request #15843 from overleaf/jpa-download-events

[web] add additional event tracking

GitOrigin-RevId: 877f92db41efff017db370ec75b8d1f861eed4f2
Jakob Ackermann 2 years ago
parent
commit
6b2daed903
18 changed files with 72 additions and 5 deletions
  1. 2 0
      services/web/frontend/js/features/editor-left-menu/components/download-pdf.tsx
  2. 2 0
      services/web/frontend/js/features/editor-left-menu/components/download-source.tsx
  3. 12 0
      services/web/frontend/js/features/event-tracking/document-first-change-event.js
  4. 2 0
      services/web/frontend/js/features/ide-react/editor/share-js-doc.ts
  5. 2 0
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.tsx
  6. 5 1
      services/web/frontend/js/features/project-list/components/modals/projects-action-modal.tsx
  7. 4 1
      services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx
  8. 5 1
      services/web/frontend/js/features/project-list/components/search-form.tsx
  9. 6 1
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/copy-project-button.tsx
  10. 3 0
      services/web/frontend/js/features/project-list/components/table/cells/action-buttons/download-project-button.tsx
  11. 2 0
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/download-projects-button.tsx
  12. 6 1
      services/web/frontend/js/features/project-list/components/table/project-tools/menu-items/copy-project-menu-item.tsx
  13. 2 0
      services/web/frontend/js/ide/editor/ShareJsDoc.js
  14. 6 0
      services/web/frontend/js/infrastructure/event-tracking.js
  15. 3 0
      services/web/test/frontend/bootstrap.js
  16. 8 0
      services/web/test/frontend/features/project-list/components/project-list-root.test.tsx
  17. 1 0
      services/web/test/frontend/features/project-list/components/project-search.test.tsx
  18. 1 0
      services/web/test/frontend/features/project-list/components/table/projects-action-modal.test.tsx

+ 2 - 0
services/web/frontend/js/features/editor-left-menu/components/download-pdf.tsx

@@ -4,6 +4,7 @@ import { useProjectContext } from '../../../shared/context/project-context'
 import Icon from '../../../shared/components/icon'
 import Tooltip from '../../../shared/components/tooltip'
 import * as eventTracking from '../../../infrastructure/event-tracking'
+import { isMobileDevice } from '../../../infrastructure/event-tracking'
 
 export default function DownloadPDF() {
   const { t } = useTranslation()
@@ -14,6 +15,7 @@ export default function DownloadPDF() {
     eventTracking.sendMB('download-pdf-button-click', {
       projectId,
       location: 'left-menu',
+      isMobileDevice,
     })
   }
 

+ 2 - 0
services/web/frontend/js/features/editor-left-menu/components/download-source.tsx

@@ -2,6 +2,7 @@ import { useTranslation } from 'react-i18next'
 import { useProjectContext } from '../../../shared/context/project-context'
 import Icon from '../../../shared/components/icon'
 import * as eventTracking from '../../../infrastructure/event-tracking'
+import { isMobileDevice } from '../../../infrastructure/event-tracking'
 
 export default function DownloadSource() {
   const { t } = useTranslation()
@@ -11,6 +12,7 @@ export default function DownloadSource() {
     eventTracking.sendMB('download-zip-button-click', {
       projectId,
       location: 'left-menu',
+      isMobileDevice,
     })
   }
 

+ 12 - 0
services/web/frontend/js/features/event-tracking/document-first-change-event.js

@@ -0,0 +1,12 @@
+import { isMobileDevice, sendMB } from '@/infrastructure/event-tracking'
+import getMeta from '@/utils/meta'
+
+// record once per page load
+let recorded = false
+
+export function recordDocumentFirstChangeEvent() {
+  if (recorded) return
+  recorded = true
+  const projectId = getMeta('ol-project_id')
+  sendMB('document-first-change', { projectId, isMobileDevice })
+}

+ 2 - 0
services/web/frontend/js/features/ide-react/editor/share-js-doc.ts

@@ -16,6 +16,7 @@ import {
   TrackChangesIdSeeds,
 } from '@/features/ide-react/editor/types/document'
 import { EditorFacade } from '@/features/source-editor/extensions/realtime'
+import { recordDocumentFirstChangeEvent } from '@/features/event-tracking/document-first-change-event'
 
 // All times below are in milliseconds
 const SINGLE_USER_FLUSH_DELAY = 2000
@@ -421,6 +422,7 @@ export class ShareJsDoc extends EventEmitter {
   private bindToDocChanges(doc: Doc) {
     const { submitOp } = doc
     doc.submitOp = (op: ShareJsOperation, callback?: () => void) => {
+      recordDocumentFirstChangeEvent()
       this.trigger('op:sent', op)
       doc.pendingCallbacks.push(() => {
         return this.trigger('op:acknowledged', op)

+ 2 - 0
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.tsx

@@ -5,6 +5,7 @@ import Icon from '../../../shared/components/icon'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
 import { useProjectContext } from '../../../shared/context/project-context'
 import * as eventTracking from '../../../infrastructure/event-tracking'
+import { isMobileDevice } from '../../../infrastructure/event-tracking'
 
 function PdfHybridDownloadButton() {
   const { pdfDownloadUrl } = useCompileContext()
@@ -20,6 +21,7 @@ function PdfHybridDownloadButton() {
     eventTracking.sendMB('download-pdf-button-click', {
       projectId,
       location: 'pdf-preview',
+      isMobileDevice,
     })
   }
 

+ 5 - 1
services/web/frontend/js/features/project-list/components/modals/projects-action-modal.tsx

@@ -6,6 +6,7 @@ import AccessibleModal from '../../../../shared/components/accessible-modal'
 import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
 import useIsMounted from '../../../../shared/hooks/use-is-mounted'
 import * as eventTracking from '../../../../infrastructure/event-tracking'
+import { isMobileDevice } from '../../../../infrastructure/event-tracking'
 
 type ProjectsActionModalProps = {
   title?: string
@@ -57,7 +58,10 @@ function ProjectsActionModal({
 
   useEffect(() => {
     if (showModal) {
-      eventTracking.sendMB('project-list-page-interaction', { action })
+      eventTracking.sendMB('project-list-page-interaction', {
+        action,
+        isMobileDevice,
+      })
     }
   }, [action, showModal])
 

+ 4 - 1
services/web/frontend/js/features/project-list/components/modals/rename-project-modal.tsx

@@ -16,6 +16,7 @@ import useAsync from '../../../../shared/hooks/use-async'
 import { useProjectListContext } from '../../context/project-list-context'
 import { getUserFacingMessage } from '../../../../infrastructure/fetch-json'
 import { debugConsole } from '@/utils/debugging'
+import { isMobileDevice } from '../../../../infrastructure/event-tracking'
 
 type RenameProjectModalProps = {
   handleCloseModal: () => void
@@ -37,9 +38,11 @@ function RenameProjectModal({
     if (showModal) {
       eventTracking.sendMB('project-list-page-interaction', {
         action: 'rename',
+        projectId: project.id,
+        isMobileDevice,
       })
     }
-  }, [showModal])
+  }, [showModal, project.id])
 
   const isValid = useMemo(
     () => newProjectName !== project.name && newProjectName.trim().length > 0,

+ 5 - 1
services/web/frontend/js/features/project-list/components/search-form.tsx

@@ -11,6 +11,7 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
 import classnames from 'classnames'
 import { Tag } from '../../../../../app/src/Features/Tags/types'
 import { Filter } from '../context/project-list-context'
+import { isMobileDevice } from '../../../infrastructure/event-tracking'
 
 type SearchFormOwnProps = {
   inputValue: string
@@ -64,7 +65,10 @@ function SearchForm({
       HTMLInputElement & Omit<FormControl, keyof HTMLInputElement>
     >
   ) => {
-    eventTracking.sendMB('project-list-page-interaction', { action: 'search' })
+    eventTracking.sendMB('project-list-page-interaction', {
+      action: 'search',
+      isMobileDevice,
+    })
     setInputValue(e.target.value)
   }
 

+ 6 - 1
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/copy-project-button.tsx

@@ -11,6 +11,7 @@ import {
   Project,
 } from '../../../../../../../../types/project/dashboard/api'
 import { useProjectTags } from '@/features/project-list/hooks/use-project-tags'
+import { isMobileDevice } from '../../../../../../infrastructure/event-tracking'
 
 type CopyButtonProps = {
   project: Project
@@ -41,7 +42,11 @@ function CopyProjectButton({ project, children }: CopyButtonProps) {
 
   const handleAfterCloned = useCallback(
     (clonedProject: ClonedProject, tags: { _id: string }[]) => {
-      eventTracking.sendMB('project-list-page-interaction', { action: 'clone' })
+      eventTracking.sendMB('project-list-page-interaction', {
+        action: 'clone',
+        projectId: project.id,
+        isMobileDevice,
+      })
       addClonedProjectToViewData(clonedProject)
       for (const tag of tags) {
         addProjectToTagInView(tag._id, clonedProject.project_id)

+ 3 - 0
services/web/frontend/js/features/project-list/components/table/cells/action-buttons/download-project-button.tsx

@@ -5,6 +5,7 @@ import Icon from '../../../../../../shared/components/icon'
 import Tooltip from '../../../../../../shared/components/tooltip'
 import * as eventTracking from '../../../../../../infrastructure/event-tracking'
 import { useLocation } from '../../../../../../shared/hooks/use-location'
+import { isMobileDevice } from '../../../../../../infrastructure/event-tracking'
 
 type DownloadProjectButtonProps = {
   project: Project
@@ -22,6 +23,8 @@ function DownloadProjectButton({
   const downloadProject = useCallback(() => {
     eventTracking.sendMB('project-list-page-interaction', {
       action: 'downloadZip',
+      projectId: project.id,
+      isMobileDevice,
     })
     location.assign(`/project/${project.id}/download/zip`)
   }, [project, location])

+ 2 - 0
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/download-projects-button.tsx

@@ -5,6 +5,7 @@ import Tooltip from '../../../../../../shared/components/tooltip'
 import * as eventTracking from '../../../../../../infrastructure/event-tracking'
 import { useProjectListContext } from '../../../../context/project-list-context'
 import { useLocation } from '../../../../../../shared/hooks/use-location'
+import { isMobileDevice } from '../../../../../../infrastructure/event-tracking'
 
 function DownloadProjectsButton() {
   const { selectedProjects, selectOrUnselectAllProjects } =
@@ -18,6 +19,7 @@ function DownloadProjectsButton() {
   const handleDownloadProjects = useCallback(() => {
     eventTracking.sendMB('project-list-page-interaction', {
       action: 'downloadZips',
+      isMobileDevice,
     })
 
     location.assign(`/project/download/zip?project_ids=${projectIds.join(',')}`)

+ 6 - 1
services/web/frontend/js/features/project-list/components/table/project-tools/menu-items/copy-project-menu-item.tsx

@@ -7,6 +7,7 @@ import { useProjectListContext } from '../../../../context/project-list-context'
 import * as eventTracking from '../../../../../../infrastructure/event-tracking'
 import { ClonedProject } from '../../../../../../../../types/project/dashboard/api'
 import { useProjectTags } from '@/features/project-list/hooks/use-project-tags'
+import { isMobileDevice } from '../../../../../../infrastructure/event-tracking'
 
 function CopyProjectMenuItem() {
   const {
@@ -33,7 +34,11 @@ function CopyProjectMenuItem() {
   const handleAfterCloned = useCallback(
     (clonedProject: ClonedProject, tags: { _id: string }[]) => {
       const project = selectedProjects[0]
-      eventTracking.sendMB('project-list-page-interaction', { action: 'clone' })
+      eventTracking.sendMB('project-list-page-interaction', {
+        action: 'clone',
+        projectId: project.id,
+        isMobileDevice,
+      })
       addClonedProjectToViewData(clonedProject)
       for (const tag of tags) {
         addProjectToTagInView(tag._id, clonedProject.project_id)

+ 2 - 0
services/web/frontend/js/ide/editor/ShareJsDoc.js

@@ -20,6 +20,7 @@ import EventEmitter from '../../utils/EventEmitter'
 import ShareJs from '../../vendor/libs/sharejs'
 import EditorWatchdogManager from '../connection/EditorWatchdogManager'
 import { debugConsole } from '@/utils/debugging'
+import { recordDocumentFirstChangeEvent } from '@/features/event-tracking/document-first-change-event'
 
 let ShareJsDoc
 const SINGLE_USER_FLUSH_DELAY = 2000 // ms
@@ -457,6 +458,7 @@ export default ShareJsDoc = (function () {
     _bindToDocChanges(doc) {
       const { submitOp } = doc
       doc.submitOp = (...args) => {
+        recordDocumentFirstChangeEvent()
         this.trigger('op:sent', ...Array.from(args))
         doc.pendingCallbacks.push(() => {
           return this.trigger('op:acknowledged', ...Array.from(args))

+ 6 - 0
services/web/frontend/js/infrastructure/event-tracking.js

@@ -51,6 +51,12 @@ export function sendMBSampled(key, body = {}, rate = 0.01) {
   }
 }
 
+// Use breakpoint @screen-xs-max from less:
+// @screen-xs-max: (@screen-sm-min - 1);
+// @screen-sm-min: @screen-sm;
+// @screen-sm: 768px;
+export const isMobileDevice = window.matchMedia('(max-width: 767px)').matches
+
 function sendBeacon(key, data) {
   if (!navigator || !navigator.sendBeacon) return
 

+ 3 - 0
services/web/test/frontend/bootstrap.js

@@ -120,6 +120,9 @@ globalThis.WebSocket = class WebSocket {
   static CLOSED = 3
 }
 
+// add stub for window.matchMedia
+window.matchMedia = () => ({ matches: false })
+
 // node-fetch doesn't accept relative URL's: https://github.com/node-fetch/node-fetch/blob/master/docs/v2-LIMITS.md#known-differences
 const fetch = require('node-fetch')
 globalThis.fetch =

+ 8 - 0
services/web/test/frontend/features/project-list/components/project-list-root.test.tsx

@@ -10,6 +10,8 @@ import {
   owner,
   archivedProjects,
   makeLongProjectList,
+  archiveableProject,
+  copyableProject,
 } from '../fixtures/projects-data'
 import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
 
@@ -818,6 +820,8 @@ describe('<ProjectListRoot />', function () {
               {
                 action: 'rename',
                 page: '/',
+                projectId: copyableProject.id,
+                isMobileDevice: false,
               }
             )
 
@@ -949,6 +953,8 @@ describe('<ProjectListRoot />', function () {
               {
                 action: 'clone',
                 page: '/',
+                projectId: archiveableProject.id,
+                isMobileDevice: false,
               }
             )
 
@@ -1106,6 +1112,8 @@ describe('<ProjectListRoot />', function () {
           {
             action: 'clone',
             page: '/',
+            projectId: archiveableProject.id,
+            isMobileDevice: false,
           }
         )
 

+ 1 - 0
services/web/test/frontend/features/project-list/components/project-search.test.tsx

@@ -85,6 +85,7 @@ describe('Project list search form', function () {
     expect(sendMBSpy).to.have.been.calledWith('project-list-page-interaction', {
       action: 'search',
       page: '/',
+      isMobileDevice: false,
     })
     expect(setInputValueMock).to.be.calledWith(value)
   })

+ 1 - 0
services/web/test/frontend/features/project-list/components/table/projects-action-modal.test.tsx

@@ -91,6 +91,7 @@ describe('<ProjectsActionModal />', function () {
     expect(sendMBSpy).to.have.been.calledWith('project-list-page-interaction', {
       action: 'archive',
       page: '/',
+      isMobileDevice: false,
     })
   })
 })