Эх сурвалжийг харах

Merge pull request #14207 from overleaf/jdt-editor-events

editor events

GitOrigin-RevId: 8d74576d4f8117ecca47402afcc9cee229dd0dca
Jimmy Domagala-Tang 3 жил өмнө
parent
commit
b2e74464a2
22 өөрчлөгдсөн 194 нэмэгдсэн , 28 устгасан
  1. 7 1
      services/web/frontend/js/features/editor-left-menu/components/actions-copy-project.tsx
  2. 8 2
      services/web/frontend/js/features/editor-left-menu/components/actions-word-count.tsx
  3. 16 1
      services/web/frontend/js/features/editor-left-menu/components/download-pdf.tsx
  4. 9 0
      services/web/frontend/js/features/editor-left-menu/components/download-source.tsx
  5. 8 1
      services/web/frontend/js/features/editor-left-menu/components/help-contact-us.tsx
  6. 8 2
      services/web/frontend/js/features/editor-left-menu/components/help-show-hotkeys.tsx
  7. 8 1
      services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx
  8. 19 5
      services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js
  9. 3 0
      services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js
  10. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.js
  11. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.js
  12. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js
  13. 2 1
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-url.js
  14. 2 0
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js
  15. 13 2
      services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu-items.js
  16. 13 2
      services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js
  17. 2 0
      services/web/frontend/js/features/hotkeys-modal/components/hotkeys-modal-bottom-text.js
  18. 39 7
      services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js
  19. 4 1
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-download-button.tsx
  20. 6 1
      services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx
  21. 6 0
      services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js
  22. 15 1
      services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js

+ 7 - 1
services/web/frontend/js/features/editor-left-menu/components/actions-copy-project.tsx

