From 02ba9065c4b776238a46c342187b3600d0548bd2 Mon Sep 17 00:00:00 2001 From: Sarah Breen Date: Fri, 6 Sep 2024 13:21:32 -0400 Subject: [PATCH] feat(app): add mixpanel tracking for quick transfer (#16200) fix PLAT-218 --- app/src/organisms/Devices/hooks/index.ts | 1 + .../hooks/useTrackEventWithRobotSerial.ts | 33 +++++++++++++ .../QuickTransferAdvancedSettings/AirGap.tsx | 15 ++++++ .../QuickTransferAdvancedSettings/BlowOut.tsx | 15 ++++++ .../QuickTransferAdvancedSettings/Delay.tsx | 15 ++++++ .../FlowRate.tsx | 9 ++++ .../QuickTransferAdvancedSettings/Mix.tsx | 15 ++++++ .../PipettePath.tsx | 15 ++++++ .../TipPosition.tsx | 9 ++++ .../TouchTip.tsx | 15 ++++++ .../QuickTransferAdvancedSettings/index.tsx | 11 ++++- .../QuickTransferFlow/SelectDestWells.tsx | 46 +++++++++++-------- .../QuickTransferFlow/SelectSourceWells.tsx | 20 +++++--- .../QuickTransferFlow/SummaryAndSettings.tsx | 35 ++++++++++++-- .../TipManagement/ChangeTip.tsx | 9 ++++ .../TipManagement/TipDropLocation.tsx | 10 ++++ .../QuickTransferFlow/TipManagement/index.tsx | 10 ++++ .../__tests__/SummaryAndSettings.test.tsx | 17 ++++++- .../TipManagement/ChangeTip.test.tsx | 16 +++++++ .../TipManagement/TipDropLocation.test.tsx | 14 ++++++ .../TipManagement/TipManagement.test.tsx | 14 ++++++ app/src/organisms/QuickTransferFlow/index.tsx | 17 ++++++- .../pages/QuickTransferDashboard/index.tsx | 18 +++++++- .../__tests__/QuickTransferDetails.test.tsx | 37 +++++++++++++++ app/src/pages/QuickTransferDetails/index.tsx | 21 +++++++++ app/src/pages/RunSummary/index.tsx | 23 ++++++++-- app/src/redux/analytics/constants.ts | 25 ++++++++++ 27 files changed, 448 insertions(+), 37 deletions(-) create mode 100644 app/src/organisms/Devices/hooks/useTrackEventWithRobotSerial.ts diff --git a/app/src/organisms/Devices/hooks/index.ts b/app/src/organisms/Devices/hooks/index.ts index f8cdeaa68e8..a386b97bec1 100644 --- a/app/src/organisms/Devices/hooks/index.ts +++ b/app/src/organisms/Devices/hooks/index.ts @@ -33,6 +33,7 @@ export * from './useProtocolRunAnalyticsData' export * from './useRobotAnalyticsData' export * from './useTrackCreateProtocolRunEvent' export * from './useTrackProtocolRunEvent' +export * from './useTrackEventWithRobotSerial' export * from './useRunStatuses' export * from './useSyncRobotClock' export * from './useIsLegacySessionInProgress' diff --git a/app/src/organisms/Devices/hooks/useTrackEventWithRobotSerial.ts b/app/src/organisms/Devices/hooks/useTrackEventWithRobotSerial.ts new file mode 100644 index 00000000000..4baa24bbc0a --- /dev/null +++ b/app/src/organisms/Devices/hooks/useTrackEventWithRobotSerial.ts @@ -0,0 +1,33 @@ +import { useSelector } from 'react-redux' +import { useTrackEvent } from '../../../redux/analytics' +import { getLocalRobot, getRobotSerialNumber } from '../../../redux/discovery' + +interface AnalyticsEvent { + name: string + properties: { [key: string]: unknown } +} + +export type TrackEventWithRobotSerial = (event: AnalyticsEvent) => void + +export function useTrackEventWithRobotSerial(): { + trackEventWithRobotSerial: TrackEventWithRobotSerial +} { + const trackEvent = useTrackEvent() + const localRobot = useSelector(getLocalRobot) + const robotSerialNumber = + localRobot?.status != null ? getRobotSerialNumber(localRobot) : null + const trackEventWithRobotSerial: TrackEventWithRobotSerial = ({ + name, + properties, + }) => { + trackEvent({ + name, + properties: { + ...properties, + robotSerial: robotSerialNumber, + }, + }) + } + + return { trackEventWithRobotSerial } +} diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/AirGap.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/AirGap.tsx index d68a48633e7..3e0982ed659 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/AirGap.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/AirGap.tsx @@ -9,10 +9,12 @@ import { COLORS, ALIGN_CENTER, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ACTIONS } from '../constants' import type { @@ -33,6 +35,7 @@ interface AirGapProps { export function AirGap(props: AirGapProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [airGapEnabled, setAirGapEnabled] = React.useState( @@ -79,10 +82,22 @@ export function AirGap(props: AirGapProps): JSX.Element { setCurrentStep(currentStep + 1) } else { dispatch({ type: action, volume: undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `AirGap_${kind}`, + }, + }) onBack() } } else if (currentStep === 2) { dispatch({ type: action, volume: volume ?? undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `AirGap_${kind}`, + }, + }) onBack() } } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx index 236fa86c462..f054999d85a 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/BlowOut.tsx @@ -14,9 +14,11 @@ import { FLEX_SINGLE_SLOT_BY_CUTOUT_ID, TRASH_BIN_ADAPTER_FIXTURE, } from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ChildNavigation } from '../../ChildNavigation' import { ACTIONS } from '../constants' @@ -91,6 +93,7 @@ export const useBlowOutLocationOptions = ( export function BlowOut(props: BlowOutProps): JSX.Element { const { onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] const [isBlowOutEnabled, setisBlowOutEnabled] = React.useState( @@ -134,6 +137,12 @@ export function BlowOut(props: BlowOutProps): JSX.Element { type: ACTIONS.SET_BLOW_OUT, location: undefined, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `BlowOut`, + }, + }) onBack() } else { setCurrentStep(currentStep + 1) @@ -143,6 +152,12 @@ export function BlowOut(props: BlowOutProps): JSX.Element { type: ACTIONS.SET_BLOW_OUT, location: blowOutLocation, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `BlowOut`, + }, + }) onBack() } } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx index f5c362830a0..d51129850be 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Delay.tsx @@ -9,10 +9,12 @@ import { COLORS, ALIGN_CENTER, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ACTIONS } from '../constants' import type { @@ -33,6 +35,7 @@ interface DelayProps { export function Delay(props: DelayProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [currentStep, setCurrentStep] = React.useState(1) @@ -85,6 +88,12 @@ export function Delay(props: DelayProps): JSX.Element { type: action, delaySettings: undefined, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `Delay_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) @@ -100,6 +109,12 @@ export function Delay(props: DelayProps): JSX.Element { positionFromBottom: position, }, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + settting: `Delay_${kind}`, + }, + }) } onBack() } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx index d438b4266c9..f7d6dfcede0 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/FlowRate.tsx @@ -13,11 +13,13 @@ import { LOW_VOLUME_PIPETTES, getTipTypeFromTipRackDefinition, } from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ACTIONS } from '../constants' import type { SupportedTip } from '@opentrons/shared-data' @@ -37,6 +39,7 @@ interface FlowRateEntryProps { export function FlowRateEntry(props: FlowRateEntryProps): JSX.Element { const { onBack, state, dispatch, kind } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [flowRate, setFlowRate] = React.useState( @@ -87,6 +90,12 @@ export function FlowRateEntry(props: FlowRateEntryProps): JSX.Element { type: flowRateAction, rate: flowRate, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `FlowRate_${kind}`, + }, + }) } onBack() } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx index 8821d076d6e..ddc6b55ef59 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/Mix.tsx @@ -9,10 +9,12 @@ import { COLORS, ALIGN_CENTER, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ACTIONS } from '../constants' import type { @@ -33,6 +35,7 @@ interface MixProps { export function Mix(props: MixProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [mixIsEnabled, setMixIsEnabled] = React.useState( @@ -85,6 +88,12 @@ export function Mix(props: MixProps): JSX.Element { type: mixAction, mixSettings: undefined, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `Mix_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) @@ -97,6 +106,12 @@ export function Mix(props: MixProps): JSX.Element { type: mixAction, mixSettings: { mixVolume, repititions: mixReps }, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `Mix_${kind}`, + }, + }) } onBack() } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx index a192af4cb6b..3bb48859603 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/PipettePath.tsx @@ -10,10 +10,12 @@ import { COLORS, ALIGN_CENTER, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { useBlowOutLocationOptions } from './BlowOut' import type { @@ -36,6 +38,7 @@ interface PipettePathProps { export function PipettePath(props: PipettePathProps): JSX.Element { const { onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const deckConfig = useNotifyDeckConfigurationQuery().data ?? [] @@ -95,6 +98,12 @@ export function PipettePath(props: PipettePathProps): JSX.Element { type: ACTIONS.SET_PIPETTE_PATH, path: selectedPath, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `PipettePath`, + }, + }) onBack() } else { setCurrentStep(2) @@ -108,6 +117,12 @@ export function PipettePath(props: PipettePathProps): JSX.Element { disposalVolume, blowOutLocation, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `PipettePath`, + }, + }) onBack() } } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx index 195abea1810..4e3762a7d8b 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TipPosition.tsx @@ -8,10 +8,12 @@ import { POSITION_FIXED, COLORS, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' import { NumericalKeyboard } from '../../../atoms/SoftwareKeyboard' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import type { QuickTransferSummaryState, @@ -32,6 +34,7 @@ interface TipPositionEntryProps { export function TipPositionEntry(props: TipPositionEntryProps): JSX.Element { const { onBack, state, dispatch, kind } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [tipPosition, setTipPosition] = React.useState( @@ -73,6 +76,12 @@ export function TipPositionEntry(props: TipPositionEntryProps): JSX.Element { type: tipPositionAction, position: tipPosition, }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TipPosition_${kind}`, + }, + }) } onBack() } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx index fd24005b144..0010667afa6 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/TouchTip.tsx @@ -9,10 +9,12 @@ import { COLORS, ALIGN_CENTER, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../redux/analytics' import { getTopPortalEl } from '../../../App/portal' import { RadioButton } from '../../../atoms/buttons' import { ChildNavigation } from '../../ChildNavigation' import { InputField } from '../../../atoms/InputField' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { ACTIONS } from '../constants' import type { @@ -33,6 +35,7 @@ interface TouchTipProps { export function TouchTip(props: TouchTipProps): JSX.Element { const { kind, onBack, state, dispatch } = props const { t } = useTranslation('quick_transfer') + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const keyboardRef = React.useRef(null) const [touchTipIsEnabled, setTouchTipIsEnabled] = React.useState( @@ -77,12 +80,24 @@ export function TouchTip(props: TouchTipProps): JSX.Element { if (currentStep === 1) { if (!touchTipIsEnabled) { dispatch({ type: touchTipAction, position: undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TouchTip_${kind}`, + }, + }) onBack() } else { setCurrentStep(2) } } else if (currentStep === 2) { dispatch({ type: touchTipAction, position: position ?? undefined }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { + setting: `TouchTip_${kind}`, + }, + }) onBack() } } diff --git a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx index 34a36d2d4fb..21b43b20a21 100644 --- a/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx +++ b/app/src/organisms/QuickTransferFlow/QuickTransferAdvancedSettings/index.tsx @@ -14,7 +14,8 @@ import { SIZE_2, TEXT_ALIGN_LEFT, } from '@opentrons/components' - +import { ANALYTICS_QUICK_TRANSFER_ADVANCED_SETTINGS_TAB } from '../../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import type { QuickTransferSummaryAction, QuickTransferSummaryState, @@ -48,8 +49,16 @@ export function QuickTransferAdvancedSettings( const [selectedSetting, setSelectedSetting] = React.useState( null ) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const { makeSnackbar } = useToaster() + React.useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_ADVANCED_SETTINGS_TAB, + properties: {}, + }) + }, []) + function getBlowoutValueCopy(): string | undefined { if (state.blowOut === 'dest_well') { return t('blow_out_into_destination_well') diff --git a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx index 5c443e87375..7d44c5ad1ea 100644 --- a/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectDestWells.tsx @@ -11,7 +11,9 @@ import { JUSTIFY_CENTER, } from '@opentrons/components' import { getAllDefinitions } from '@opentrons/shared-data' +import { ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION } from '../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../Devices/hooks' import { getTopPortalEl } from '../../App/portal' import { Modal } from '../../molecules/Modal' import { ChildNavigation } from '../../organisms/ChildNavigation' @@ -39,6 +41,7 @@ interface SelectDestWellsProps { export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const { makeToast } = useToaster() @@ -72,6 +75,21 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { selectionUnits = t('grids') } + let labwareDefinition = + state.destination === 'source' ? state.source : state.destination + if (labwareDefinition?.parameters.format === '96Standard') { + const allDefinitions = getAllDefinitions() + if (Object.values(labwareDefinition.wells)[0].shape === 'circular') { + labwareDefinition = allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] + } else { + labwareDefinition = + allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] + } + } + const is384WellPlate = labwareDefinition?.parameters.format === '384Standard' + + const [analyticsStartTime] = React.useState(new Date()) + const handleClickNext = (): void => { if ( selectedWellCount === 1 || @@ -82,6 +100,14 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { type: 'SET_DEST_WELLS', wells: Object.keys(selectedWells), }) + const duration = new Date().getTime() - analyticsStartTime.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION, + properties: { + is384WellPlate, + duration: `${duration / 1000} seconds`, + }, + }) onNext() } else { setIsNumberWellsSelectedError(true) @@ -113,17 +139,7 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { e.currentTarget.blur?.() }, } - let labwareDefinition = - state.destination === 'source' ? state.source : state.destination - if (labwareDefinition?.parameters.format === '96Standard') { - const allDefinitions = getAllDefinitions() - if (Object.values(labwareDefinition.wells)[0].shape === 'circular') { - labwareDefinition = allDefinitions[CIRCULAR_WELL_96_PLATE_DEFINITION_URI] - } else { - labwareDefinition = - allDefinitions[RECTANGULAR_WELL_96_PLATE_DEFINITION_URI] - } - } + return ( <> {createPortal( @@ -159,13 +175,7 @@ export function SelectDestWells(props: SelectDestWellsProps): JSX.Element { width="100%" > {labwareDefinition != null ? ( - + { diff --git a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx index 5f95482fe92..1fea995c4b2 100644 --- a/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx +++ b/app/src/organisms/QuickTransferFlow/SelectSourceWells.tsx @@ -8,7 +8,8 @@ import { SPACING, } from '@opentrons/components' import { getAllDefinitions } from '@opentrons/shared-data' - +import { ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION } from '../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../Devices/hooks' import { ChildNavigation } from '../../organisms/ChildNavigation' import { WellSelection } from '../../organisms/WellSelection' @@ -33,6 +34,7 @@ export const RECTANGULAR_WELL_96_PLATE_DEFINITION_URI = export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { const { onNext, onBack, state, dispatch } = props const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const sourceWells = state.sourceWells ?? [] const sourceWellGroup = sourceWells.reduce((acc, well) => { @@ -40,12 +42,22 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { }, {}) const [selectedWells, setSelectedWells] = React.useState(sourceWellGroup) + const [startingTimeStamp] = React.useState(new Date()) + const is384WellPlate = state.source?.parameters.format === '384Standard' const handleClickNext = (): void => { dispatch({ type: 'SET_SOURCE_WELLS', wells: Object.keys(selectedWells), }) + const duration = new Date().getTime() - startingTimeStamp?.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION, + properties: { + is384WellPlate, + duration: `${duration / 1000} seconds`, + }, + }) onNext() } @@ -91,11 +103,7 @@ export function SelectSourceWells(props: SelectSourceWellsProps): JSX.Element { width="100%" > {state.source != null && displayLabwareDefinition != null ? ( - + { diff --git a/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx b/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx index 44cfd13f5eb..8897a5eb4ad 100644 --- a/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx +++ b/app/src/organisms/QuickTransferFlow/SummaryAndSettings.tsx @@ -17,6 +17,12 @@ import { useCreateRunMutation, useHost, } from '@opentrons/react-api-client' +import { + ANALYTICS_QUICK_TRANSFER_TIME_TO_CREATE, + ANALYTICS_QUICK_TRANSFER_SAVE_FOR_LATER, + ANALYTICS_QUICK_TRANSFER_RUN_NOW, +} from '../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../Devices/hooks' import { useNotifyDeckConfigurationQuery } from '../../resources/deck_configuration' import { ChildNavigation } from '../ChildNavigation' @@ -33,13 +39,15 @@ import type { QuickTransferWizardState } from './types' interface SummaryAndSettingsProps { exitButtonProps: React.ComponentProps state: QuickTransferWizardState + analyticsStartTime: Date } export function SummaryAndSettings( props: SummaryAndSettingsProps ): JSX.Element | null { - const { exitButtonProps, state: wizardFlowState } = props + const { exitButtonProps, state: wizardFlowState, analyticsStartTime } = props const navigate = useNavigate() + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const queryClient = useQueryClient() const host = useHost() const { t } = useTranslation(['quick_transfer', 'shared']) @@ -82,6 +90,17 @@ export function SummaryAndSettings( host ) + const handleClickCreateTransfer = (): void => { + setShowSaveOrRunModal(true) + const duration = new Date().getTime() - analyticsStartTime.getTime() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_TIME_TO_CREATE, + properties: { + duration: `${duration / 1000} seconds`, + }, + }) + } + const handleClickSave = (protocolName: string): void => { const protocolFile = createQuickTransferFile( state, @@ -94,6 +113,12 @@ export function SummaryAndSettings( }).then(() => { navigate('/quick-transfer') }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_SAVE_FOR_LATER, + properties: { + name: protocolName, + }, + }) } const handleClickRun = (): void => { @@ -106,6 +131,10 @@ export function SummaryAndSettings( protocolId: data.data.id, }) }) + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_RUN_NOW, + properties: {}, + }) } return showSaveOrRunModal ? ( @@ -115,9 +144,7 @@ export function SummaryAndSettings( { - setShowSaveOrRunModal(true) - }} + onClickButton={handleClickCreateTransfer} secondaryButtonProps={exitButtonProps} /> ( null ) + React.useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB, + properties: {}, + }) + }, []) + const displayItems = [ { option: t('change_tip'), diff --git a/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx index a6cfc429cb1..546b30fa7c6 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx +++ b/app/src/organisms/QuickTransferFlow/__tests__/SummaryAndSettings.test.tsx @@ -5,10 +5,12 @@ import { useCreateProtocolMutation, useCreateRunMutation, } from '@opentrons/react-api-client' +import { ANALYTICS_QUICK_TRANSFER_RUN_NOW } from '../../../redux/analytics' import { useNotifyDeckConfigurationQuery } from '../../../resources/deck_configuration' import { createQuickTransferFile } from '../utils' import { renderWithProviders } from '../../../__testing-utils__' import { i18n } from '../../../i18n' +import { useTrackEventWithRobotSerial } from '../../Devices/hooks' import { SummaryAndSettings } from '../SummaryAndSettings' import { NameQuickTransfer } from '../NameQuickTransfer' import { Overview } from '../Overview' @@ -25,6 +27,7 @@ vi.mock('react-router-dom', async importOriginal => { }) vi.mock('../Overview') vi.mock('../NameQuickTransfer') +vi.mock('../../Devices/hooks') vi.mock('../utils', async () => { const actual = await vi.importActual('../utils') return { @@ -41,6 +44,7 @@ const render = (props: React.ComponentProps) => { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any describe('SummaryAndSettings', () => { let props: React.ComponentProps @@ -65,13 +69,19 @@ describe('SummaryAndSettings', () => { transferType: 'transfer', volume: 25, }, + analyticsStartTime: new Date(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ data: { data: [], }, } as any) - + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) vi.mocked(useCreateProtocolMutation).mockReturnValue({ mutateAsync: createProtocol, } as any) @@ -112,6 +122,7 @@ describe('SummaryAndSettings', () => { render(props) const continueBtn = screen.getByTestId('ChildNavigation_Primary_Button') fireEvent.click(continueBtn) + expect(mockTrackEventWithRobotSerial).toHaveBeenCalled() screen.getByText('Do you want to run your quick transfer now?') screen.getByText('Save your quick transfer to run it in the future.') }) @@ -129,6 +140,10 @@ describe('SummaryAndSettings', () => { fireEvent.click(continueBtn) const runBtn = screen.getByText('Run now') fireEvent.click(runBtn) + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_RUN_NOW, + properties: {}, + }) expect(vi.mocked(createQuickTransferFile)).toHaveBeenCalled() expect(vi.mocked(createProtocol)).toHaveBeenCalled() }) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx index a45a8381035..13c68b821dc 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx +++ b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/ChangeTip.test.tsx @@ -4,14 +4,20 @@ import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { renderWithProviders } from '../../../../__testing-utils__' import { i18n } from '../../../../i18n' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../../Devices/hooks' import { ChangeTip } from '../../TipManagement/ChangeTip' +vi.mock('../../../Devices/hooks') + const render = (props: React.ComponentProps): any => { return renderWithProviders(, { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any + describe('ChangeTip', () => { let props: React.ComponentProps @@ -28,6 +34,12 @@ describe('ChangeTip', () => { } as any, dispatch: vi.fn(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) }) afterEach(() => { vi.resetAllMocks() @@ -49,6 +61,10 @@ describe('ChangeTip', () => { const saveBtn = screen.getByText('Save') fireEvent.click(saveBtn) expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { setting: 'ChangeTip' }, + }) }) it('renders correct change tip options when single transfer of less than 96 wells', () => { render(props) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx index faaf3c16ca7..340829eca21 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx +++ b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipDropLocation.test.tsx @@ -5,15 +5,19 @@ import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { renderWithProviders } from '../../../../__testing-utils__' import { i18n } from '../../../../i18n' import { useNotifyDeckConfigurationQuery } from '../../../../resources/deck_configuration' +import { ANALYTICS_QUICK_TRANSFER_SETTING_SAVED } from '../../../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../../Devices/hooks' import { TipDropLocation } from '../../TipManagement/TipDropLocation' vi.mock('../../../../resources/deck_configuration') +vi.mock('../../../Devices/hooks') const render = (props: React.ComponentProps): any => { return renderWithProviders(, { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any describe('TipDropLocation', () => { let props: React.ComponentProps @@ -26,6 +30,12 @@ describe('TipDropLocation', () => { } as any, dispatch: vi.fn(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) vi.mocked(useNotifyDeckConfigurationQuery).mockReturnValue({ data: [ { @@ -62,5 +72,9 @@ describe('TipDropLocation', () => { const saveBtn = screen.getByText('Save') fireEvent.click(saveBtn) expect(props.dispatch).toHaveBeenCalled() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_SETTING_SAVED, + properties: { setting: 'TipDropLocation' }, + }) }) }) diff --git a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx index 97e231a15ec..f52a78fe78a 100644 --- a/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx +++ b/app/src/organisms/QuickTransferFlow/__tests__/TipManagement/TipManagement.test.tsx @@ -4,18 +4,22 @@ import { describe, it, expect, afterEach, vi, beforeEach } from 'vitest' import { renderWithProviders } from '../../../../__testing-utils__' import { i18n } from '../../../../i18n' +import { ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB } from '../../../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../../Devices/hooks' import { ChangeTip } from '../../TipManagement/ChangeTip' import { TipDropLocation } from '../../TipManagement/TipDropLocation' import { TipManagement } from '../../TipManagement/' vi.mock('../../TipManagement/ChangeTip') vi.mock('../../TipManagement/TipDropLocation') +vi.mock('../../../Devices/hooks') const render = (props: React.ComponentProps): any => { return renderWithProviders(, { i18nInstance: i18n, }) } +let mockTrackEventWithRobotSerial: any describe('TipManagement', () => { let props: React.ComponentProps @@ -30,6 +34,12 @@ describe('TipManagement', () => { } as any, dispatch: vi.fn(), } + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) }) afterEach(() => { vi.resetAllMocks() @@ -41,6 +51,10 @@ describe('TipManagement', () => { screen.getByText('Once at the start of the transfer') screen.getByText('Tip drop location') screen.getByText('Trash bin') + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB, + properties: {}, + }) }) it('renders Change tip component when seleted', () => { render(props) diff --git a/app/src/organisms/QuickTransferFlow/index.tsx b/app/src/organisms/QuickTransferFlow/index.tsx index 6b589ca0b45..34809af3998 100644 --- a/app/src/organisms/QuickTransferFlow/index.tsx +++ b/app/src/organisms/QuickTransferFlow/index.tsx @@ -6,6 +6,8 @@ import { StepMeter, POSITION_STICKY, } from '@opentrons/components' +import { ANALYTICS_QUICK_TRANSFER_EXIT_EARLY } from '../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../Devices/hooks' import { ConfirmExitModal } from './ConfirmExitModal' import { CreateNewTransfer } from './CreateNewTransfer' import { SelectPipette } from './SelectPipette' @@ -27,17 +29,26 @@ const initialQuickTransferState: QuickTransferWizardState = {} export const QuickTransferFlow = (): JSX.Element => { const navigate = useNavigate() const { i18n, t } = useTranslation(['quick_transfer', 'shared']) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const [state, dispatch] = React.useReducer( quickTransferWizardReducer, initialQuickTransferState ) const [currentStep, setCurrentStep] = React.useState(0) + const [analyticsStartTime] = React.useState(new Date()) + const { confirm: confirmExit, showConfirmation: showConfirmExit, cancel: cancelExit, } = useConditionalConfirm(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_EXIT_EARLY, + properties: { + step: currentStep, + }, + }) navigate('/quick-transfer') }, true) @@ -73,7 +84,11 @@ export const QuickTransferFlow = (): JSX.Element => { , , , - , + , ] return ( diff --git a/app/src/pages/QuickTransferDashboard/index.tsx b/app/src/pages/QuickTransferDashboard/index.tsx index b47d14f5ed8..1ef8d3fa4c3 100644 --- a/app/src/pages/QuickTransferDashboard/index.tsx +++ b/app/src/pages/QuickTransferDashboard/index.tsx @@ -20,9 +20,13 @@ import { useAllProtocolsQuery, useInstrumentsQuery, } from '@opentrons/react-api-client' - +import { + ANALYTICS_QUICK_TRANSFER_TAB_SELECTED, + ANALYTICS_QUICK_TRANSFER_FLOW_STARTED, +} from '../../redux/analytics' import { SmallButton, FloatingActionButton } from '../../atoms/buttons' import { Navigation } from '../../organisms/Navigation' +import { useTrackEventWithRobotSerial } from '../../organisms/Devices/hooks' import { getPinnedQuickTransferIds, getQuickTransfersOnDeviceSortKey, @@ -69,6 +73,14 @@ export function QuickTransferDashboard(): JSX.Element { const [targetTransferId, setTargetTransferId] = React.useState('') const sortBy = useSelector(getQuickTransfersOnDeviceSortKey) ?? 'alphabetical' const hasDismissedIntro = useSelector(getHasDismissedQuickTransferIntro) + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + + React.useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_TAB_SELECTED, + properties: {}, + }) + }, []) const pipetteIsAttached = attachedInstruments?.data.some( (i): i is PipetteData => i.ok && i.instrumentType === 'pipette' @@ -151,6 +163,10 @@ export function QuickTransferDashboard(): JSX.Element { } else if (quickTransfersData.length >= 20) { setShowStorageLimitReachedModal(true) } else { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_FLOW_STARTED, + properties: {}, + }) navigate('/quick-transfer/new') } } diff --git a/app/src/pages/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx b/app/src/pages/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx index 929f5d46f82..8e379d9fe67 100644 --- a/app/src/pages/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx +++ b/app/src/pages/QuickTransferDetails/__tests__/QuickTransferDetails.test.tsx @@ -12,6 +12,11 @@ import { useProtocolAnalysisAsDocumentQuery, } from '@opentrons/react-api-client' import { i18n } from '../../../i18n' +import { + ANALYTICS_QUICK_TRANSFER_DETAILS_PAGE, + ANALYTICS_QUICK_TRANSFER_RUN_FROM_DETAILS, +} from '../../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../../organisms/Devices/hooks' import { useHardwareStatusText } from '../../../organisms/OnDeviceDisplay/RobotDashboard/hooks' import { useOffsetCandidatesForAnalysis } from '../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../../Protocols/hooks' @@ -40,6 +45,7 @@ vi.mock('../../../organisms/ProtocolSetupParameters') vi.mock('@opentrons/api-client') vi.mock('@opentrons/react-api-client') vi.mock('../../../organisms/OnDeviceDisplay/RobotDashboard/hooks') +vi.mock('../../../organisms/Devices/hooks') vi.mock( '../../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' ) @@ -70,6 +76,7 @@ const MOCK_DATA = { key: '26ed5a82-502f-4074-8981-57cdda1d066d', }, } +let mockTrackEventWithRobotSerial: any const render = (path = '/quick-transfer/fakeTransferId') => { return renderWithProviders( @@ -89,6 +96,12 @@ const render = (path = '/quick-transfer/fakeTransferId') => { describe('ODDQuickTransferDetails', () => { beforeEach(() => { + mockTrackEventWithRobotSerial = vi.fn( + () => new Promise(resolve => resolve({})) + ) + vi.mocked(useTrackEventWithRobotSerial).mockReturnValue({ + trackEventWithRobotSerial: mockTrackEventWithRobotSerial, + }) vi.mocked(useCreateRunMutation).mockReturnValue({ createRun: mockCreateRun, } as any) @@ -128,11 +141,35 @@ describe('ODDQuickTransferDetails', () => { ) }) + it('calls analytics tracking event for showing a quick transfer details page', () => { + render() + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_DETAILS_PAGE, + properties: { + name: + 'Nextera XT DNA Library Prep Kit Protocol: Part 1/4 - Tagment Genomic DNA and Amplify Libraries', + }, + }) + }) + it('renders the start setup button', () => { render() screen.getByText('Start setup') }) + it('fires analytics event when start setup is pressed', () => { + render() + const setupBtn = screen.getByText('Start setup') + fireEvent.click(setupBtn) + expect(mockTrackEventWithRobotSerial).toHaveBeenCalledWith({ + name: ANALYTICS_QUICK_TRANSFER_RUN_FROM_DETAILS, + properties: { + name: + 'Nextera XT DNA Library Prep Kit Protocol: Part 1/4 - Tagment Genomic DNA and Amplify Libraries', + }, + }) + }) + it('renders the transfer description', () => { render() screen.getByText('A short mock quick transfer') diff --git a/app/src/pages/QuickTransferDetails/index.tsx b/app/src/pages/QuickTransferDetails/index.tsx index 9551f9b4371..12cc6b18246 100644 --- a/app/src/pages/QuickTransferDetails/index.tsx +++ b/app/src/pages/QuickTransferDetails/index.tsx @@ -45,6 +45,11 @@ import { getPinnedQuickTransferIds, updateConfigValue, } from '../../redux/config' +import { + ANALYTICS_QUICK_TRANSFER_DETAILS_PAGE, + ANALYTICS_QUICK_TRANSFER_RUN_FROM_DETAILS, +} from '../../redux/analytics' +import { useTrackEventWithRobotSerial } from '../../organisms/Devices/hooks' import { useOffsetCandidatesForAnalysis } from '../../organisms/ApplyHistoricOffsets/hooks/useOffsetCandidatesForAnalysis' import { useMissingProtocolHardware } from '../Protocols/hooks' import { DeleteTransferConfirmationModal } from '../QuickTransferDashboard/DeleteTransferConfirmationModal' @@ -73,6 +78,7 @@ const QuickTransferHeader = ({ isTransferFetching, }: QuickTransferHeaderProps): JSX.Element => { const navigate = useNavigate() + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() const { t } = useTranslation('protocol_details') const [truncate, setTruncate] = React.useState(true) const [startSetup, setStartSetup] = React.useState(false) @@ -85,6 +91,15 @@ const QuickTransferHeader = ({ displayedTitle = truncateString(displayedTitle, 80, 60) } + React.useEffect(() => { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_DETAILS_PAGE, + properties: { + name: title, + }, + }) + }, []) + return ( { setStartSetup(true) handleRunTransfer() + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_RUN_FROM_DETAILS, + properties: { + name: title, + }, + }) }} buttonText={t('start_setup')} disabled={isTransferFetching} diff --git a/app/src/pages/RunSummary/index.tsx b/app/src/pages/RunSummary/index.tsx index e88bf872c0c..7669ebc75cc 100644 --- a/app/src/pages/RunSummary/index.tsx +++ b/app/src/pages/RunSummary/index.tsx @@ -49,6 +49,7 @@ import { import { useRunCreatedAtTimestamp, useTrackProtocolRunEvent, + useTrackEventWithRobotSerial, useRobotAnalyticsData, } from '../../organisms/Devices/hooks' import { useCloseCurrentRun } from '../../organisms/ProtocolUpload/hooks' @@ -59,6 +60,7 @@ import { useTrackEvent, ANALYTICS_PROTOCOL_RUN_ACTION, ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + ANALYTICS_QUICK_TRANSFER_RERUN, } from '../../redux/analytics' import { getLocalRobot } from '../../redux/discovery' import { RunFailedModal } from '../../organisms/OnDeviceDisplay/RunningProtocol' @@ -143,6 +145,8 @@ export function RunSummary(): JSX.Element { const { reset, isResetRunLoading } = useRunControls(runId, onCloneRunSuccess) const trackEvent = useTrackEvent() + const { trackEventWithRobotSerial } = useTrackEventWithRobotSerial() + const { closeCurrentRun, isClosingCurrentRun } = useCloseCurrentRun() // Close the current run only if it's active and then execute the onSuccess callback. Prefer this wrapper over // closeCurrentRun directly, since the callback is swallowed if currentRun is null. @@ -261,11 +265,20 @@ export function RunSummary(): JSX.Element { const runAgain = (): void => { setShowRunAgainSpinner(true) reset() - trackEvent({ - name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, - properties: { sourceLocation: 'RunSummary', robotSerialNumber }, - }) - trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN }) + if (isQuickTransfer) { + trackEventWithRobotSerial({ + name: ANALYTICS_QUICK_TRANSFER_RERUN, + properties: { + name: protocolName, + }, + }) + } else { + trackEvent({ + name: ANALYTICS_PROTOCOL_PROCEED_TO_RUN, + properties: { sourceLocation: 'RunSummary', robotSerialNumber }, + }) + trackProtocolRunEvent({ name: ANALYTICS_PROTOCOL_RUN_ACTION.AGAIN }) + } } // If no pipettes have tips attached, execute the routing callback. diff --git a/app/src/redux/analytics/constants.ts b/app/src/redux/analytics/constants.ts index 18e10cc8be8..cf99bfad9ea 100644 --- a/app/src/redux/analytics/constants.ts +++ b/app/src/redux/analytics/constants.ts @@ -72,3 +72,28 @@ export const ANALYTICS_RECOVERY_VIEW_ERROR_DETAILS = 'recoveryViewErrorDetails' export const ANALYTICS_RECOVERY_ACTION_RESULT = 'recoverySelectedRecoveryActionResult' export const ANALYTICS_RECOVERY_RUN_RESULT = 'recoveryRunResultAfterError' + +/** + * Quick Transfer Analytics + */ + +export const ANALYTICS_QUICK_TRANSFER_TAB_SELECTED = 'quickTransferTab' +export const ANALYTICS_QUICK_TRANSFER_FLOW_STARTED = 'quickTransferFlowStarted' +export const ANALYTICS_QUICK_TRANSFER_WELL_SELECTION_DURATION = + 'quickTransferWellSelectionDuration' +export const ANALYTICS_QUICK_TRANSFER_EXIT_EARLY = 'quickTransferExitEarly' +export const ANALYTICS_QUICK_TRANSFER_ADVANCED_SETTINGS_TAB = + 'quickTransferAdvancedSettingsTab' +export const ANALYTICS_QUICK_TRANSFER_TIP_MANAGEMENT_TAB = + 'quickTransferTipManagementTab' +export const ANALYTICS_QUICK_TRANSFER_SETTING_SAVED = + 'quickTransferSettingSaved' +export const ANALYTICS_QUICK_TRANSFER_TIME_TO_CREATE = + 'quickTransferTimeToCreate' +export const ANALYTICS_QUICK_TRANSFER_SAVE_FOR_LATER = + 'quickTransferSaveForLater' +export const ANALYTICS_QUICK_TRANSFER_RUN_NOW = 'quickTransferRunNow' +export const ANALYTICS_QUICK_TRANSFER_DETAILS_PAGE = 'quickTransferDetailsPage' +export const ANALYTICS_QUICK_TRANSFER_RUN_FROM_DETAILS = + 'quickTransferRunFromDetails' +export const ANALYTICS_QUICK_TRANSFER_RERUN = 'quickTransferReRunFromSummary'