Skip to content

Commit f969b11

Browse files
authored
feat(app): Mixpanel Analytics for Camera (#19974)
# Overview This PR enables mixpanel tracking for We will track - Camera enablement & settings - Photo usage - Live feed usage - Photo access and management ## Test Plan and Hands on Testing - View analytics of robots in office on mixpanel ## Changelog ## Review requests - Ensure I am not missing any spots in the code base where a camera action can be set. - How do I get livestream open duration time? ## Risk assessment low
1 parent 7ba2feb commit f969b11

File tree

30 files changed

+493
-43
lines changed

30 files changed

+493
-43
lines changed

app/src/organisms/Desktop/Devices/HistoricalProtocolRunDrawer.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,11 @@ import {
3939

4040
import { LegacyOffsetVector } from '/app/molecules/LegacyOffsetVector'
4141
import { downloadFile } from '/app/organisms/Desktop/Devices/utils'
42-
import { useIsFlex } from '/app/redux-resources/robots'
42+
import {
43+
SOURCE_RUN_RECORD,
44+
useCameraAnalytics,
45+
} from '/app/redux-resources/analytics/'
46+
import { useIsFlex, useRobotType } from '/app/redux-resources/robots'
4347
import { useRunGeneratedDataFiles } from '/app/resources/dataFiles/useRunGeneratedDataFiles'
4448
import { useMostRecentCompletedAnalysis } from '/app/resources/runs'
4549

@@ -67,8 +71,9 @@ export function HistoricalProtocolRunDrawer(
6771
(a, b) =>
6872
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
6973
) ?? []
70-
const totalOutputFileCount =
71-
outputFileIds.jpeg.length + outputFileIds.csv.length
74+
const totalImageFileCount = outputFileIds.jpeg.length
75+
const totalOutputFileCount = totalImageFileCount + outputFileIds.csv.length
76+
7277
const runCsvFileIds =
7378
'runTimeParameters' in run
7479
? run.runTimeParameters.reduce<string[]>((acc, parameter) => {
@@ -379,6 +384,11 @@ function ImagesFileDataRow({
379384
robotName: string
380385
}): JSX.Element {
381386
const { t } = useTranslation('run_details')
387+
const robotType = useRobotType(robotName)
388+
const { reportPhotoAccessUsage } = useCameraAnalytics({
389+
source: SOURCE_RUN_RECORD,
390+
robotType: robotType,
391+
})
382392
const { data: imagesZipFile, isLoading } = useAllRunImagesRaw(run.id)
383393
const runTimestamp = format(new Date(run.createdAt), 'M/d/yy_HH:mm:ss')
384394
const buildImagesZipName = (): string =>
@@ -418,6 +428,9 @@ function ImagesFileDataRow({
418428
onClick={() => {
419429
if (imagesZipFile != null) {
420430
downloadFile(imagesZipFile, buildImagesZipName())
431+
reportPhotoAccessUsage({
432+
action: 'downloadZip',
433+
})
421434
}
422435
}}
423436
>

app/src/organisms/Desktop/Devices/HistoricalProtocolRunOverflowMenu.tsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ import { getModalPortalEl } from '/app/App/portal'
4242
import { Divider } from '/app/atoms/structure'
4343
import { useRunControls } from '/app/organisms/RunTimeControl'
4444
import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics'
45-
import { useRobot } from '/app/redux-resources/robots'
45+
import {
46+
SOURCE_RUN_RECORD,
47+
useCameraAnalytics,
48+
} from '/app/redux-resources/analytics/'
49+
import { useRobot, useRobotType } from '/app/redux-resources/robots'
4650
import {
4751
ANALYTICS_PROTOCOL_PROCEED_TO_RUN,
4852
ANALYTICS_PROTOCOL_RUN_ACTION,
@@ -159,9 +163,10 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element {
159163
)
160164
const { deleteRun, isLoading: isDeletingImages } = useDeleteRunMutation()
161165
const robot = useRobot(robotName)
166+
const robotType = useRobotType(robotName)
167+
162168
const robotSerialNumber =
163169
robot?.health?.robot_serial ?? robot?.serverHealth?.serialNumber ?? null
164-
165170
const handleResetClick: MouseEventHandler<HTMLButtonElement> = (e): void => {
166171
e.preventDefault()
167172
e.stopPropagation()
@@ -189,12 +194,19 @@ function MenuDropdown(props: MenuDropdownProps): JSX.Element {
189194
onClose()
190195
})
191196
}
192-
197+
const { reportPhotoAccessUsage } = useCameraAnalytics({
198+
source: SOURCE_RUN_RECORD,
199+
robotType: robotType,
200+
})
193201
const onClearRunImages: MouseEventHandler<HTMLButtonElement> = e => {
194202
handleDeleteRunImagesModal({ onDeleteRunImages })
195203
e.preventDefault()
196204
e.stopPropagation()
197205
closeOverflowMenu(e)
206+
207+
reportPhotoAccessUsage({
208+
action: 'delete',
209+
})
198210
}
199211

200212
return (

app/src/organisms/Desktop/Devices/Peripherals/CameraCard.tsx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import systemCameraFlex from '/app/assets/images/system_camera_flex.png'
1818
import systemCameraOT2 from '/app/assets/images/system_camera_ot2.png'
1919
import { useCameraUsageSettings } from '/app/local-resources/images/hooks/useCameraUsageSettings'
2020
import { CameraControls } from '/app/organisms/Desktop/Camera/CameraControls'
21+
import {
22+
SOURCE_ROBOT_SETTINGS,
23+
useCameraAnalytics,
24+
} from '/app/redux-resources/analytics/'
25+
import { useRobotType } from '/app/redux-resources/robots'
2126
import { useFeatureFlag } from '/app/redux/config'
2227
import { useCurrentRunId } from '/app/resources/runs'
2328

@@ -43,7 +48,7 @@ export function CameraCard({
4348
}
4449

4550
const runId = useCurrentRunId()
46-
51+
const robotType = useRobotType(robotName)
4752
const doesRunExist = runId != null
4853

4954
const cardOverflowWrapperRef = useOnClickOutside<HTMLDivElement>({
@@ -52,12 +57,29 @@ export function CameraCard({
5257
},
5358
})
5459

55-
const { isCameraEnabled, toggleCameraEnabled } = useCameraUsageSettings()
60+
const {
61+
isCameraEnabled,
62+
toggleCameraEnabled,
63+
isLiveVideoEnabled,
64+
isRecoveryCaptureEnabled,
65+
} = useCameraUsageSettings()
5666

5767
const toggleControls = (): void => {
5868
setShowControls(!showControls)
5969
}
6070

71+
const { reportCameraEnablementSettings } = useCameraAnalytics({
72+
source: SOURCE_ROBOT_SETTINGS,
73+
robotType,
74+
})
75+
const handleToggleCamera = (): void => {
76+
toggleCameraEnabled()
77+
reportCameraEnablementSettings({
78+
cameraEnabled: !isCameraEnabled,
79+
liveFeedEnabled: isLiveVideoEnabled,
80+
recoveryCaptureEnabled: isRecoveryCaptureEnabled,
81+
})
82+
}
6183
const navigateToUsageSettings = (): void => {
6284
navigate(`/devices/${robotName}/robot-settings/camera`)
6385
}
@@ -103,7 +125,7 @@ export function CameraCard({
103125
>
104126
<CameraCardOverflowMenu
105127
cameraEnabled={isCameraEnabled}
106-
toggleCameraEnabled={toggleCameraEnabled}
128+
handleToggleCamera={handleToggleCamera}
107129
toggleControls={toggleControls}
108130
navigateToUsageSettings={navigateToUsageSettings}
109131
/>
@@ -120,12 +142,12 @@ export function CameraCard({
120142

121143
function CameraCardOverflowMenu({
122144
cameraEnabled,
123-
toggleCameraEnabled,
145+
handleToggleCamera,
124146
toggleControls,
125147
navigateToUsageSettings,
126148
}: {
127149
cameraEnabled: boolean
128-
toggleCameraEnabled: () => void
150+
handleToggleCamera: () => void
129151
toggleControls: () => void
130152
navigateToUsageSettings: () => void
131153
}): JSX.Element {
@@ -135,7 +157,7 @@ function CameraCardOverflowMenu({
135157
return (
136158
<div className={styles.card_overflow_menu_container}>
137159
<div className={styles.card_overflow_menu_content_container}>
138-
<MenuItem onClick={toggleCameraEnabled}>
160+
<MenuItem onClick={handleToggleCamera}>
139161
{cameraEnabled ? t('disable_camera') : t('enable_camera')}
140162
</MenuItem>
141163
{cameraControlsEnabled && (

app/src/organisms/Desktop/Devices/Peripherals/__tests__/CameraCard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ vi.mock('/app/organisms/Desktop/Camera/CameraControls')
2121
vi.mock('/app/local-resources/images/hooks/useCameraUsageSettings')
2222
vi.mock('/app/resources/runs')
2323
vi.mock('/app/redux/config')
24+
vi.mock('/app/redux/discovery/selectors')
2425

2526
const mockNavigate = vi.fn()
2627
vi.mock('react-router-dom', async () => {

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/ImageGalleryContainer/GalleryContainerOverflowMenu.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ import {
1313
import { useAllRunImagesRaw } from '@opentrons/react-api-client'
1414

1515
import { downloadFile } from '/app/organisms/Desktop/Devices/utils'
16+
import {
17+
SOURCE_RUN_RECORD,
18+
useCameraAnalytics,
19+
} from '/app/redux-resources/analytics/'
20+
import { useRobotType } from '/app/redux-resources/robots'
1621

1722
import styles from './gallery.module.css'
1823

@@ -39,6 +44,12 @@ export function GalleryContainerOverflowMenu({
3944
const [isPendingDownload, setIsPendingDownload] = useState(false)
4045
const { data: imagesZipFile, isLoading } = useAllRunImagesRaw(runId)
4146

47+
const robotType = useRobotType(robotName)
48+
49+
const { reportPhotoAccessUsage } = useCameraAnalytics({
50+
source: SOURCE_RUN_RECORD,
51+
robotType,
52+
})
4253
const formattedRunTs = format(new Date(runTimestamp), 'M/d/yy_HH:mm:ss')
4354
const buildImagesZipName = (): string =>
4455
`${robotName}_${protocolName}_${formattedRunTs}_${t('images')}.zip`
@@ -51,6 +62,9 @@ export function GalleryContainerOverflowMenu({
5162
} else {
5263
setIsPendingDownload(true)
5364
}
65+
reportPhotoAccessUsage({
66+
action: 'downloadZip',
67+
})
5468
}
5569

5670
if (imagesZipFile != null && isPendingDownload) {

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/ImageGalleryContainer/GalleryItemOverflowMenu.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import {
88
} from '@opentrons/components'
99

1010
import { GalleryItemErrorModal } from '/app/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/ImageGalleryContainer/GalleryItemErrorModal'
11+
import {
12+
SOURCE_RUN_RECORD,
13+
useCameraAnalytics,
14+
} from '/app/redux-resources/analytics/'
15+
import { useRobotType } from '/app/redux-resources/robots'
1116

1217
import styles from './gallery.module.css'
1318

@@ -39,14 +44,21 @@ export function GalleryItemOverflowMenu({
3944
} = useMenuHandleClickOutside()
4045

4146
const isErroredCommand = currentCommand?.error != null
47+
const robotType = useRobotType(robotName)
48+
const { reportPhotoAccessUsage } = useCameraAnalytics({
49+
source: SOURCE_RUN_RECORD,
50+
robotType,
51+
})
4252

4353
const onDownloadImage = (): void => {
4454
setShowOverflowMenu(false)
4555
const a = document.createElement('a')
4656
a.download = imageFilename
4757
a.href = imagePath ?? ''
4858
a.click()
49-
59+
reportPhotoAccessUsage({
60+
action: 'download',
61+
})
5062
a.remove()
5163
}
5264

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/LaunchLivestreamBtn.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,32 @@ import { useDispatch } from 'react-redux'
44
import { Icon, SecondaryButton } from '@opentrons/components'
55
import { useHost } from '@opentrons/react-api-client'
66

7+
import {
8+
SOURCE_RUN_RECORD,
9+
useCameraAnalytics,
10+
} from '/app/redux-resources/analytics/'
711
import { cameraStreamOpenAction } from '/app/redux/shell'
812

913
import styles from './runcamera.module.css'
1014

11-
export function LaunchLivestreamBtn({ runId }: { runId: string }): JSX.Element {
12-
const { t } = useTranslation(['run_details', 'branded'])
15+
import type { RobotType } from '@opentrons/shared-data'
16+
17+
export function LaunchLivestreamBtn({
18+
runId,
19+
robotType,
20+
}: {
21+
runId: string
22+
robotType: RobotType
23+
}): JSX.Element {
24+
const { t } = useTranslation('run_details')
1325
const dispatch = useDispatch()
1426
const host = useHost()
1527
const isLaunchCameraEnabled =
1628
host?.robotName != null && host?.hostname != null
17-
29+
const { reportLiveFeedUsage } = useCameraAnalytics({
30+
source: SOURCE_RUN_RECORD,
31+
robotType,
32+
})
1833
const handleOpenCameraStream = (): void => {
1934
dispatch(
2035
cameraStreamOpenAction(
@@ -24,6 +39,9 @@ export function LaunchLivestreamBtn({ runId }: { runId: string }): JSX.Element {
2439
t('branded:livestream_window_title') as string
2540
)
2641
)
42+
reportLiveFeedUsage({
43+
runId: runId,
44+
})
2745
}
2846

2947
return (

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/__tests__/LaunchLivestreamBtn.test.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ vi.mock('react-redux', async () => {
2222
vi.mock('@opentrons/react-api-client')
2323

2424
const render = () => {
25-
return renderWithProviders(<LaunchLivestreamBtn runId="MOCK-RUN-ID" />, {
26-
i18nInstance: i18n,
27-
})
25+
return renderWithProviders(
26+
<LaunchLivestreamBtn runId="MOCK-RUN-ID" robotType="OT-3 Standard" />,
27+
{
28+
i18nInstance: i18n,
29+
}
30+
)
2831
}
2932

3033
describe('LaunchLivestreamBtn', () => {

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunCamera/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export function ProtocolRunCamera({
7171
{!isTerminalRunStatus(runStatus) &&
7272
isCameraEnabled &&
7373
showLivestreamBtn ? (
74-
<LaunchLivestreamBtn runId={runId} />
74+
<LaunchLivestreamBtn runId={runId} robotType={robotType} />
7575
) : null}
7676
</div>
7777
<Divider width="100%" />

app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderContent/ActionButton/hooks/useActionButtonProperties.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import { useAddCameraSettingsToRunMutation } from '@opentrons/react-api-client'
1111

1212
import { useIsHeaterShakerInProtocol } from '/app/organisms/ModuleCard/hooks'
1313
import { useTrackProtocolRunEvent } from '/app/redux-resources/analytics'
14+
import {
15+
SOURCE_RUN_RECORD,
16+
useCameraAnalytics,
17+
} from '/app/redux-resources/analytics/'
18+
import { useRobotType } from '/app/redux-resources/robots'
1419
import {
1520
ANALYTICS_PROTOCOL_PROCEED_TO_RUN,
1621
ANALYTICS_PROTOCOL_RUN_ACTION,
@@ -72,6 +77,11 @@ export function useActionButtonProperties({
7277
const { t } = useTranslation(['run_details', 'shared'])
7378
const { play, pause, reset } = protocolRunControls
7479
const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName)
80+
const robotType = useRobotType(robotName)
81+
const { reportCameraEnablementSettings } = useCameraAnalytics({
82+
source: SOURCE_RUN_RECORD,
83+
robotType: robotType,
84+
})
7585
const isHeaterShakerInProtocol = useIsHeaterShakerInProtocol()
7686
const isHeaterShakerShaking = isAnyHeaterShakerShaking(attachedModules)
7787
const trackEvent = useTrackEvent()
@@ -99,6 +109,12 @@ export function useActionButtonProperties({
99109
? robotAnalyticsData
100110
: {},
101111
})
112+
const { enabled, recoveryEnabled, liveStreamEnabled } = runCameraSettings
113+
reportCameraEnablementSettings({
114+
cameraEnabled: enabled,
115+
liveFeedEnabled: liveStreamEnabled,
116+
recoveryCaptureEnabled: recoveryEnabled,
117+
})
102118
}
103119

104120
if (isProtocolNotReady) {
@@ -142,6 +158,7 @@ export function useActionButtonProperties({
142158
else if (!areCameraPreferencesConfirmed) {
143159
const { enabled, recoveryEnabled, liveStreamEnabled } =
144160
runCameraSettings
161+
145162
addCameraSettingsToRun(
146163
{
147164
runId,

0 commit comments

Comments
 (0)