Răsfoiți Sursa

Add new event tracking to react and angular version of the empty project page (#11472)

GitOrigin-RevId: 28ffe919851121bdba82eea3b6ef29cd6fffeb76
M Fahru 3 ani în urmă
părinte
comite
3507463c4e

+ 39 - 3
services/web/app/views/project/list/empty-project-list.pug

@@ -4,9 +4,21 @@
 			div.welcome.text-centered(ng-cloak)
 				h2 #{translate("welcome_to_sl")}
 				p #{translate("new_to_latex_look_at")} 
-					a(href="/templates") #{translate("templates").toLowerCase()}
+					a(
+						href="/templates"
+						event-tracking="welcome-page-templates-click"
+						event-tracking-mb="true"
+						event-tracking-trigger="click"
+						event-segmentation='{"project-dashboard-react": "default"}'
+					) #{translate("templates").toLowerCase()}
 					|   #{translate("or")}
-					a(href="/learn")  #{translate("latex_help_guide")}
+					a(
+						href="/learn"
+						event-tracking="welcome-page-latex-help-click"
+						event-tracking-mb="true"
+						event-tracking-trigger="click"
+						event-segmentation='{"project-dashboard-react": "default"}'
+					)  #{translate("latex_help_guide")}
 
 
 				.row
@@ -16,7 +28,12 @@
 								href="#",
 								data-toggle="dropdown",
 								dropdown-toggle
+								event-tracking="welcome-page-create-first-project-click"
+								event-tracking-mb="true"
+								event-tracking-trigger="click"
+								event-segmentation='{"project-dashboard-react": "default", "dropdownMenu": "main-button", "dropdownOpen": "null" }'
 							)
+								//- We can't know if dropdown is open or not, so will send "dropdownOpen: null" as a segmentation above
 								| Create First Project
 
 							ul.dropdown-menu.minimal-create-proj-dropdown-menu(role="menu")
@@ -24,23 +41,42 @@
 									a(
 										href,
 										ng-click="openCreateProjectModal()"
+										event-tracking="welcome-page-create-first-project-click"
+										event-tracking-mb="true"
+										event-tracking-trigger="click"
+										event-segmentation='{"project-dashboard-react": "default", "dropdownMenu": "blank-project", "dropdownOpen": "true" }'
 									) #{translate("blank_project")}
 								li
 									a(
 										href,
 										ng-click="openCreateProjectModal('example')"
+										event-tracking="welcome-page-create-first-project-click"
+										event-tracking-mb="true"
+										event-tracking-trigger="click"
+										event-segmentation='{"project-dashboard-react": "default", "dropdownMenu": "example-project", "dropdownOpen": "true" }'
 									) #{translate("example_project")}
 								li
 									a(
 										href,
 										ng-click="openUploadProjectModal()"
+										event-tracking="welcome-page-create-first-project-click"
+										event-tracking-mb="true"
+										event-tracking-trigger="click"
+										event-segmentation='{"project-dashboard-react": "default", "dropdownMenu": "upload-project", "dropdownOpen": "true" }'
 									) #{translate("upload_project")}
 								!= moduleIncludes("newProjectMenu", locals)
 								if (templates)
 									li.divider
 									li.dropdown-header #{translate("templates")}
 									each item in templates
+										- var eventSegmentation = '{"project-dashboard-react": "default", "dropdownMenu":"' + item.trackingKey + '", "dropdownOpen": "true" }'
 										li
-											a.menu-indent(href=item.url) #{translate(item.name)}
+											a.menu-indent(
+												href=item.url
+												event-tracking="welcome-page-create-first-project-click"
+												event-tracking-mb="true"
+												event-tracking-trigger="click"
+												event-segmentation=eventSegmentation
+											) #{translate(item.name)}
 
 							

+ 149 - 6
services/web/frontend/js/features/project-list/components/new-project-button.tsx

@@ -1,4 +1,4 @@
-import { useState } from 'react'
+import { useCallback, useState } from 'react'
 import { Dropdown, MenuItem } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import { ExposedSettings } from '../../../../../types/exposed-settings'
@@ -9,17 +9,35 @@ import NewProjectButtonModal, {
   NewProjectButtonModalVariant,
 } from './new-project-button/new-project-button-modal'
 import { Nullable } from '../../../../../types/utils'
