diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 249b2b1891..d6e1112536 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -33,6 +33,7 @@ import DictionarySettingsModal from './settings/editor-settings/dictionary-setti import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import OLIconButton from '@/features/ui/components/ol/ol-icon-button' import { useChatContext } from '@/features/chat/context/chat-context' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' type RailElement = { icon: AvailableUnfilledIcon @@ -76,6 +77,7 @@ const RAIL_MODALS: { ] export const RailLayout = () => { + const { sendEvent } = useEditorAnalytics() const { t } = useTranslation() const { activeModal, @@ -147,16 +149,20 @@ export const RailLayout = () => { key: 'settings', icon: 'settings', title: t('settings'), - action: () => setLeftMenuShown(true), + action: () => { + sendEvent('rail-click', { tab: 'settings' }) + setLeftMenuShown(true) + }, }, ], - [setLeftMenuShown, t] + [setLeftMenuShown, t, sendEvent] ) const onTabSelect = useCallback( (key: string | null) => { if (key === selectedTab) { togglePane() + sendEvent('rail-click', { tab: key, type: 'toggle' }) } else { // HACK: Apparently the onSelect event is triggered with href attributes // from DropdownItems @@ -164,15 +170,17 @@ export const RailLayout = () => { // Attempting to open a non-existent tab return } + const keyOrDefault = key ?? 'file-tree' // Change the selected tab and make sure it's open - openTab((key ?? 'file-tree') as RailTabKey) + openTab(keyOrDefault as RailTabKey) + sendEvent('rail-click', { tab: keyOrDefault }) if (key === 'chat') { markMessagesAsRead() } } }, - [openTab, togglePane, selectedTab, railTabs, markMessagesAsRead] + [openTab, togglePane, selectedTab, railTabs, sendEvent, markMessagesAsRead] ) const isReviewPanelOpen = selectedTab === 'review-panel' diff --git a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx index dd063fc115..6942674de5 100644 --- a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx @@ -14,6 +14,7 @@ import { import Notification from '@/shared/components/notification' import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' import { Trans, useTranslation } from 'react-i18next' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' export const IdeRedesignSwitcherModal = () => { const { t } = useTranslation() @@ -66,13 +67,18 @@ const SwitcherModalContentEnabled: FC = ({ loading, }) => { const { t } = useTranslation() + const { sendEvent } = useEditorAnalytics() const disable = useCallback(() => { + sendEvent('editor-redesign-toggle', { + action: 'disable', + location: 'modal', + }) setEditorRedesignStatus(false) .then(hide) .catch(() => { // do nothing, we're already showing the error }) - }, [setEditorRedesignStatus, hide]) + }, [setEditorRedesignStatus, hide, sendEvent]) return ( <> @@ -116,13 +122,18 @@ const SwitcherModalContentDisabled: FC = ({ loading, }) => { const { t } = useTranslation() + const { sendEvent } = useEditorAnalytics() const enable = useCallback(() => { + sendEvent('editor-redesign-toggle', { + action: 'enable', + location: 'modal', + }) setEditorRedesignStatus(true) .then(hide) .catch(() => { // do nothing, we're already showing the error }) - }, [setEditorRedesignStatus, hide]) + }, [setEditorRedesignStatus, hide, sendEvent]) return ( <> diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx index 19d4905d22..0ca4531e2f 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx @@ -9,10 +9,10 @@ import { } from '@/shared/context/layout-context' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import * as eventTracking from '../../../../infrastructure/event-tracking' import useEventListener from '@/shared/hooks/use-event-listener' import { DetachRole } from '@/shared/context/detach-context' import { Spinner } from 'react-bootstrap' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf' @@ -87,6 +87,7 @@ const LayoutDropdownItem = ({ } export default function ChangeLayoutOptions() { + const { sendEvent } = useEditorAnalytics() const { reattach, detach, @@ -99,16 +100,16 @@ export default function ChangeLayoutOptions() { const handleDetach = useCallback(() => { detach() - eventTracking.sendMB('project-layout-detach') - }, [detach]) + sendEvent('project-layout-detach') + }, [detach, sendEvent]) const handleReattach = useCallback(() => { if (detachRole !== 'detacher') { return } reattach() - eventTracking.sendMB('project-layout-reattach') - }, [detachRole, reattach]) + sendEvent('project-layout-reattach') + }, [detachRole, reattach, sendEvent]) // reattach when the PDF pane opens useEventListener('ui:pdf-open', handleReattach) @@ -117,12 +118,12 @@ export default function ChangeLayoutOptions() { (newLayout: IdeLayout, newView?: IdeView) => { handleReattach() changeLayout(newLayout, newView) - eventTracking.sendMB('project-layout-change', { + sendEvent('project-layout-change', { layout: newLayout, view: newView, }) }, - [changeLayout, handleReattach] + [changeLayout, handleReattach, sendEvent] ) const { t } = useTranslation() diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx index ca23de7fce..e08cf8873a 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx @@ -101,6 +101,7 @@ const CommandDropdownChild = ({ item }: { item: Entry }) => { if (isTaggedCommand(item)) { return ( { const { t } = useTranslation() @@ -210,6 +211,7 @@ export const ToolbarMenuBar = () => { Editor settings { className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued" > - + { @@ -263,14 +273,19 @@ export const ToolbarMenuBar = () => { const SwitchToOldEditorMenuBarOption = () => { const { loading, error, setEditorRedesignStatus } = useSwitchEnableNewEditorState() + const { sendEvent } = useEditorAnalytics() const disable: MouseEventHandler = useCallback( event => { // Don't close the dropdown event.stopPropagation() + sendEvent('editor-redesign-toggle', { + action: 'disable', + location: 'menu-bar', + }) setEditorRedesignStatus(false) }, - [setEditorRedesignStatus] + [setEditorRedesignStatus, sendEvent] ) let icon = null if (loading) { @@ -280,6 +295,7 @@ const SwitchToOldEditorMenuBarOption = () => { } return ( { const action = view === 'history' ? 'close' : 'open' - eventTracking.sendMB('navigation-clicked-history', { action }) + sendEvent('navigation-clicked-history', { action }) setView(view === 'history' ? 'editor' : 'history') - }, [view, setView]) + }, [view, setView, sendEvent]) return (
diff --git a/services/web/frontend/js/infrastructure/event-tracking.ts b/services/web/frontend/js/infrastructure/event-tracking.ts index a2b65a1ce4..6fbe93a53a 100644 --- a/services/web/frontend/js/infrastructure/event-tracking.ts +++ b/services/web/frontend/js/infrastructure/event-tracking.ts @@ -1,7 +1,7 @@ import sessionStorage from './session-storage' import getMeta from '@/utils/meta' -type Segmentation = Record< +export type Segmentation = Record< string, string | number | boolean | undefined | unknown | any // TODO: RecurlyError > diff --git a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx index 692f555ea7..9eaa9fb26a 100644 --- a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx +++ b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx @@ -1,7 +1,8 @@ import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item' import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useNestableDropdown } from '@/shared/hooks/use-nestable-dropdown' -import { MouseEventHandler, ReactNode } from 'react' +import { MouseEventHandler, ReactNode, useCallback } from 'react' type MenuBarOptionProps = { title: string @@ -11,18 +12,30 @@ type MenuBarOptionProps = { href?: string target?: string rel?: string + eventKey?: string } export const MenuBarOption = ({ title, - onClick, + onClick: clickHandler, href, disabled, trailingIcon, target, rel, + eventKey, }: MenuBarOptionProps) => { const { setSelected } = useNestableDropdown() + const { sendEvent } = useEditorAnalytics() + const onClick: MouseEventHandler = useCallback( + e => { + if (eventKey) { + sendEvent('menu-bar-option-click', { key: eventKey }) + } + return clickHandler?.(e) + }, + [clickHandler, eventKey, sendEvent] + ) return ( { + const editorRedesign = useIsNewEditorEnabled() + + const populateSegmentation = useCallback( + (segmentation: Segmentation | undefined = {}): Segmentation => { + return editorRedesign + ? { ...segmentation, 'editor-redesign': 'enabled' } + : segmentation + }, + [editorRedesign] + ) + + const sendEvent: typeof sendMB = useCallback( + (key, segmentation) => { + sendMB(key, populateSegmentation(segmentation)) + }, + [populateSegmentation] + ) + + const sendEventOnce: typeof sendMBOnce = useCallback( + (key, segmentation) => { + sendMBOnce(key, populateSegmentation(segmentation)) + }, + [populateSegmentation] + ) + + const sendEventSampled: typeof sendMBSampled = useCallback( + (key, segmentation, rate) => { + sendMBSampled(key, populateSegmentation(segmentation), rate) + }, + [populateSegmentation] + ) + + return { sendEvent, sendEventOnce, sendEventSampled } +}