Skip to content

Commit 7232cc9

Browse files
authored
feat(app): Add livestream duration event (#20094)
Closes EXEC-2049
1 parent f9237ea commit 7232cc9

File tree

5 files changed

+111
-6
lines changed

5 files changed

+111
-6
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { useEffect, useState } from 'react'
2+
3+
import { FLEX_ROBOT_TYPE } from '@opentrons/shared-data'
4+
5+
import { isTerminalRunStatus } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/utils'
6+
import {
7+
SOURCE_RUN_RECORD,
8+
useCameraAnalytics,
9+
} from '/app/redux-resources/analytics'
10+
11+
import type { RunStatus } from '@opentrons/api-client'
12+
13+
export function useReportWindowDurationEvent(
14+
runId: string | null,
15+
runStatus: RunStatus | null,
16+
isLivestreamViewable: boolean
17+
): void {
18+
const [streamStartTime, setStreamStartTime] = useState<number | null>(null)
19+
const { reportLiveFeedDuration } = useCameraAnalytics({
20+
source: SOURCE_RUN_RECORD,
21+
robotType: FLEX_ROBOT_TYPE,
22+
})
23+
24+
// On initial render or at the start of a new run, start the timer if the
25+
// livestream is viewable.
26+
useEffect(() => {
27+
if (
28+
runId != null &&
29+
streamStartTime === null &&
30+
!isTerminalRunStatus(runStatus) &&
31+
isLivestreamViewable
32+
) {
33+
const startTime = new Date().getTime()
34+
setStreamStartTime(startTime)
35+
}
36+
}, [streamStartTime, runStatus, isLivestreamViewable, runId])
37+
38+
// If a run is terminal, we block livestream viewing, so send the event if the
39+
// stream was enabled.
40+
useEffect(() => {
41+
if (
42+
runId != null &&
43+
isTerminalRunStatus(runStatus) &&
44+
streamStartTime !== null
45+
) {
46+
reportLiveFeedDuration({
47+
runId,
48+
durationSeconds: getDurationSeconds(streamStartTime),
49+
})
50+
setStreamStartTime(null)
51+
}
52+
}, [reportLiveFeedDuration, runId, runStatus, streamStartTime])
53+
54+
// If the user closes the window while a run is not terminal, report it if
55+
// the stream was enabled.
56+
useEffect(() => {
57+
const handleBeforeUnload = (): void => {
58+
if (
59+
runId != null &&
60+
!isTerminalRunStatus(runStatus) &&
61+
streamStartTime !== null
62+
) {
63+
reportLiveFeedDuration({
64+
runId,
65+
durationSeconds: getDurationSeconds(streamStartTime),
66+
})
67+
}
68+
}
69+
70+
window.addEventListener('beforeunload', handleBeforeUnload)
71+
72+
return () => {
73+
window.removeEventListener('beforeunload', handleBeforeUnload)
74+
}
75+
}, [reportLiveFeedDuration, runId, runStatus, streamStartTime])
76+
}
77+
78+
const getDurationSeconds = (runStartTime: number): number =>
79+
(new Date().getTime() - runStartTime) / 1000

app/src/pages/Desktop/LivestreamViewer/index.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
44
import { Chip } from '@opentrons/components'
55

66
import { useHlsVideo } from '/app/pages/Desktop/LivestreamViewer/hooks/useHlsVideo'
7+
import { useReportWindowDurationEvent } from '/app/pages/Desktop/LivestreamViewer/hooks/useReportWindowDurationEvent'
78
import {
89
LivestreamInfoScreen,
910
useLivestreamInfoScreen,
@@ -44,6 +45,12 @@ export function LivestreamViewer(): JSX.Element {
4445
videoError
4546
)
4647

48+
useReportWindowDurationEvent(
49+
retainedRunId,
50+
runStatus,
51+
cameraData?.liveStreamEnabled ?? false
52+
)
53+
4754
return (
4855
<div className={styles.container}>
4956
<div className={styles.video_container}>

app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ describe('ProtocolSetup', () => {
351351
reportPhotoAccessUsage: vi.fn(),
352352
reportImageCaptureUsage: vi.fn(),
353353
reportLiveFeedUsage: vi.fn(),
354+
reportLiveFeedDuration: vi.fn(),
354355
})
355356
vi.mocked(useScrollPosition).mockReturnValue({
356357
isScrolled: false,

app/src/redux-resources/analytics/hooks/useCameraAnalytics.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
ANALYTICS_CAMERA_ENABLEMENT_KIND,
33
ANALYTICS_CAMERA_SETTINGS_KIND,
44
ANALYTICS_IMAGE_CAPTURE_KIND,
5+
ANALYTICS_LIVE_FEED_DURATION,
56
ANALYTICS_LIVE_FEED_KIND,
67
ANALYTICS_PHOTO_ACCESS,
78
useTrackEvent,
@@ -36,6 +37,9 @@ interface CaptureParams {
3637
interface LiveFeedParams {
3738
runId: string
3839
}
40+
interface LiveFeedDurationParams extends LiveFeedParams {
41+
durationSeconds: number
42+
}
3943
interface MediaAccessParams {
4044
action: 'download' | 'downloadZip' | 'delete' | 'storageWarning'
4145
transactionId?: string
@@ -53,6 +57,8 @@ export interface UseCameraUsageAnalyticsResult {
5357
/* Reports how often images are downloaded together, seperately,
5458
how often images are deleted, and how often storage warnings appear. */
5559
reportPhotoAccessUsage: (data: MediaAccessParams) => void
60+
/* Reports the duration that the livestream was viewed for a particular session. */
61+
reportLiveFeedDuration: (data: LiveFeedDurationParams) => void
5662
}
5763

5864
export function useCameraAnalytics({
@@ -90,7 +96,6 @@ export function useCameraAnalytics({
9096
name: ANALYTICS_IMAGE_CAPTURE_KIND,
9197
properties: {
9298
robotType,
93-
source,
9499
transactionId: data.transactionId,
95100
amount: data.amount,
96101
},
@@ -101,13 +106,22 @@ export function useCameraAnalytics({
101106
doTrackEvent({
102107
name: ANALYTICS_LIVE_FEED_KIND,
103108
properties: {
104-
source,
105109
robotType,
106110
runId: data.runId,
107111
},
108112
})
109113
}
110114

115+
const reportLiveFeedDuration = (data: LiveFeedDurationParams): void => {
116+
doTrackEvent({
117+
name: ANALYTICS_LIVE_FEED_DURATION,
118+
properties: {
119+
runId: data.runId,
120+
duration: `${data.durationSeconds} seconds`,
121+
},
122+
})
123+
}
124+
111125
const reportPhotoAccessUsage = (data: MediaAccessParams): void => {
112126
const baseProperties = {
113127
robotType,
@@ -128,6 +142,7 @@ export function useCameraAnalytics({
128142
reportCameraSettings,
129143
reportImageCaptureUsage,
130144
reportLiveFeedUsage,
145+
reportLiveFeedDuration,
131146
reportPhotoAccessUsage,
132147
}
133148
}

app/src/redux/analytics/constants.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,10 @@ export const ANALYTICS_CAMERA_ENABLEMENT_KIND: 'cameraEnablementType' =
141141
export const ANALYTICS_CAMERA_SETTINGS_KIND: 'cameraSettingsKind' =
142142
'cameraSettingsKind'
143143

144-
export const ANALYTICS_IMAGE_CAPTURE_KIND: 'imageCaptureKind' =
145-
'imageCaptureKind'
146-
export const ANALYTICS_LIVE_FEED_KIND: 'liveFeed' = 'liveFeed'
147-
export const ANALYTICS_PHOTO_ACCESS: 'photoAccessKind' = 'photoAccessKind'
144+
export const ANALYTICS_IMAGE_CAPTURE_KIND: 'cameraImageCaptureKind' =
145+
'cameraImageCaptureKind'
146+
export const ANALYTICS_LIVE_FEED_KIND: 'cameraLiveFeed' = 'cameraLiveFeed'
147+
export const ANALYTICS_PHOTO_ACCESS: 'cameraPhotoAccessKind' =
148+
'cameraPhotoAccessKind'
149+
export const ANALYTICS_LIVE_FEED_DURATION: 'cameraLiveFeedDuration' =
150+
'cameraLiveFeedDuration'

0 commit comments

Comments
 (0)