diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts index 48eda0ebfa5..1e0d1e5c073 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/RunHeaderModalContainer/useRunHeaderModalContainer.ts @@ -16,9 +16,15 @@ import { useProtocolDetailsForRun } from '/app/resources/runs' import { getFallbackRobotSerialNumber } from '../utils' import { ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + ANALYTICS_PROTOCOL_RUN_ACTION, useTrackEvent, } from '/app/redux/analytics' +import { + useRobotAnalyticsData, + useTrackProtocolRunEvent, +} from '/app/redux-resources/analytics' import { useRobot, useRobotType } from '/app/redux-resources/robots' + import type { AttachedModule, RunStatus, Run } from '@opentrons/api-client' import type { UseErrorRecoveryResult } from '/app/organisms/ErrorRecoveryFlows' import type { @@ -71,7 +77,9 @@ export function useRunHeaderModalContainer({ const robot = useRobot(robotName) const robotSerialNumber = getFallbackRobotSerialNumber(robot) const trackEvent = useTrackEvent() + const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) const robotType = useRobotType(robotName) + const robotAnalyticsData = useRobotAnalyticsData(robotName) function handleProceedToRunClick(): void { navigate(`/devices/${robotName}/protocol-runs/${runId}/run-preview`) @@ -79,6 +87,10 @@ export function useRunHeaderModalContainer({ name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, properties: { robotSerialNumber }, }) + trackProtocolRunEvent({ + name: ANALYTICS_PROTOCOL_RUN_ACTION.START, + properties: robotAnalyticsData ?? {}, + }) protocolRunControls.play() } diff --git a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts index 31399cbc541..95658999f4a 100644 --- a/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts +++ b/app/src/organisms/Desktop/Devices/ProtocolRun/ProtocolRunHeader/hooks/useRunAnalytics.ts @@ -28,15 +28,12 @@ export function useRunAnalytics({ useEffect(() => { const areReportConditionsValid = - isRunCurrent && - runId != null && - robotAnalyticsData != null && - isTerminalRunStatus(runStatus) + isRunCurrent && runId != null && isTerminalRunStatus(runStatus) if (areReportConditionsValid) { trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.FINISH, - properties: robotAnalyticsData, + properties: robotAnalyticsData ?? undefined, }) } }, [runStatus, isRunCurrent, runId, robotAnalyticsData]) diff --git a/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx b/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx index 5f7d1f8cfc8..5863d70ba93 100644 --- a/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx +++ b/app/src/pages/ODD/ProtocolSetup/__tests__/ProtocolSetup.test.tsx @@ -576,7 +576,6 @@ describe('ProtocolSetup', () => { render(`/runs/${RUN_ID}/setup/`) fireEvent.click(screen.getByRole('button', { name: 'play' })) - expect(mockTrackProtocolRunEvent).toBeCalledTimes(1) expect(mockTrackProtocolRunEvent).toHaveBeenCalledWith({ name: ANALYTICS_PROTOCOL_RUN_ACTION.START, properties: {}, diff --git a/app/src/pages/ODD/ProtocolSetup/index.tsx b/app/src/pages/ODD/ProtocolSetup/index.tsx index 25c978e6717..1df659c633b 100644 --- a/app/src/pages/ODD/ProtocolSetup/index.tsx +++ b/app/src/pages/ODD/ProtocolSetup/index.tsx @@ -741,11 +741,19 @@ export function ProtocolSetup(): JSX.Element { robotType, protocolName ) + + const { trackProtocolRunEvent } = useTrackProtocolRunEvent(runId, robotName) + const robotAnalyticsData = useRobotAnalyticsData(robotName) + const handleProceedToRunClick = (): void => { trackEvent({ name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, properties: { robotSerialNumber }, }) + trackProtocolRunEvent({ + name: ANALYTICS_PROTOCOL_RUN_ACTION.START, + properties: robotAnalyticsData ?? {}, + }) play() } const configBypassHeaterShakerAttachmentConfirmation = useSelector( diff --git a/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx b/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx index 3172c8d1fbc..f769dc005c4 100644 --- a/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx +++ b/app/src/redux-resources/analytics/hooks/__tests__/useTrackProtocolRunEvent.test.tsx @@ -2,7 +2,7 @@ import type * as React from 'react' import { createStore } from 'redux' import { Provider } from 'react-redux' import { QueryClient, QueryClientProvider } from 'react-query' -import { vi, it, expect, describe, beforeEach, afterEach } from 'vitest' +import { vi, it, expect, describe, beforeEach } from 'vitest' import { when } from 'vitest-when' import { waitFor, renderHook } from '@testing-library/react' @@ -63,10 +63,6 @@ describe('useTrackProtocolRunEvent hook', () => { }) }) - afterEach(() => { - vi.resetAllMocks() - }) - it('returns trackProtocolRunEvent function', () => { const { result } = renderHook( () => useTrackProtocolRunEvent(RUN_ID, ROBOT_NAME), @@ -92,7 +88,7 @@ describe('useTrackProtocolRunEvent hook', () => { ) expect(mockTrackEvent).toHaveBeenCalledWith({ name: ANALYTICS_PROTOCOL_RUN_ACTION.START, - properties: PROTOCOL_PROPERTIES, + properties: { ...PROTOCOL_PROPERTIES, transactionId: RUN_ID }, }) }) diff --git a/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts b/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts index 2f9f085fd64..05c3ce16746 100644 --- a/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts +++ b/app/src/redux-resources/analytics/hooks/useTrackProtocolRunEvent.ts @@ -34,6 +34,9 @@ export function useTrackProtocolRunEvent( ...properties, ...protocolRunAnalyticsData, runTime, + // It's sometimes unavoidable (namely on the desktop app) to prevent sending an event multiple times. + // In these circumstances, we need an idempotency key to accurately filter events in Mixpanel. + transactionId: runId, }, }) })