@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next'
 import EditorCloneProjectModalWrapper from '../../clone-project-modal/components/editor-clone-project-modal-wrapper'
 import LeftMenuButton from './left-menu-button'
 import { useLocation } from '../../../shared/hooks/use-location'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 type ProjectCopyResponse = {
   project_id: string
@@ -20,10 +21,15 @@ export default function ActionsCopyProject() {
     [location]
   )
 
+  const handleShowModal = useCallback(() => {
+    eventTracking.sendMB('left-menu-copy')
+    setShowModal(true)
+  }, [])
+
   return (
     <>
       <LeftMenuButton
-        onClick={() => setShowModal(true)}
+        onClick={handleShowModal}
         icon={{
           type: 'copy',
           fw: true,

+ 8 - 2
services/web/frontend/js/features/editor-left-menu/components/actions-word-count.tsx

@@ -1,20 +1,26 @@
-import { useState } from 'react'
+import { useState, useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
 import Tooltip from '../../../shared/components/tooltip'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
 import WordCountModal from '../../word-count-modal/components/word-count-modal'
 import LeftMenuButton from './left-menu-button'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 export default function ActionsWordCount() {
   const [showModal, setShowModal] = useState(false)
   const { pdfUrl } = useCompileContext()
   const { t } = useTranslation()
 
+  const handleShowModal = useCallback(() => {
+    eventTracking.sendMB('left-menu-count')
+    setShowModal(true)
+  }, [])
+
   return (
     <>
       {pdfUrl ? (
         <LeftMenuButton
-          onClick={() => setShowModal(true)}
+          onClick={handleShowModal}
           icon={{
             type: 'eye',
             fw: true,

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

@@ -1,15 +1,30 @@
 import { useTranslation } from 'react-i18next'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
+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'
 
 export default function DownloadPDF() {
   const { t } = useTranslation()
   const { pdfDownloadUrl, pdfUrl } = useCompileContext()
+  const { _id: projectId } = useProjectContext()
+
+  function sendDownloadEvent() {
+    eventTracking.sendMB('download-pdf-button-click', {
+      projectId,
+      location: 'left-menu',
+    })
+  }
 
   if (pdfUrl) {
     return (
-      <a href={pdfDownloadUrl || pdfUrl} target="_blank" rel="noreferrer">
+      <a
+        href={pdfDownloadUrl || pdfUrl}
+        target="_blank"
+        rel="noreferrer"
+        onClick={sendDownloadEvent}
+      >
         <Icon type="file-pdf-o" modifier="2x" />
         <br />
         PDF

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

@@ -1,16 +1,25 @@
 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'
 
 export default function DownloadSource() {
   const { t } = useTranslation()
   const { _id: projectId } = useProjectContext()
 
+  function sendDownloadEvent() {
+    eventTracking.sendMB('download-zip-button-click', {
+      projectId,
+      location: 'left-menu',
+    })
+  }
+
   return (
     <a
       href={`/project/${projectId}/download/zip`}
       target="_blank"
       rel="noreferrer"
+      onClick={sendDownloadEvent}
     >
       <Icon type="file-archive-o" modifier="2x" />
       <br />

+ 8 - 1
services/web/frontend/js/features/editor-left-menu/components/help-contact-us.tsx

@@ -1,4 +1,6 @@
 import { useTranslation } from 'react-i18next'
+import { useCallback } from 'react'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 import { useContactUsModal } from '../../../shared/hooks/use-contact-us-modal'
 import LeftMenuButton from './left-menu-button'
 
@@ -6,10 +8,15 @@ export default function HelpContactUs() {
   const { modal, showModal } = useContactUsModal()
   const { t } = useTranslation()
 
+  const showModalWithAnalytics = useCallback(() => {
+    eventTracking.sendMB('left-menu-contact')
+    showModal()
+  }, [showModal])
+
   return (
     <>
       <LeftMenuButton
-        onClick={showModal}
+        onClick={showModalWithAnalytics}
         icon={{
           type: 'question',
           fw: true,

+ 8 - 2
services/web/frontend/js/features/editor-left-menu/components/help-show-hotkeys.tsx

@@ -1,5 +1,6 @@
-import { useState } from 'react'
+import { useState, useCallback } from 'react'
 import { useTranslation } from 'react-i18next'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 import { useProjectContext } from '../../../shared/context/project-context'
 import HotkeysModal from '../../hotkeys-modal/components/hotkeys-modal'
 import useScopeValue from '../../../shared/hooks/use-scope-value'
@@ -12,10 +13,15 @@ export default function HelpShowHotkeys() {
   const { features } = useProjectContext()
   const isMac = /Mac/.test(window.navigator?.platform)
 
+  const showModalWithAnalytics = useCallback(() => {
+    eventTracking.sendMB('left-menu-hotkeys')
+    setShowModal(true)
+  }, [])
+
   return (
     <>
       <LeftMenuButton
-        onClick={() => setShowModal(true)}
+        onClick={showModalWithAnalytics}
         icon={{
           type: 'keyboard-o',
           fw: true,

+ 8 - 1
services/web/frontend/js/features/editor-navigation-toolbar/components/back-to-projects-button.tsx

@@ -1,6 +1,7 @@
 import { useTranslation } from 'react-i18next'
 import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 function BackToProjectsButton() {
   const { t } = useTranslation()
@@ -12,7 +13,13 @@ function BackToProjectsButton() {
       overlayProps={{ placement: 'right' }}
     >
       <div className="toolbar-item">
-        <a className="btn btn-full-height" href="/project">
+        <a
+          className="btn btn-full-height"
+          href="/project"
+          onClick={() => {
+            eventTracking.sendMB('navigation-clicked-home')
+          }}
+        >
           <Icon
             type="home"
             fw

+ 19 - 5
services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js

@@ -5,6 +5,7 @@ import { useEditorContext } from '../../../shared/context/editor-context'
 import { useChatContext } from '../../chat/context/chat-context'
 import { useLayoutContext } from '../../../shared/context/layout-context'
 import { useProjectContext } from '../../../shared/context/project-context'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 const projectContextPropTypes = {
   name: PropTypes.string.isRequired,
@@ -38,6 +39,10 @@ const chatContextPropTypes = {
   unreadMessageCount: PropTypes.number.isRequired,
 }
 
+function isOpentoString(open) {
+  return open ? 'open' : 'close'
+}
+
 const EditorNavigationToolbarRoot = React.memo(
   function EditorNavigationToolbarRoot({
     onlineUsersArray,
@@ -74,29 +79,38 @@ const EditorNavigationToolbarRoot = React.memo(
       if (!chatIsOpen) {
         markMessagesAsRead()
       }
+      eventTracking.sendMB('navigation-clicked-chat', {
+        action: isOpentoString(!chatIsOpen),
+      })
       setChatIsOpen(value => !value)
     }, [chatIsOpen, setChatIsOpen, markMessagesAsRead])
 
     const toggleReviewPanelOpen = useCallback(
       event => {
         event.preventDefault()
+        eventTracking.sendMB('navigation-clicked-review', {
+          action: isOpentoString(!reviewPanelOpen),
+        })
         setReviewPanelOpen(value => !value)
       },
-      [setReviewPanelOpen]
+      [reviewPanelOpen, setReviewPanelOpen]
     )
 
     const toggleHistoryOpen = useCallback(() => {
+      const action = view === 'history' ? 'close' : 'open'
+      eventTracking.sendMB('navigation-clicked-history', { action })
       setView(view === 'history' ? 'editor' : 'history')
     }, [view, setView])
 
     const openShareModal = useCallback(() => {
+      eventTracking.sendMB('navigation-clicked-share')
       openShareProjectModal()
     }, [openShareProjectModal])
 
-    const onShowLeftMenuClick = useCallback(
-      () => setLeftMenuShown(value => !value),
-      [setLeftMenuShown]
-    )
+    const onShowLeftMenuClick = useCallback(() => {
+      eventTracking.sendMB('navigation-clicked-menu')
+      setLeftMenuShown(value => !value)
+    }, [setLeftMenuShown])
 
     const goToUser = useCallback(
       user => {

+ 3 - 0
services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.js

@@ -149,6 +149,9 @@ function LayoutDropdownButton() {
       )}
       <ControlledDropdown
         id="layout-dropdown"
+        onMainButtonClick={() => {
+          eventTracking.sendMB('navigation-clicked-layout')
+        }}
         className="toolbar-item layout-dropdown"
         pullRight
       >

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.js

@@ -3,12 +3,14 @@ import { Button } from 'react-bootstrap'
 import PropTypes from 'prop-types'
 import Icon from '../../../../shared/components/icon'
 import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
+import * as eventTracking from '../../../../infrastructure/event-tracking'
 
 export default function FileTreeModalCreateFileMode({ mode, icon, label }) {
   const { newFileCreateMode, startCreatingFile } = useFileTreeActionable()
 
   const handleClick = () => {
     startCreatingFile(mode)
+    eventTracking.sendMB('file-modal-click', { method: mode })
   }
 
   return (

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-create-new-doc.js

@@ -3,6 +3,7 @@ import FileTreeCreateNameInput from '../file-tree-create-name-input'
 import { useFileTreeActionable } from '../../../contexts/file-tree-actionable'
 import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name'
 import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form'
+import * as eventTracking from '../../../../../infrastructure/event-tracking'
 import ErrorMessage from '../error-message'
 
 export default function FileTreeCreateNewDoc() {
@@ -21,6 +22,7 @@ export default function FileTreeCreateNewDoc() {
       event.preventDefault()
 
       finishCreatingDoc({ name })
+      eventTracking.sendMB('new-file-created', { method: 'doc' })
     },
     [finishCreatingDoc, name]
   )

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js

@@ -12,6 +12,7 @@ import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name'
 import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form'
 import { useProjectContext } from '../../../../../shared/context/project-context'
 import ErrorMessage from '../error-message'
+import * as eventTracking from '../../../../../infrastructure/event-tracking'
 
 export default function FileTreeImportFromProject() {
   const { t } = useTranslation()
@@ -85,6 +86,7 @@ export default function FileTreeImportFromProject() {
   // form submission: create a linked file with this name, from this entity or output file
   const handleSubmit = event => {
     event.preventDefault()
+    eventTracking.sendMB('new-file-created', { method: 'project' })
 
     if (isOutputFilesMode) {
       finishCreatingLinkedFile({

+ 2 - 1
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-url.js

@@ -6,6 +6,7 @@ import { useFileTreeActionable } from '../../../contexts/file-tree-actionable'
 import { useFileTreeCreateName } from '../../../contexts/file-tree-create-name'
 import { useFileTreeCreateForm } from '../../../contexts/file-tree-create-form'
 import ErrorMessage from '../error-message'
+import * as eventTracking from '../../../../../infrastructure/event-tracking'
 
 export default function FileTreeImportFromUrl() {
   const { t } = useTranslation()
@@ -35,7 +36,7 @@ export default function FileTreeImportFromUrl() {
   // form submission: create a linked file with this name, from this URL
   const handleSubmit = event => {
     event.preventDefault()
-
+    eventTracking.sendMB('new-file-created', { method: 'url' })
     finishCreatingLinkedFile({
       name,
       provider: 'url',

+ 2 - 0
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-upload-doc.js

@@ -7,6 +7,7 @@ import XHRUpload from '@uppy/xhr-upload'
 import { Dashboard, useUppy } from '@uppy/react'
 import { useFileTreeActionable } from '../../../contexts/file-tree-actionable'
 import { useProjectContext } from '../../../../../shared/context/project-context'
+import * as eventTracking from '../../../../../infrastructure/event-tracking'
 
 import '@uppy/core/dist/style.css'
 import '@uppy/dashboard/dist/style.css'
@@ -94,6 +95,7 @@ export default function FileTreeUploadDoc() {
         })
         // broadcast doc metadata after each successful upload
         .on('upload-success', (file, response) => {
+          eventTracking.sendMB('new-file-created', { method: 'upload' })
           if (response.body.entity_type === 'doc') {
             window.setTimeout(() => {
               refreshProjectMetadata(projectId, response.body.entity_id)

+ 13 - 2
services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu-items.js

@@ -1,4 +1,5 @@
 import { useTranslation } from 'react-i18next'
+import * as eventTracking from '../../../../infrastructure/event-tracking'
 
 import { MenuItem } from 'react-bootstrap'
 import { useFileTreeActionable } from '../../contexts/file-tree-actionable'
@@ -18,6 +19,16 @@ function FileTreeItemMenuItems() {
     downloadPath,
   } = useFileTreeActionable()
 
+  const createWithAnalytics = () => {
+    eventTracking.sendMB('new-file-click', { location: 'file-menu' })
+    startCreatingDocOrFile()
+  }
+
+  const uploadWithAnalytics = () => {
+    eventTracking.sendMB('upload-click', { location: 'file-menu' })
+    startUploadingDocOrFile()
+  }
+
   return (
     <>
       {canRename ? (
@@ -34,9 +45,9 @@ function FileTreeItemMenuItems() {
       {canCreate ? (
         <>
           <MenuItem divider />
-          <MenuItem onClick={startCreatingDocOrFile}>{t('new_file')}</MenuItem>
+          <MenuItem onClick={createWithAnalytics}>{t('new_file')}</MenuItem>
           <MenuItem onClick={startCreatingFolder}>{t('new_folder')}</MenuItem>
-          <MenuItem onClick={startUploadingDocOrFile}>{t('upload')}</MenuItem>
+          <MenuItem onClick={uploadWithAnalytics}>{t('upload')}</MenuItem>
         </>
       ) : null}
     </>

+ 13 - 2
services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js

@@ -1,5 +1,6 @@
 import PropTypes from 'prop-types'
 import { useTranslation } from 'react-i18next'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 import { Button } from 'react-bootstrap'
 import Tooltip from '../../../shared/components/tooltip'
@@ -34,6 +35,16 @@ function FileTreeToolbarLeft() {
     startUploadingDocOrFile,
   } = useFileTreeActionable()
 
+  const createWithAnalytics = () => {
+    eventTracking.sendMB('new-file-click', { location: 'toolbar' })
+    startCreatingDocOrFile()
+  }
+
+  const uploadWithAnalytics = () => {
+    eventTracking.sendMB('upload-click', { location: 'toolbar' })
+    startUploadingDocOrFile()
+  }
+
   if (!canCreate) return null
 
   return (
@@ -43,7 +54,7 @@ function FileTreeToolbarLeft() {
         description={t('new_file')}
         overlayProps={{ placement: 'bottom' }}
       >
-        <Button onClick={startCreatingDocOrFile} bsStyle={null}>
+        <Button onClick={createWithAnalytics} bsStyle={null}>
           <Icon type="file" fw accessibilityLabel={t('new_file')} />
         </Button>
       </Tooltip>
@@ -61,7 +72,7 @@ function FileTreeToolbarLeft() {
         description={t('upload')}
         overlayProps={{ placement: 'bottom' }}
       >
-        <Button onClick={startUploadingDocOrFile}>
+        <Button onClick={uploadWithAnalytics}>
           <Icon type="upload" fw accessibilityLabel={t('upload')} />
         </Button>
       </Tooltip>

+ 2 - 0
services/web/frontend/js/features/hotkeys-modal/components/hotkeys-modal-bottom-text.js

@@ -1,4 +1,5 @@
 import { Trans } from 'react-i18next'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 export default function HotkeysModalBottomText() {
   return (
@@ -8,6 +9,7 @@ export default function HotkeysModalBottomText() {
         components={[
           // eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key
           <a
+            onClick={() => eventTracking.sendMB('left-menu-hotkeys-template')}
             href="/articles/overleaf-keyboard-shortcuts/qykqfvmxdnjf"
             target="_blank"
           />,

+ 39 - 7
services/web/frontend/js/features/pdf-preview/components/pdf-compile-button.js

@@ -5,9 +5,18 @@ import { useDetachCompileContext as useCompileContext } from '../../../shared/co
 import { useStopOnFirstError } from '../../../shared/hooks/use-stop-on-first-error'
 import SplitMenu from '../../../shared/components/split-menu'
 import Icon from '../../../shared/components/icon'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 const modifierKey = /Mac/i.test(navigator.platform) ? 'Cmd' : 'Ctrl'
 
+function sendEventAndSet(value, setter, settingName) {
+  eventTracking.sendMB('recompile-setting-changed', {
+    setting: settingName,
+    settingVal: value,
+  })
+  setter(value)
+}
+
 function PdfCompileButton() {
   const {
     animateCompileDropdownArrow,
@@ -30,6 +39,13 @@ function PdfCompileButton() {
 
   const { t } = useTranslation()
 
+  const fromScratchWithEvent = () => {
+    eventTracking.sendMB('recompile-setting-changed', {
+      setting: 'from-scratch',
+    })
+    recompileFromScratch()
+  }
+
   const compileButtonLabel = compiling ? `${t('compiling')}…` : t('recompile')
   const tooltipElement = (
     <>
@@ -76,36 +92,52 @@ function PdfCompileButton() {
     >
       <SplitMenu.Item header>{t('auto_compile')}</SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setAutoCompile(true)}>
+      <SplitMenu.Item
+        onSelect={() => sendEventAndSet(true, setAutoCompile, 'auto-compile')}
+      >
         <Icon type={autoCompile ? 'check' : ''} fw />
         {t('on')}
       </SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setAutoCompile(false)}>
+      <SplitMenu.Item
+        onSelect={() => sendEventAndSet(false, setAutoCompile, 'auto-compile')}
+      >
         <Icon type={!autoCompile ? 'check' : ''} fw />
         {t('off')}
       </SplitMenu.Item>
 
       <SplitMenu.Item header>{t('compile_mode')}</SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setDraft(false)}>
+      <SplitMenu.Item
+        onSelect={() => sendEventAndSet(false, setDraft, 'compile-mode')}
+      >
         <Icon type={!draft ? 'check' : ''} fw />
         {t('normal')}
       </SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setDraft(true)}>
+      <SplitMenu.Item
+        onSelect={() => sendEventAndSet(true, setDraft, 'compile-mode')}
+      >
         <Icon type={draft ? 'check' : ''} fw />
         {t('fast')} <span className="subdued">[draft]</span>
       </SplitMenu.Item>
 
       <SplitMenu.Item header>Syntax Checks</SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setStopOnValidationError(true)}>
+      <SplitMenu.Item
+        onSelect={() =>
+          sendEventAndSet(true, setStopOnValidationError, 'syntax-check')
+        }
+      >
         <Icon type={stopOnValidationError ? 'check' : ''} fw />
         {t('stop_on_validation_error')}
       </SplitMenu.Item>
 
-      <SplitMenu.Item onSelect={() => setStopOnValidationError(false)}>
+      <SplitMenu.Item
+        onSelect={() =>
+          sendEventAndSet(false, setStopOnValidationError, 'syntax-check')
+        }
+      >
         <Icon type={!stopOnValidationError ? 'check' : ''} fw />
         {t('ignore_validation_errors')}
       </SplitMenu.Item>
@@ -133,7 +165,7 @@ function PdfCompileButton() {
       </SplitMenu.Item>
 
       <SplitMenu.Item
-        onSelect={() => recompileFromScratch()}
+        onSelect={fromScratchWithEvent}
         disabled={compiling}
         aria-disabled={compiling}
       >

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

@@ -17,7 +17,10 @@ function PdfHybridDownloadButton() {
     : t('please_compile_pdf_before_download')
 
   function handleOnClick() {
-    eventTracking.sendMB('download-pdf-button-click', { projectId })
+    eventTracking.sendMB('download-pdf-button-click', {
+      projectId,
+      location: 'pdf-preview',
+    })
   }
 
   return (

+ 6 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-hybrid-logs-button.tsx

@@ -4,6 +4,7 @@ import { Button, Label } from 'react-bootstrap'
 import Tooltip from '../../../shared/components/tooltip'
 import Icon from '../../../shared/components/icon'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 function PdfHybridLogsButton() {
   const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
@@ -12,8 +13,12 @@ function PdfHybridLogsButton() {
   const { t } = useTranslation()
 
   const handleClick = useCallback(() => {
+    // only send analytics on open
+    if (!showLogs) {
+      eventTracking.sendMB('logs-click')
+    }
     toggleLogs()
-  }, [toggleLogs])
+  }, [toggleLogs, showLogs])
 
   const errorCount = Number(logEntries?.errors?.length)
   const warningCount = Number(logEntries?.warnings?.length)

+ 6 - 0
services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js

@@ -10,6 +10,7 @@ import withErrorBoundary from '../../../infrastructure/error-boundary'
 import PdfPreviewErrorBoundaryFallback from './pdf-preview-error-boundary-fallback'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
 import { captureException } from '../../../infrastructure/error-reporter'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 import { getPdfCachingMetrics } from '../util/metrics'
 
 function PdfJsViewer({ url, pdfFile }) {
@@ -205,6 +206,11 @@ function PdfJsViewer({ url, pdfFile }) {
             )
 
             if (clickPosition) {
+              eventTracking.sendMB('jump-to-location', {
+                direction: 'pdf-location-in-code',
+                method: 'double-click',
+              })
+
               window.dispatchEvent(
                 new CustomEvent('synctex:sync-to-position', {
                   detail: clickPosition,

+ 15 - 1
services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js

@@ -18,6 +18,7 @@ import useDetachAction from '../../../shared/hooks/use-detach-action'
 import localStorage from '../../../infrastructure/local-storage'
 import { useFileTreeData } from '../../../shared/context/file-tree-data-context'
 import useScopeEventListener from '../../../shared/hooks/use-scope-event-listener'
+import * as eventTracking from '../../../infrastructure/event-tracking'
 
 function GoToCodeButton({
   position,
@@ -38,6 +39,14 @@ function GoToCodeButton({
     buttonIcon = <Icon type="arrow-left" className="synctex-control-icon" />
   }
 
+  const syncToCodeWithButton = () => {
+    eventTracking.sendMB('jump-to-location', {
+      direction: 'pdf-location-in-code',
+      method: 'arrow',
+    })
+    syncToCode(position, 72)
+  }
+
   return (
     <Tooltip
       id="sync-to-code"
@@ -47,7 +56,7 @@ function GoToCodeButton({
       <Button
         bsStyle={null}
         bsSize="xs"
-        onClick={() => syncToCode(position, 72)}
+        onClick={syncToCodeWithButton}
         disabled={syncToCodeInFlight}
         className={buttonClasses}
         aria-label={t('go_to_pdf_location_in_code')}
@@ -228,6 +237,11 @@ function PdfSynctexControls() {
         column: cursorPosition.column,
       }).toString()
 
+      eventTracking.sendMB('jump-to-location', {
+        direction: 'code-location-in-pdf',
+        method: 'arrow',
+      })
+
       goToPdfLocation(params)
     },
     [getCurrentFilePath, goToPdfLocation]