+import { sendMB } from '../../../infrastructure/event-tracking'
+
+type SendTrackingEvent = {
+  dropdownMenu: string
+  dropdownOpen: boolean
+  institutionTemplateName?: string
+}
+
+type Segmentation = SendTrackingEvent & {
+  'project-dashboard-react': 'enabled'
+}
+
+type ModalMenuClickOptions = {
+  modalVariant: NewProjectButtonModalVariant
+  dropdownMenuEvent: string
+}
 
 type NewProjectButtonProps = {
   id: string
   buttonText?: string
   className?: string
+  trackingKey?: string
 }
 
 function NewProjectButton({
   id,
   buttonText,
   className,
+  trackingKey,
 }: NewProjectButtonProps) {
   const { t } = useTranslation()
   const { templateLinks } = getMeta('ol-ExposedSettings') as ExposedSettings
@@ -27,9 +45,100 @@ function NewProjectButton({
     useState<Nullable<NewProjectButtonModalVariant>>(null)
   const portalTemplates = getMeta('ol-portalTemplates') as PortalTemplate[]
 
+  const sendTrackingEvent = useCallback(
+    ({
+      dropdownMenu,
+      dropdownOpen,
+      institutionTemplateName,
+    }: SendTrackingEvent) => {
+      if (trackingKey) {
+        let segmentation: Segmentation = {
+          'project-dashboard-react': 'enabled',
+          dropdownMenu,
+          dropdownOpen,
+        }
+
+        if (institutionTemplateName) {
+          segmentation = {
+            ...segmentation,
+            institutionTemplateName,
+          }
+        }
+
+        sendMB(trackingKey, segmentation)
+      }
+    },
+    [trackingKey]
+  )
+
+  const handleMainButtonClick = useCallback(
+    (dropdownOpen: boolean) => {
+      sendTrackingEvent({
+        dropdownMenu: 'main-button',
+        dropdownOpen,
+      })
+    },
+    [sendTrackingEvent]
+  )
+
+  const handleModalMenuClick = useCallback(
+    (
+      e: React.MouseEvent<Record<string, unknown>>,
+      { modalVariant, dropdownMenuEvent }: ModalMenuClickOptions
+    ) => {
+      // avoid invoking the "onClick" callback on the main dropdown button
+      e.stopPropagation()
+
+      sendTrackingEvent({
+        dropdownMenu: dropdownMenuEvent,
+        dropdownOpen: true,
+      })
+
+      setModal(modalVariant)
+    },
+    [sendTrackingEvent]
+  )
+
+  const handlePortalTemplateClick = useCallback(
+    (
+      e: React.MouseEvent<Record<string, unknown>>,
+      institutionTemplateName: string
+    ) => {
+      // avoid invoking the "onClick" callback on the main dropdown button
+      e.stopPropagation()
+
+      sendTrackingEvent({
+        dropdownMenu: 'institution-template',
+        dropdownOpen: true,
+        institutionTemplateName,
+      })
+    },
+    [sendTrackingEvent]
+  )
+
+  const handleStaticTemplateClick = useCallback(
+    (
+      e: React.MouseEvent<Record<string, unknown>>,
+      templateTrackingKey: string
+    ) => {
+      // avoid invoking the "onClick" callback on the main dropdown button
+      e.stopPropagation()
+
+      sendTrackingEvent({
+        dropdownMenu: templateTrackingKey,
+        dropdownOpen: true,
+      })
+    },
+    [sendTrackingEvent]
+  )
+
   return (
     <>
-      <ControlledDropdown id={id} className={className}>
+      <ControlledDropdown
+        id={id}
+        className={className}
+        onMainButtonClick={handleMainButtonClick}
+      >
         <Dropdown.Toggle
           noCaret
           className="new-project-button"
@@ -38,16 +147,44 @@ function NewProjectButton({
           {buttonText || t('new_project')}
         </Dropdown.Toggle>
         <Dropdown.Menu>
-          <MenuItem onClick={() => setModal('blank_project')}>
+          <MenuItem
+            onClick={e =>
+              handleModalMenuClick(e, {
+                modalVariant: 'blank_project',
+                dropdownMenuEvent: 'blank-project',
+              })
+            }
+          >
             {t('blank_project')}
           </MenuItem>
-          <MenuItem onClick={() => setModal('example_project')}>
+          <MenuItem
+            onClick={e =>
+              handleModalMenuClick(e, {
+                modalVariant: 'example_project',
+                dropdownMenuEvent: 'example-project',
+              })
+            }
+          >
             {t('example_project')}
           </MenuItem>
-          <MenuItem onClick={() => setModal('upload_project')}>
+          <MenuItem
+            onClick={e =>
+              handleModalMenuClick(e, {
+                modalVariant: 'upload_project',
+                dropdownMenuEvent: 'upload-project',
+              })
+            }
+          >
             {t('upload_project')}
           </MenuItem>
-          <MenuItem onClick={() => setModal('import_from_github')}>
+          <MenuItem
+            onClick={e =>
+              handleModalMenuClick(e, {
+                modalVariant: 'import_from_github',
+                dropdownMenuEvent: 'import-from-github',
+              })
+            }
+          >
             {t('import_from_github')}
           </MenuItem>
           {portalTemplates?.length > 0 ? (
@@ -60,6 +197,9 @@ function NewProjectButton({
                 <MenuItem
                   key={`portal-template-${index}`}
                   href={`${portalTemplate.url}#templates`}
+                  onClick={e =>
+                    handlePortalTemplateClick(e, portalTemplate.name)
+                  }
                 >
                   {portalTemplate.name}
                 </MenuItem>
@@ -72,6 +212,9 @@ function NewProjectButton({
             <MenuItem
               key={`new-project-button-template-${index}`}
               href={templateLink.url}
+              onClick={e =>
+                handleStaticTemplateClick(e, templateLink.trackingKey)
+              }
             >
               {templateLink.name === 'view_all'
                 ? t('view_all')

+ 21 - 2
services/web/frontend/js/features/project-list/components/welcome-message.tsx

@@ -1,19 +1,37 @@
+import { useCallback } from 'react'
 import { Col, Row } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
+import { sendMB } from '../../../infrastructure/event-tracking'
 import NewProjectButton from './new-project-button'
 
 export default function WelcomeMessage() {
   const { t } = useTranslation()
 
+  const handleTemplatesClick = useCallback(() => {
+    sendMB('welcome-page-templates-click', {
+      'project-dashboard-react': 'enabled',
+    })
+  }, [])
+
+  const handleLatexHelpClick = useCallback(() => {
+    sendMB('welcome-page-latex-help-click', {
+      'project-dashboard-react': 'enabled',
+    })
+  }, [])
+
   return (
     <div className="card card-thin">
       <div className="welcome text-centered">
         <h2>{t('welcome_to_sl')}</h2>
         <p>
           {t('new_to_latex_look_at')}&nbsp;
-          <a href="/templates">{t('templates').toLowerCase()}</a>
+          <a href="/templates" onClick={handleTemplatesClick}>
+            {t('templates').toLowerCase()}
+          </a>
           &nbsp;{t('or')}&nbsp;
-          <a href="/learn">{t('latex_help_guide')}</a>
+          <a href="/learn" onClick={handleLatexHelpClick}>
+            {t('latex_help_guide')}
+          </a>
         </p>
         <Row>
           <Col md={4} mdOffset={4}>
@@ -21,6 +39,7 @@ export default function WelcomeMessage() {
               <NewProjectButton
                 id="new-project-button-welcome"
                 buttonText={t('create_first_project')}
+                trackingKey="welcome-page-create-first-project-click"
               />
             </div>
           </Col>

+ 30 - 6
services/web/frontend/js/shared/components/controlled-dropdown.tsx

@@ -1,14 +1,38 @@
-import { Children, cloneElement, type FC, isValidElement } from 'react'
+import React, {
+  Children,
+  cloneElement,
+  type FC,
+  isValidElement,
+  useCallback,
+} from 'react'
 import { Dropdown, DropdownProps } from 'react-bootstrap'
 import useDropdown from '../hooks/use-dropdown'
 
-const ControlledDropdown: FC<
-  DropdownProps & { defaultOpen?: boolean }
-> = props => {
-  const dropdownProps = useDropdown(Boolean(props.defaultOpen))
+type ControlledDropdownProps = DropdownProps & {
+  defaultOpen?: boolean
+  onMainButtonClick?: (dropdownOpen: boolean) => void
+}
+
+const ControlledDropdown: FC<ControlledDropdownProps> = ({
+  defaultOpen,
+  onMainButtonClick,
+  ...props
+}) => {
+  const { onClick, ...dropdownProps } = useDropdown(Boolean(defaultOpen))
+
+  const handleClick = useCallback(
+    (e: React.MouseEvent<Dropdown, MouseEvent>) => {
+      onClick(e)
+
+      if (onMainButtonClick) {
+        onMainButtonClick(dropdownProps.open)
+      }
+    },
+    [onClick, onMainButtonClick, dropdownProps.open]
+  )
 
   return (
-    <Dropdown {...props} {...dropdownProps}>
+    <Dropdown {...props} {...dropdownProps} onClick={handleClick}>
       {Children.map(props.children, child => {
         if (!isValidElement(child)) {
           return child

+ 1 - 0
services/web/types/exposed-settings.ts

@@ -1,6 +1,7 @@
 type TemplateLink = {
   name: string
   url: string
+  trackingKey: string
 }
 
 export type ExposedSettings = {