Ver Fonte

Additional segment events (#4371)

* changed input params

---------

Co-authored-by: Artem <Artem Slugin>
attemka há 1 ano atrás
pai
commit
87329949bd

+ 14 - 6
packages/atlas/src/MainLayout.tsx

@@ -59,6 +59,17 @@ export const MainLayout: FC = () => {
   const { trackPageView } = useSegmentAnalytics()
 
   useEffect(() => {
+    // had to include this timeout to make sure the page title is updated
+    const trackRequestTimeout = setTimeout(
+      () =>
+        trackPageView(
+          document.title,
+          'viewer',
+          (location.pathname === absoluteRoutes.viewer.ypp() && searchParams.get('referrer')) || undefined
+        ),
+      1000
+    )
+
     if (!atlasConfig.analytics.sentry?.dsn) {
       return
     }
@@ -74,12 +85,9 @@ export const MainLayout: FC = () => {
         stopReplay()
       }
     }
-
-    trackPageView(
-      location.pathname,
-      '',
-      (location.pathname === absoluteRoutes.viewer.ypp() && searchParams.get('referrer')) || undefined
-    )
+    return () => {
+      clearTimeout(trackRequestTimeout)
+    }
   }, [location.pathname, trackPageView, searchParams])
 
   const { clearOverlays } = useOverlayManager()

+ 4 - 1
packages/atlas/src/components/_auth/SignInModal/SignInModal.tsx

@@ -8,6 +8,7 @@ import { Button } from '@/components/_buttons/Button'
 import { DialogButtonProps } from '@/components/_overlays/Dialog'
 import { atlasConfig } from '@/config'
 import { FAUCET_URL } from '@/config/env'
+import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
 import { MemberId } from '@/joystream-lib/types'
 import { hapiBnToTokenNumber } from '@/joystream-lib/utils'
 import { useJoystream } from '@/providers/joystream/joystream.hooks'
@@ -56,6 +57,7 @@ export const SignInModal: FC = () => {
   )
 
   const { displaySnackbar } = useSnackbar()
+  const { identifyUser } = useSegmentAnalytics()
   const { walletStatus, refetchUserMemberships, setActiveUser, isLoggedIn } = useUser()
   const { signInModalOpen, setSignInModalOpen } = useUserStore(
     (state) => ({ signInModalOpen: state.signInModalOpen, setSignInModalOpen: state.actions.setSignInModalOpen }),
@@ -132,6 +134,7 @@ export const SignInModal: FC = () => {
           const lastCreatedMembership = data.memberships[data.memberships.length - 1]
           if (lastCreatedMembership) {
             setActiveUser({ accountId: selectedAddress, memberId: lastCreatedMembership.id, channelId: null })
+            identifyUser(lastCreatedMembership.id)
             displaySnackbar({
               title: 'Your membership has been created',
               description: 'Browse, watch, create, collect videos across the platform and have fun!',
@@ -219,11 +222,11 @@ export const SignInModal: FC = () => {
       displaySnackbar,
       goToNextStep,
       goToPreviousStep,
+      identifyUser,
       joystream,
       refetchUserMemberships,
       selectedAddress,
       setActiveUser,
-      setPreviouslyFailedData,
       setSignInModalOpen,
     ]
   )

+ 4 - 1
packages/atlas/src/components/_overlays/MemberDropdown/MemberDropdown.tsx

@@ -7,6 +7,7 @@ import { useTransition } from 'react-spring'
 import useResizeObserver from 'use-resize-observer'
 
 import { absoluteRoutes } from '@/config/routes'
+import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
 import { getMemberAvatar } from '@/providers/assets/assets.helpers'
 import { useSubscribeAccountBalance } from '@/providers/joystream/joystream.hooks'
 import { useUser } from '@/providers/user/user.hooks'
@@ -30,6 +31,7 @@ export const MemberDropdown = forwardRef<HTMLDivElement, MemberDropdownProps>(
     const navigate = useNavigate()
     const { channelId, activeMembership, memberships, signOut, setActiveUser, setSignInModalOpen, membershipsLoading } =
       useUser()
+    const { identifyUser } = useSegmentAnalytics()
     const [showWithdrawDialog, setShowWithdrawDialog] = useState(false)
     const [disableScrollDuringAnimation, setDisableScrollDuringAnimation] = useState(false)
 
@@ -77,13 +79,14 @@ export const MemberDropdown = forwardRef<HTMLDivElement, MemberDropdownProps>(
     const handleMemberChange = useCallback(
       (memberId: string, accountId: string, channelId: string | null) => {
         setActiveUser({ accountId, memberId, channelId })
+        identifyUser(memberId)
         setIsList(false)
 
         if (publisher) {
           navigate(absoluteRoutes.studio.index())
         }
       },
-      [navigate, publisher, setActiveUser]
+      [identifyUser, navigate, publisher, setActiveUser]
     )
 
     const handleAddNewChannel = useCallback(() => {

+ 42 - 12
packages/atlas/src/components/_video/VideoPlayer/VideoPlayer.tsx

@@ -20,6 +20,7 @@ import { SvgActionClose, SvgControlsCaptionsOutline, SvgControlsCaptionsSolid }
 import { Avatar } from '@/components/Avatar'
 import { absoluteRoutes } from '@/config/routes'
 import { useMediaMatch } from '@/hooks/useMediaMatch'
+import { useSegmentAnalytics, videoPlaybackParams } from '@/hooks/useSegmentAnalytics'
 import { usePersonalDataStore } from '@/providers/personalData'
 import { isMobile } from '@/utils/browser'
 import { ConsoleLogger, SentryLogger } from '@/utils/logs'
@@ -133,6 +134,12 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
   const [isSharingOverlayOpen, setIsSharingOverlayOpen] = useState(false)
   const { height: playerHeight = 0 } = useResizeObserver({ box: 'border-box', ref: playerRef })
   const customControlsRef = useRef<HTMLDivElement>(null)
+  const {
+    trackVideoPlaybackResumed,
+    trackVideoPlaybackPaused,
+    trackVideoPlaybackStarted,
+    trackVideoPlaybackCompleted,
+  } = useSegmentAnalytics()
 
   const customControlsOffset =
     ((customControlsRef.current?.getBoundingClientRect().top || 0) -
@@ -177,6 +184,18 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
     )
   }, [availableTextTracks, captionsLanguage, storedLanguageExists])
 
+  const videoPlaybackData = useMemo(
+    (): videoPlaybackParams => ({
+      videoId: videoId ?? 'no data',
+      channelId: video?.channel.id ?? 'no data',
+      title: video?.title ?? 'no data',
+      totalLength: video?.duration ?? -1,
+      fullScreen: isFullScreen,
+      quality: video?.mediaMetadata?.pixelHeight?.toString() ?? '1',
+    }),
+    [videoId, video?.channel.id, video?.title, video?.duration, isFullScreen, video?.mediaMetadata?.pixelHeight]
+  )
+
   const playVideo = useCallback(
     async (player: VideoJsPlayer | null, withIndicator?: boolean, callback?: () => void) => {
       if (!player) {
@@ -186,6 +205,9 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
       try {
         setNeedsManualPlay(false)
         const playPromise = await player.play()
+        if (playPromise) {
+          trackVideoPlaybackResumed(videoPlaybackData)
+        }
         if (playPromise && callback) callback()
       } catch (error) {
         if (error.name === 'AbortError') {
@@ -204,17 +226,21 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
         }
       }
     },
-    [videoId, videoJsConfig.videoUrls]
+    [trackVideoPlaybackResumed, videoId, videoPlaybackData, videoJsConfig.videoUrls]
   )
 
-  const pauseVideo = useCallback((player: VideoJsPlayer | null, withIndicator?: boolean, callback?: () => void) => {
-    if (!player) {
-      return
-    }
-    withIndicator && player.trigger(CustomVideojsEvents.PauseControl)
-    callback?.()
-    player.pause()
-  }, [])
+  const pauseVideo = useCallback(
+    (player: VideoJsPlayer | null, withIndicator?: boolean, callback?: () => void) => {
+      if (!player) {
+        return
+      }
+      withIndicator && player.trigger(CustomVideojsEvents.PauseControl)
+      callback?.()
+      player.pause()
+      trackVideoPlaybackPaused(videoPlaybackData)
+    },
+    [trackVideoPlaybackPaused, videoPlaybackData]
+  )
 
   useEffect(() => {
     if (!isVideoPending) {
@@ -335,12 +361,13 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
     }
     const handler = () => {
       setPlayerState('ended')
+      trackVideoPlaybackCompleted(videoPlaybackData)
     }
     player.on('ended', handler)
     return () => {
       player.off('ended', handler)
     }
-  }, [nextVideo, player])
+  }, [nextVideo, player, trackVideoPlaybackCompleted, videoPlaybackData])
 
   // handle loadstart
   useEffect(() => {
@@ -367,13 +394,14 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
         .then(() => {
           onAddVideoView?.()
           setIsPlaying(true)
+          trackVideoPlaybackStarted(videoPlaybackData)
         })
         .catch((e) => {
           setNeedsManualPlay(true)
           ConsoleLogger.warn('Video autoplay failed', e)
         })
     }
-  }, [player, isLoaded, autoplay, onAddVideoView])
+  }, [player, isLoaded, autoplay, onAddVideoView, trackVideoPlaybackStarted, videoPlaybackData])
 
   // handle playing and pausing from outside the component
   useEffect(() => {
@@ -382,10 +410,12 @@ const VideoPlayerComponent: ForwardRefRenderFunction<HTMLVideoElement, VideoPlay
     }
     if (playing) {
       playVideo(player)
+      trackVideoPlaybackResumed(videoPlaybackData)
     } else {
       player.pause()
+      trackVideoPlaybackPaused(videoPlaybackData)
     }
-  }, [playVideo, player, playing])
+  }, [playVideo, player, playing, trackVideoPlaybackPaused, trackVideoPlaybackResumed, videoPlaybackData])
 
   // handle playing and pausing
   useEffect(() => {

+ 57 - 9
packages/atlas/src/hooks/useSegmentAnalytics.ts

@@ -2,9 +2,25 @@ import { useCallback } from 'react'
 
 import useSegmentAnalyticsContext from '@/providers/segmentAnalytics/useSegmentAnalyticsContext'
 
+export type videoPlaybackParams = {
+  videoId: string
+  channelId: string
+  title: string
+  totalLength: number
+  fullScreen: boolean
+  quality: string
+}
+
 export const useSegmentAnalytics = () => {
   const { analytics } = useSegmentAnalyticsContext()
 
+  const identifyUser = useCallback(
+    (email: string) => {
+      analytics.identify({ email })
+    },
+    [analytics]
+  )
+
   const trackPageView = useCallback(
     (name: string, category = 'App', referrer = 'no data') => {
       analytics.page(category, name, {
@@ -47,14 +63,37 @@ export const useSegmentAnalytics = () => {
     [analytics]
   )
 
-  const trackVideoView = useCallback(
-    (videoId: string, channelId: string, channelTitle: string, description: string, isNft: boolean) => {
-      analytics.track('video viewed', {
-        videoId,
-        channelId,
-        channelTitle,
-        description,
-        isNft,
+  const trackVideoPlaybackStarted = useCallback(
+    (params: videoPlaybackParams) => {
+      analytics.track('video playback started', {
+        ...params,
+      })
+    },
+    [analytics]
+  )
+
+  const trackVideoPlaybackPaused = useCallback(
+    (params: videoPlaybackParams) => {
+      analytics.track('video playback paused', {
+        ...params,
+      })
+    },
+    [analytics]
+  )
+
+  const trackVideoPlaybackResumed = useCallback(
+    (params: videoPlaybackParams) => {
+      analytics.track('video playback resumed', {
+        ...params,
+      })
+    },
+    [analytics]
+  )
+
+  const trackVideoPlaybackCompleted = useCallback(
+    (params: videoPlaybackParams) => {
+      analytics.track('video playback completed', {
+        ...params,
       })
     },
     [analytics]
@@ -129,12 +168,20 @@ export const useSegmentAnalytics = () => {
     [analytics]
   )
 
+  const trackYppSignInButtonClick = useCallback(() => {
+    analytics.track('YPP Landing Sign In w Google Clicked')
+  }, [analytics])
+
   return {
+    identifyUser,
     trackPageView,
     trackYppOptIn,
     trackAccountCreation,
     trackChannelCreation,
-    trackVideoView,
+    trackVideoPlaybackStarted,
+    trackVideoPlaybackPaused,
+    trackVideoPlaybackResumed,
+    trackVideoPlaybackCompleted,
     trackVideoUpload,
     trackNftMint,
     trackNftSale,
@@ -142,5 +189,6 @@ export const useSegmentAnalytics = () => {
     trackLikeAdded,
     trackDislikeAdded,
     trackChannelFollow,
+    trackYppSignInButtonClick,
   }
 }

+ 1 - 2
packages/atlas/src/providers/uploads/uploads.manager.tsx

@@ -73,9 +73,8 @@ export const UploadsManager: FC = () => {
             actionText: 'See on Joystream',
             onActionClick: () => openInNewTab(absoluteRoutes.viewer.video(video.parentObject.id), true),
           })
+          trackVideoUpload(video.parentObject?.title ?? 'no data', channelId ?? 'no data')
         }
-
-        trackVideoUpload(video.parentObject?.title ?? 'no data', channelId ?? 'no data')
       })
       videoAssetsRef.current = videoAssets
     }

+ 13 - 1
packages/atlas/src/views/global/YppLandingView/YppLandingView.tsx

@@ -10,6 +10,7 @@ import { YppReferralBanner } from '@/components/_ypp/YppReferralBanner'
 import { atlasConfig } from '@/config'
 import { absoluteRoutes } from '@/config/routes'
 import { useHeadTags } from '@/hooks/useHeadTags'
+import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
 import { useSnackbar } from '@/providers/snackbars'
 import { useUser } from '@/providers/user/user.hooks'
 import { useYppStore } from '@/providers/ypp/ypp.store'
@@ -34,6 +35,7 @@ export const YppLandingView: FC = () => {
   const { setSelectedChannelId, setShouldContinueYppFlow } = useYppStore((store) => store.actions)
   const { displaySnackbar } = useSnackbar()
   const navigate = useNavigate()
+  const { trackYppSignInButtonClick } = useSegmentAnalytics()
   const selectedChannelTitle = activeMembership?.channels.find((channel) => channel.id === channelId)?.title
   const { data } = useQuery('ypp-quota-fetch', () =>
     axios
@@ -68,6 +70,7 @@ export const YppLandingView: FC = () => {
     }
 
     if (!isLoggedIn) {
+      trackYppSignInButtonClick()
       await signIn()
       setWasSignInTriggered(true)
       return
@@ -82,7 +85,16 @@ export const YppLandingView: FC = () => {
       setCurrentStep('requirements')
       return
     }
-  }, [currentStep, displaySnackbar, isLoggedIn, isTodaysQuotaReached, isYppSigned, navigate, signIn])
+  }, [
+    currentStep,
+    displaySnackbar,
+    isLoggedIn,
+    isTodaysQuotaReached,
+    isYppSigned,
+    navigate,
+    signIn,
+    trackYppSignInButtonClick,
+  ])
 
   useEffect(() => {
     // rerun handleYppSignUpClick after sign in flow

+ 9 - 0
packages/atlas/src/views/studio/StudioLayout.tsx

@@ -14,6 +14,7 @@ import { SidenavStudio } from '@/components/_navigation/SidenavStudio'
 import { TopbarStudio } from '@/components/_navigation/TopbarStudio'
 import { atlasConfig } from '@/config'
 import { absoluteRoutes, relativeRoutes } from '@/config/routes'
+import { useSegmentAnalytics } from '@/hooks/useSegmentAnalytics'
 import { useConfirmationModal } from '@/providers/confirmationModal'
 import { ConnectionStatusManager, useConnectionStatusStore } from '@/providers/connectionStatus'
 import { UploadsManager } from '@/providers/uploads/uploads.manager'
@@ -49,6 +50,7 @@ const StudioLayout = () => {
 
   const [openUnsupportedBrowserDialog, closeUnsupportedBrowserDialog] = useConfirmationModal()
   const [enterLocation] = useState(location.pathname)
+  const { trackPageView } = useSegmentAnalytics()
   const isMembershipLoaded = !membershipsLoading && !isAuthLoading && !isWalletLoading
   const hasMembership = !!memberships?.length
 
@@ -77,6 +79,13 @@ const StudioLayout = () => {
     }
   }, [closeUnsupportedBrowserDialog, openUnsupportedBrowserDialog])
 
+  useEffect(() => {
+    // had to include this timeout to make sure the page title is updated
+    const trackRequestTimeout = setTimeout(() => trackPageView(document.title, 'studio', undefined), 1000)
+
+    return () => clearTimeout(trackRequestTimeout)
+  }, [location.pathname, trackPageView])
+
   const yppRedirect = useCallback(() => {
     if (!channelSet) {
       return ENTRY_POINT_ROUTE

+ 2 - 13
packages/atlas/src/views/viewer/VideoView/VideoView.tsx

@@ -96,7 +96,7 @@ export const VideoView: FC = () => {
   const nftWidgetProps = useNftWidget(video)
   const { likeOrDislikeVideo } = useReactionTransactions()
   const { withdrawBid } = useNftTransactions()
-  const { trackVideoView, trackLikeAdded, trackDislikeAdded } = useSegmentAnalytics()
+  const { trackLikeAdded, trackDislikeAdded } = useSegmentAnalytics()
 
   const mdMatch = useMediaMatch('md')
   const { addVideoView } = useAddVideoView()
@@ -162,7 +162,6 @@ export const VideoView: FC = () => {
   const channelId = video?.channel?.id
   const channelName = video?.channel?.title
   const videoId = video?.id
-  const videoDescription = video?.description
   const numberOfLikes = video?.reactions.filter(({ reaction }) => reaction === 'LIKE').length
   const numberOfDislikes = video?.reactions.filter(({ reaction }) => reaction === 'UNLIKE').length
   const videoNotAvailable = !loading && !video
@@ -270,8 +269,6 @@ export const VideoView: FC = () => {
     setShareDialogOpen(true)
   }
 
-  const isNft = !!nftWidgetProps
-
   const handleAddVideoView = useCallback(() => {
     if (!videoId || !channelId) {
       return
@@ -283,15 +280,7 @@ export const VideoView: FC = () => {
     }).catch((error) => {
       SentryLogger.error('Failed to increase video views', 'VideoView', error)
     })
-
-    trackVideoView(
-      videoId ?? 'no data',
-      channelId ?? 'no data',
-      channelName ?? 'no data',
-      videoDescription ?? 'no data',
-      isNft
-    )
-  }, [videoId, channelId, addVideoView, trackVideoView, channelName, videoDescription, isNft])
+  }, [videoId, channelId, addVideoView])
 
   if (error) {
     return <ViewErrorFallback />